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