import copy
import difflib
import os
import re
from typing import Set, Dict, Any
from .utils import path_from_root, exit_with_error
from . import diagnostics
MEM_SIZE_SETTINGS = {
'GLOBAL_BASE',
'STACK_SIZE',
'TOTAL_STACK',
'INITIAL_HEAP',
'INITIAL_MEMORY',
'MEMORY_GROWTH_LINEAR_STEP',
'MEMORY_GROWTH_GEOMETRIC_CAP',
'GL_MAX_TEMP_BUFFER_SIZE',
'MAXIMUM_MEMORY',
'DEFAULT_PTHREAD_STACK_SIZE',
'ASYNCIFY_STACK_SIZE',
}
PORTS_SETTINGS = {
'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',
}
JS_ONLY_SETTINGS = {
'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE',
'INCLUDE_FULL_LIBRARY',
'PROXY_TO_WORKER',
'PROXY_TO_WORKER_FILENAME',
'BUILD_AS_WORKER',
'STRICT_JS',
'SMALL_XHR_CHUNKS',
'MODULARIZE',
'EXPORT_ES6',
'EXPORT_NAME',
'DYNAMIC_EXECUTION',
'PTHREAD_POOL_SIZE',
'PTHREAD_POOL_SIZE_STRICT',
'PTHREAD_POOL_DELAY_LOAD',
'DEFAULT_PTHREAD_STACK_SIZE',
}
COMPILE_TIME_SETTINGS = {
'MEMORY64',
'INLINING_LIMIT',
'DISABLE_EXCEPTION_CATCHING',
'DISABLE_EXCEPTION_THROWING',
'WASM_LEGACY_EXCEPTIONS',
'MAIN_MODULE',
'SIDE_MODULE',
'RELOCATABLE',
'LINKABLE',
'STRICT',
'EMSCRIPTEN_TRACING',
'PTHREADS',
'USE_PTHREADS',
'SHARED_MEMORY',
'SUPPORT_LONGJMP',
'WASM_OBJECT_FILES',
'WASM_WORKERS',
'BULK_MEMORY',
'EXCEPTION_CATCHING_ALLOWED',
'WASM_EXCEPTIONS',
'LTO',
'OPT_LEVEL',
'DEBUG_LEVEL',
'GL_ENABLE_GET_PROC_ADDRESS',
'RUNTIME_LINKED_LIBS',
}.union(PORTS_SETTINGS)
DEPRECATED_SETTINGS = {
'RUNTIME_LINKED_LIBS': 'you can simply list the libraries directly on the commandline now',
'CLOSURE_WARNINGS': 'use -Wclosure/-Wno-closure instead',
'LEGALIZE_JS_FFI': 'to disable JS type legalization use `-sWASM_BIGINT` or `-sSTANDALONE_WASM`',
'ASYNCIFY_EXPORTS': 'please use JSPI_EXPORTS instead',
'ASYNCIFY_LAZY_LOAD_CODE': 'lack of usage',
'USE_WEBGPU': 'please try migrating to --use-port=emdawnwebgpu, which implements a newer, incompatible version of webgpu.h (see tools/ports/emdawnwebgpu.py for more info)',
}
INTERNAL_SETTINGS = {
'SIDE_MODULE_IMPORTS',
}
user_settings: Dict[str, str] = {}
def default_setting(name, new_default):
if name not in user_settings:
setattr(settings, name, new_default)
class SettingsManager:
attrs: Dict[str, Any] = {}
defaults: Dict[str, tuple] = {}
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.defaults.clear()
self.alt_names.clear()
self.internal_settings.clear()
self.allowed_settings.clear()
def read_js_settings(filename, attrs):
with open(filename) as fh:
settings = fh.read()
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()
strict_override = False
if 'EMCC_STRICT' in os.environ:
strict_override = int(os.environ.get('EMCC_STRICT'))
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 strict_override:
self.attrs[name] = default_value
self.internal_settings.update(internal_attrs.keys())
self.defaults.update(copy.deepcopy(self.attrs))
if strict_override:
self.attrs['STRICT'] = strict_override
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={}):
external_settings = {}
for key, value in self.dict().items():
if value != self.defaults.get(key) and key not in INTERNAL_SETTINGS and key not in skip_keys:
external_settings[key] = value
if not self.attrs['STRICT']:
for key in self.legacy_settings:
external_settings[key] = self.attrs[key]
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:
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(f'invalid command line setting `-s{name}={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', 'MODULARIZE'):
return
expected_type = self.types.get(name)
if not expected_type:
return
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) is not 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
def backup(self):
return copy.deepcopy(self.attrs)
def restore(self, previous):
self.attrs.update(previous)
settings = SettingsManager()