diff --git a/emcc.py b/emcc.py index e43e77a58725..7e89c4260298 100755 --- a/emcc.py +++ b/emcc.py @@ -2713,6 +2713,8 @@ def generate_html(target, options, js_target, target_basename, } else if (Module['memoryInitializerPrefixURL']) { memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer; } + // If URL to memory initializer is relative, treat it relative with respect to Module['scriptDirectory'], if that is present. + if (Module['scriptDirectory'] && !/^(?:[a-z]+:)?[\/\\\\]\/?/i.test(memoryInitializer)) memoryInitializer = Module['scriptDirectory'] + memoryInitializer; Module['memoryInitializerRequestURL'] = memoryInitializer; var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest(); meminitXHR.open('GET', memoryInitializer, true); diff --git a/src/Fetch.js b/src/Fetch.js index 7ad126f4f03e..1a2d7f82b1dc 100644 --- a/src/Fetch.js +++ b/src/Fetch.js @@ -115,6 +115,7 @@ var Fetch = { // of these are passed, then the default URL 'pthread-main.js' relative to the main html file is loaded. if (typeof Module['locateFile'] === 'function') fetchJs = Module['locateFile'](fetchJs); else if (Module['pthreadMainPrefixURL']) fetchJs = Module['pthreadMainPrefixURL'] + fetchJs; + fetchJs = joinUrl(Module['scriptDirectory'], fetchJs); Fetch.worker = new Worker(fetchJs); Fetch.worker.onmessage = function(e) { out('fetch-worker sent a message: ' + e.filename + ':' + e.lineno + ': ' + e.message); diff --git a/src/library_debugger_toolkit.js b/src/library_debugger_toolkit.js index 29cb532aecaa..f70d3caf73ae 100644 --- a/src/library_debugger_toolkit.js +++ b/src/library_debugger_toolkit.js @@ -451,6 +451,7 @@ var CyberDWARFHeapPrinter = function(cdFileLocation) { } else if (Module['cdInitializerPrefixURL']) { cdFileLocation = Module['cdInitializerPrefixURL'] + cdFileLocation; } + cdFileLocation = joinUrl(Module['scriptDirectory'], cdFileLocation); if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) { var data = Module['read'](cdFileLocation); install_cyberdwarf(data); diff --git a/src/library_pthread.js b/src/library_pthread.js index b1a3d9824c2e..0b4a46a2890e 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -260,6 +260,7 @@ var LibraryPThread = { else if (Module['pthreadMainPrefixURL']) pthreadMainJs = Module['pthreadMainPrefixURL'] + pthreadMainJs; for (var i = 0; i < numWorkers; ++i) { + pthreadMainJs = joinUrl(Module['scriptDirectory'], pthreadMainJs); var worker = new Worker(pthreadMainJs); (function(worker) { diff --git a/src/postamble.js b/src/postamble.js index f81ef5fc6709..873445c5daae 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -45,7 +45,9 @@ if (memoryInitializer) { } else if (Module['memoryInitializerPrefixURL']) { memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer; } + memoryInitializer = joinUrl(Module['scriptDirectory'], memoryInitializer); } + if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) { var data = Module['readBinary'](memoryInitializer); HEAPU8.set(data, GLOBAL_BASE); diff --git a/src/preamble.js b/src/preamble.js index 296507a0053b..782cd38ef399 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -844,6 +844,18 @@ function stackTrace() { return demangleAll(js); } +function isAbsolutePath(path) { + return isDataURI(path) || /^(?:[a-z]+:)?[\/\\]\/?/i.test(path); +} + +// Joins relative URL 'b' to URL 'a'. 'a' may be empty, in which case 'b' is returned. +// If 'b' is an absolute URL, then 'a' is ignored and 'b' is returned as-is as well. +function joinUrl(a, b) { +#if ASSERTIONS + if (a && !a.endsWith('/')) throw 'First parameter to joinUrl() should end in /!'; +#endif + return (a && !isAbsolutePath(b)) ? a + b : b; +} // Memory management var PAGE_SIZE = 16384; @@ -2031,6 +2043,9 @@ function integrateWasmJS() { asmjsCodeFile = Module['locateFile'](asmjsCodeFile); } } + wasmTextFile = joinUrl(Module['scriptDirectory'], wasmTextFile); + wasmBinaryFile = joinUrl(Module['scriptDirectory'], wasmBinaryFile); + asmjsCodeFile = joinUrl(Module['scriptDirectory'], asmjsCodeFile); // utilities diff --git a/src/shell.js b/src/shell.js index 66bd75be99fa..f7eab9159e49 100644 --- a/src/shell.js +++ b/src/shell.js @@ -94,6 +94,8 @@ if (ENVIRONMENT_IS_NODE) { // Expose functionality in the same simple way that the shells work // Note that we pollute the global namespace here, otherwise we break in node + if (!Module['scriptDirectory']) Module['scriptDirectory'] = __dirname + '/'; + var nodeFS; var nodePath; diff --git a/tests/test_other.py b/tests/test_other.py index 90eeb422e99f..3a3ea8d38e6d 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -8539,3 +8539,56 @@ def test_html_preprocess(self): T4:NO_EXIT_RUNTIME > 1 T5:NO_EXIT_RUNTIME T6:(else) !NO_EXIT_RUNTIME""", output) + + # Tests that Emscripten-compiled applications can be run from a relative path with node command line that is different than the current working directory. + def test_node_js_run_from_different_directory(self): + args = ['-O3', '-s', 'WASM=1', '--memory-init-file', '1', '-s', 'BINARYEN_METHOD="asmjs,native-wasm"'] + # Test that .mem.js is loaded up properly even if running the build output from a separate directory. + os.mkdir('subdir') + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', os.path.join('subdir', 'a.js')] + args).communicate() + ret = Popen(NODE_JS + [os.path.join('subdir', 'a.js')], stdout=PIPE).communicate()[0] + try_delete('subdir') + assert 'hello, world!' in ret + + # Test that the build is loaded properly when Module.locateFile is being used, where Module.locateFile() specifies an absolute path already. + os.mkdir('subdir') + open(os.path.join('subdir', 'pre.js'), 'w').write(''' + var Module = {}; + Module.memoryInitializerPrefixURL = 'this_should_be_getting_ignored_since_locateFile_is_specified/'; + Module.locateFile = function(f) { return __dirname + '/' + f; } + ''') + + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', os.path.join('subdir', 'a.js'), '--pre-js', os.path.join('subdir', 'pre.js')] + args).communicate() + ret = Popen(NODE_JS + [os.path.join('subdir', 'a.js')], stdout=PIPE).communicate()[0] + try_delete('subdir') + assert 'hello, world!' in ret + + # Test that the build is loaded properly when Module.locateFile is being used, and it returns a relative path. + os.mkdir('subdir') + open(os.path.join('subdir', 'pre.js'), 'w').write(''' + var Module = {}; + Module.memoryInitializerPrefixURL = 'this_should_be_getting_ignored_since_locateFile_is_specified/'; + Module.locateFile = function(f) { return 'data/' + f; } + ''') + + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', os.path.join('subdir', 'a.js'), '--pre-js', os.path.join('subdir', 'pre.js')] + args).communicate() + os.mkdir(os.path.join('subdir', 'data')) + os.rename(os.path.join('subdir', 'a.js.mem'), os.path.join('subdir', 'data', 'a.js.mem')) + os.rename(os.path.join('subdir', 'a.asm.js'), os.path.join('subdir', 'data', 'a.asm.js')) + ret = Popen(NODE_JS + [os.path.join('subdir', 'a.js')], stdout=PIPE).communicate()[0] + try_delete('subdir') + assert 'hello, world!' in ret + + # Test that the build is loaded properly when memoryInitializerPrefixURL is being used, and it returns a relative path. + os.mkdir('subdir') + open(os.path.join('subdir', 'pre.js'), 'w').write(''' + var Module = {}; + Module.memoryInitializerPrefixURL = 'data/'; + ''') + + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', os.path.join('subdir', 'a.js'), '--pre-js', os.path.join('subdir', 'pre.js')] + args).communicate() + os.mkdir(os.path.join('subdir', 'data')) + os.rename(os.path.join('subdir', 'a.js.mem'), os.path.join('subdir', 'data', 'a.js.mem')) + ret = Popen(NODE_JS + [os.path.join('subdir', 'a.js')], stdout=PIPE).communicate()[0] + try_delete('subdir') + assert 'hello, world!' in ret diff --git a/tools/file_packager.py b/tools/file_packager.py index f702432fdcb6..a6ba6eda1fb3 100644 --- a/tools/file_packager.py +++ b/tools/file_packager.py @@ -738,6 +738,7 @@ def was_seen(name): var REMOTE_METADATA_NAME = typeof Module['locateFile'] === 'function' ? Module['locateFile']('%(metadata_file)s') : ((Module['filePackagePrefixURL'] || '') + '%(metadata_file)s'); + REMOTE_METADATA_NAME = joinUrl(Module['scriptDirectory'], REMOTE_METADATA_NAME); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) {