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