Buckets:
| # 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') | |
| 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_flag('-m64'): | |
| settings.MEMORY64 = 1 | |
| elif check_flag('-m32'): | |
| settings.MEMORY64 = 0 | |
| 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): | |
| """Convert a string with KB/MB size suffix, such as "32MB", to number of bytes.""" | |
| 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] | |
| 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 | |
Xet Storage Details
- Size:
- 31.8 kB
- Xet hash:
- ee277ecb3ab006fc60676b956474c3470b438ee9307c24017ccb1c502fcda514
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.