Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/settings.py
6162 views
1
# Copyright 2021 The Emscripten Authors. All rights reserved.
2
# Emscripten is available under two separate licenses, the MIT license and the
3
# University of Illinois/NCSA Open Source License. Both these licenses can be
4
# found in the LICENSE file.
5
6
import copy
7
import difflib
8
import os
9
import re
10
from typing import Any
11
12
from . import diagnostics
13
from .utils import exit_with_error, path_from_root
14
15
# Subset of settings that take a memory size (i.e. 1Gb, 64kb etc)
16
MEM_SIZE_SETTINGS = {
17
'GLOBAL_BASE',
18
'STACK_SIZE',
19
'TOTAL_STACK',
20
'INITIAL_HEAP',
21
'INITIAL_MEMORY',
22
'MEMORY_GROWTH_LINEAR_STEP',
23
'MEMORY_GROWTH_GEOMETRIC_CAP',
24
'GL_MAX_TEMP_BUFFER_SIZE',
25
'MAXIMUM_MEMORY',
26
'DEFAULT_PTHREAD_STACK_SIZE',
27
'ASYNCIFY_STACK_SIZE',
28
}
29
30
PORTS_SETTINGS = {
31
# All port-related settings are valid at compile time
32
'USE_SDL',
33
'USE_LIBPNG',
34
'USE_BULLET',
35
'USE_ZLIB',
36
'USE_BZIP2',
37
'USE_VORBIS',
38
'USE_COCOS2D',
39
'USE_ICU',
40
'USE_MODPLUG',
41
'USE_SDL_MIXER',
42
'USE_SDL_IMAGE',
43
'USE_SDL_TTF',
44
'USE_SDL_NET',
45
'USE_SDL_GFX',
46
'USE_LIBJPEG',
47
'USE_OGG',
48
'USE_REGAL',
49
'USE_BOOST_HEADERS',
50
'USE_HARFBUZZ',
51
'USE_MPG123',
52
'USE_GIFLIB',
53
'USE_FREETYPE',
54
'SDL2_MIXER_FORMATS',
55
'SDL2_IMAGE_FORMATS',
56
'USE_SQLITE3',
57
}
58
59
# Subset of settings that apply only when generating JS
60
JS_ONLY_SETTINGS = {
61
'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE',
62
'INCLUDE_FULL_LIBRARY',
63
'BUILD_AS_WORKER',
64
'STRICT_JS',
65
'SMALL_XHR_CHUNKS',
66
'MODULARIZE',
67
'EXPORT_ES6',
68
'EXPORT_NAME',
69
'DYNAMIC_EXECUTION',
70
'PTHREAD_POOL_SIZE',
71
'PTHREAD_POOL_SIZE_STRICT',
72
'PTHREAD_POOL_DELAY_LOAD',
73
'DEFAULT_PTHREAD_STACK_SIZE',
74
}
75
76
# Subset of settings that apply at compile time.
77
# (Keep in sync with [compile] comments in settings.js)
78
COMPILE_TIME_SETTINGS = {
79
'MEMORY64',
80
'INLINING_LIMIT',
81
'DISABLE_EXCEPTION_CATCHING',
82
'DISABLE_EXCEPTION_THROWING',
83
'WASM_LEGACY_EXCEPTIONS',
84
'MAIN_MODULE',
85
'SIDE_MODULE',
86
'RELOCATABLE',
87
'LINKABLE',
88
'STRICT',
89
'EMSCRIPTEN_TRACING',
90
'PTHREADS',
91
'USE_PTHREADS', # legacy name of PTHREADS setting
92
'SHARED_MEMORY',
93
'SUPPORT_LONGJMP',
94
'WASM_OBJECT_FILES',
95
'WASM_WORKERS',
96
97
# Internal settings used during compilation
98
'EXCEPTION_CATCHING_ALLOWED',
99
'WASM_EXCEPTIONS',
100
'LTO',
101
'OPT_LEVEL',
102
'DEBUG_LEVEL',
103
}.union(PORTS_SETTINGS)
104
105
# Unlike `LEGACY_SETTINGS`, deprecated settings can still be used
106
# both on the command line and in the emscripten codebase.
107
#
108
# At some point in the future, once folks have stopped using these
109
# settings we can move them to `LEGACY_SETTINGS`.
110
#
111
# All settings here should be tagged as `[deprecated]` in settings.js
112
DEPRECATED_SETTINGS = {
113
'RUNTIME_LINKED_LIBS': 'you can simply list the libraries directly on the commandline now',
114
'CLOSURE_WARNINGS': 'use -Wclosure/-Wno-closure instead',
115
'LEGALIZE_JS_FFI': 'to disable JS type legalization use `-sWASM_BIGINT` or `-sSTANDALONE_WASM`',
116
'ASYNCIFY_EXPORTS': 'please use JSPI_EXPORTS instead',
117
'LINKABLE': 'under consideration for removal (https://github.com/emscripten-core/emscripten/issues/25262)',
118
'RELOCATABLE': ' under consideration for removal (https://github.com/emscripten-core/emscripten/issues/25262)',
119
}
120
121
# Settings that don't need to be externalized when serializing to json because they
122
# are not used by the JS compiler.
123
INTERNAL_SETTINGS = {
124
'SIDE_MODULE_IMPORTS',
125
}
126
127
# List of incompatible settings, of the form (SETTINGS_A, SETTING_B, OPTIONAL_REASON_FOR_INCOMPAT)
128
INCOMPATIBLE_SETTINGS = [
129
('MINIMAL_RUNTIME', 'RELOCATABLE', None),
130
('WASM2JS', 'MAIN_MODULE', 'wasm2js does not support dynamic linking'),
131
('WASM2JS', 'SIDE_MODULE', 'wasm2js does not support dynamic linking'),
132
('MODULARIZE', 'NO_DECLARE_ASM_MODULE_EXPORTS', None),
133
('EVAL_CTORS', 'WASM2JS', None),
134
('EVAL_CTORS', 'RELOCATABLE', 'movable segments'),
135
# In Asyncify exports can be called more than once, and this seems to not
136
# work properly yet (see test_emscripten_scan_registers).
137
('EVAL_CTORS', 'ASYNCIFY', None),
138
('PTHREADS_PROFILING', 'NO_ASSERTIONS', 'only works with ASSERTIONS enabled'),
139
('SOURCE_PHASE_IMPORTS', 'NO_EXPORT_ES6', None),
140
('STANDALONE_WASM', 'MINIMAL_RUNTIME', None),
141
('STRICT_JS', 'MODULARIZE', None),
142
('STRICT_JS', 'EXPORT_ES6', None),
143
('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION', 'MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION', 'they are mutually exclusive'),
144
('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION', 'SINGLE_FILE', None),
145
('MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION', 'SINGLE_FILE', None),
146
('SEPARATE_DWARF', 'WASM2JS', 'as there is no wasm file'),
147
('GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS', 'NO_GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS', None),
148
('MODULARIZE', 'NODEJS_CATCH_REJECTION', None),
149
('MODULARIZE', 'NODEJS_CATCH_EXIT', None),
150
('LEGACY_VM_SUPPORT', 'MEMORY64', None),
151
('CROSS_ORIGIN', 'NO_DYNAMIC_EXECUTION', None),
152
('CROSS_ORIGIN', 'NO_PTHREADS', None),
153
]
154
155
EXPERIMENTAL_SETTINGS = {
156
'SPLIT_MODULE': '-sSPLIT_MODULE is experimental and subject to change',
157
'WASM_JS_TYPES': '-sWASM_JS_TYPES is only supported under a flag in certain browsers',
158
'SOURCE_PHASE_IMPORTS': '-sSOURCE_PHASE_IMPORTS is experimental and not yet supported in browsers',
159
'JS_BASE64_API': '-sJS_BASE64_API is experimental and not yet supported in browsers',
160
'GROWABLE_ARRAYBUFFERS': '-sGROWABLE_ARRAYBUFFERS is experimental and not yet supported in browsers',
161
'SUPPORT_BIG_ENDIAN': '-sSUPPORT_BIG_ENDIAN is experimental, not all features are fully supported.',
162
'WASM_ESM_INTEGRATION': '-sWASM_ESM_INTEGRATION is still experimental and not yet supported in browsers',
163
}
164
165
# For renamed settings the format is:
166
# [OLD_NAME, NEW_NAME]
167
# For removed settings (which now effectively have a fixed value and can no
168
# longer be changed) the format is:
169
# [OPTION_NAME, POSSIBLE_VALUES, ERROR_EXPLANATION], where POSSIBLE_VALUES is
170
# an array of values that will still be silently accepted by the compiler.
171
# First element in the list is the canonical/fixed value going forward.
172
# This allows existing build systems to keep specifying one of the supported
173
# settings, for backwards compatibility.
174
# When a setting has been removed, and we want to error on all values of it,
175
# we can set POSSIBLE_VALUES to an impossible value (like "disallowed" for a
176
# numeric setting, or -1 for a string setting).
177
LEGACY_SETTINGS = [
178
['BINARYEN', 'WASM'],
179
['TOTAL_STACK', 'STACK_SIZE'],
180
['BINARYEN_ASYNC_COMPILATION', 'WASM_ASYNC_COMPILATION'],
181
['UNALIGNED_MEMORY', [0], 'forced unaligned memory not supported in fastcomp'],
182
['FORCE_ALIGNED_MEMORY', [0], 'forced aligned memory is not supported in fastcomp'],
183
['PGO', [0], 'pgo no longer supported'],
184
['QUANTUM_SIZE', [4], 'altering the QUANTUM_SIZE is not supported'],
185
['FUNCTION_POINTER_ALIGNMENT', [2], 'Starting from Emscripten 1.37.29, no longer available (https://github.com/emscripten-core/emscripten/pull/6091)'],
186
# Reserving function pointers is not needed - allowing table growth allows any number of new functions to be added.
187
['RESERVED_FUNCTION_POINTERS', 'ALLOW_TABLE_GROWTH'],
188
['BUILD_AS_SHARED_LIB', [0], 'Starting from Emscripten 1.38.16, no longer available (https://github.com/emscripten-core/emscripten/pull/7433)'],
189
['SAFE_SPLIT_MEMORY', [0], 'Starting from Emscripten 1.38.19, SAFE_SPLIT_MEMORY codegen is no longer available (https://github.com/emscripten-core/emscripten/pull/7465)'],
190
['SPLIT_MEMORY', [0], 'Starting from Emscripten 1.38.19, SPLIT_MEMORY codegen is no longer available (https://github.com/emscripten-core/emscripten/pull/7465)'],
191
['BINARYEN_METHOD', ['native-wasm'], 'Starting from Emscripten 1.38.23, Emscripten now always builds either to Wasm (-sWASM - default), or to JavaScript (-sWASM=0), other methods are not supported (https://github.com/emscripten-core/emscripten/pull/7836)'],
192
['BINARYEN_TRAP_MODE', [-1], 'The wasm backend does not support a trap mode (it always clamps, in effect)'],
193
['PRECISE_I64_MATH', [1, 2], 'Starting from Emscripten 1.38.26, PRECISE_I64_MATH is always enabled (https://github.com/emscripten-core/emscripten/pull/7935)'],
194
['MEMFS_APPEND_TO_TYPED_ARRAYS', [1], 'Starting from Emscripten 1.38.26, MEMFS_APPEND_TO_TYPED_ARRAYS=0 is no longer supported. MEMFS no longer supports using JS arrays for file data (https://github.com/emscripten-core/emscripten/pull/7918)'],
195
['ERROR_ON_MISSING_LIBRARIES', [1], 'missing libraries are always an error now'],
196
['EMITTING_JS', [1], 'The new STANDALONE_WASM flag replaces this (replace EMITTING_JS=0 with STANDALONE_WASM=1)'],
197
['SKIP_STACK_IN_SMALL', [0, 1], 'SKIP_STACK_IN_SMALL is no longer needed as the backend can optimize it directly'],
198
['SAFE_STACK', [0], 'Replace SAFE_STACK=1 with STACK_OVERFLOW_CHECK=2'],
199
['MEMORY_GROWTH_STEP', 'MEMORY_GROWTH_LINEAR_STEP'],
200
['ELIMINATE_DUPLICATE_FUNCTIONS', [0, 1], 'Duplicate function elimination for wasm is handled automatically by binaryen'],
201
['ELIMINATE_DUPLICATE_FUNCTIONS_DUMP_EQUIVALENT_FUNCTIONS', [0], 'Duplicate function elimination for wasm is handled automatically by binaryen'],
202
['ELIMINATE_DUPLICATE_FUNCTIONS_PASSES', [5], 'Duplicate function elimination for wasm is handled automatically by binaryen'],
203
['WASM_OBJECT_FILES', [1], 'For LTO, use -flto or -fto=thin instead. Otherwise, Wasm object files are the default'],
204
['TOTAL_MEMORY', 'INITIAL_MEMORY'],
205
['WASM_MEM_MAX', 'MAXIMUM_MEMORY'],
206
['BINARYEN_MEM_MAX', 'MAXIMUM_MEMORY'],
207
['BINARYEN_PASSES', [''], 'Use BINARYEN_EXTRA_PASSES to add additional passes'],
208
['SWAPPABLE_ASM_MODULE', [0], 'Fully swappable asm modules are no longer supported'],
209
['ASM_JS', [1], 'asm.js output is not supported anymore'],
210
['FINALIZE_ASM_JS', [0, 1], 'asm.js output is not supported anymore'],
211
['ASYNCIFY_WHITELIST', 'ASYNCIFY_ONLY'],
212
['ASYNCIFY_BLACKLIST', 'ASYNCIFY_REMOVE'],
213
['EXCEPTION_CATCHING_WHITELIST', 'EXCEPTION_CATCHING_ALLOWED'],
214
['SEPARATE_ASM', [0], 'Separate asm.js only made sense for fastcomp with asm.js output'],
215
['SEPARATE_ASM_MODULE_NAME', [''], 'Separate asm.js only made sense for fastcomp with asm.js output'],
216
['FAST_UNROLLED_MEMCPY_AND_MEMSET', [0, 1], 'The wasm backend implements memcpy/memset in C'],
217
['DOUBLE_MODE', [0, 1], 'The wasm backend always implements doubles normally'],
218
['PRECISE_F32', [0, 1, 2], 'The wasm backend always implements floats normally'],
219
['ALIASING_FUNCTION_POINTERS', [0, 1], 'The wasm backend always uses a single index space for function pointers, in a single Table'],
220
['AGGRESSIVE_VARIABLE_ELIMINATION', [0, 1], 'Wasm ignores asm.js-specific optimization flags'],
221
['SIMPLIFY_IFS', [1], 'Wasm ignores asm.js-specific optimization flags'],
222
['DEAD_FUNCTIONS', [[]], 'The wasm backend does not support dead function removal'],
223
['WASM_BACKEND', [-1], 'Only the wasm backend is now supported (note that setting it as -s has never been allowed anyhow)'],
224
['EXPORT_BINDINGS', [0, 1], 'No longer needed'],
225
['RUNNING_JS_OPTS', [0], 'Fastcomp cared about running JS which could alter asm.js validation, but not upstream'],
226
['EXPORT_FUNCTION_TABLES', [0], 'No longer needed'],
227
['BINARYEN_SCRIPTS', [''], 'No longer needed'],
228
['WARN_UNALIGNED', [0, 1], 'No longer needed'],
229
['ASM_PRIMITIVE_VARS', [[]], 'No longer needed'],
230
['WORKAROUND_IOS_9_RIGHT_SHIFT_BUG', [0], 'Wasm2JS does not support iPhone 4s, iPad 2, iPad 3, iPad Mini 1, Pod Touch 5 (devices with end-of-life at iOS 9.3.5) and older'],
231
['RUNTIME_FUNCS_TO_IMPORT', [[]], 'No longer needed'],
232
['LIBRARY_DEPS_TO_AUTOEXPORT', [[]], 'No longer needed'],
233
['EMIT_EMSCRIPTEN_METADATA', [0], 'No longer supported'],
234
['SHELL_FILE', [''], 'No longer supported'],
235
['LLD_REPORT_UNDEFINED', [1], 'Disabling is no longer supported'],
236
['MEM_INIT_METHOD', [0], 'No longer supported'],
237
['USE_PTHREADS', [0, 1], 'No longer needed. Use -pthread instead'],
238
['USES_DYNAMIC_ALLOC', [1], 'No longer supported. Use -sMALLOC=none'],
239
['REVERSE_DEPS', ['auto', 'all', 'none'], 'No longer needed'],
240
['RUNTIME_LOGGING', 'RUNTIME_DEBUG'],
241
['MIN_EDGE_VERSION', [0x7FFFFFFF], 'No longer supported'],
242
['MIN_IE_VERSION', [0x7FFFFFFF], 'No longer supported'],
243
['WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG', [0], 'No longer supported'],
244
['AUTO_ARCHIVE_INDEXES', [0, 1], 'No longer needed'],
245
['USE_ES6_IMPORT_META', [1], 'Disabling is no longer supported'],
246
['EXTRA_EXPORTED_RUNTIME_METHODS', [[]], 'No longer supported, use EXPORTED_RUNTIME_METHODS'],
247
['SUPPORT_ERRNO', [0], 'No longer supported'],
248
['DEMANGLE_SUPPORT', [0], 'No longer supported'],
249
['MAYBE_WASM2JS', [0], 'No longer supported (use -sWASM=2)'],
250
['HEADLESS', [0], 'No longer supported, use headless browsers or Node.js with JSDOM'],
251
['USE_OFFSET_COVERTER', [0], 'No longer supported, not needed with modern v8 versions'],
252
['ASYNCIFY_LAZY_LOAD_CODE', [0], 'No longer supported'],
253
['USE_WEBGPU', [0], 'No longer supported; replaced by --use-port=emdawnwebgpu, which implements a newer (but incompatible) version of webgpu.h - see tools/ports/emdawnwebgpu.py'],
254
['PROXY_TO_WORKER', [0], 'No longer supported'],
255
]
256
257
user_settings: dict[str, str] = {}
258
259
260
def default_setting(name, new_default):
261
if name not in user_settings:
262
setattr(settings, name, new_default)
263
264
265
class SettingsManager:
266
attrs: dict[str, Any] = {}
267
defaults: dict[str, tuple] = {}
268
types: dict[str, Any] = {}
269
allowed_settings: set[str] = set()
270
legacy_settings: dict[str, tuple] = {}
271
alt_names: dict[str, str] = {}
272
internal_settings: set[str] = set()
273
274
def __init__(self):
275
self.attrs.clear()
276
self.legacy_settings.clear()
277
self.defaults.clear()
278
self.alt_names.clear()
279
self.internal_settings.clear()
280
self.allowed_settings.clear()
281
282
# Load the JS defaults into python.
283
def read_js_settings(filename, attrs):
284
with open(filename) as fh:
285
settings = fh.read()
286
# Use a bunch of regexs to convert the file from JS to python
287
# TODO(sbc): This is kind hacky and we should probably convert
288
# this file in format that python can read directly (since we
289
# no longer read this file from JS at all).
290
settings = settings.replace('//', '#')
291
settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings)
292
settings = re.sub(r'=\s+false\s*;', '= False', settings)
293
settings = re.sub(r'=\s+true\s*;', '= True', settings)
294
exec(settings, {'attrs': attrs})
295
296
internal_attrs = {}
297
read_js_settings(path_from_root('src/settings.js'), self.attrs)
298
read_js_settings(path_from_root('src/settings_internal.js'), internal_attrs)
299
self.attrs.update(internal_attrs)
300
self.infer_types()
301
302
strict_override = False
303
if 'EMCC_STRICT' in os.environ:
304
strict_override = int(os.environ.get('EMCC_STRICT'))
305
306
# Special handling for LEGACY_SETTINGS. See src/setting.js for more
307
# details
308
for legacy in LEGACY_SETTINGS:
309
if len(legacy) == 2:
310
name, new_name = legacy
311
self.legacy_settings[name] = (None, 'setting renamed to ' + new_name)
312
self.alt_names[name] = new_name
313
self.alt_names[new_name] = name
314
default_value = self.attrs[new_name]
315
else:
316
name, fixed_values, err = legacy
317
self.legacy_settings[name] = (fixed_values, err)
318
default_value = fixed_values[0]
319
assert name not in self.attrs, 'legacy setting (%s) cannot also be a regular setting' % name
320
if not strict_override:
321
self.attrs[name] = default_value
322
323
self.internal_settings.update(internal_attrs.keys())
324
# Stash a deep copy of all settings in self.defaults. This allows us to detect which settings
325
# have local mods.
326
self.defaults.update(copy.deepcopy(self.attrs))
327
328
if strict_override:
329
self.attrs['STRICT'] = strict_override
330
331
def infer_types(self):
332
for key, value in self.attrs.items():
333
self.types[key] = type(value)
334
335
def dict(self):
336
return self.attrs
337
338
def external_dict(self, skip_keys={}): # noqa
339
external_settings = {}
340
for key, value in self.dict().items():
341
if value != self.defaults.get(key) and key not in INTERNAL_SETTINGS and key not in skip_keys:
342
external_settings[key] = value # noqa: PERF403
343
if not self.attrs['STRICT']:
344
# When not running in strict mode we also externalize all legacy settings
345
# (Since the external tools do process LEGACY_SETTINGS themselves)
346
for key in self.legacy_settings:
347
external_settings[key] = self.attrs[key]
348
return external_settings
349
350
def keys(self):
351
return self.attrs.keys()
352
353
def limit_settings(self, allowed):
354
self.allowed_settings.clear()
355
if allowed:
356
self.allowed_settings.update(allowed)
357
358
def __getattr__(self, attr):
359
if self.allowed_settings:
360
assert attr in self.allowed_settings, f"internal error: attempt to read setting '{attr}' while in limited settings mode"
361
362
if attr in self.attrs:
363
return self.attrs[attr]
364
else:
365
raise AttributeError(f"no such setting: '{attr}'")
366
367
def __setattr__(self, name, value):
368
if self.allowed_settings:
369
assert name in self.allowed_settings, f"internal error: attempt to write setting '{name}' while in limited settings mode"
370
371
if name == 'STRICT' and value:
372
for a in self.legacy_settings:
373
self.attrs.pop(a, None)
374
375
if name in self.legacy_settings:
376
# TODO(sbc): Rather then special case this we should have STRICT turn on the
377
# legacy-settings warning below
378
if self.attrs['STRICT']:
379
exit_with_error('legacy setting used in strict mode: %s', name)
380
fixed_values, error_message = self.legacy_settings[name]
381
if fixed_values and value not in fixed_values:
382
exit_with_error(f'invalid command line setting `-s{name}={value}`: {error_message}')
383
diagnostics.warning('legacy-settings', 'use of legacy setting: %s (%s)', name, error_message)
384
385
if name in self.alt_names:
386
alt_name = self.alt_names[name]
387
self.attrs[alt_name] = value
388
389
if name not in self.attrs:
390
msg = "Attempt to set a non-existent setting: '%s'\n" % name
391
valid_keys = set(self.attrs.keys()).difference(self.internal_settings)
392
suggestions = difflib.get_close_matches(name, valid_keys)
393
suggestions = [s for s in suggestions if s not in self.legacy_settings]
394
suggestions = ', '.join(suggestions)
395
if suggestions:
396
msg += ' - did you mean one of %s?\n' % suggestions
397
msg += " - perhaps a typo in emcc's -sX=Y notation?\n"
398
msg += ' - (see src/settings.js for valid values)'
399
exit_with_error(msg)
400
401
self.check_type(name, value)
402
self.attrs[name] = value
403
404
def check_type(self, name, value):
405
# These settings have a variable type so cannot be easily type checked.
406
if name in ('EXECUTABLE', 'SUPPORT_LONGJMP', 'PTHREAD_POOL_SIZE', 'SEPARATE_DWARF', 'LTO', 'MODULARIZE'):
407
return
408
expected_type = self.types.get(name)
409
if not expected_type:
410
return
411
# Allow integers 1 and 0 for type `bool`
412
if expected_type == bool:
413
if value in (1, 0):
414
value = bool(value)
415
if value in ('True', 'False', 'true', 'false'):
416
exit_with_error(f'attempt to set `{name}` to `{value}`; use 1/0 to set boolean settings')
417
if type(value) is not expected_type:
418
exit_with_error(f'setting `{name}` expects `{expected_type.__name__}` but got `{type(value).__name__}`')
419
420
def __getitem__(self, key):
421
return self.attrs[key]
422
423
def __setitem__(self, key, value):
424
self.attrs[key] = value
425
426
def backup(self):
427
return copy.deepcopy(self.attrs)
428
429
def restore(self, previous):
430
self.attrs.update(previous)
431
432
433
settings = SettingsManager()
434
435