| # Copyright 2021 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 difflib |
| import os |
| import re |
| from typing import Set, Dict, Any |
| |
| from .utils import path_from_root, exit_with_error |
| from . import diagnostics |
| |
| # Subset of settings that take a memory size (i.e. 1Gb, 64kb etc) |
| MEM_SIZE_SETTINGS = { |
| 'STACK_SIZE', |
| 'TOTAL_STACK', |
| 'INITIAL_MEMORY', |
| 'MEMORY_GROWTH_LINEAR_STEP', |
| 'MEMORY_GROWTH_GEOMETRIC_CAP', |
| 'GL_MAX_TEMP_BUFFER_SIZE', |
| 'MAXIMUM_MEMORY', |
| 'DEFAULT_PTHREAD_STACK_SIZE' |
| } |
| |
| PORTS_SETTINGS = { |
| # All port-related settings are valid at compile time |
| 'USE_SDL', |
| 'USE_LIBPNG', |
| 'USE_BULLET', |
| 'USE_ZLIB', |
| 'USE_BZIP2', |
| 'USE_VORBIS', |
| 'USE_COCOS2D', |
| 'USE_ICU', |
| 'USE_MODPLUG', |
| 'USE_SDL_MIXER', |
| 'USE_SDL_IMAGE', |
| 'USE_SDL_TTF', |
| 'USE_SDL_NET', |
| 'USE_SDL_GFX', |
| 'USE_LIBJPEG', |
| 'USE_OGG', |
| 'USE_REGAL', |
| 'USE_BOOST_HEADERS', |
| 'USE_HARFBUZZ', |
| 'USE_MPG123', |
| 'USE_GIFLIB', |
| 'USE_FREETYPE', |
| 'SDL2_MIXER_FORMATS', |
| 'SDL2_IMAGE_FORMATS', |
| 'USE_SQLITE3', |
| } |
| |
| # Subset of settings that apply at compile time. |
| # (Keep in sync with [compile] comments in settings.js) |
| COMPILE_TIME_SETTINGS = { |
| 'MEMORY64', |
| 'INLINING_LIMIT', |
| 'DISABLE_EXCEPTION_CATCHING', |
| 'DISABLE_EXCEPTION_THROWING', |
| 'MAIN_MODULE', |
| 'SIDE_MODULE', |
| 'RELOCATABLE', |
| 'STRICT', |
| 'EMSCRIPTEN_TRACING', |
| 'PTHREADS', |
| 'USE_PTHREADS', # legacy name of PTHREADS setting |
| 'SHARED_MEMORY', |
| 'SUPPORT_LONGJMP', |
| 'DEFAULT_TO_CXX', |
| 'WASM_OBJECT_FILES', |
| 'WASM_WORKERS', |
| 'BULK_MEMORY', |
| |
| # Internal settings used during compilation |
| 'EXCEPTION_CATCHING_ALLOWED', |
| 'WASM_EXCEPTIONS', |
| 'LTO', |
| 'OPT_LEVEL', |
| 'DEBUG_LEVEL', |
| |
| # This is legacy setting that we happen to handle very early on |
| 'RUNTIME_LINKED_LIBS', |
| }.union(PORTS_SETTINGS) |
| |
| |
| # Settings that don't need to be externalized when serializing to json because they |
| # are not used by the JS compiler. |
| INTERNAL_SETTINGS = { |
| 'SIDE_MODULE_IMPORTS', |
| } |
| |
| user_settings: Dict[str, str] = {} |
| |
| |
| class SettingsManager: |
| attrs: Dict[str, Any] = {} |
| types: Dict[str, Any] = {} |
| allowed_settings: Set[str] = set() |
| legacy_settings: Dict[str, tuple] = {} |
| alt_names: Dict[str, str] = {} |
| internal_settings: Set[str] = set() |
| |
| def __init__(self): |
| self.attrs.clear() |
| self.legacy_settings.clear() |
| self.alt_names.clear() |
| self.internal_settings.clear() |
| self.allowed_settings.clear() |
| |
| # Load the JS defaults into python. |
| def read_js_settings(filename, attrs): |
| with open(filename) as fh: |
| settings = fh.read() |
| # Use a bunch of regexs to convert the file from JS to python |
| # TODO(sbc): This is kind hacky and we should probably covert |
| # this file in format that python can read directly (since we |
| # no longer read this file from JS at all). |
| settings = settings.replace('//', '#') |
| settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) |
| settings = re.sub(r'=\s+false\s*;', '= False', settings) |
| settings = re.sub(r'=\s+true\s*;', '= True', settings) |
| exec(settings, {'attrs': attrs}) |
| |
| internal_attrs = {} |
| read_js_settings(path_from_root('src/settings.js'), self.attrs) |
| read_js_settings(path_from_root('src/settings_internal.js'), internal_attrs) |
| self.attrs.update(internal_attrs) |
| self.infer_types() |
| |
| if 'EMCC_STRICT' in os.environ: |
| self.attrs['STRICT'] = int(os.environ.get('EMCC_STRICT')) |
| |
| # Special handling for LEGACY_SETTINGS. See src/setting.js for more |
| # details |
| for legacy in self.attrs['LEGACY_SETTINGS']: |
| if len(legacy) == 2: |
| name, new_name = legacy |
| self.legacy_settings[name] = (None, 'setting renamed to ' + new_name) |
| self.alt_names[name] = new_name |
| self.alt_names[new_name] = name |
| default_value = self.attrs[new_name] |
| else: |
| name, fixed_values, err = legacy |
| self.legacy_settings[name] = (fixed_values, err) |
| default_value = fixed_values[0] |
| assert name not in self.attrs, 'legacy setting (%s) cannot also be a regular setting' % name |
| if not self.attrs['STRICT']: |
| self.attrs[name] = default_value |
| |
| self.internal_settings.update(internal_attrs.keys()) |
| |
| def infer_types(self): |
| for key, value in self.attrs.items(): |
| self.types[key] = type(value) |
| |
| def dict(self): |
| return self.attrs |
| |
| def external_dict(self, skip_keys={}): # noqa |
| external_settings = {k: v for k, v in self.dict().items() if k not in INTERNAL_SETTINGS and k not in skip_keys} |
| # Only the names of the legacy settings are used by the JS compiler |
| # so we can reduce the size of serialized json by simplifying this |
| # otherwise complex value. |
| external_settings['LEGACY_SETTINGS'] = [l[0] for l in external_settings['LEGACY_SETTINGS']] |
| return external_settings |
| |
| def keys(self): |
| return self.attrs.keys() |
| |
| def limit_settings(self, allowed): |
| self.allowed_settings.clear() |
| if allowed: |
| self.allowed_settings.update(allowed) |
| |
| def __getattr__(self, attr): |
| if self.allowed_settings: |
| assert attr in self.allowed_settings, f"internal error: attempt to read setting '{attr}' while in limited settings mode" |
| |
| if attr in self.attrs: |
| return self.attrs[attr] |
| else: |
| raise AttributeError(f"no such setting: '{attr}'") |
| |
| def __setattr__(self, name, value): |
| if self.allowed_settings: |
| assert name in self.allowed_settings, f"internal error: attempt to write setting '{name}' while in limited settings mode" |
| |
| if name == 'STRICT' and value: |
| for a in self.legacy_settings: |
| self.attrs.pop(a, None) |
| |
| if name in self.legacy_settings: |
| # TODO(sbc): Rather then special case this we should have STRICT turn on the |
| # legacy-settings warning below |
| if self.attrs['STRICT']: |
| exit_with_error('legacy setting used in strict mode: %s', name) |
| fixed_values, error_message = self.legacy_settings[name] |
| if fixed_values and value not in fixed_values: |
| exit_with_error('Invalid command line option -s ' + name + '=' + str(value) + ': ' + error_message) |
| diagnostics.warning('legacy-settings', 'use of legacy setting: %s (%s)', name, error_message) |
| |
| if name in self.alt_names: |
| alt_name = self.alt_names[name] |
| self.attrs[alt_name] = value |
| |
| if name not in self.attrs: |
| msg = "Attempt to set a non-existent setting: '%s'\n" % name |
| valid_keys = set(self.attrs.keys()).difference(self.internal_settings) |
| suggestions = difflib.get_close_matches(name, valid_keys) |
| suggestions = [s for s in suggestions if s not in self.legacy_settings] |
| suggestions = ', '.join(suggestions) |
| if suggestions: |
| msg += ' - did you mean one of %s?\n' % suggestions |
| msg += " - perhaps a typo in emcc's -sX=Y notation?\n" |
| msg += ' - (see src/settings.js for valid values)' |
| exit_with_error(msg) |
| |
| self.check_type(name, value) |
| self.attrs[name] = value |
| |
| def check_type(self, name, value): |
| if name in ('SUPPORT_LONGJMP', 'PTHREAD_POOL_SIZE', 'SEPARATE_DWARF', 'LTO'): |
| return |
| expected_type = self.types.get(name) |
| if not expected_type: |
| return |
| # Allow itegers 1 and 0 for type `bool` |
| if expected_type == bool: |
| if value in (1, 0): |
| value = bool(value) |
| if value in ('True', 'False', 'true', 'false'): |
| exit_with_error('attempt to set `%s` to `%s`; use 1/0 to set boolean settings' % (name, value)) |
| if type(value) != expected_type: |
| exit_with_error('setting `%s` expects `%s` but got `%s`' % (name, expected_type.__name__, type(value).__name__)) |
| |
| def __getitem__(self, key): |
| return self.attrs[key] |
| |
| def __setitem__(self, key, value): |
| self.attrs[key] = value |
| |
| |
| settings = SettingsManager() |