Skip to content

Commit 6b67e42

Browse files
committedJul 31, 2023
Allow certain command line settings to be additive
For certain command line settings (specifically most settings that take a list) it is useful to be able to add to the existing setting rather than replace it. For example, the `-sEXPORTED_FUNCTIONS` argument. In a build system where different components can inject link flags it makes sense that each component be able add to the list of exported functions without clobbering the current list. We have had this feature requested several times in the past. For example, from the bazel toolchain folks.
1 parent 995ba81 commit 6b67e42

File tree

4 files changed

+61
-18
lines changed

4 files changed

+61
-18
lines changed
 

‎ChangeLog.md

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.45 (in development)
2222
-----------------------
23+
- Command line settings that accept lists are now add to existing occurances
24+
of the setting rather than replacing them. For example, specifying
25+
`-sEXPORTED_FUNCTIONS=foo -sEXPORTED_FUNCTIONS=bar` is not equivalent of
26+
`-sEXPORTED_FUNCTIONS=foo,bar`. This is useful in build systems where
27+
separate components each want to contribute to this list.
2328

2429
3.1.44 - 07/25/23
2530
-----------------

‎emcc.py

+21-18
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from tools import webassembly
5555
from tools import config
5656
from tools import cache
57-
from tools.settings import user_settings, settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS
57+
from tools.settings import user_settings, settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS, APPENDING_SETTINGS
5858
from tools.utils import read_file, write_file, read_binary, delete_file, removeprefix
5959

6060
logger = logging.getLogger('emcc')
@@ -417,15 +417,15 @@ def default_setting(name, new_default):
417417
setattr(settings, name, new_default)
418418

419419

420-
def apply_user_settings():
420+
def apply_setting(cmdline_settings):
421421
"""Take a map of users settings {NAME: VALUE} and apply them to the global
422422
settings object.
423423
"""
424424

425425
# Stash a copy of all available incoming APIs before the user can potentially override it
426426
settings.ALL_INCOMING_MODULE_JS_API = settings.INCOMING_MODULE_JS_API + EXTRA_INCOMING_JS_API
427427

428-
for key, value in user_settings.items():
428+
for key, value in cmdline_settings:
429429
if key in settings.internal_settings:
430430
exit_with_error('%s is an internal setting and cannot be set from command line', key)
431431

@@ -459,6 +459,10 @@ def apply_user_settings():
459459
except Exception as e:
460460
exit_with_error('a problem occurred in evaluating the content after a "-s", specifically "%s=%s": %s', key, value, str(e))
461461

462+
if key in APPENDING_SETTINGS:
463+
value += getattr(settings, key)
464+
465+
user_settings[user_key] = value
462466
setattr(settings, user_key, value)
463467

464468
if key == 'EXPORTED_FUNCTIONS':
@@ -1426,23 +1430,22 @@ def phase_parse_arguments(state):
14261430
explicit_settings_changes, newargs = parse_s_args(newargs)
14271431
settings_changes += explicit_settings_changes
14281432

1433+
cmdline_settings = []
14291434
for s in settings_changes:
14301435
key, value = s.split('=', 1)
14311436
key, value = normalize_boolean_setting(key, value)
1432-
user_settings[key] = value
1433-
1434-
# STRICT is used when applying settings so it needs to be applied first before
1435-
# calling `apply_user_settings`.
1436-
strict_cmdline = user_settings.get('STRICT')
1437-
if strict_cmdline:
1438-
settings.STRICT = int(strict_cmdline)
1437+
# STRICT is used when applying settings so it needs to be applied first before
1438+
# calling `apply_setting`.
1439+
if key == 'STRICT' and value:
1440+
settings.STRICT = int(value)
1441+
cmdline_settings.append((key, value))
14391442

14401443
# Apply user -jsD settings
14411444
for s in user_js_defines:
14421445
settings[s[0]] = s[1]
14431446

14441447
# Apply -s settings in newargs here (after optimization levels, so they can override them)
1445-
apply_user_settings()
1448+
apply_setting(cmdline_settings)
14461449

14471450
return options, newargs
14481451

@@ -1629,7 +1632,7 @@ def phase_setup(options, state, newargs):
16291632
# If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED
16301633
# on the command line. This is no longer valid so report either an error or a warning (for
16311634
# backwards compat with the old `DISABLE_EXCEPTION_CATCHING=2`
1632-
if user_settings['DISABLE_EXCEPTION_CATCHING'] in ('0', '2'):
1635+
if user_settings['DISABLE_EXCEPTION_CATCHING'] in (0, 2):
16331636
diagnostics.warning('deprecated', 'DISABLE_EXCEPTION_CATCHING=X is no longer needed when specifying EXCEPTION_CATCHING_ALLOWED')
16341637
else:
16351638
exit_with_error('DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED are mutually exclusive')
@@ -1638,9 +1641,9 @@ def phase_setup(options, state, newargs):
16381641
settings.DISABLE_EXCEPTION_CATCHING = 0
16391642

16401643
if settings.WASM_EXCEPTIONS:
1641-
if user_settings.get('DISABLE_EXCEPTION_CATCHING') == '0':
1644+
if user_settings.get('DISABLE_EXCEPTION_CATCHING') == 0:
16421645
exit_with_error('DISABLE_EXCEPTION_CATCHING=0 is not compatible with -fwasm-exceptions')
1643-
if user_settings.get('DISABLE_EXCEPTION_THROWING') == '0':
1646+
if user_settings.get('DISABLE_EXCEPTION_THROWING') == 0:
16441647
exit_with_error('DISABLE_EXCEPTION_THROWING=0 is not compatible with -fwasm-exceptions')
16451648
# -fwasm-exceptions takes care of enabling them, so users aren't supposed to
16461649
# pass them explicitly, regardless of their values
@@ -1649,7 +1652,7 @@ def phase_setup(options, state, newargs):
16491652
settings.DISABLE_EXCEPTION_CATCHING = 1
16501653
settings.DISABLE_EXCEPTION_THROWING = 1
16511654

1652-
if user_settings.get('ASYNCIFY') == '1':
1655+
if user_settings.get('ASYNCIFY') == 1:
16531656
diagnostics.warning('emcc', 'ASYNCIFY=1 is not compatible with -fwasm-exceptions. Parts of the program that mix ASYNCIFY and exceptions will not compile.')
16541657

16551658
if user_settings.get('SUPPORT_LONGJMP') == 'emscripten':
@@ -1672,11 +1675,11 @@ def phase_setup(options, state, newargs):
16721675
# Wasm SjLj cannot be used with Emscripten EH. We error out if
16731676
# DISABLE_EXCEPTION_THROWING=0 is explicitly requested by the user;
16741677
# otherwise we disable it here.
1675-
if user_settings.get('DISABLE_EXCEPTION_THROWING') == '0':
1678+
if user_settings.get('DISABLE_EXCEPTION_THROWING') == 0:
16761679
exit_with_error('SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_THROWING=0')
16771680
# We error out for DISABLE_EXCEPTION_CATCHING=0, because it is 1 by default
16781681
# and this can be 0 only if the user specifies so.
1679-
if user_settings.get('DISABLE_EXCEPTION_CATCHING') == '0':
1682+
if user_settings.get('DISABLE_EXCEPTION_CATCHING') == 0:
16801683
exit_with_error('SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_CATCHING=0')
16811684
default_setting('DISABLE_EXCEPTION_THROWING', 1)
16821685

@@ -1991,7 +1994,7 @@ def phase_linker_setup(options, state, newargs):
19911994

19921995
# For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also
19931996
# want to opt out of ERROR_ON_UNDEFINED_SYMBOLS.
1994-
if user_settings.get('WARN_ON_UNDEFINED_SYMBOLS') == '0':
1997+
if user_settings.get('WARN_ON_UNDEFINED_SYMBOLS') == 0:
19951998
default_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0)
19961999

19972000
# It is unlikely that developers targeting "native web" APIs with MINIMAL_RUNTIME need

‎test/test_other.py

+21
Original file line numberDiff line numberDiff line change
@@ -13635,3 +13635,24 @@ def test_memory64_proxies(self):
1363513635
'-Wno-experimental',
1363613636
'--extern-post-js', test_file('other/test_memory64_proxies.js')])
1363713637
self.run_js('a.out.js')
13638+
13639+
def test_settings_append(self):
13640+
create_file('pre.js', '''
13641+
Module.onRuntimeInitialized = () => {
13642+
_foo();
13643+
}
13644+
''')
13645+
create_file('test.c', r'''
13646+
#include <stdio.h>
13647+
13648+
void foo() {
13649+
printf("foo\n");
13650+
}
13651+
13652+
int main() {
13653+
printf("main\n");
13654+
return 0;
13655+
}
13656+
''')
13657+
expected = 'foo\nmain\n'
13658+
self.do_runf('test.c', expected, emcc_args=['--pre-js=pre.js', '-sEXPORTED_FUNCTIONS=_foo', '-sEXPORTED_FUNCTIONS=_main'])

‎tools/settings.py

+14
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@
8585
'RUNTIME_LINKED_LIBS',
8686
}.union(PORTS_SETTINGS)
8787

88+
# Settings for which repeated occurances add to a list rather then replacing
89+
# the current one.
90+
APPENDING_SETTINGS = {
91+
'EXPORTED_FUNCTIONS',
92+
'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE',
93+
'EXPORTED_RUNTIME_METHODS',
94+
'SIGNATURE_CONVERSIONS',
95+
'EXCEPTION_CATCHING_ALLOWED',
96+
'ASYNCIFY_IMPORTS',
97+
'ASYNCIFY_REMOVE',
98+
'ASYNCIFY_ADD',
99+
'ASYNCIFY_ONLY',
100+
'ASYNCIFY_EXPORTS',
101+
}
88102

89103
# Settings that don't need to be externalized when serializing to json because they
90104
# are not used by the JS compiler.

0 commit comments

Comments
 (0)
Failed to load comments.