|
22 | 22 |
|
23 | 23 | from tools.toolchain_profiler import ToolchainProfiler
|
24 | 24 |
|
25 |
| -import json |
26 | 25 | import logging
|
27 | 26 | import os
|
28 |
| -import re |
29 | 27 | import shlex
|
30 | 28 | import shutil
|
31 | 29 | import sys
|
|
41 | 39 | from tools.shared import run_process, exit_with_error, DEBUG
|
42 | 40 | from tools.shared import in_temp
|
43 | 41 | 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 |
45 | 43 | from tools.response_file import substitute_response_files
|
46 | 44 | from tools import config
|
47 | 45 | 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 |
50 | 48 |
|
51 | 49 | logger = logging.getLogger('emcc')
|
52 | 50 |
|
|
70 | 68 | os.devnull, # consider the special endingless filenames like /dev/null to be C
|
71 | 69 | } | PREPROCESSED_EXTENSIONS
|
72 | 70 |
|
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 |
| - |
79 | 71 | LINK_ONLY_FLAGS = {
|
80 | 72 | '--bind', '--closure', '--cpuprofiler', '--embed-file',
|
81 | 73 | '--emit-symbol-map', '--emrun', '--exclude-file', '--extern-post-js',
|
@@ -172,82 +164,6 @@ def make_relative(filename):
|
172 | 164 | reproduce_file.add(rsp_name, os.path.join(root, 'response.txt'))
|
173 | 165 |
|
174 | 166 |
|
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 |
| - |
251 | 167 | def cxx_to_c_compiler(cxx):
|
252 | 168 | # Convert C++ compiler name into C compiler name
|
253 | 169 | dirname, basename = os.path.split(cxx)
|
@@ -444,7 +360,7 @@ def run(args):
|
444 | 360 |
|
445 | 361 | ## Process argument and setup the compiler
|
446 | 362 | state = EmccState(args)
|
447 |
| - options, newargs = phase_parse_arguments(state) |
| 363 | + options, newargs = cmdline.parse_arguments(state.orig_args) |
448 | 364 |
|
449 | 365 | if not shared.SKIP_SUBPROCS:
|
450 | 366 | shared.check_sanity()
|
@@ -562,72 +478,6 @@ def run(args):
|
562 | 478 | return 0
|
563 | 479 |
|
564 | 480 |
|
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 |
| - |
631 | 481 | def separate_linker_flags(newargs):
|
632 | 482 | """Process argument list separating out compiler args and linker args.
|
633 | 483 |
|
@@ -886,105 +736,6 @@ def compile_source_file(input_file):
|
886 | 736 | return [f.value for f in linker_args]
|
887 | 737 |
|
888 | 738 |
|
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 |
| - |
988 | 739 | @ToolchainProfiler.profile()
|
989 | 740 | def main(args):
|
990 | 741 | start_time = time.time()
|
|
0 commit comments