Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/config.py
6162 views
1
# Copyright 2020 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 logging
7
import os
8
import shutil
9
import sys
10
11
from . import diagnostics, utils
12
from .utils import __rootpath__, exit_with_error, path_from_root
13
14
logger = logging.getLogger('config')
15
16
# The following class can be overridden by the config file and/or
17
# environment variables. Specifically any variable whose name
18
# is in ALL_UPPER_CASE is considered a valid config file key.
19
# See parse_config_file below.
20
EMSCRIPTEN_ROOT = __rootpath__
21
NODE_JS = None
22
BINARYEN_ROOT = None
23
LLVM_ADD_VERSION = None
24
CLANG_ADD_VERSION = None
25
CLOSURE_COMPILER = None
26
FROZEN_CACHE = None
27
CACHE = None
28
PORTS = None
29
COMPILER_WRAPPER = None
30
31
# Set by init()
32
EM_CONFIG = None
33
34
# Settings that are only used for testing. emcc itself does not use
35
# any of these.
36
NODE_JS_TEST = None
37
SPIDERMONKEY_ENGINE = None
38
V8_ENGINE: list[str] | None = None
39
LLVM_ROOT = None
40
JS_ENGINES: list[list[str]] = []
41
WASMER = None
42
WASMTIME = None
43
WASM_ENGINES: list[list[str]] = []
44
45
46
def listify(x):
47
if x is None or type(x) is list:
48
return x
49
return [x]
50
51
52
def fix_js_engine(old, new):
53
if old is None:
54
return
55
global JS_ENGINES
56
JS_ENGINES = [new if x == old else x for x in JS_ENGINES]
57
return new
58
59
60
def normalize_config_settings():
61
global CACHE, PORTS, LLVM_ADD_VERSION, CLANG_ADD_VERSION, CLOSURE_COMPILER
62
global NODE_JS, NODE_JS_TEST, V8_ENGINE, JS_ENGINES, SPIDERMONKEY_ENGINE, WASM_ENGINES
63
64
SPIDERMONKEY_ENGINE = fix_js_engine(SPIDERMONKEY_ENGINE, listify(SPIDERMONKEY_ENGINE))
65
NODE_JS = fix_js_engine(NODE_JS, listify(NODE_JS))
66
NODE_JS_TEST = fix_js_engine(NODE_JS_TEST, listify(NODE_JS_TEST))
67
V8_ENGINE = fix_js_engine(V8_ENGINE, listify(V8_ENGINE))
68
JS_ENGINES = [listify(engine) for engine in JS_ENGINES]
69
WASM_ENGINES = [listify(engine) for engine in WASM_ENGINES]
70
CLOSURE_COMPILER = listify(CLOSURE_COMPILER)
71
if not CACHE:
72
CACHE = path_from_root('cache')
73
if not PORTS:
74
PORTS = os.path.join(CACHE, 'ports')
75
76
77
def set_config_from_tool_location(config_key, tool_binary, f):
78
val = globals()[config_key]
79
if val is None:
80
path = shutil.which(tool_binary)
81
if not path:
82
if not os.path.isfile(EM_CONFIG):
83
diagnostics.warn('config file not found: %s. You can create one by hand or run `emcc --generate-config`', EM_CONFIG)
84
exit_with_error('%s not set in config (%s), and `%s` not found in PATH', config_key, EM_CONFIG, tool_binary)
85
globals()[config_key] = f(path)
86
elif not val:
87
exit_with_error('%s is set to empty value in %s', config_key, EM_CONFIG)
88
89
90
def parse_config_file():
91
"""Parse the emscripten config file using python's exec.
92
93
Also check EM_<KEY> environment variables to override specific config keys.
94
"""
95
config = {'__file__': EM_CONFIG}
96
config_text = utils.read_file(EM_CONFIG)
97
try:
98
exec(config_text, config)
99
except Exception as e:
100
exit_with_error('error in evaluating config file (%s): %s, text: %s', EM_CONFIG, e, config_text)
101
102
CONFIG_KEYS = (
103
'NODE_JS',
104
'NODE_JS_TEST',
105
'BINARYEN_ROOT',
106
'SPIDERMONKEY_ENGINE',
107
'V8_ENGINE',
108
'LLVM_ROOT',
109
'LLVM_ADD_VERSION',
110
'CLANG_ADD_VERSION',
111
'CLOSURE_COMPILER',
112
'JS_ENGINES',
113
'WASMER',
114
'WASMTIME',
115
'WASM_ENGINES',
116
'FROZEN_CACHE',
117
'CACHE',
118
'PORTS',
119
'COMPILER_WRAPPER',
120
)
121
122
# Only propagate certain settings from the config file.
123
for key in CONFIG_KEYS:
124
env_var = 'EM_' + key
125
env_value = os.environ.get(env_var)
126
if env_value is not None:
127
if env_value in ('', '0'):
128
env_value = None
129
# Unlike the other keys these two should always be lists.
130
if env_var in ('EM_JS_ENGINES', 'EM_WASM_ENGINES'):
131
env_value = env_value.split(',')
132
if env_var in ('EM_CONFIG', 'EM_CACHE', 'EM_PORTS', 'EM_LLVM_ROOT', 'EM_BINARYEN_ROOT'):
133
if not os.path.isabs(env_value):
134
exit_with_error(f'environment variable {env_var} must be an absolute path: {env_value}')
135
globals()[key] = env_value
136
elif key in config:
137
globals()[key] = config[key]
138
139
140
def read_config():
141
if os.path.isfile(EM_CONFIG):
142
parse_config_file()
143
144
# In the past the default-generated .emscripten config file would read
145
# certain environment variables.
146
LEGACY_ENV_VARS = {
147
'LLVM': 'EM_LLVM_ROOT',
148
'BINARYEN': 'EM_BINARYEN_ROOT',
149
'NODE': 'EM_NODE_JS',
150
'LLVM_ADD_VERSION': 'EM_LLVM_ADD_VERSION',
151
'CLANG_ADD_VERSION': 'EM_CLANG_ADD_VERSION',
152
}
153
154
for key, new_key in LEGACY_ENV_VARS.items():
155
env_value = os.environ.get(key)
156
if env_value and new_key not in os.environ:
157
msg = f'legacy environment variable found: `{key}`. Please switch to using `{new_key}` instead`'
158
# Use `debug` instead of `warning` for `NODE` specifically
159
# since there can be false positives:
160
# See https://github.com/emscripten-core/emsdk/issues/862
161
if key == 'NODE':
162
logger.debug(msg)
163
else:
164
logger.warning(msg)
165
166
set_config_from_tool_location('LLVM_ROOT', 'clang', os.path.dirname)
167
set_config_from_tool_location('NODE_JS', 'node', lambda x: x)
168
set_config_from_tool_location('BINARYEN_ROOT', 'wasm-opt', lambda x: os.path.dirname(os.path.dirname(x)))
169
170
normalize_config_settings()
171
172
173
def generate_config(path):
174
if os.path.exists(path):
175
exit_with_error(f'config file already exists: `{path}`')
176
177
# Note: repr is used to ensure the paths are escaped correctly on Windows.
178
# The full string is replaced so that the template stays valid Python.
179
180
config_data = utils.read_file(path_from_root('tools/config_template.py'))
181
config_data = config_data.splitlines()[3:] # remove the initial comment
182
config_data = '\n'.join(config_data) + '\n'
183
# autodetect some default paths
184
llvm_root = os.path.dirname(shutil.which('wasm-ld') or '/usr/bin/wasm-ld')
185
config_data = config_data.replace('\'{{{ LLVM_ROOT }}}\'', repr(llvm_root))
186
187
binaryen_root = os.path.dirname(os.path.dirname(shutil.which('wasm-opt') or '/usr/local/bin/wasm-opt'))
188
config_data = config_data.replace('\'{{{ BINARYEN_ROOT }}}\'', repr(binaryen_root))
189
190
node = shutil.which('node') or shutil.which('nodejs') or 'node'
191
config_data = config_data.replace('\'{{{ NODE }}}\'', repr(node))
192
193
# write
194
utils.write_file(path, config_data)
195
196
print('''\
197
An Emscripten settings file has been generated at:
198
199
%s
200
201
It contains our best guesses for the important paths, which are:
202
203
LLVM_ROOT = %s
204
BINARYEN_ROOT = %s
205
NODE_JS = %s
206
207
Please edit the file if any of those are incorrect.\
208
''' % (path, llvm_root, binaryen_root, node), file=sys.stderr)
209
210
211
def find_config_file():
212
# Emscripten configuration is done through the --em-config command line option
213
# or the EM_CONFIG environment variable. If the specified string value contains
214
# newline or semicolon-separated definitions, then these definitions will be
215
# used to configure Emscripten. Otherwise, the string is understood to be a
216
# path to a settings file that contains the required definitions.
217
# The search order from the config file is as follows:
218
# 1. Specified on the command line (--em-config)
219
# 2. Specified via EM_CONFIG environment variable
220
# 3. Local .emscripten file, if found
221
# 4. Local .emscripten file, as used by `emsdk --embedded` (two levels above,
222
# see below)
223
# 5. User home directory config (~/.emscripten), if found.
224
225
if '--em-config' in sys.argv:
226
i = sys.argv.index('--em-config')
227
if len(sys.argv) <= i + 1:
228
exit_with_error('--em-config must be followed by a filename')
229
del sys.argv[i]
230
# Now the i'th argument is the emconfig filename
231
return sys.argv.pop(i)
232
233
if 'EM_CONFIG' in os.environ:
234
return os.environ['EM_CONFIG']
235
236
embedded_config = path_from_root('.emscripten')
237
if os.path.isfile(embedded_config):
238
return embedded_config
239
240
# For compatibility with `emsdk --embedded` mode also look two levels up. The
241
# layout of the emsdk puts emcc two levels below emsdk. For example:
242
# - emsdk/upstream/emscripten/emcc
243
# - emsdk/emscripten/1.38.31/emcc
244
# However `emsdk --embedded` stores the config file in the emsdk root.
245
# Without this check, when emcc is run from within the emsdk in embedded mode
246
# and the user forgets to first run `emsdk_env.sh` (which sets EM_CONFIG) emcc
247
# will not see any config file at all and fall back to creating a new/empty
248
# one.
249
# We could remove this special case if emsdk were to write its embedded config
250
# file into the emscripten directory itself.
251
# See: https://github.com/emscripten-core/emsdk/pull/367
252
emsdk_root = os.path.dirname(os.path.dirname(path_from_root()))
253
emsdk_embedded_config = os.path.join(emsdk_root, '.emscripten')
254
255
if os.path.isfile(emsdk_embedded_config):
256
return emsdk_embedded_config
257
258
user_home_config = os.path.expanduser('~/.emscripten')
259
if os.path.isfile(user_home_config):
260
return user_home_config
261
262
return embedded_config
263
264
265
def init():
266
global EM_CONFIG
267
EM_CONFIG = find_config_file()
268
269
# We used to support inline EM_CONFIG.
270
if '\n' in EM_CONFIG:
271
exit_with_error('inline EM_CONFIG data no longer supported. Please use a config file.')
272
273
EM_CONFIG = os.path.expanduser(EM_CONFIG)
274
275
# This command line flag needs to work even in the absence of a config
276
# file, so we must process it here at script import time (otherwise
277
# the error below will trigger).
278
if '--generate-config' in sys.argv:
279
generate_config(EM_CONFIG)
280
sys.exit(0)
281
282
if os.path.isfile(EM_CONFIG):
283
logger.debug(f'using config file: {EM_CONFIG}')
284
else:
285
logger.debug('config file not found; using default config')
286
287
# Emscripten compiler spawns other processes, which can reimport shared.py, so
288
# make sure that those child processes get the same configuration file by
289
# setting it to the currently active environment.
290
os.environ['EM_CONFIG'] = EM_CONFIG
291
292
read_config()
293
294
295
init()
296
297