Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/cmdline.py
4128 views
1
# Copyright 2025 The Emscripten Authors. All rights reserved.
2
# Emscripten is available under two separate licenses, the MIT license and the
3
# University of Illinois/NCSA Open Source License. Both these licenses can be
4
# found in the LICENSE file.
5
6
from tools.toolchain_profiler import ToolchainProfiler
7
8
import json
9
import logging
10
import os
11
import re
12
import shlex
13
import sys
14
from enum import Enum, auto, unique
15
from subprocess import PIPE
16
17
from tools import shared, utils, ports, diagnostics, config
18
from tools import cache, feature_matrix, colored_logger
19
from tools.shared import exit_with_error
20
from tools.settings import settings, user_settings, MEM_SIZE_SETTINGS
21
from tools.utils import removeprefix, read_file
22
23
SIMD_INTEL_FEATURE_TOWER = ['-msse', '-msse2', '-msse3', '-mssse3', '-msse4.1', '-msse4.2', '-msse4', '-mavx', '-mavx2']
24
SIMD_NEON_FLAGS = ['-mfpu=neon']
25
CLANG_FLAGS_WITH_ARGS = {
26
'-MT', '-MF', '-MJ', '-MQ', '-D', '-U', '-o', '-x',
27
'-Xpreprocessor', '-include', '-imacros', '-idirafter',
28
'-iprefix', '-iwithprefix', '-iwithprefixbefore',
29
'-isysroot', '-imultilib', '-A', '-isystem', '-iquote',
30
'-install_name', '-compatibility_version', '-mllvm',
31
'-current_version', '-I', '-L', '-include-pch', '-u',
32
'-undefined', '-target', '-Xlinker', '-Xclang', '-z',
33
}
34
# These symbol names are allowed in INCOMING_MODULE_JS_API but are not part of the
35
# default set.
36
EXTRA_INCOMING_JS_API = [
37
'fetchSettings',
38
]
39
40
logger = logging.getLogger('args')
41
42
43
@unique
44
class OFormat(Enum):
45
# Output a relocatable object file. We use this
46
# today for `-r` and `-shared`.
47
OBJECT = auto()
48
WASM = auto()
49
JS = auto()
50
MJS = auto()
51
HTML = auto()
52
BARE = auto()
53
54
55
class EmccOptions:
56
def __init__(self):
57
self.target = ''
58
self.output_file = None
59
self.input_files = []
60
self.no_minify = False
61
self.post_link = False
62
self.save_temps = False
63
self.executable = False
64
self.compiler_wrapper = None
65
self.oformat = None
66
self.requested_debug = None
67
self.emit_symbol_map = False
68
self.use_closure_compiler = None
69
self.js_transform = None
70
self.pre_js = [] # before all js
71
self.post_js = [] # after all js
72
self.extern_pre_js = [] # before all js, external to optimized code
73
self.extern_post_js = [] # after all js, external to optimized code
74
self.preload_files = []
75
self.embed_files = []
76
self.exclude_files = []
77
self.ignore_dynamic_linking = False
78
self.shell_path = None
79
self.source_map_base = ''
80
self.emit_tsd = ''
81
self.embind_emit_tsd = ''
82
self.emrun = False
83
self.cpu_profiler = False
84
self.memory_profiler = False
85
self.use_preload_cache = False
86
self.use_preload_plugins = False
87
self.valid_abspaths = []
88
# Specifies the line ending format to use for all generated text files.
89
# Defaults to using the native EOL on each platform (\r\n on Windows, \n on
90
# Linux & MacOS)
91
self.output_eol = os.linesep
92
self.no_entry = False
93
self.shared = False
94
self.relocatable = False
95
self.reproduce = None
96
self.syntax_only = False
97
self.dash_c = False
98
self.dash_E = False
99
self.dash_S = False
100
self.dash_M = False
101
self.input_language = None
102
self.nostdlib = False
103
self.nostdlibxx = False
104
self.nodefaultlibs = False
105
self.nolibc = False
106
self.nostartfiles = False
107
self.sanitize_minimal_runtime = False
108
self.sanitize = set()
109
self.lib_dirs = []
110
111
112
def is_int(s):
113
try:
114
int(s)
115
return True
116
except ValueError:
117
return False
118
119
120
def validate_arg_level(level_string, max_level, err_msg, clamp=False):
121
try:
122
level = int(level_string)
123
except ValueError:
124
exit_with_error(err_msg)
125
if clamp:
126
if level > max_level:
127
logger.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead")
128
level = max_level
129
if not 0 <= level <= max_level:
130
exit_with_error(err_msg)
131
return level
132
133
134
def version_string():
135
# if the emscripten folder is not a git repo, don't run git show - that can
136
# look up and find the revision in a parent directory that is a git repo
137
revision_suffix = ''
138
if os.path.exists(utils.path_from_root('.git')):
139
git_rev = shared.run_process(
140
['git', 'rev-parse', 'HEAD'],
141
stdout=PIPE, stderr=PIPE, cwd=utils.path_from_root()).stdout.strip()
142
revision_suffix = ' (%s)' % git_rev
143
elif os.path.exists(utils.path_from_root('emscripten-revision.txt')):
144
rev = read_file(utils.path_from_root('emscripten-revision.txt')).strip()
145
revision_suffix = ' (%s)' % rev
146
return f'emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) {utils.EMSCRIPTEN_VERSION}{revision_suffix}'
147
148
149
def is_valid_abspath(options, path_name):
150
# Any path that is underneath the emscripten repository root must be ok.
151
if utils.normalize_path(path_name).startswith(utils.normalize_path(utils.path_from_root())):
152
return True
153
154
def in_directory(root, child):
155
# make both path absolute
156
root = os.path.realpath(root)
157
child = os.path.realpath(child)
158
159
# return true, if the common prefix of both is equal to directory
160
# e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
161
return os.path.commonprefix([root, child]) == root
162
163
for valid_abspath in options.valid_abspaths:
164
if in_directory(valid_abspath, path_name):
165
return True
166
return False
167
168
169
def is_dash_s_for_emcc(args, i):
170
# -s OPT=VALUE or -s OPT or -sOPT are all interpreted as emscripten flags.
171
# -s by itself is a linker option (alias for --strip-all)
172
if args[i] == '-s':
173
if len(args) <= i + 1:
174
return False
175
arg = args[i + 1]
176
else:
177
arg = removeprefix(args[i], '-s')
178
arg = arg.split('=')[0]
179
return arg.isidentifier() and arg.isupper()
180
181
182
def parse_s_args(args):
183
settings_changes = []
184
for i in range(len(args)):
185
if args[i].startswith('-s'):
186
if is_dash_s_for_emcc(args, i):
187
if args[i] == '-s':
188
key = args[i + 1]
189
args[i + 1] = ''
190
else:
191
key = removeprefix(args[i], '-s')
192
args[i] = ''
193
194
# If not = is specified default to 1
195
if '=' not in key:
196
key += '=1'
197
198
# Special handling of browser version targets. A version -1 means that the specific version
199
# is not supported at all. Replace those with INT32_MAX to make it possible to compare e.g.
200
# #if MIN_FIREFOX_VERSION < 68
201
if re.match(r'MIN_.*_VERSION(=.*)?', key):
202
try:
203
if int(key.split('=')[1]) < 0:
204
key = key.split('=')[0] + '=0x7FFFFFFF'
205
except Exception:
206
pass
207
208
settings_changes.append(key)
209
210
newargs = [a for a in args if a]
211
return (settings_changes, newargs)
212
213
214
def parse_args(newargs): # noqa: C901, PLR0912, PLR0915
215
"""Future modifications should consider refactoring to reduce complexity.
216
217
* The McCabe cyclomatiic complexity is currently 117 vs 10 recommended.
218
* There are currently 115 branches vs 12 recommended.
219
* There are currently 302 statements vs 50 recommended.
220
221
To revalidate these numbers, run `ruff check --select=C901,PLR091`.
222
"""
223
options = EmccOptions()
224
settings_changes = []
225
user_js_defines = []
226
should_exit = False
227
skip = False
228
229
for i in range(len(newargs)):
230
if skip:
231
skip = False
232
continue
233
234
# Support legacy '--bind' flag, by mapping to `-lembind` which now
235
# has the same effect
236
if newargs[i] == '--bind':
237
newargs[i] = '-lembind'
238
239
arg = newargs[i]
240
arg_value = None
241
242
if arg in CLANG_FLAGS_WITH_ARGS:
243
# Ignore the next argument rather than trying to parse it. This is needed
244
# because that next arg could, for example, start with `-o` and we don't want
245
# to confuse that with a normal `-o` flag.
246
skip = True
247
elif arg == '-s' and is_dash_s_for_emcc(newargs, i):
248
skip = True
249
250
def check_flag(value):
251
# Check for and consume a flag
252
if arg == value:
253
newargs[i] = ''
254
return True
255
return False
256
257
def check_arg(name):
258
nonlocal arg_value
259
if arg.startswith(name) and '=' in arg:
260
arg_value = arg.split('=', 1)[1]
261
newargs[i] = ''
262
return True
263
if arg == name:
264
if len(newargs) <= i + 1:
265
exit_with_error(f"option '{arg}' requires an argument")
266
arg_value = newargs[i + 1]
267
newargs[i] = ''
268
newargs[i + 1] = ''
269
return True
270
return False
271
272
def consume_arg():
273
nonlocal arg_value
274
assert arg_value is not None
275
rtn = arg_value
276
arg_value = None
277
return rtn
278
279
def consume_arg_file():
280
name = consume_arg()
281
if not os.path.isfile(name):
282
exit_with_error("'%s': file not found: '%s'" % (arg, name))
283
return name
284
285
if arg.startswith('-O'):
286
# Let -O default to -O2, which is what gcc does.
287
requested_level = removeprefix(arg, '-O') or '2'
288
if requested_level == 's':
289
requested_level = 2
290
settings.SHRINK_LEVEL = 1
291
elif requested_level == 'z':
292
requested_level = 2
293
settings.SHRINK_LEVEL = 2
294
elif requested_level == 'g':
295
requested_level = 1
296
settings.SHRINK_LEVEL = 0
297
settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 1)
298
elif requested_level == 'fast':
299
# TODO(https://github.com/emscripten-core/emscripten/issues/21497):
300
# If we ever map `-ffast-math` to `wasm-opt --fast-math` then
301
# then we should enable that too here.
302
requested_level = 3
303
settings.SHRINK_LEVEL = 0
304
else:
305
settings.SHRINK_LEVEL = 0
306
settings.OPT_LEVEL = validate_arg_level(requested_level, 3, 'invalid optimization level: ' + arg, clamp=True)
307
elif check_arg('--js-opts'):
308
logger.warning('--js-opts ignored when using llvm backend')
309
consume_arg()
310
elif check_arg('--llvm-opts'):
311
diagnostics.warning('deprecated', '--llvm-opts is deprecated. All non-emcc args are passed through to clang.')
312
elif arg.startswith('-flto'):
313
if '=' in arg:
314
settings.LTO = arg.split('=')[1]
315
else:
316
settings.LTO = 'full'
317
elif arg == "-fno-lto":
318
settings.LTO = 0
319
elif arg == "--save-temps":
320
options.save_temps = True
321
elif check_arg('--llvm-lto'):
322
logger.warning('--llvm-lto ignored when using llvm backend')
323
consume_arg()
324
elif check_arg('--closure-args'):
325
args = consume_arg()
326
settings.CLOSURE_ARGS += shlex.split(args)
327
elif check_arg('--closure'):
328
options.use_closure_compiler = int(consume_arg())
329
elif check_arg('--js-transform'):
330
options.js_transform = consume_arg()
331
elif check_arg('--reproduce'):
332
options.reproduce = consume_arg()
333
elif check_arg('--pre-js'):
334
options.pre_js.append(consume_arg_file())
335
elif check_arg('--post-js'):
336
options.post_js.append(consume_arg_file())
337
elif check_arg('--extern-pre-js'):
338
options.extern_pre_js.append(consume_arg_file())
339
elif check_arg('--extern-post-js'):
340
options.extern_post_js.append(consume_arg_file())
341
elif check_arg('--compiler-wrapper'):
342
config.COMPILER_WRAPPER = consume_arg()
343
elif check_flag('--post-link'):
344
options.post_link = True
345
elif check_arg('--oformat'):
346
formats = [f.lower() for f in OFormat.__members__]
347
fmt = consume_arg()
348
if fmt not in formats:
349
exit_with_error('invalid output format: `%s` (must be one of %s)' % (fmt, formats))
350
options.oformat = getattr(OFormat, fmt.upper())
351
elif check_arg('--minify'):
352
arg = consume_arg()
353
if arg != '0':
354
exit_with_error('0 is the only supported option for --minify; 1 has been deprecated')
355
options.no_minify = True
356
elif arg.startswith('-g'):
357
options.requested_debug = arg
358
requested_level = removeprefix(arg, '-g') or '3'
359
if is_int(requested_level):
360
# the -gX value is the debug level (-g1, -g2, etc.)
361
settings.DEBUG_LEVEL = validate_arg_level(requested_level, 4, 'invalid debug level: ' + arg)
362
if settings.DEBUG_LEVEL == 0:
363
# Set these explicitly so -g0 overrides previous -g on the cmdline
364
settings.GENERATE_DWARF = 0
365
settings.GENERATE_SOURCE_MAP = 0
366
settings.EMIT_NAME_SECTION = 0
367
elif settings.DEBUG_LEVEL > 1:
368
settings.EMIT_NAME_SECTION = 1
369
# if we don't need to preserve LLVM debug info, do not keep this flag
370
# for clang
371
if settings.DEBUG_LEVEL < 3:
372
newargs[i] = '-g0'
373
else:
374
# for 3+, report -g3 to clang as -g4 etc. are not accepted
375
newargs[i] = '-g3'
376
if settings.DEBUG_LEVEL == 3:
377
settings.GENERATE_DWARF = 1
378
if settings.DEBUG_LEVEL == 4:
379
settings.GENERATE_SOURCE_MAP = 1
380
diagnostics.warning('deprecated', 'please replace -g4 with -gsource-map')
381
else:
382
if requested_level.startswith('force_dwarf'):
383
exit_with_error('gforce_dwarf was a temporary option and is no longer necessary (use -g)')
384
elif requested_level.startswith('separate-dwarf'):
385
# emit full DWARF but also emit it in a file on the side
386
newargs[i] = '-g'
387
# if a file is provided, use that; otherwise use the default location
388
# (note that we do not know the default location until all args have
389
# been parsed, so just note True for now).
390
if requested_level != 'separate-dwarf':
391
if not requested_level.startswith('separate-dwarf=') or requested_level.count('=') != 1:
392
exit_with_error('invalid -gseparate-dwarf=FILENAME notation')
393
settings.SEPARATE_DWARF = requested_level.split('=')[1]
394
else:
395
settings.SEPARATE_DWARF = True
396
settings.GENERATE_DWARF = 1
397
elif requested_level in ['source-map', 'source-map=inline']:
398
settings.GENERATE_SOURCE_MAP = 1 if requested_level == 'source-map' else 2
399
settings.EMIT_NAME_SECTION = 1
400
newargs[i] = '-g'
401
elif requested_level == 'z':
402
# Ignore `-gz`. We don't support debug info compression.
403
continue
404
else:
405
# Other non-integer levels (e.g. -gline-tables-only or -gdwarf-5) are
406
# usually clang flags that emit DWARF. So we pass them through to
407
# clang and make the emscripten code treat it like any other DWARF.
408
settings.GENERATE_DWARF = 1
409
settings.EMIT_NAME_SECTION = 1
410
# In all cases set the emscripten debug level to 3 so that we do not
411
# strip during link (during compile, this does not make a difference).
412
# TODO: possibly decouple some of these flags from the final debug level (#20462)
413
settings.DEBUG_LEVEL = 3
414
elif check_flag('-profiling') or check_flag('--profiling'):
415
settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 2)
416
settings.EMIT_NAME_SECTION = 1
417
elif check_flag('-profiling-funcs') or check_flag('--profiling-funcs'):
418
settings.EMIT_NAME_SECTION = 1
419
elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler':
420
if newargs[i] == '--memoryprofiler':
421
options.memory_profiler = True
422
newargs[i] = ''
423
settings_changes.append('EMSCRIPTEN_TRACING=1')
424
elif check_flag('--emit-symbol-map'):
425
options.emit_symbol_map = True
426
settings.EMIT_SYMBOL_MAP = 1
427
elif check_arg('--emit-minification-map'):
428
settings.MINIFICATION_MAP = consume_arg()
429
elif check_arg('--embed-file'):
430
options.embed_files.append(consume_arg())
431
elif check_arg('--preload-file'):
432
options.preload_files.append(consume_arg())
433
elif check_arg('--exclude-file'):
434
options.exclude_files.append(consume_arg())
435
elif check_flag('--use-preload-cache'):
436
options.use_preload_cache = True
437
elif check_flag('--no-heap-copy'):
438
diagnostics.warning('legacy-settings', 'ignoring legacy flag --no-heap-copy (that is the only mode supported now)')
439
elif check_flag('--use-preload-plugins'):
440
options.use_preload_plugins = True
441
elif check_flag('--ignore-dynamic-linking'):
442
options.ignore_dynamic_linking = True
443
elif arg == '-v':
444
shared.PRINT_SUBPROCS = True
445
elif arg == '-###':
446
shared.SKIP_SUBPROCS = True
447
elif check_arg('--shell-file'):
448
options.shell_path = consume_arg_file()
449
elif check_arg('--source-map-base'):
450
options.source_map_base = consume_arg()
451
elif check_arg('--embind-emit-tsd'):
452
diagnostics.warning('deprecated', '--embind-emit-tsd is deprecated. Use --emit-tsd instead.')
453
options.emit_tsd = consume_arg()
454
elif check_arg('--emit-tsd'):
455
options.emit_tsd = consume_arg()
456
elif check_flag('--no-entry'):
457
options.no_entry = True
458
elif check_flag('--remove-duplicates'):
459
diagnostics.warning('legacy-settings', '--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase')
460
elif check_flag('--jcache'):
461
logger.error('jcache is no longer supported')
462
elif check_arg('--cache'):
463
config.CACHE = os.path.abspath(consume_arg())
464
cache.setup()
465
# Ensure child processes share the same cache (e.g. when using emcc to compiler system
466
# libraries)
467
os.environ['EM_CACHE'] = config.CACHE
468
elif check_flag('--clear-cache'):
469
logger.info('clearing cache as requested by --clear-cache: `%s`', cache.cachedir)
470
cache.erase()
471
shared.perform_sanity_checks() # this is a good time for a sanity check
472
should_exit = True
473
elif check_flag('--clear-ports'):
474
logger.info('clearing ports and cache as requested by --clear-ports')
475
ports.clear()
476
cache.erase()
477
shared.perform_sanity_checks() # this is a good time for a sanity check
478
should_exit = True
479
elif check_flag('--check'):
480
print(version_string(), file=sys.stderr)
481
shared.check_sanity(force=True)
482
should_exit = True
483
elif check_flag('--show-ports'):
484
ports.show_ports()
485
should_exit = True
486
elif check_arg('--memory-init-file'):
487
exit_with_error('--memory-init-file is no longer supported')
488
elif check_flag('--proxy-to-worker'):
489
settings_changes.append('PROXY_TO_WORKER=1')
490
elif check_arg('--valid-abspath'):
491
options.valid_abspaths.append(consume_arg())
492
elif check_flag('--separate-asm'):
493
exit_with_error('cannot --separate-asm with the wasm backend, since not emitting asm.js')
494
elif arg.startswith(('-I', '-L')):
495
path_name = arg[2:]
496
# Look for '/' explicitly so that we can also diagnose identically if -I/foo/bar is passed on Windows.
497
# Python since 3.13 does not treat '/foo/bar' as an absolute path on Windows.
498
if (path_name.startswith('/') or os.path.isabs(path_name)) and not is_valid_abspath(options, path_name):
499
# Of course an absolute path to a non-system-specific library or header
500
# is fine, and you can ignore this warning. The danger are system headers
501
# that are e.g. x86 specific and non-portable. The emscripten bundled
502
# headers are modified to be portable, local system ones are generally not.
503
diagnostics.warning(
504
'absolute-paths', f'-I or -L of an absolute path "{arg}" '
505
'encountered. If this is to a local system header/library, it may '
506
'cause problems (local system files make sense for compiling natively '
507
'on your system, but not necessarily to JavaScript).')
508
if arg.startswith('-L'):
509
options.lib_dirs.append(path_name)
510
elif check_flag('--emrun'):
511
options.emrun = True
512
elif check_flag('--cpuprofiler'):
513
options.cpu_profiler = True
514
elif check_flag('--threadprofiler'):
515
settings_changes.append('PTHREADS_PROFILING=1')
516
elif arg in ('-fcolor-diagnostics', '-fdiagnostics-color', '-fdiagnostics-color=always'):
517
diagnostics.color_enabled = True
518
elif arg in ('-fno-color-diagnostics', '-fdiagnostics-color=never'):
519
diagnostics.color_enabled = False
520
elif arg == '-fansi-escape-codes':
521
diagnostics.force_ansi = True
522
elif arg == '-fno-exceptions':
523
settings.DISABLE_EXCEPTION_CATCHING = 1
524
settings.DISABLE_EXCEPTION_THROWING = 1
525
settings.WASM_EXCEPTIONS = 0
526
elif arg == '-mbulk-memory':
527
settings.BULK_MEMORY = 1
528
feature_matrix.enable_feature(feature_matrix.Feature.BULK_MEMORY,
529
'-mbulk-memory',
530
override=True)
531
elif arg == '-mno-bulk-memory':
532
settings.BULK_MEMORY = 0
533
feature_matrix.disable_feature(feature_matrix.Feature.BULK_MEMORY)
534
elif arg == '-msign-ext':
535
feature_matrix.enable_feature(feature_matrix.Feature.SIGN_EXT,
536
'-msign-ext',
537
override=True)
538
elif arg == '-mno-sign-ext':
539
feature_matrix.disable_feature(feature_matrix.Feature.SIGN_EXT)
540
elif arg == '-mnontrappting-fptoint':
541
feature_matrix.enable_feature(feature_matrix.Feature.NON_TRAPPING_FPTOINT,
542
'-mnontrapping-fptoint',
543
override=True)
544
elif arg == '-mno-nontrapping-fptoint':
545
feature_matrix.disable_feature(feature_matrix.Feature.NON_TRAPPING_FPTOINT)
546
elif arg == '-fexceptions':
547
# TODO Currently -fexceptions only means Emscripten EH. Switch to wasm
548
# exception handling by default when -fexceptions is given when wasm
549
# exception handling becomes stable.
550
settings.DISABLE_EXCEPTION_THROWING = 0
551
settings.DISABLE_EXCEPTION_CATCHING = 0
552
elif arg == '-fwasm-exceptions':
553
settings.WASM_EXCEPTIONS = 1
554
elif arg == '-fignore-exceptions':
555
settings.DISABLE_EXCEPTION_CATCHING = 1
556
elif check_arg('--default-obj-ext'):
557
exit_with_error('--default-obj-ext is no longer supported by emcc')
558
elif arg.startswith('-fsanitize=cfi'):
559
exit_with_error('emscripten does not currently support -fsanitize=cfi')
560
elif check_arg('--output_eol') or check_arg('--output-eol'):
561
style = consume_arg()
562
if style.lower() == 'windows':
563
options.output_eol = '\r\n'
564
elif style.lower() == 'linux':
565
options.output_eol = '\n'
566
else:
567
exit_with_error(f'invalid value for --output-eol: `{style}`')
568
# Record PTHREADS setting because it controls whether --shared-memory is passed to lld
569
elif arg == '-pthread':
570
settings.PTHREADS = 1
571
# Also set the legacy setting name, in case use JS code depends on it.
572
settings.USE_PTHREADS = 1
573
elif arg == '-no-pthread':
574
settings.PTHREADS = 0
575
# Also set the legacy setting name, in case use JS code depends on it.
576
settings.USE_PTHREADS = 0
577
elif arg == '-pthreads':
578
exit_with_error('unrecognized command-line option `-pthreads`; did you mean `-pthread`?')
579
elif arg in ('-fno-diagnostics-color', '-fdiagnostics-color=never'):
580
colored_logger.disable()
581
diagnostics.color_enabled = False
582
elif arg == '-fno-rtti':
583
settings.USE_RTTI = 0
584
elif arg == '-frtti':
585
settings.USE_RTTI = 1
586
elif arg.startswith('-jsD'):
587
key = removeprefix(arg, '-jsD')
588
if '=' in key:
589
key, value = key.split('=')
590
else:
591
value = '1'
592
if key in settings.keys():
593
exit_with_error(f'{arg}: cannot change built-in settings values with a -jsD directive. Pass -s{key}={value} instead!')
594
user_js_defines += [(key, value)]
595
newargs[i] = ''
596
elif check_flag('-shared'):
597
options.shared = True
598
elif check_flag('-r'):
599
options.relocatable = True
600
elif arg.startswith('-o'):
601
options.output_file = removeprefix(arg, '-o')
602
elif check_arg('-target') or check_arg('--target'):
603
options.target = consume_arg()
604
if options.target not in ('wasm32', 'wasm64', 'wasm64-unknown-emscripten', 'wasm32-unknown-emscripten'):
605
exit_with_error(f'unsupported target: {options.target} (emcc only supports wasm64-unknown-emscripten and wasm32-unknown-emscripten)')
606
elif check_arg('--use-port'):
607
ports.handle_use_port_arg(settings, consume_arg())
608
elif arg in ('-c', '--precompile'):
609
options.dash_c = True
610
elif arg == '-S':
611
options.dash_S = True
612
elif arg == '-E':
613
options.dash_E = True
614
elif arg in ('-M', '-MM'):
615
options.dash_M = True
616
elif arg.startswith('-x'):
617
# TODO(sbc): Handle multiple -x flags on the same command line
618
options.input_language = arg
619
elif arg == '-fsyntax-only':
620
options.syntax_only = True
621
elif arg in SIMD_INTEL_FEATURE_TOWER or arg in SIMD_NEON_FLAGS:
622
# SSEx is implemented on top of SIMD128 instruction set, but do not pass SSE flags to LLVM
623
# so it won't think about generating native x86 SSE code.
624
newargs[i] = ''
625
elif arg == '-nostdlib':
626
options.nostdlib = True
627
elif arg == '-nostdlibxx':
628
options.nostdlibxx = True
629
elif arg == '-nodefaultlibs':
630
options.nodefaultlibs = True
631
elif arg == '-nolibc':
632
options.nolibc = True
633
elif arg == '-nostartfiles':
634
options.nostartfiles = True
635
elif arg == '-fsanitize-minimal-runtime':
636
options.sanitize_minimal_runtime = True
637
elif arg.startswith('-fsanitize='):
638
options.sanitize.update(arg.split('=', 1)[1].split(','))
639
elif arg.startswith('-fno-sanitize='):
640
options.sanitize.difference_update(arg.split('=', 1)[1].split(','))
641
elif arg and (arg == '-' or not arg.startswith('-')):
642
options.input_files.append(arg)
643
644
if should_exit:
645
sys.exit(0)
646
647
newargs = [a for a in newargs if a]
648
return options, settings_changes, user_js_defines, newargs
649
650
651
def expand_byte_size_suffixes(value):
652
"""Given a string with KB/MB size suffixes, such as "32MB", computes how
653
many bytes that is and returns it as an integer.
654
"""
655
value = value.strip()
656
match = re.match(r'^(\d+)\s*([kmgt]?b)?$', value, re.I)
657
if not match:
658
exit_with_error("invalid byte size `%s`. Valid suffixes are: kb, mb, gb, tb" % value)
659
value, suffix = match.groups()
660
value = int(value)
661
if suffix:
662
size_suffixes = {suffix: 1024 ** i for i, suffix in enumerate(['b', 'kb', 'mb', 'gb', 'tb'])}
663
value *= size_suffixes[suffix.lower()]
664
return value
665
666
667
def parse_symbol_list_file(contents):
668
"""Parse contents of one-symbol-per-line response file. This format can by used
669
with, for example, -sEXPORTED_FUNCTIONS=@filename and avoids the need for any
670
kind of quoting or escaping.
671
"""
672
values = contents.splitlines()
673
return [v.strip() for v in values if not v.startswith('#')]
674
675
676
def parse_value(text, expected_type):
677
# Note that using response files can introduce whitespace, if the file
678
# has a newline at the end. For that reason, we rstrip() in relevant
679
# places here.
680
def parse_string_value(text):
681
first = text[0]
682
if first in {"'", '"'}:
683
text = text.rstrip()
684
if text[-1] != text[0] or len(text) < 2:
685
raise ValueError(f'unclosed quoted string. expected final character to be "{text[0]}" and length to be greater than 1 in "{text[0]}"')
686
return text[1:-1]
687
return text
688
689
def parse_string_list_members(text):
690
sep = ','
691
values = text.split(sep)
692
result = []
693
index = 0
694
while True:
695
current = values[index].lstrip() # Cannot safely rstrip for cases like: "HERE-> ,"
696
if not len(current):
697
raise ValueError('empty value in string list')
698
first = current[0]
699
if first not in {"'", '"'}:
700
result.append(current.rstrip())
701
else:
702
start = index
703
while True: # Continue until closing quote found
704
if index >= len(values):
705
raise ValueError(f"unclosed quoted string. expected final character to be '{first}' in '{values[start]}'")
706
new = values[index].rstrip()
707
if new and new[-1] == first:
708
if start == index:
709
result.append(current.rstrip()[1:-1])
710
else:
711
result.append((current + sep + new)[1:-1])
712
break
713
else:
714
current += sep + values[index]
715
index += 1
716
717
index += 1
718
if index >= len(values):
719
break
720
return result
721
722
def parse_string_list(text):
723
text = text.rstrip()
724
if text and text[0] == '[':
725
if text[-1] != ']':
726
raise ValueError('unterminated string list. expected final character to be "]"')
727
text = text[1:-1]
728
if text.strip() == "":
729
return []
730
return parse_string_list_members(text)
731
732
if expected_type == list or (text and text[0] == '['):
733
# if json parsing fails, we fall back to our own parser, which can handle a few
734
# simpler syntaxes
735
try:
736
parsed = json.loads(text)
737
except ValueError:
738
return parse_string_list(text)
739
740
# if we succeeded in parsing as json, check some properties of it before returning
741
if type(parsed) not in (str, list):
742
raise ValueError(f'settings must be strings or lists (not ${type(parsed)})')
743
if type(parsed) is list:
744
for elem in parsed:
745
if type(elem) is not str:
746
raise ValueError(f'list members in settings must be strings (not ${type(elem)})')
747
748
return parsed
749
750
if expected_type == float:
751
try:
752
return float(text)
753
except ValueError:
754
pass
755
756
try:
757
if text.startswith('0x'):
758
base = 16
759
else:
760
base = 10
761
return int(text, base)
762
except ValueError:
763
return parse_string_value(text)
764
765
766
def apply_user_settings():
767
"""Take a map of users settings {NAME: VALUE} and apply them to the global
768
settings object.
769
"""
770
771
# Stash a copy of all available incoming APIs before the user can potentially override it
772
settings.ALL_INCOMING_MODULE_JS_API = settings.INCOMING_MODULE_JS_API + EXTRA_INCOMING_JS_API
773
774
for key, value in user_settings.items():
775
if key in settings.internal_settings:
776
exit_with_error('%s is an internal setting and cannot be set from command line', key)
777
778
# map legacy settings which have aliases to the new names
779
# but keep the original key so errors are correctly reported via the `setattr` below
780
user_key = key
781
if key in settings.legacy_settings and key in settings.alt_names:
782
key = settings.alt_names[key]
783
784
# In those settings fields that represent amount of memory, translate suffixes to multiples of 1024.
785
if key in MEM_SIZE_SETTINGS:
786
value = str(expand_byte_size_suffixes(value))
787
788
filename = None
789
if value and value[0] == '@':
790
filename = removeprefix(value, '@')
791
if not os.path.isfile(filename):
792
exit_with_error('%s: file not found parsing argument: %s=%s' % (filename, key, value))
793
value = read_file(filename).strip()
794
else:
795
value = value.replace('\\', '\\\\')
796
797
expected_type = settings.types.get(key)
798
799
if filename and expected_type == list and value.strip()[0] != '[':
800
# Prefer simpler one-line-per value parser
801
value = parse_symbol_list_file(value)
802
else:
803
try:
804
value = parse_value(value, expected_type)
805
except Exception as e:
806
exit_with_error(f'error parsing "-s" setting "{key}={value}": {e}')
807
808
setattr(settings, user_key, value)
809
810
if key == 'EXPORTED_FUNCTIONS':
811
# used for warnings in emscripten.py
812
settings.USER_EXPORTS = settings.EXPORTED_FUNCTIONS.copy()
813
814
# TODO(sbc): Remove this legacy way.
815
if key == 'WASM_OBJECT_FILES':
816
settings.LTO = 0 if value else 'full'
817
818
if key == 'JSPI':
819
settings.ASYNCIFY = 2
820
if key == 'JSPI_IMPORTS':
821
settings.ASYNCIFY_IMPORTS = value
822
if key == 'JSPI_EXPORTS':
823
settings.ASYNCIFY_EXPORTS = value
824
825
826
def normalize_boolean_setting(name, value):
827
# boolean NO_X settings are aliases for X
828
# (note that *non*-boolean setting values have special meanings,
829
# and we can't just flip them, so leave them as-is to be
830
# handled in a special way later)
831
if name.startswith('NO_') and value in ('0', '1'):
832
name = removeprefix(name, 'NO_')
833
value = str(1 - int(value))
834
return name, value
835
836
837
@ToolchainProfiler.profile()
838
def parse_arguments(args):
839
newargs = list(args)
840
841
# Scan and strip emscripten specific cmdline warning flags.
842
# This needs to run before other cmdline flags have been parsed, so that
843
# warnings are properly printed during arg parse.
844
newargs = diagnostics.capture_warnings(newargs)
845
846
if not diagnostics.is_enabled('deprecated'):
847
settings.WARN_DEPRECATED = 0
848
849
for i in range(len(newargs)):
850
if newargs[i] in ('-l', '-L', '-I', '-z', '--js-library', '-o', '-x', '-u'):
851
# Scan for flags that can be written as either one or two arguments
852
# and normalize them to the single argument form.
853
if newargs[i] == '--js-library':
854
newargs[i] += '='
855
if len(newargs) <= i + 1:
856
exit_with_error(f"option '{newargs[i]}' requires an argument")
857
newargs[i] += newargs[i + 1]
858
newargs[i + 1] = ''
859
860
options, settings_changes, user_js_defines, newargs = parse_args(newargs)
861
862
if options.post_link or options.oformat == OFormat.BARE:
863
diagnostics.warning('experimental', '--oformat=bare/--post-link are experimental and subject to change.')
864
865
explicit_settings_changes, newargs = parse_s_args(newargs)
866
settings_changes += explicit_settings_changes
867
868
for s in settings_changes:
869
key, value = s.split('=', 1)
870
key, value = normalize_boolean_setting(key, value)
871
user_settings[key] = value
872
873
# STRICT is used when applying settings so it needs to be applied first before
874
# calling `apply_user_settings`.
875
strict_cmdline = user_settings.get('STRICT')
876
if strict_cmdline:
877
settings.STRICT = int(strict_cmdline)
878
879
# Apply user -jsD settings
880
for s in user_js_defines:
881
settings[s[0]] = s[1]
882
883
# Apply -s settings in newargs here (after optimization levels, so they can override them)
884
apply_user_settings()
885
886
return options, newargs
887
888