# 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 .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 = ( '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', ) # 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', 'USE_PTHREADS', 'SUPPORT_LONGJMP', 'DEFAULT_TO_CXX', 'WASM_OBJECT_FILES', # Internal settings used during compilation 'EXCEPTION_CATCHING_ALLOWED', 'EXCEPTION_HANDLING', 'LTO', 'OPT_LEVEL', 'DEBUG_LEVEL', # This is legacy setting that we happen to handle very early on 'RUNTIME_LINKED_LIBS', # TODO: should not be here 'AUTO_ARCHIVE_INDEXES', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ) + PORTS_SETTINGS class SettingsManager: attrs = {} allowed_settings = [] legacy_settings = {} alt_names = {} internal_settings = 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. with open(path_from_root('src', 'settings.js')) as fh: settings = fh.read().replace('//', '#') settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) # Variable TARGET_NOT_SUPPORTED is referenced by value settings.js (also beyond declaring it), # so must pass it there explicitly. exec(settings, {'attrs': self.attrs}) with open(path_from_root('src', 'settings_internal.js')) as fh: settings = fh.read().replace('//', '#') settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) internal_attrs = {} exec(settings, {'attrs': internal_attrs}) self.attrs.update(internal_attrs) 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 dict(self): return self.attrs def keys(self): return self.attrs.keys() def limit_settings(self, allowed): self.allowed_settings.clear() if allowed: self.allowed_settings.extend(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 suggestions = difflib.get_close_matches(name, list(self.attrs.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 -s X=Y notation?\n" msg += ' - (see src/settings.js for valid values)' exit_with_error(msg) self.attrs[name] = value def __getitem__(self, key): return self.attrs[key] def __setitem__(self, key, value): self.attrs[key] = value settings = SettingsManager()