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