|
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