Make output scripts executable by default when targeting node
This is something we already did autoconf mode.
diff --git a/ChangeLog.md b/ChangeLog.md
index cd0025f..e6a8c73 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -20,6 +20,9 @@
3.1.33 (in development)
-----------------------
+- When targetting `node` (i.e. when node is included in `ENVIRONMENT`) the
+ output file is now marked as executable and includes a !# line by default.
+ This can be disabled explictly via `-sEXECUTABLE_OUTPUT=0`.
- Removed `sys/sysctl.h` compatibility header. We don't implement the function
it defines. (#18863)
- Update SDL2_ttf port to 2.20.2 (#18804)
diff --git a/emcc.py b/emcc.py
index 165e0c2..21b0f9c 100755
--- a/emcc.py
+++ b/emcc.py
@@ -253,7 +253,7 @@
def __init__(self):
self.output_file = None
self.post_link = False
- self.executable = False
+ self.autoconf = False
self.compiler_wrapper = None
self.oformat = None
self.requested_debug = ''
@@ -749,20 +749,23 @@
return passes
-def make_js_executable(script):
+def make_js_executable(options, script):
src = read_file(script)
- cmd = config.NODE_JS
- if settings.MEMORY64 == 1:
- cmd += shared.node_memory64_flags()
- elif settings.WASM_BIGINT:
- cmd += shared.node_bigint_flags()
- if len(cmd) > 1 or not os.path.isabs(cmd[0]):
- # Using -S (--split-string) here means that arguments to the executable are
- # correctly parsed. We don't do this by default because old versions of env
- # don't support -S.
- cmd = '/usr/bin/env -S ' + shared.shlex_join(cmd)
+ if options.autoconf:
+ cmd = config.NODE_JS
+ if settings.MEMORY64 == 1:
+ cmd += shared.node_memory64_flags()
+ elif settings.WASM_BIGINT:
+ cmd += shared.node_bigint_flags()
+ if len(cmd) > 1 or not os.path.isabs(cmd[0]):
+ # Using -S (--split-string) here means that arguments to the executable are
+ # correctly parsed. We don't do this by default because old versions of env
+ # don't support -S.
+ cmd = '/usr/bin/env -S ' + shared.shlex_join(cmd)
+ else:
+ cmd = shared.shlex_join(cmd)
else:
- cmd = shared.shlex_join(cmd)
+ cmd = '/usr/bin/env node'
logger.debug('adding `#!` to JavaScript file: %s' % cmd)
# add shebang
with open(script, 'w') as f:
@@ -1760,14 +1763,14 @@
@ToolchainProfiler.profile_block('linker_setup')
def phase_linker_setup(options, state, newargs):
- autoconf = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in state.orig_args or 'conftest.cpp' in state.orig_args
- if autoconf:
+ options.autoconf = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in state.orig_args or 'conftest.cpp' in state.orig_args
+ if options.autoconf:
# configure tests want a more shell-like style, where we emit return codes on exit()
settings.EXIT_RUNTIME = 1
# use node.js raw filesystem access, to behave just like a native executable
settings.NODERAWFS = 1
# Add `#!` line to output JS and make it executable.
- options.executable = True
+ settings.EXECUTABLE_OUTPUT = True
system_libpath = '-L' + str(cache.get_lib_dir(absolute=True))
add_link_flag(state, sys.maxsize, system_libpath)
@@ -1775,6 +1778,9 @@
if settings.OPT_LEVEL >= 1:
default_setting('ASSERTIONS', 0)
+ if settings.ENVIRONMENT_MAY_BE_NODE and not settings.MODULARIZE:
+ default_setting('EXECUTABLE_OUTPUT', 1)
+
if options.emrun:
options.pre_js.append(utils.path_from_root('src/emrun_prejs.js'))
options.post_js.append(utils.path_from_root('src/emrun_postjs.js'))
@@ -1826,7 +1832,7 @@
dirname = os.path.dirname(target)
if dirname and not os.path.isdir(dirname):
exit_with_error("specified output file (%s) is in a directory that does not exist" % target)
- elif autoconf:
+ elif options.autoconf:
# Autoconf expects the executable output file to be called `a.out`
target = 'a.out'
elif settings.SIDE_MODULE:
@@ -3291,8 +3297,8 @@
for f in generated_text_files_with_native_eols:
tools.line_endings.convert_line_endings_in_file(f, os.linesep, options.output_eol)
- if options.executable:
- make_js_executable(js_target)
+ if settings.EXECUTABLE_OUTPUT and settings.ENVIRONMENT_MAY_BE_NODE:
+ make_js_executable(options, js_target)
def version_string():
diff --git a/src/settings.js b/src/settings.js
index 704d987..46819fb 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -2101,6 +2101,10 @@
// library symbol.
var LEGACY_RUNTIME = false;
+// Mark JS output file as executable and include #! line at the top.
+// This defaults to true when node is included in ENVIRONMENT.
+var EXECUTABLE_OUTPUT = false;
+
//===========================================
// Internal, used for testing only, from here
//===========================================
diff --git a/test/test_core.py b/test/test_core.py
index f52fe17..bf46fc1 100644
--- a/test/test_core.py
+++ b/test/test_core.py
@@ -429,9 +429,7 @@
self.assertEqual(prefix, output[:len(prefix)])
def verify_in_strict_mode(self, filename):
- js = read_file(filename)
- filename += '.strict.js'
- write_file(filename, '"use strict";\n' + js)
+ self.node_args.append('--use_strict')
self.run_js(filename)
def do_core_test(self, testname, **kwargs):
diff --git a/test/test_other.py b/test/test_other.py
index bd157ba..8a6c71a 100644
--- a/test/test_other.py
+++ b/test/test_other.py
@@ -11309,6 +11309,12 @@
output = self.run_process([os.path.abspath('a.out')], stdout=PIPE).stdout
self.assertContained('hello, world!', output)
+ @no_windows('windows does not support shbang syntax')
+ def test_executable_output(self):
+ self.run_process([EMCC, test_file('hello_world.c')])
+ output = self.run_process([os.path.abspath('a.out.js')], stdout=PIPE).stdout
+ self.assertContained('hello, world!', output)
+
def test_standalone_export_main(self):
# Tests that explicitly exported `_main` does not fail, even though `_start` is the entry
# point.