Skip to content

Commit 5ff34e9

Browse files
authored
Add ENVIRONMENT to specify which environment the code will run in (#6565)
By default we still emit code that can run in node, web, shell, etc., but with the option the user can fix only one to be emitted. This also sets ENVIRONMENT to "web" if the user specifies an HTML output (emcc [..] -o code.html). In that case the indication is clear that the code should only run on the web. This is technically a breaking change though, as people may have tried to run the .js file outside of the web, and that may have worked in some cases. To prevent confusion, when ASSERTIONS are on we show an error if we hit a problem, and also in debug logging we mention doing HTML output setting the environment that way. The benefit to doing this is that it gets all the benefits of web-only emitting in a natural way for the main use case. For more details on the benefits, see #6542 #1140, but basically * When targeting only web, we don't emit node.js support code which includes require() operations that confuse bundlers. * We emit less unneeded code.
1 parent 83ed013 commit 5ff34e9

File tree

7 files changed

+91
-9
lines changed

7 files changed

+91
-9
lines changed

src/compiler.js

+13
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,19 @@ load('jsifier.js');
197197
globalEval(processMacros(preprocess(read('runtime.js'), 'runtime.js')));
198198
Runtime.QUANTUM_SIZE = QUANTUM_SIZE;
199199

200+
// State computations
201+
202+
ENVIRONMENT_MAY_BE_WEB = !ENVIRONMENT || ENVIRONMENT === 'web';
203+
ENVIRONMENT_MAY_BE_WORKER = !ENVIRONMENT || ENVIRONMENT === 'worker';
204+
ENVIRONMENT_MAY_BE_NODE = !ENVIRONMENT || ENVIRONMENT === 'node';
205+
ENVIRONMENT_MAY_BE_SHELL = !ENVIRONMENT || ENVIRONMENT === 'shell';
206+
207+
ENVIRONMENT_MAY_BE_WEB_OR_WORKER = ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER;
208+
209+
if (ENVIRONMENT && !(ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER || ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL)) {
210+
throw 'Invalid environment specified in "ENVIRONMENT": ' + ENVIRONMENT + '. Should be one of: web, worker, node, shell.';
211+
}
212+
200213
//===============================
201214
// Main
202215
//===============================

src/library_fs.js

+2
Original file line numberDiff line numberDiff line change
@@ -1295,8 +1295,10 @@ mergeInto(LibraryManager.library, {
12951295
var randomBuffer = new Uint8Array(1);
12961296
random_device = function() { crypto.getRandomValues(randomBuffer); return randomBuffer[0]; };
12971297
} else if (ENVIRONMENT_IS_NODE) {
1298+
#if ENVIRONMENT_MAY_BE_NODE
12981299
// for nodejs
12991300
random_device = function() { return require('crypto')['randomBytes'](1)[0]; };
1301+
#endif // ENVIRONMENT_MAY_BE_NODE
13001302
} else {
13011303
// default for ES5 platforms
13021304
random_device = function() { return (Math.random()*256)|0; };

src/library_sockfs.js

+6
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,13 @@ mergeInto(LibraryManager.library, {
206206
// If node we use the ws library.
207207
var WebSocketConstructor;
208208
if (ENVIRONMENT_IS_NODE) {
209+
#if ENVIRONMENT_MAY_BE_NODE
209210
WebSocketConstructor = require('ws');
211+
#endif ENVIRONMENT_MAY_BE_NODE
210212
} else if (ENVIRONMENT_IS_WEB) {
213+
#if ENVIRONMENT_MAY_BE_WEB
211214
WebSocketConstructor = window['WebSocket'];
215+
#endif // ENVIRONMENT_MAY_BE_WEB
212216
} else {
213217
WebSocketConstructor = WebSocket;
214218
}
@@ -482,6 +486,7 @@ mergeInto(LibraryManager.library, {
482486
if (!ENVIRONMENT_IS_NODE) {
483487
throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP);
484488
}
489+
#if ENVIRONMENT_MAY_BE_NODE
485490
if (sock.server) {
486491
throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already listening
487492
}
@@ -535,6 +540,7 @@ mergeInto(LibraryManager.library, {
535540
Module['websocket'].emit('error', [sock.stream.fd, sock.error, 'EHOSTUNREACH: Host is unreachable']);
536541
// don't throw
537542
});
543+
#endif // ENVIRONMENT_MAY_BE_NODE
538544
},
539545
accept: function(listensock) {
540546
if (!listensock.server) {

src/library_uuid.js

+2
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ mergeInto(LibraryManager.library, {
3030
var uuid = null;
3131

3232
if (ENVIRONMENT_IS_NODE) {
33+
#if ENVIRONMENT_MAY_BE_NODE
3334
// If Node.js try to use crypto.randomBytes
3435
try {
3536
var rb = require('crypto')['randomBytes'];
3637
uuid = rb(16);
3738
} catch(e) {}
39+
#endif // ENVIRONMENT_MAY_BE_NODE
3840
} else if (ENVIRONMENT_IS_WEB &&
3941
typeof(window.crypto) !== 'undefined' &&
4042
typeof(window.crypto.getRandomValues) !== 'undefined') {

src/settings.js

+19
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,17 @@ var LEGACY_VM_SUPPORT = 0; // Enable this to get support for non-modern browsers
318318
// * Add polyfilling for Math.clz32, Math.trunc, Math.imul, Math.fround.
319319
// * Disable WebAssembly.
320320

321+
var ENVIRONMENT = ''; // By default, emscripten output will run on the web, in a web worker,
322+
// in node.js, or in a JS shell like d8, js, or jsc. You can set this option to
323+
// specify that the output should only run in one particular environment, which
324+
// must be one of
325+
// 'web' - the normal web environment.
326+
// 'worker' - a web worker environment.
327+
// 'node' - Node.js.
328+
// 'shell' - a JS shell like d8, js, or jsc.
329+
// There is also a 'pthread' environment, see shell.js, but it cannot be specified
330+
// manually yet TODO
331+
321332
var LZ4 = 0; // Enable this to support lz4-compressed file packages. They are stored compressed in memory, and
322333
// decompressed on the fly, avoiding storing the entire decompressed data in memory at once.
323334
// If you run the file packager separately, you still need to build the main program with this flag,
@@ -924,3 +935,11 @@ var MEM_INIT_IN_WASM = 0; // for internal use only
924935

925936
var SUPPORT_BASE64_EMBEDDING = 0; // If set to 1, src/base64Utils.js will be included in the bundle.
926937
// This is set internally when needed (SINGLE_FILE)
938+
939+
// For internal use only, the possible environments the code may run in.
940+
var ENVIRONMENT_MAY_BE_WEB = 1;
941+
var ENVIRONMENT_MAY_BE_WORKER = 1;
942+
var ENVIRONMENT_MAY_BE_NODE = 1;
943+
var ENVIRONMENT_MAY_BE_SHELL = 1;
944+
var ENVIRONMENT_MAY_BE_WEB_OR_WORKER = 1;
945+

src/shell.js

+23-9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ Module['quit'] = function(status, toThrow) {
4646
Module['preRun'] = [];
4747
Module['postRun'] = [];
4848

49+
#if ENVIRONMENT
50+
51+
var ENVIRONMENT_IS_WEB = {{{ ENVIRONMENT === 'web' }}};
52+
var ENVIRONMENT_IS_WORKER = {{{ ENVIRONMENT === 'worker' }}};
53+
var ENVIRONMENT_IS_NODE = {{{ ENVIRONMENT === 'node' }}};
54+
var ENVIRONMENT_IS_SHELL = {{{ ENVIRONMENT === 'shell' }}};
55+
56+
#else // ENVIRONMENT
4957
// The environment setup code below is customized to use Module.
5058
// *** Environment setup code ***
5159
var ENVIRONMENT_IS_WEB = false;
@@ -76,6 +84,7 @@ if (Module['ENVIRONMENT']) {
7684
ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof require === 'function' && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER;
7785
ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
7886
}
87+
#endif // ENVIRONMENT
7988

8089
#if USE_PTHREADS
8190
var ENVIRONMENT_IS_PTHREAD;
@@ -85,6 +94,7 @@ if (!ENVIRONMENT_IS_PTHREAD) PthreadWorkerInit = {};
8594
var currentScriptUrl = (typeof document !== 'undefined' && document.currentScript) ? document.currentScript.src : undefined;
8695
#endif
8796

97+
#if ENVIRONMENT_MAY_BE_NODE
8898
if (ENVIRONMENT_IS_NODE) {
8999
// Expose functionality in the same simple way that the shells work
90100
// Note that we pollute the global namespace here, otherwise we break in node
@@ -148,8 +158,10 @@ if (ENVIRONMENT_IS_NODE) {
148158
});
149159

150160
Module['inspect'] = function () { return '[Emscripten Module object]'; };
151-
}
152-
else if (ENVIRONMENT_IS_SHELL) {
161+
} else
162+
#endif // ENVIRONMENT_MAY_BE_NODE
163+
#if ENVIRONMENT_MAY_BE_SHELL
164+
if (ENVIRONMENT_IS_SHELL) {
153165
if (typeof read != 'undefined') {
154166
Module['read'] = function shell_read(f) {
155167
#if SUPPORT_BASE64_EMBEDDING
@@ -189,8 +201,10 @@ else if (ENVIRONMENT_IS_SHELL) {
189201
quit(status);
190202
}
191203
}
192-
}
193-
else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
204+
} else
205+
#endif // ENVIRONMENT_MAY_BE_SHELL
206+
#if ENVIRONMENT_MAY_BE_WEB_OR_WORKER
207+
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
194208
Module['read'] = function shell_read(url) {
195209
#if SUPPORT_BASE64_EMBEDDING
196210
try {
@@ -255,13 +269,13 @@ else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
255269
};
256270

257271
Module['setWindowTitle'] = function(title) { document.title = title };
258-
}
272+
} else
273+
#endif // ENVIRONMENT_MAY_BE_WEB_OR_WORKER
274+
{
259275
#if ASSERTIONS
260-
else {
261-
// Unreachable because SHELL is dependent on the others
262-
throw new Error('unknown runtime environment');
263-
}
276+
throw new Error('not compiled with support for this runtime environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)');
264277
#endif // ASSERTIONS
278+
}
265279

266280
// console.log is checked first, as 'print' on the web will open a print dialogue
267281
// printErr is preferable to console.warn (works better in shells)

tests/test_core.py

+26
Original file line numberDiff line numberDiff line change
@@ -7625,6 +7625,32 @@ def test_mallinfo(self):
76257625
def test_wrap_malloc(self):
76267626
self.do_run(open(path_from_root('tests', 'wrap_malloc.cpp')).read(), 'OK.')
76277627

7628+
def test_environment(self):
7629+
for engine in JS_ENGINES:
7630+
for work in (1, 0):
7631+
for assertions in (0, 1):
7632+
if work and assertions: continue # we care about assertions when we fail
7633+
# set us to test in just this engine
7634+
self.banned_js_engines = [e for e in JS_ENGINES if e != engine]
7635+
# tell the compiler to build with just that engine
7636+
if engine == NODE_JS and work:
7637+
Settings.ENVIRONMENT = 'node'
7638+
else:
7639+
Settings.ENVIRONMENT = 'shell'
7640+
Settings.ASSERTIONS = assertions
7641+
print(engine, work, Settings.ENVIRONMENT, assertions)
7642+
try:
7643+
self.do_run_in_out_file_test('tests', 'core', 'test_hello_world')
7644+
except Exception as e:
7645+
if not work:
7646+
if assertions:
7647+
# with assertions, an error should be shown
7648+
self.assertContained('not compiled with support for this runtime environment', str(e))
7649+
else:
7650+
raise
7651+
js = open('src.cpp.o.js').read()
7652+
assert ('require(' in js) == (Settings.ENVIRONMENT == 'node'), 'we should have require() calls only if node js specified'
7653+
76287654
# Generate tests for everything
76297655
def make_run(fullname, name=-1, compiler=-1, embetter=0, quantum_size=0,
76307656
typed_arrays=0, emcc_args=None, env=None):

0 commit comments

Comments
 (0)