Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/shared.py
4128 views
1
# Copyright 2011 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
from .toolchain_profiler import ToolchainProfiler
7
8
from subprocess import PIPE
9
import atexit
10
import logging
11
import os
12
import re
13
import shutil
14
import shlex
15
import subprocess
16
import signal
17
import stat
18
import sys
19
import tempfile
20
21
# We depend on python 3.8 features
22
if sys.version_info < (3, 8): # noqa: UP036
23
print(f'error: emscripten requires python 3.8 or above ({sys.executable} {sys.version})', file=sys.stderr)
24
sys.exit(1)
25
26
from . import colored_logger
27
28
# Configure logging before importing any other local modules so even
29
# log message during import are shown as expected.
30
DEBUG = int(os.environ.get('EMCC_DEBUG', '0'))
31
EMCC_LOGGING = int(os.environ.get('EMCC_LOGGING', '1'))
32
log_level = logging.ERROR
33
if DEBUG:
34
log_level = logging.DEBUG
35
elif EMCC_LOGGING:
36
log_level = logging.INFO
37
# can add %(asctime)s to see timestamps
38
logging.basicConfig(format='%(name)s:%(levelname)s: %(message)s', level=log_level)
39
colored_logger.enable()
40
41
from .utils import path_from_root, exit_with_error, safe_ensure_dirs, WINDOWS, set_version_globals, memoize
42
from . import cache, tempfiles
43
from . import diagnostics
44
from . import config
45
from . import filelock
46
from . import utils
47
from .settings import settings
48
import contextlib
49
50
51
DEBUG_SAVE = DEBUG or int(os.environ.get('EMCC_DEBUG_SAVE', '0'))
52
PRINT_SUBPROCS = int(os.getenv('EMCC_VERBOSE', '0'))
53
SKIP_SUBPROCS = False
54
55
# Minimum node version required to run the emscripten compiler. This is
56
# distinct from the minimum version required to execute the generated code
57
# (settings.MIN_NODE_VERSION).
58
# This is currently set to v18 since this is the version of node available
59
# in debian/stable (bookworm). We need at least v18.3.0 because we make
60
# use of util.parseArg which was added in v18.3.0.
61
MINIMUM_NODE_VERSION = (18, 3, 0)
62
EXPECTED_LLVM_VERSION = 22
63
64
# These get set by setup_temp_dirs
65
TEMP_DIR = None
66
EMSCRIPTEN_TEMP_DIR = None
67
68
logger = logging.getLogger('shared')
69
70
# warning about absolute-paths is disabled by default, and not enabled by -Wall
71
diagnostics.add_warning('absolute-paths', enabled=False, part_of_all=False)
72
# unused diagnostic flags. TODO(sbc): remove at some point
73
diagnostics.add_warning('almost-asm')
74
diagnostics.add_warning('experimental')
75
# Don't show legacy settings warnings by default
76
diagnostics.add_warning('legacy-settings', enabled=False, part_of_all=False)
77
# Catch-all for other emcc warnings
78
diagnostics.add_warning('linkflags')
79
diagnostics.add_warning('emcc')
80
diagnostics.add_warning('undefined', error=True)
81
diagnostics.add_warning('deprecated', shared=True)
82
diagnostics.add_warning('version-check')
83
diagnostics.add_warning('export-main')
84
diagnostics.add_warning('map-unrecognized-libraries')
85
diagnostics.add_warning('unused-command-line-argument', shared=True)
86
diagnostics.add_warning('pthreads-mem-growth')
87
diagnostics.add_warning('transpile')
88
diagnostics.add_warning('limited-postlink-optimizations')
89
diagnostics.add_warning('em-js-i64')
90
diagnostics.add_warning('js-compiler')
91
diagnostics.add_warning('compatibility')
92
diagnostics.add_warning('unsupported')
93
diagnostics.add_warning('unused-main')
94
# Closure warning are not (yet) enabled by default
95
diagnostics.add_warning('closure', enabled=False)
96
97
98
def run_process(cmd, check=True, input=None, *args, **kw):
99
"""Runs a subprocess returning the exit code.
100
101
By default this function will raise an exception on failure. Therefore this should only be
102
used if you want to handle such failures. For most subprocesses, failures are not recoverable
103
and should be fatal. In those cases the `check_call` wrapper should be preferred.
104
"""
105
106
# Flush standard streams otherwise the output of the subprocess may appear in the
107
# output before messages that we have already written.
108
sys.stdout.flush()
109
sys.stderr.flush()
110
kw.setdefault('text', True)
111
kw.setdefault('encoding', 'utf-8')
112
ret = subprocess.run(cmd, check=check, input=input, *args, **kw)
113
debug_text = '%sexecuted %s' % ('successfully ' if check else '', shlex.join(cmd))
114
logger.debug(debug_text)
115
return ret
116
117
118
def get_num_cores():
119
return int(os.environ.get('EMCC_CORES', os.cpu_count()))
120
121
122
def returncode_to_str(code):
123
assert code != 0
124
if code < 0:
125
signal_name = signal.Signals(-code).name
126
return f'received {signal_name} ({code})'
127
128
return f'returned {code}'
129
130
131
def cap_max_workers_in_pool(max_workers):
132
# Python has an issue that it can only use max 61 cores on Windows: https://github.com/python/cpython/issues/89240
133
if WINDOWS:
134
return min(max_workers, 61)
135
return max_workers
136
137
138
def run_multiple_processes(commands,
139
env=None,
140
route_stdout_to_temp_files_suffix=None,
141
cwd=None):
142
"""Runs multiple subprocess commands.
143
144
route_stdout_to_temp_files_suffix : string
145
if not None, all stdouts are instead written to files, and an array
146
of filenames is returned.
147
"""
148
149
if env is None:
150
env = os.environ.copy()
151
152
std_outs = []
153
154
# TODO: Experiment with registering a signal handler here to see if that helps with Ctrl-C locking up the command prompt
155
# when multiple child processes have been spawned.
156
# import signal
157
# def signal_handler(sig, frame):
158
# sys.exit(1)
159
# signal.signal(signal.SIGINT, signal_handler)
160
161
# Map containing all currently running processes.
162
# command index -> proc/Popen object
163
processes = {}
164
165
def get_finished_process():
166
while True:
167
for idx, proc in processes.items():
168
if proc.poll() is not None:
169
return idx
170
# All processes still running; wait a short while for the first
171
# (oldest) process to finish, then look again if any process has completed.
172
idx, proc = next(iter(processes.items()))
173
try:
174
proc.communicate(timeout=0.2)
175
return idx
176
except subprocess.TimeoutExpired:
177
pass
178
179
num_parallel_processes = get_num_cores()
180
temp_files = get_temp_files()
181
i = 0
182
num_completed = 0
183
while num_completed < len(commands):
184
if i < len(commands) and len(processes) < num_parallel_processes:
185
# Not enough parallel processes running, spawn a new one.
186
if route_stdout_to_temp_files_suffix:
187
stdout = temp_files.get(route_stdout_to_temp_files_suffix)
188
else:
189
stdout = None
190
if DEBUG:
191
logger.debug('Running subprocess %d/%d: %s' % (i + 1, len(commands), ' '.join(commands[i])))
192
print_compiler_stage(commands[i])
193
proc = subprocess.Popen(commands[i], stdout=stdout, stderr=None, env=env, cwd=cwd)
194
processes[i] = proc
195
if route_stdout_to_temp_files_suffix:
196
std_outs.append((i, stdout.name))
197
i += 1
198
else:
199
# Not spawning a new process (Too many commands running in parallel, or
200
# no commands left): find if a process has finished.
201
idx = get_finished_process()
202
finished_process = processes.pop(idx)
203
if finished_process.returncode != 0:
204
exit_with_error('subprocess %d/%d failed (%s)! (cmdline: %s)' % (idx + 1, len(commands), returncode_to_str(finished_process.returncode), shlex.join(commands[idx])))
205
num_completed += 1
206
207
if route_stdout_to_temp_files_suffix:
208
# If processes finished out of order, sort the results to the order of the input.
209
std_outs.sort(key=lambda x: x[0])
210
return [x[1] for x in std_outs]
211
212
213
def check_call(cmd, *args, **kw):
214
"""Like `run_process` above but treat failures as fatal and exit_with_error."""
215
print_compiler_stage(cmd)
216
if SKIP_SUBPROCS:
217
return 0
218
try:
219
return run_process(cmd, *args, **kw)
220
except subprocess.CalledProcessError as e:
221
exit_with_error("'%s' failed (%s)", shlex.join(cmd), returncode_to_str(e.returncode))
222
except OSError as e:
223
exit_with_error("'%s' failed: %s", shlex.join(cmd), str(e))
224
225
226
def exec_process(cmd):
227
print_compiler_stage(cmd)
228
if utils.WINDOWS:
229
rtn = run_process(cmd, stdin=sys.stdin, check=False).returncode
230
sys.exit(rtn)
231
else:
232
sys.stdout.flush()
233
sys.stderr.flush()
234
os.execvp(cmd[0], cmd)
235
236
237
def run_js_tool(filename, jsargs=[], node_args=[], **kw): # noqa: B006
238
"""Execute a javascript tool.
239
240
This is used by emcc to run parts of the build process that are written
241
implemented in javascript.
242
"""
243
command = config.NODE_JS + node_args + [filename] + jsargs
244
return check_call(command, **kw).stdout
245
246
247
def get_npm_cmd(name, missing_ok=False):
248
if WINDOWS:
249
cmd = [path_from_root('node_modules/.bin', name + '.cmd')]
250
else:
251
cmd = config.NODE_JS + [path_from_root('node_modules/.bin', name)]
252
if not os.path.exists(cmd[-1]):
253
if missing_ok:
254
return None
255
else:
256
exit_with_error(f'{name} was not found! Please run "npm install" in Emscripten root directory to set up npm dependencies')
257
return cmd
258
259
260
@memoize
261
def get_clang_version():
262
if not os.path.exists(CLANG_CC):
263
exit_with_error('clang executable not found at `%s`' % CLANG_CC)
264
proc = check_call([CLANG_CC, '--version'], stdout=PIPE)
265
m = re.search(r'[Vv]ersion\s+(\d+\.\d+)', proc.stdout)
266
return m and m.group(1)
267
268
269
def check_llvm_version():
270
actual = get_clang_version()
271
if actual.startswith('%d.' % EXPECTED_LLVM_VERSION):
272
return True
273
# When running in CI environment we also silently allow the next major
274
# version of LLVM here so that new versions of LLVM can be rolled in
275
# without disruption.
276
if 'BUILDBOT_BUILDNUMBER' in os.environ:
277
if actual.startswith('%d.' % (EXPECTED_LLVM_VERSION + 1)):
278
return True
279
diagnostics.warning('version-check', 'LLVM version for clang executable "%s" appears incorrect (seeing "%s", expected "%s")', CLANG_CC, actual, EXPECTED_LLVM_VERSION)
280
return False
281
282
283
def get_clang_targets():
284
if not os.path.exists(CLANG_CC):
285
exit_with_error('clang executable not found at `%s`' % CLANG_CC)
286
try:
287
target_info = run_process([CLANG_CC, '-print-targets'], stdout=PIPE).stdout
288
except subprocess.CalledProcessError:
289
exit_with_error('error running `clang -print-targets`. Check your llvm installation (%s)' % CLANG_CC)
290
if 'Registered Targets:' not in target_info:
291
exit_with_error('error parsing output of `clang -print-targets`. Check your llvm installation (%s)' % CLANG_CC)
292
return target_info.split('Registered Targets:')[1]
293
294
295
def check_llvm():
296
targets = get_clang_targets()
297
if 'wasm32' not in targets:
298
logger.critical('LLVM has not been built with the WebAssembly backend, clang reports:')
299
print('===========================================================================', file=sys.stderr)
300
print(targets, file=sys.stderr)
301
print('===========================================================================', file=sys.stderr)
302
return False
303
304
return True
305
306
307
def get_node_directory():
308
return os.path.dirname(config.NODE_JS[0] if type(config.NODE_JS) is list else config.NODE_JS)
309
310
311
# When we run some tools from npm (closure, html-minifier-terser), those
312
# expect that the tools have node.js accessible in PATH. Place our node
313
# there when invoking those tools.
314
def env_with_node_in_path():
315
env = os.environ.copy()
316
env['PATH'] = get_node_directory() + os.pathsep + env['PATH']
317
return env
318
319
320
def _get_node_version_pair(nodejs):
321
actual = run_process(nodejs + ['--version'], stdout=PIPE).stdout.strip()
322
version = actual.replace('v', '')
323
version = version.split('-')[0].split('.')
324
version = tuple(int(v) for v in version)
325
return actual, version
326
327
328
def get_node_version(nodejs):
329
return _get_node_version_pair(nodejs)[1]
330
331
332
@memoize
333
def check_node_version():
334
try:
335
actual, version = _get_node_version_pair(config.NODE_JS)
336
except Exception as e:
337
diagnostics.warning('version-check', 'cannot check node version: %s', e)
338
return
339
340
if version < MINIMUM_NODE_VERSION:
341
expected = '.'.join(str(v) for v in MINIMUM_NODE_VERSION)
342
diagnostics.warning('version-check', f'node version appears too old (seeing "{actual}", expected "v{expected}")')
343
344
return version
345
346
347
def node_reference_types_flags(nodejs):
348
node_version = get_node_version(nodejs)
349
# reference types were enabled by default in node v18.
350
if node_version and node_version < (18, 0, 0):
351
return ['--experimental-wasm-reftypes']
352
else:
353
return []
354
355
356
def node_exception_flags(nodejs):
357
node_version = get_node_version(nodejs)
358
# Legacy exception handling was enabled by default in node v17.
359
if node_version and node_version < (17, 0, 0):
360
return ['--experimental-wasm-eh']
361
# Standard exception handling was supported behind flag in node v22.
362
if node_version and node_version >= (22, 0, 0) and not settings.WASM_LEGACY_EXCEPTIONS:
363
return ['--experimental-wasm-exnref']
364
return []
365
366
367
def node_pthread_flags(nodejs):
368
node_version = get_node_version(nodejs)
369
# bulk memory and wasm threads were enabled by default in node v16.
370
if node_version and node_version < (16, 0, 0):
371
return ['--experimental-wasm-bulk-memory', '--experimental-wasm-threads']
372
else:
373
return []
374
375
376
@memoize
377
@ToolchainProfiler.profile()
378
def check_node():
379
try:
380
run_process(config.NODE_JS + ['-e', 'console.log("hello")'], stdout=PIPE)
381
except Exception as e:
382
exit_with_error('the configured node executable (%s) does not seem to work, check the paths in %s (%s)', config.NODE_JS, config.EM_CONFIG, str(e))
383
384
385
def generate_sanity():
386
return f'{utils.EMSCRIPTEN_VERSION}|{config.LLVM_ROOT}\n'
387
388
389
@memoize
390
def perform_sanity_checks(quiet=False):
391
# some warning, mostly not fatal checks - do them even if EM_IGNORE_SANITY is on
392
check_node_version()
393
check_llvm_version()
394
395
llvm_ok = check_llvm()
396
397
if os.environ.get('EM_IGNORE_SANITY'):
398
logger.info('EM_IGNORE_SANITY set, ignoring sanity checks')
399
return
400
401
if not quiet:
402
logger.info('(Emscripten: Running sanity checks)')
403
404
if not llvm_ok:
405
exit_with_error('failing sanity checks due to previous llvm failure')
406
407
check_node()
408
409
with ToolchainProfiler.profile_block('sanity LLVM'):
410
for cmd in (CLANG_CC, LLVM_AR):
411
if not os.path.exists(cmd) and not os.path.exists(cmd + '.exe'): # .exe extension required for Windows
412
exit_with_error('cannot find %s, check the paths in %s', cmd, config.EM_CONFIG)
413
414
415
@ToolchainProfiler.profile()
416
def check_sanity(force=False, quiet=False):
417
"""Check that basic stuff we need (a JS engine to compile, Node.js, and Clang
418
and LLVM) exists.
419
420
The test runner always does this check (through |force|). emcc does this less
421
frequently, only when ${EM_CONFIG}_sanity does not exist or is older than
422
EM_CONFIG (so, we re-check sanity when the settings are changed). We also
423
re-check sanity and clear the cache when the version changes.
424
"""
425
if not force and os.environ.get('EMCC_SKIP_SANITY_CHECK') == '1':
426
return
427
428
# We set EMCC_SKIP_SANITY_CHECK so that any subprocesses that we launch will
429
# not re-run the tests.
430
os.environ['EMCC_SKIP_SANITY_CHECK'] = '1'
431
432
# In DEBUG mode we perform the sanity checks even when
433
# early return due to the file being up-to-date.
434
if DEBUG:
435
force = True
436
437
if config.FROZEN_CACHE:
438
if force:
439
perform_sanity_checks(quiet)
440
return
441
442
if os.environ.get('EM_IGNORE_SANITY'):
443
perform_sanity_checks(quiet)
444
return
445
446
expected = generate_sanity()
447
448
sanity_file = cache.get_path('sanity.txt')
449
450
def sanity_is_correct():
451
sanity_data = None
452
# We can't simply check for the existence of sanity_file and then read from
453
# it here because we don't hold the cache lock yet and some other process
454
# could clear the cache between checking for, and reading from, the file.
455
with contextlib.suppress(Exception):
456
sanity_data = utils.read_file(sanity_file)
457
if sanity_data == expected:
458
logger.debug(f'sanity file up-to-date: {sanity_file}')
459
# Even if the sanity file is up-to-date we still run the checks
460
# when force is set.
461
if force:
462
perform_sanity_checks(quiet)
463
return True # all is well
464
return False
465
466
if sanity_is_correct():
467
# Early return without taking the cache lock
468
return
469
470
with cache.lock('sanity'):
471
# Check again once the cache lock as acquired
472
if sanity_is_correct():
473
return
474
475
if os.path.exists(sanity_file):
476
sanity_data = utils.read_file(sanity_file)
477
logger.info('old sanity: %s', sanity_data.strip())
478
logger.info('new sanity: %s', expected.strip())
479
logger.info('(Emscripten: config changed, clearing cache)')
480
cache.erase()
481
else:
482
logger.debug(f'sanity file not found: {sanity_file}')
483
484
perform_sanity_checks()
485
486
# Only create/update this file if the sanity check succeeded, i.e., we got here
487
utils.write_file(sanity_file, expected)
488
489
490
def llvm_tool_path_with_suffix(tool, suffix):
491
if suffix:
492
tool += '-' + suffix
493
llvm_root = os.path.expanduser(config.LLVM_ROOT)
494
return os.path.join(llvm_root, exe_suffix(tool))
495
496
497
# Some distributions ship with multiple llvm versions so they add
498
# the version to the binaries, cope with that
499
def llvm_tool_path(tool):
500
return llvm_tool_path_with_suffix(tool, config.LLVM_ADD_VERSION)
501
502
503
# Some distributions ship with multiple clang versions so they add
504
# the version to the binaries, cope with that
505
def clang_tool_path(tool):
506
return llvm_tool_path_with_suffix(tool, config.CLANG_ADD_VERSION)
507
508
509
def exe_suffix(cmd):
510
return cmd + '.exe' if WINDOWS else cmd
511
512
513
def bat_suffix(cmd):
514
return cmd + '.bat' if WINDOWS else cmd
515
516
517
def replace_suffix(filename, new_suffix):
518
assert new_suffix[0] == '.'
519
return os.path.splitext(filename)[0] + new_suffix
520
521
522
# In MINIMAL_RUNTIME mode, keep suffixes of generated files simple
523
# ('.mem' instead of '.js.mem'; .'symbols' instead of '.js.symbols' etc)
524
# Retain the original naming scheme in traditional runtime.
525
def replace_or_append_suffix(filename, new_suffix):
526
assert new_suffix[0] == '.'
527
return replace_suffix(filename, new_suffix) if settings.MINIMAL_RUNTIME else filename + new_suffix
528
529
530
# Temp dir. Create a random one, unless EMCC_DEBUG is set, in which case use the canonical
531
# temp directory (TEMP_DIR/emscripten_temp).
532
@memoize
533
def get_emscripten_temp_dir():
534
"""Returns a path to EMSCRIPTEN_TEMP_DIR, creating one if it didn't exist."""
535
global EMSCRIPTEN_TEMP_DIR
536
if not EMSCRIPTEN_TEMP_DIR:
537
EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_', dir=TEMP_DIR)
538
539
if not DEBUG_SAVE:
540
def prepare_to_clean_temp(d):
541
def clean_temp():
542
utils.delete_dir(d)
543
544
atexit.register(clean_temp)
545
# this global var might change later
546
prepare_to_clean_temp(EMSCRIPTEN_TEMP_DIR)
547
return EMSCRIPTEN_TEMP_DIR
548
549
550
def in_temp(name):
551
return os.path.join(get_emscripten_temp_dir(), os.path.basename(name))
552
553
554
def get_canonical_temp_dir(temp_dir):
555
return os.path.join(temp_dir, 'emscripten_temp')
556
557
558
def setup_temp_dirs():
559
global EMSCRIPTEN_TEMP_DIR, CANONICAL_TEMP_DIR, TEMP_DIR
560
EMSCRIPTEN_TEMP_DIR = None
561
562
TEMP_DIR = os.environ.get("EMCC_TEMP_DIR", tempfile.gettempdir())
563
if not os.path.isdir(TEMP_DIR):
564
exit_with_error(f'The temporary directory `{TEMP_DIR}` does not exist! Please make sure that the path is correct.')
565
566
CANONICAL_TEMP_DIR = get_canonical_temp_dir(TEMP_DIR)
567
568
if DEBUG:
569
EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR
570
try:
571
safe_ensure_dirs(EMSCRIPTEN_TEMP_DIR)
572
except Exception as e:
573
exit_with_error(str(e) + f'Could not create canonical temp dir. Check definition of TEMP_DIR in {config.EM_CONFIG}')
574
575
# Since the canonical temp directory is, by definition, the same
576
# between all processes that run in DEBUG mode we need to use a multi
577
# process lock to prevent more than one process from writing to it.
578
# This is because emcc assumes that it can use non-unique names inside
579
# the temp directory.
580
# Sadly we need to allow child processes to access this directory
581
# though, since emcc can recursively call itself when building
582
# libraries and ports.
583
if 'EM_HAVE_TEMP_DIR_LOCK' not in os.environ:
584
filelock_name = os.path.join(EMSCRIPTEN_TEMP_DIR, 'emscripten.lock')
585
lock = filelock.FileLock(filelock_name)
586
os.environ['EM_HAVE_TEMP_DIR_LOCK'] = '1'
587
lock.acquire()
588
atexit.register(lock.release)
589
590
591
@memoize
592
def get_temp_files():
593
if DEBUG_SAVE:
594
# In debug mode store all temp files in the emscripten-specific temp dir
595
# and don't worry about cleaning them up.
596
return tempfiles.TempFiles(get_emscripten_temp_dir(), save_debug_files=True)
597
else:
598
# Otherwise use the system tempdir and try to clean up after ourselves.
599
return tempfiles.TempFiles(TEMP_DIR, save_debug_files=False)
600
601
602
def print_compiler_stage(cmd):
603
"""Emulate the '-v/-###' flags of clang/gcc by printing the sub-commands
604
that we run."""
605
606
def maybe_quote(arg):
607
if all(c.isalnum() or c in './-_' for c in arg):
608
return arg
609
else:
610
return f'"{arg}"'
611
612
if SKIP_SUBPROCS:
613
print(' ' + ' '.join([maybe_quote(a) for a in cmd]), file=sys.stderr)
614
sys.stderr.flush()
615
elif PRINT_SUBPROCS:
616
print(' %s %s' % (maybe_quote(cmd[0]), shlex.join(cmd[1:])), file=sys.stderr)
617
sys.stderr.flush()
618
619
620
def demangle_c_symbol_name(name):
621
if not is_c_symbol(name):
622
return '$' + name
623
return name[1:] if name.startswith('_') else name
624
625
626
def is_c_symbol(name):
627
return name.startswith('_')
628
629
630
def is_internal_global(name):
631
internal_start_stop_symbols = {'__start_em_asm', '__stop_em_asm',
632
'__start_em_js', '__stop_em_js',
633
'__start_em_lib_deps', '__stop_em_lib_deps',
634
'__em_lib_deps'}
635
internal_prefixes = ('__em_js__', '__em_lib_deps')
636
return name in internal_start_stop_symbols or any(name.startswith(p) for p in internal_prefixes)
637
638
639
def is_user_export(name):
640
if is_internal_global(name):
641
return False
642
return name not in ['__indirect_function_table', 'memory'] and not name.startswith(('dynCall_', 'orig$'))
643
644
645
def asmjs_mangle(name):
646
"""Mangle a name the way asm.js/JSBackend globals are mangled.
647
648
Prepends '_' and replaces non-alphanumerics with '_'.
649
Used by wasm backend for JS library consistency with asm.js.
650
"""
651
# We also use this function to convert the clang-mangled `__main_argc_argv`
652
# to simply `main` which is expected by the emscripten JS glue code.
653
if name == '__main_argc_argv':
654
name = 'main'
655
if is_user_export(name):
656
return '_' + name
657
return name
658
659
660
def suffix(name):
661
"""Return the file extension"""
662
return os.path.splitext(name)[1]
663
664
665
def unsuffixed(name):
666
"""Return the filename without the extension.
667
668
If there are multiple extensions this strips only the final one.
669
"""
670
return os.path.splitext(name)[0]
671
672
673
def unsuffixed_basename(name):
674
return os.path.basename(unsuffixed(name))
675
676
677
def get_file_suffix(filename):
678
"""Parses the essential suffix of a filename, discarding Unix-style version
679
numbers in the name. For example for 'libz.so.1.2.8' returns '.so'"""
680
while filename:
681
filename, suffix = os.path.splitext(filename)
682
if not suffix[1:].isdigit():
683
return suffix
684
return ''
685
686
687
def make_writable(filename):
688
assert os.path.exists(filename)
689
old_mode = stat.S_IMODE(os.stat(filename).st_mode)
690
os.chmod(filename, old_mode | stat.S_IWUSR)
691
692
693
def safe_copy(src, dst):
694
logging.debug('copy: %s -> %s', src, dst)
695
src = os.path.abspath(src)
696
dst = os.path.abspath(dst)
697
if os.path.isdir(dst):
698
dst = os.path.join(dst, os.path.basename(src))
699
if src == dst:
700
return
701
if dst == os.devnull:
702
return
703
# Copies data and permission bits, but not other metadata such as timestamp
704
shutil.copy(src, dst)
705
# We always want the target file to be writable even when copying from
706
# read-only source. (e.g. a read-only install of emscripten).
707
make_writable(dst)
708
709
710
def do_replace(input_, pattern, replacement):
711
if pattern not in input_:
712
exit_with_error('expected to find pattern in input JS: %s' % pattern)
713
return input_.replace(pattern, replacement)
714
715
716
def get_llvm_target():
717
if settings.MEMORY64:
718
return 'wasm64-unknown-emscripten'
719
else:
720
return 'wasm32-unknown-emscripten'
721
722
723
def init():
724
set_version_globals()
725
setup_temp_dirs()
726
727
728
# ============================================================================
729
# End declarations.
730
# ============================================================================
731
732
# Everything below this point is top level code that get run when importing this
733
# file. TODO(sbc): We should try to reduce that amount we do here and instead
734
# have consumers explicitly call initialization functions.
735
736
CLANG_CC = clang_tool_path('clang')
737
CLANG_CXX = clang_tool_path('clang++')
738
CLANG_SCAN_DEPS = llvm_tool_path('clang-scan-deps')
739
LLVM_AR = llvm_tool_path('llvm-ar')
740
LLVM_DWP = llvm_tool_path('llvm-dwp')
741
LLVM_RANLIB = llvm_tool_path('llvm-ranlib')
742
LLVM_NM = llvm_tool_path('llvm-nm')
743
LLVM_DWARFDUMP = llvm_tool_path('llvm-dwarfdump')
744
LLVM_OBJCOPY = llvm_tool_path('llvm-objcopy')
745
LLVM_STRIP = llvm_tool_path('llvm-strip')
746
WASM_LD = llvm_tool_path('wasm-ld')
747
LLVM_PROFDATA = llvm_tool_path('llvm-profdata')
748
LLVM_COV = llvm_tool_path('llvm-cov')
749
750
EMCC = bat_suffix(path_from_root('emcc'))
751
EMXX = bat_suffix(path_from_root('em++'))
752
EMAR = bat_suffix(path_from_root('emar'))
753
EMRANLIB = bat_suffix(path_from_root('emranlib'))
754
EM_NM = bat_suffix(path_from_root('emnm'))
755
FILE_PACKAGER = bat_suffix(path_from_root('tools/file_packager'))
756
WASM_SOURCEMAP = bat_suffix(path_from_root('tools/wasm-sourcemap'))
757
# Windows .dll suffix is not included in this list, since those are never
758
# linked to directly on the command line.
759
DYLIB_EXTENSIONS = ['.dylib', '.so']
760
761
run_via_emxx = False
762
763
init()
764
765