Skip to content

Commit cc453b2

Browse files
authored
Move more argument parsing code to tools/cmdline.py. NFC (#24531)
1 parent f5056c3 commit cc453b2

File tree

3 files changed

+257
-256
lines changed

3 files changed

+257
-256
lines changed

emcc.py

Lines changed: 4 additions & 253 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222

2323
from tools.toolchain_profiler import ToolchainProfiler
2424

25-
import json
2625
import logging
2726
import os
28-
import re
2927
import shlex
3028
import shutil
3129
import sys
@@ -41,12 +39,12 @@
4139
from tools.shared import run_process, exit_with_error, DEBUG
4240
from tools.shared import in_temp
4341
from tools.shared import DYLIB_EXTENSIONS
44-
from tools.cmdline import SIMD_INTEL_FEATURE_TOWER, SIMD_NEON_FLAGS, CLANG_FLAGS_WITH_ARGS, OFormat
42+
from tools.cmdline import SIMD_INTEL_FEATURE_TOWER, SIMD_NEON_FLAGS, CLANG_FLAGS_WITH_ARGS
4543
from tools.response_file import substitute_response_files
4644
from tools import config
4745
from tools import cache
48-
from tools.settings import default_setting, user_settings, settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS
49-
from tools.utils import read_file, removeprefix, memoize
46+
from tools.settings import default_setting, user_settings, settings, COMPILE_TIME_SETTINGS
47+
from tools.utils import read_file, memoize
5048

5149
logger = logging.getLogger('emcc')
5250

@@ -70,12 +68,6 @@
7068
os.devnull, # consider the special endingless filenames like /dev/null to be C
7169
} | PREPROCESSED_EXTENSIONS
7270

73-
# These symbol names are allowed in INCOMING_MODULE_JS_API but are not part of the
74-
# default set.
75-
EXTRA_INCOMING_JS_API = [
76-
'fetchSettings',
77-
]
78-
7971
LINK_ONLY_FLAGS = {
8072
'--bind', '--closure', '--cpuprofiler', '--embed-file',
8173
'--emit-symbol-map', '--emrun', '--exclude-file', '--extern-post-js',
@@ -172,82 +164,6 @@ def make_relative(filename):
172164
reproduce_file.add(rsp_name, os.path.join(root, 'response.txt'))
173165

174166

175-
def expand_byte_size_suffixes(value):
176-
"""Given a string with KB/MB size suffixes, such as "32MB", computes how
177-
many bytes that is and returns it as an integer.
178-
"""
179-
value = value.strip()
180-
match = re.match(r'^(\d+)\s*([kmgt]?b)?$', value, re.I)
181-
if not match:
182-
exit_with_error("invalid byte size `%s`. Valid suffixes are: kb, mb, gb, tb" % value)
183-
value, suffix = match.groups()
184-
value = int(value)
185-
if suffix:
186-
size_suffixes = {suffix: 1024 ** i for i, suffix in enumerate(['b', 'kb', 'mb', 'gb', 'tb'])}
187-
value *= size_suffixes[suffix.lower()]
188-
return value
189-
190-
191-
def apply_user_settings():
192-
"""Take a map of users settings {NAME: VALUE} and apply them to the global
193-
settings object.
194-
"""
195-
196-
# Stash a copy of all available incoming APIs before the user can potentially override it
197-
settings.ALL_INCOMING_MODULE_JS_API = settings.INCOMING_MODULE_JS_API + EXTRA_INCOMING_JS_API
198-
199-
for key, value in user_settings.items():
200-
if key in settings.internal_settings:
201-
exit_with_error('%s is an internal setting and cannot be set from command line', key)
202-
203-
# map legacy settings which have aliases to the new names
204-
# but keep the original key so errors are correctly reported via the `setattr` below
205-
user_key = key
206-
if key in settings.legacy_settings and key in settings.alt_names:
207-
key = settings.alt_names[key]
208-
209-
# In those settings fields that represent amount of memory, translate suffixes to multiples of 1024.
210-
if key in MEM_SIZE_SETTINGS:
211-
value = str(expand_byte_size_suffixes(value))
212-
213-
filename = None
214-
if value and value[0] == '@':
215-
filename = removeprefix(value, '@')
216-
if not os.path.isfile(filename):
217-
exit_with_error('%s: file not found parsing argument: %s=%s' % (filename, key, value))
218-
value = read_file(filename).strip()
219-
else:
220-
value = value.replace('\\', '\\\\')
221-
222-
expected_type = settings.types.get(key)
223-
224-
if filename and expected_type == list and value.strip()[0] != '[':
225-
# Prefer simpler one-line-per value parser
226-
value = parse_symbol_list_file(value)
227-
else:
228-
try:
229-
value = parse_value(value, expected_type)
230-
except Exception as e:
231-
exit_with_error(f'error parsing "-s" setting "{key}={value}": {e}')
232-
233-
setattr(settings, user_key, value)
234-
235-
if key == 'EXPORTED_FUNCTIONS':
236-
# used for warnings in emscripten.py
237-
settings.USER_EXPORTS = settings.EXPORTED_FUNCTIONS.copy()
238-
239-
# TODO(sbc): Remove this legacy way.
240-
if key == 'WASM_OBJECT_FILES':
241-
settings.LTO = 0 if value else 'full'
242-
243-
if key == 'JSPI':
244-
settings.ASYNCIFY = 2
245-
if key == 'JSPI_IMPORTS':
246-
settings.ASYNCIFY_IMPORTS = value
247-
if key == 'JSPI_EXPORTS':
248-
settings.ASYNCIFY_EXPORTS = value
249-
250-
251167
def cxx_to_c_compiler(cxx):
252168
# Convert C++ compiler name into C compiler name
253169
dirname, basename = os.path.split(cxx)
@@ -444,7 +360,7 @@ def run(args):
444360

445361
## Process argument and setup the compiler
446362
state = EmccState(args)
447-
options, newargs = phase_parse_arguments(state)
363+
options, newargs = cmdline.parse_arguments(state.orig_args)
448364

449365
if not shared.SKIP_SUBPROCS:
450366
shared.check_sanity()
@@ -562,72 +478,6 @@ def run(args):
562478
return 0
563479

564480

565-
def normalize_boolean_setting(name, value):
566-
# boolean NO_X settings are aliases for X
567-
# (note that *non*-boolean setting values have special meanings,
568-
# and we can't just flip them, so leave them as-is to be
569-
# handled in a special way later)
570-
if name.startswith('NO_') and value in ('0', '1'):
571-
name = removeprefix(name, 'NO_')
572-
value = str(1 - int(value))
573-
return name, value
574-
575-
576-
@ToolchainProfiler.profile_block('parse arguments')
577-
def phase_parse_arguments(state):
578-
"""The first phase of the compiler. Parse command line argument and
579-
populate settings.
580-
"""
581-
newargs = list(state.orig_args)
582-
583-
# Scan and strip emscripten specific cmdline warning flags.
584-
# This needs to run before other cmdline flags have been parsed, so that
585-
# warnings are properly printed during arg parse.
586-
newargs = diagnostics.capture_warnings(newargs)
587-
588-
if not diagnostics.is_enabled('deprecated'):
589-
settings.WARN_DEPRECATED = 0
590-
591-
for i in range(len(newargs)):
592-
if newargs[i] in ('-l', '-L', '-I', '-z', '--js-library', '-o', '-x', '-u'):
593-
# Scan for flags that can be written as either one or two arguments
594-
# and normalize them to the single argument form.
595-
if newargs[i] == '--js-library':
596-
newargs[i] += '='
597-
if len(newargs) <= i + 1:
598-
exit_with_error(f"option '{newargs[i]}' requires an argument")
599-
newargs[i] += newargs[i + 1]
600-
newargs[i + 1] = ''
601-
602-
options, settings_changes, user_js_defines, newargs = cmdline.parse_args(newargs)
603-
604-
if options.post_link or options.oformat == OFormat.BARE:
605-
diagnostics.warning('experimental', '--oformat=bare/--post-link are experimental and subject to change.')
606-
607-
explicit_settings_changes, newargs = cmdline.parse_s_args(newargs)
608-
settings_changes += explicit_settings_changes
609-
610-
for s in settings_changes:
611-
key, value = s.split('=', 1)
612-
key, value = normalize_boolean_setting(key, value)
613-
user_settings[key] = value
614-
615-
# STRICT is used when applying settings so it needs to be applied first before
616-
# calling `apply_user_settings`.
617-
strict_cmdline = user_settings.get('STRICT')
618-
if strict_cmdline:
619-
settings.STRICT = int(strict_cmdline)
620-
621-
# Apply user -jsD settings
622-
for s in user_js_defines:
623-
settings[s[0]] = s[1]
624-
625-
# Apply -s settings in newargs here (after optimization levels, so they can override them)
626-
apply_user_settings()
627-
628-
return options, newargs
629-
630-
631481
def separate_linker_flags(newargs):
632482
"""Process argument list separating out compiler args and linker args.
633483
@@ -886,105 +736,6 @@ def compile_source_file(input_file):
886736
return [f.value for f in linker_args]
887737

888738

889-
def parse_symbol_list_file(contents):
890-
"""Parse contents of one-symbol-per-line response file. This format can by used
891-
with, for example, -sEXPORTED_FUNCTIONS=@filename and avoids the need for any
892-
kind of quoting or escaping.
893-
"""
894-
values = contents.splitlines()
895-
return [v.strip() for v in values if not v.startswith('#')]
896-
897-
898-
def parse_value(text, expected_type):
899-
# Note that using response files can introduce whitespace, if the file
900-
# has a newline at the end. For that reason, we rstrip() in relevant
901-
# places here.
902-
def parse_string_value(text):
903-
first = text[0]
904-
if first in {"'", '"'}:
905-
text = text.rstrip()
906-
if text[-1] != text[0] or len(text) < 2:
907-
raise ValueError(f'unclosed quoted string. expected final character to be "{text[0]}" and length to be greater than 1 in "{text[0]}"')
908-
return text[1:-1]
909-
return text
910-
911-
def parse_string_list_members(text):
912-
sep = ','
913-
values = text.split(sep)
914-
result = []
915-
index = 0
916-
while True:
917-
current = values[index].lstrip() # Cannot safely rstrip for cases like: "HERE-> ,"
918-
if not len(current):
919-
raise ValueError('empty value in string list')
920-
first = current[0]
921-
if first not in {"'", '"'}:
922-
result.append(current.rstrip())
923-
else:
924-
start = index
925-
while True: # Continue until closing quote found
926-
if index >= len(values):
927-
raise ValueError(f"unclosed quoted string. expected final character to be '{first}' in '{values[start]}'")
928-
new = values[index].rstrip()
929-
if new and new[-1] == first:
930-
if start == index:
931-
result.append(current.rstrip()[1:-1])
932-
else:
933-
result.append((current + sep + new)[1:-1])
934-
break
935-
else:
936-
current += sep + values[index]
937-
index += 1
938-
939-
index += 1
940-
if index >= len(values):
941-
break
942-
return result
943-
944-
def parse_string_list(text):
945-
text = text.rstrip()
946-
if text and text[0] == '[':
947-
if text[-1] != ']':
948-
raise ValueError('unterminated string list. expected final character to be "]"')
949-
text = text[1:-1]
950-
if text.strip() == "":
951-
return []
952-
return parse_string_list_members(text)
953-
954-
if expected_type == list or (text and text[0] == '['):
955-
# if json parsing fails, we fall back to our own parser, which can handle a few
956-
# simpler syntaxes
957-
try:
958-
parsed = json.loads(text)
959-
except ValueError:
960-
return parse_string_list(text)
961-
962-
# if we succeeded in parsing as json, check some properties of it before returning
963-
if type(parsed) not in (str, list):
964-
raise ValueError(f'settings must be strings or lists (not ${type(parsed)})')
965-
if type(parsed) is list:
966-
for elem in parsed:
967-
if type(elem) is not str:
968-
raise ValueError(f'list members in settings must be strings (not ${type(elem)})')
969-
970-
return parsed
971-
972-
if expected_type == float:
973-
try:
974-
return float(text)
975-
except ValueError:
976-
pass
977-
978-
try:
979-
if text.startswith('0x'):
980-
base = 16
981-
else:
982-
base = 10
983-
return int(text, base)
984-
except ValueError:
985-
return parse_string_value(text)
986-
987-
988739
@ToolchainProfiler.profile()
989740
def main(args):
990741
start_time = time.time()

emscan-deps.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
argv = sys.argv[1:]
1717

1818
# Parse and discard any emcc-specific flags (e.g. -sXXX).
19-
newargs = cmdline.parse_args(argv)[3]
20-
newargs = cmdline.parse_s_args(newargs)[1]
19+
newargs = cmdline.parse_arguments(argv)[1]
2120

2221
# Add any clang flags that emcc would add.
2322
newargs += emcc.get_cflags(tuple(argv))

0 commit comments

Comments
 (0)