from tools.toolchain_profiler import ToolchainProfiler
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 shared, utils, ports, diagnostics, config
from tools import cache, feature_matrix, colored_logger
from tools.shared import exit_with_error
from tools.settings import settings, user_settings, MEM_SIZE_SETTINGS
from tools.utils import removeprefix, 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',
}
EXTRA_INCOMING_JS_API = [
'fetchSettings',
]
logger = logging.getLogger('args')
@unique
class OFormat(Enum):
OBJECT = auto()
WASM = auto()
JS = auto()
MJS = auto()
HTML = auto()
BARE = auto()
class EmccOptions:
def __init__(self):
self.target = ''
self.output_file = None
self.input_files = []
self.no_minify = False
self.post_link = False
self.save_temps = False
self.executable = False
self.compiler_wrapper = None
self.oformat = None
self.requested_debug = None
self.emit_symbol_map = False
self.use_closure_compiler = None
self.js_transform = None
self.pre_js = []
self.post_js = []
self.extern_pre_js = []
self.extern_post_js = []
self.preload_files = []
self.embed_files = []
self.exclude_files = []
self.ignore_dynamic_linking = False
self.shell_path = None
self.source_map_base = ''
self.emit_tsd = ''
self.embind_emit_tsd = ''
self.emrun = False
self.cpu_profiler = False
self.memory_profiler = False
self.use_preload_cache = False
self.use_preload_plugins = False
self.valid_abspaths = []
self.output_eol = os.linesep
self.no_entry = False
self.shared = False
self.relocatable = False
self.reproduce = None
self.syntax_only = False
self.dash_c = False
self.dash_E = False
self.dash_S = False
self.dash_M = False
self.input_language = None
self.nostdlib = False
self.nostdlibxx = False
self.nodefaultlibs = False
self.nolibc = False
self.nostartfiles = False
self.sanitize_minimal_runtime = False
self.sanitize = set()
self.lib_dirs = []
def is_int(s):
try:
int(s)
return True
except ValueError:
return False
def validate_arg_level(level_string, max_level, err_msg, clamp=False):
try:
level = int(level_string)
except ValueError:
exit_with_error(err_msg)
if clamp:
if level > max_level:
logger.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead")
level = max_level
if not 0 <= level <= max_level:
exit_with_error(err_msg)
return level
def version_string():
revision_suffix = ''
if os.path.exists(utils.path_from_root('.git')):
git_rev = shared.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(options, path_name):
if utils.normalize_path(path_name).startswith(utils.normalize_path(utils.path_from_root())):
return True
def in_directory(root, child):
root = os.path.realpath(root)
child = os.path.realpath(child)
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):
if args[i] == '-s':
if len(args) <= i + 1:
return False
arg = args[i + 1]
else:
arg = removeprefix(args[i], '-s')
arg = arg.split('=')[0]
return arg.isidentifier() and arg.isupper()
def parse_s_args(args):
settings_changes = []
for i in range(len(args)):
if args[i].startswith('-s'):
if is_dash_s_for_emcc(args, i):
if args[i] == '-s':
key = args[i + 1]
args[i + 1] = ''
else:
key = removeprefix(args[i], '-s')
args[i] = ''
if '=' not in key:
key += '=1'
if re.match(r'MIN_.*_VERSION(=.*)?', key):
try:
if int(key.split('=')[1]) < 0:
key = key.split('=')[0] + '=0x7FFFFFFF'
except Exception:
pass
settings_changes.append(key)
newargs = [a for a in args if a]
return (settings_changes, newargs)
def parse_args(newargs):
"""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`.
"""
options = EmccOptions()
settings_changes = []
user_js_defines = []
should_exit = False
skip = False
for i in range(len(newargs)):
if skip:
skip = False
continue
if newargs[i] == '--bind':
newargs[i] = '-lembind'
arg = newargs[i]
arg_value = None
if arg in CLANG_FLAGS_WITH_ARGS:
skip = True
elif arg == '-s' and is_dash_s_for_emcc(newargs, i):
skip = True
def check_flag(value):
if arg == value:
newargs[i] = ''
return True
return False
def check_arg(name):
nonlocal arg_value
if arg.startswith(name) and '=' in arg:
arg_value = arg.split('=', 1)[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.startswith('-O'):
requested_level = removeprefix(arg, '-O') or '2'
if requested_level == 's':
requested_level = 2
settings.SHRINK_LEVEL = 1
elif requested_level == 'z':
requested_level = 2
settings.SHRINK_LEVEL = 2
elif requested_level == 'g':
requested_level = 1
settings.SHRINK_LEVEL = 0
settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 1)
elif requested_level == 'fast':
requested_level = 3
settings.SHRINK_LEVEL = 0
else:
settings.SHRINK_LEVEL = 0
settings.OPT_LEVEL = validate_arg_level(requested_level, 3, 'invalid optimization level: ' + arg, clamp=True)
elif check_arg('--js-opts'):
logger.warning('--js-opts ignored when using llvm backend')
consume_arg()
elif check_arg('--llvm-opts'):
diagnostics.warning('deprecated', '--llvm-opts is deprecated. All non-emcc args are passed through to clang.')
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('--llvm-lto'):
logger.warning('--llvm-lto ignored when using llvm backend')
consume_arg()
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
requested_level = removeprefix(arg, '-g') or '3'
if is_int(requested_level):
settings.DEBUG_LEVEL = validate_arg_level(requested_level, 4, 'invalid debug level: ' + arg)
if settings.DEBUG_LEVEL == 0:
settings.GENERATE_DWARF = 0
settings.GENERATE_SOURCE_MAP = 0
settings.EMIT_NAME_SECTION = 0
elif settings.DEBUG_LEVEL > 1:
settings.EMIT_NAME_SECTION = 1
if settings.DEBUG_LEVEL < 3:
newargs[i] = '-g0'
else:
newargs[i] = '-g3'
if settings.DEBUG_LEVEL == 3:
settings.GENERATE_DWARF = 1
if settings.DEBUG_LEVEL == 4:
settings.GENERATE_SOURCE_MAP = 1
diagnostics.warning('deprecated', 'please replace -g4 with -gsource-map')
else:
if requested_level.startswith('force_dwarf'):
exit_with_error('gforce_dwarf was a temporary option and is no longer necessary (use -g)')
elif requested_level.startswith('separate-dwarf'):
newargs[i] = '-g'
if requested_level != 'separate-dwarf':
if not requested_level.startswith('separate-dwarf=') or requested_level.count('=') != 1:
exit_with_error('invalid -gseparate-dwarf=FILENAME notation')
settings.SEPARATE_DWARF = requested_level.split('=')[1]
else:
settings.SEPARATE_DWARF = True
settings.GENERATE_DWARF = 1
elif requested_level in ['source-map', 'source-map=inline']:
settings.GENERATE_SOURCE_MAP = 1 if requested_level == 'source-map' else 2
settings.EMIT_NAME_SECTION = 1
newargs[i] = '-g'
elif requested_level == 'z':
continue
else:
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_changes.append('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('--no-heap-copy'):
diagnostics.warning('legacy-settings', 'ignoring legacy flag --no-heap-copy (that is the only mode supported now)')
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_path = consume_arg_file()
elif check_arg('--source-map-base'):
options.source_map_base = consume_arg()
elif check_arg('--embind-emit-tsd'):
diagnostics.warning('deprecated', '--embind-emit-tsd is deprecated. Use --emit-tsd instead.')
options.emit_tsd = consume_arg()
elif check_arg('--emit-tsd'):
options.emit_tsd = consume_arg()
elif check_flag('--no-entry'):
options.no_entry = True
elif check_flag('--remove-duplicates'):
diagnostics.warning('legacy-settings', '--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase')
elif check_flag('--jcache'):
logger.error('jcache is no longer supported')
elif check_arg('--cache'):
config.CACHE = os.path.abspath(consume_arg())
cache.setup()
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()
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()
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('--memory-init-file'):
exit_with_error('--memory-init-file is no longer supported')
elif check_flag('--proxy-to-worker'):
settings_changes.append('PROXY_TO_WORKER=1')
elif check_arg('--valid-abspath'):
options.valid_abspaths.append(consume_arg())
elif check_flag('--separate-asm'):
exit_with_error('cannot --separate-asm with the wasm backend, since not emitting asm.js')
elif arg.startswith(('-I', '-L')):
path_name = arg[2:]
if (path_name.startswith('/') or os.path.isabs(path_name)) and not is_valid_abspath(options, path_name):
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_changes.append('PTHREADS_PROFILING=1')
elif arg in ('-fcolor-diagnostics', '-fdiagnostics-color', '-fdiagnostics-color=always'):
diagnostics.color_enabled = True
elif arg in ('-fno-color-diagnostics', '-fdiagnostics-color=never'):
diagnostics.color_enabled = False
elif arg == '-fansi-escape-codes':
diagnostics.force_ansi = True
elif arg == '-fno-exceptions':
settings.DISABLE_EXCEPTION_CATCHING = 1
settings.DISABLE_EXCEPTION_THROWING = 1
settings.WASM_EXCEPTIONS = 0
elif arg == '-mbulk-memory':
settings.BULK_MEMORY = 1
feature_matrix.enable_feature(feature_matrix.Feature.BULK_MEMORY,
'-mbulk-memory',
override=True)
elif arg == '-mno-bulk-memory':
settings.BULK_MEMORY = 0
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 == '-mnontrappting-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':
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 check_arg('--default-obj-ext'):
exit_with_error('--default-obj-ext is no longer supported by emcc')
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}`')
elif arg == '-pthread':
settings.PTHREADS = 1
settings.USE_PTHREADS = 1
elif arg == '-no-pthread':
settings.PTHREADS = 0
settings.USE_PTHREADS = 0
elif arg == '-pthreads':
exit_with_error('unrecognized command-line option `-pthreads`; did you mean `-pthread`?')
elif arg in ('-fno-diagnostics-color', '-fdiagnostics-color=never'):
colored_logger.disable()
diagnostics.color_enabled = False
elif arg == '-fno-rtti':
settings.USE_RTTI = 0
elif arg == '-frtti':
settings.USE_RTTI = 1
elif arg.startswith('-jsD'):
key = removeprefix(arg, '-jsD')
if '=' in key:
key, value = key.split('=')
else:
value = '1'
if key in settings.keys():
exit_with_error(f'{arg}: cannot change built-in settings values with a -jsD directive. Pass -s{key}={value} instead!')
user_js_defines += [(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 = removeprefix(arg, '-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'):
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:
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)
newargs = [a for a in newargs if a]
return options, settings_changes, user_js_defines, newargs
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):
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()
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:
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 text.strip() == "":
return []
return parse_string_list_members(text)
if expected_type == list or (text and text[0] == '['):
try:
parsed = json.loads(text)
except ValueError:
return parse_string_list(text)
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.
"""
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)
user_key = key
if key in settings.legacy_settings and key in settings.alt_names:
key = settings.alt_names[key]
if key in MEM_SIZE_SETTINGS:
value = str(expand_byte_size_suffixes(value))
filename = None
if value and value[0] == '@':
filename = removeprefix(value, '@')
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] != '[':
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':
settings.USER_EXPORTS = settings.EXPORTED_FUNCTIONS.copy()
if key == 'WASM_OBJECT_FILES':
settings.LTO = 0 if value else 'full'
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):
if name.startswith('NO_') and value in ('0', '1'):
name = removeprefix(name, 'NO_')
value = str(1 - int(value))
return name, value
@ToolchainProfiler.profile()
def parse_arguments(args):
newargs = list(args)
newargs = diagnostics.capture_warnings(newargs)
if not diagnostics.is_enabled('deprecated'):
settings.WARN_DEPRECATED = 0
for i in range(len(newargs)):
if newargs[i] in ('-l', '-L', '-I', '-z', '--js-library', '-o', '-x', '-u'):
if newargs[i] == '--js-library':
newargs[i] += '='
if len(newargs) <= i + 1:
exit_with_error(f"option '{newargs[i]}' requires an argument")
newargs[i] += newargs[i + 1]
newargs[i + 1] = ''
options, settings_changes, user_js_defines, 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.')
explicit_settings_changes, newargs = parse_s_args(newargs)
settings_changes += explicit_settings_changes
for s in settings_changes:
key, value = s.split('=', 1)
key, value = normalize_boolean_setting(key, value)
user_settings[key] = value
strict_cmdline = user_settings.get('STRICT')
if strict_cmdline:
settings.STRICT = int(strict_cmdline)
for s in user_js_defines:
settings[s[0]] = s[1]
apply_user_settings()
return options, newargs