blob: e8c197e08355bf535e7a6010fd6be6ab55f24f8e [file] [log] [blame] [edit]
# Copyright 2013 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
import logging
import os
import sys
import time
from subprocess import Popen, PIPE, CalledProcessError
TRACK_PROCESS_SPAWNS = int(os.getenv('EM_BUILD_VERBOSE', '0')) >= 3
WORKING_ENGINES = {} # Holds all configured engines and whether they work: maps path -> True/False
def timeout_run(proc, timeout=None, note='unnamed process', full_output=False, note_args=[], throw_on_failure=True):
start = time.time()
if timeout is not None:
while time.time() - start < timeout and proc.poll() is None:
time.sleep(0.1)
if proc.poll() is None:
proc.kill() # XXX bug: killing emscripten.py does not kill it's child process!
raise Exception("Timed out: " + note)
stdout, stderr = proc.communicate()
out = ['' if o is None else o for o in (stdout, stderr)]
if throw_on_failure and proc.returncode != 0:
raise CalledProcessError(proc.returncode, ' '.join(note_args), stdout, stderr)
if TRACK_PROCESS_SPAWNS:
logging.info('Process ' + str(proc.pid) + ' finished after ' + str(time.time() - start) + ' seconds. Exit code: ' + str(proc.returncode))
return '\n'.join(out) if full_output else out[0]
def make_command(filename, engine=None, args=[]):
if type(engine) is not list:
engine = [engine]
# Emscripten supports multiple javascript runtimes. The default is nodejs but
# it can also use d8 (the v8 engine shell) or jsc (JavaScript Core aka
# Safari). Both d8 and jsc require a '--' to delimit arguments to be passed
# to the executed script from d8/jsc options. Node does not require a
# delimeter--arguments after the filename are passed to the script.
#
# Check only the last part of the engine path to ensure we don't accidentally
# label a path to nodejs containing a 'd8' as spidermonkey instead.
jsengine = os.path.basename(engine[0])
# Use "'d8' in" because the name can vary, e.g. d8_g, d8, etc.
is_d8 = 'd8' in jsengine or 'v8' in jsengine
is_jsc = 'jsc' in jsengine
is_wasmer = 'wasmer' in jsengine
is_wasmtime = 'wasmtime' in jsengine
# Disable true async compilation (async apis will in fact be synchronous) for now
# due to https://bugs.chromium.org/p/v8/issues/detail?id=6263
shell_option_flags = ['--no-wasm-async-compilation'] if is_d8 else []
command_flags = []
if is_wasmer:
command_flags += ['run']
if is_wasmer or is_wasmtime:
# in a wasm runtime, run the wasm, not the js
filename = shared.unsuffixed(filename) + '.wasm'
# Separates engine flags from script flags
flag_separator = ['--'] if is_d8 or is_jsc else []
return engine + command_flags + [filename] + shell_option_flags + flag_separator + args
def check_engine(engine):
if type(engine) is list:
engine_path = engine[0]
else:
engine_path = engine
global WORKING_ENGINES
if engine_path in WORKING_ENGINES:
return WORKING_ENGINES[engine_path]
try:
logging.debug('Checking JS engine %s' % engine)
output = run_js(shared.path_from_root('src', 'hello_world.js'), engine,
skip_check=True)
if 'hello, world!' in output:
WORKING_ENGINES[engine_path] = True
except Exception as e:
logging.info('Checking JS engine %s failed. Check your config file. Details: %s' % (str(engine), str(e)))
WORKING_ENGINES[engine_path] = False
return WORKING_ENGINES[engine_path]
def require_engine(engine):
engine_path = engine[0]
if engine_path not in WORKING_ENGINES:
check_engine(engine)
if not WORKING_ENGINES[engine_path]:
logging.critical('The JavaScript shell (%s) does not seem to work, check the paths in the config file' % engine)
sys.exit(1)
def run_js_tool(filename, engine, jsargs=[], *args, **kw):
"""Execute a javascript tool.
This is used by emcc to run parts of the build process that are written
implemented in javascript.
"""
command = make_command(filename, engine, jsargs)
return shared.check_call(command, *args, **kw).stdout
# TODO(sbc): Move to into test directory
def run_js(filename, engine=None, args=[], check_timeout=False, stdin=None, stdout=PIPE, stderr=None, cwd=None,
full_output=False, assert_returncode=0, skip_check=False):
"""Execute javascript code generated by tests, with possible timeout."""
if not os.path.exists(filename):
raise Exception('JavaScript file not found: ' + filename)
# # code to serialize out the test suite files
# # XXX make sure to disable memory init files, and clear out the base_dir. you may also need to manually grab e.g. paper.pdf.js from a run of test_poppler
# import shutil, json
# base_dir = '/tmp/emscripten_suite'
# if not os.path.exists(base_dir):
# os.makedirs(base_dir)
# commands_file = os.path.join(base_dir, 'commands.txt')
# commands = ''
# if os.path.exists(commands_file):
# commands = open(commands_file).read()
# i = 0
# while True:
# curr = os.path.join(base_dir, str(i) + '.js')
# if not os.path.exists(curr): break
# i += 1
# shutil.copyfile(filename, curr)
# commands += os.path.basename(curr) + ',' + json.dumps(args) + '\n'
# open(commands_file, 'w').write(commands)
command = make_command(filename, engine, args)
try:
proc = Popen(
command,
stdin=stdin,
stdout=stdout,
stderr=stderr,
cwd=cwd,
universal_newlines=True)
except Exception:
# the failure may be because the engine is not present. show the proper
# error in that case
if not skip_check:
require_engine(engine)
# if we got here, then require_engine succeeded, so we can raise the original error
raise
timeout = 15 * 60 if check_timeout else None
if TRACK_PROCESS_SPAWNS:
logging.info('Blocking on process ' + str(proc.pid) + ': ' + str(command) + (' for ' + str(timeout) + ' seconds' if timeout else ' until it finishes.'))
try:
ret = timeout_run(
proc,
timeout,
'Execution',
full_output=full_output,
throw_on_failure=False)
except Exception:
# the failure may be because the engine does not work. show the proper
# error in that case
if not skip_check:
require_engine(engine)
# if we got here, then require_engine succeeded, so we can raise the original error
raise
if assert_returncode is not None and proc.returncode is not assert_returncode:
raise CalledProcessError(proc.returncode, ' '.join(command), str(ret))
return ret
try:
from . import shared
except ImportError:
# Python 2 circular import compatibility
import shared