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