| import logging |
| import os |
| import re |
| import sys |
|
|
| __scriptdir__ = os.path.dirname(os.path.abspath(__file__)) |
| __rootdir__ = os.path.dirname(__scriptdir__) |
| sys.path.insert(0, __rootdir__) |
|
|
| from . import building, shared, utils |
| from .settings import settings |
|
|
| logger = logging.getLogger('minimal_runtime_shell') |
|
|
|
|
| def generate_minimal_runtime_load_statement(target_basename): |
| |
| prefix_statements = [] |
| |
| then_statements = [] |
| |
| modularize_imports = [] |
|
|
| |
| |
| |
| |
| if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION: |
| if settings.MIN_SAFARI_VERSION < 150000: |
| |
| download_wasm = f"WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('{target_basename}.wasm')) : binary('{target_basename}.wasm')" |
| else: |
| |
| download_wasm = f"WebAssembly.compileStreaming(fetch('{target_basename}.wasm'))" |
| elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION: |
| |
| |
| if settings.MIN_SAFARI_VERSION < 150000: |
| download_wasm = f"!WebAssembly.instantiateStreaming && binary('{target_basename}.wasm')" |
| else: |
| |
| |
| download_wasm = None |
| else: |
| download_wasm = f"binary('{target_basename}.wasm')" |
|
|
| |
| files_to_load = [f"script('{settings.TARGET_JS_NAME}')"] |
|
|
| |
| if (settings.WASM == 1 and settings.WASM2JS == 0) or not download_wasm: |
| if settings.MODULARIZE: |
| modularize_imports += [f'wasm: r[{len(files_to_load)}]'] |
| else: |
| then_statements += [f"{settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"] |
| if download_wasm: |
| files_to_load += [download_wasm] |
|
|
| |
| if settings.WASM_WORKERS and settings.MODULARIZE: |
| modularize_imports += ['js: js'] |
|
|
| |
| if settings.WASM == 2: |
| if settings.MODULARIZE: |
| modularize_imports += [f'wasm: supportsWasm ? r[{len(files_to_load)}] : 0'] |
| else: |
| then_statements += [f"if (supportsWasm) {settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"] |
| files_to_load += [f"supportsWasm ? {download_wasm} : script('{target_basename}.wasm.js')"] |
|
|
| |
| if settings.MODULARIZE: |
| modularize_imports = ',\n '.join(modularize_imports) |
| if settings.WASM_WORKERS: |
| then_statements += ['''\ |
| // Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" |
| // directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see |
| // https://bugzil.la/1540101 |
| var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' })); |
| script(js).then((c) => c({ |
| %s |
| }));''' % modularize_imports] |
| else: |
| then_statements += ['''\ |
| // Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this" |
| // directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see |
| // https://bugzil.la/1540101 |
| var js = r[0]; |
| js({ |
| %s |
| });''' % modularize_imports] |
|
|
| binary_xhr = ' var binary = (url) => fetch(url).then((rsp) => rsp.arrayBuffer());' |
|
|
| script_xhr = '''\ |
| function script(url) { // Downloads a script file and adds it to DOM |
| return new Promise((ok) => { |
| var s = document.createElement('script'); |
| s.src = url; |
| s.onload = () => { |
| #if MODULARIZE |
| #if WASM == 2 |
| // In MODULARIZEd WASM==2 builds, we use this same function to download |
| // both .js and .asm.js that are structured with {{{ EXPORT_NAME }}} |
| // at the top level, but also use this function to download the Wasm2JS |
| // file that does not have an {{{ EXPORT_NAME }}} function, hence the |
| // variable typeof check: |
| if (typeof {{{ EXPORT_NAME }}} !== 'undefined') { |
| var c = {{{ EXPORT_NAME }}}; |
| delete {{{ EXPORT_NAME }}}; |
| ok(c); |
| } else { |
| ok(); |
| } |
| #else |
| var c = {{{ EXPORT_NAME }}}; |
| delete {{{ EXPORT_NAME }}}; |
| ok(c); |
| #endif |
| #else |
| ok(); |
| #endif |
| }; |
| document.body.appendChild(s); |
| }); |
| } |
| ''' |
|
|
| |
| if len(files_to_load) == 1: |
| if settings.MODULARIZE: |
| return script_xhr + files_to_load[0] + ".then(js);" |
| else: |
| return script_xhr + files_to_load[0] + ";" |
|
|
| if not settings.MODULARIZE or settings.WASM_WORKERS: |
| |
| |
| |
| |
| |
| if settings.PTHREADS or settings.WASM_WORKERS: |
| script_load = "script(url)" |
| else: |
| script_load = "script(url).then(() => URL.revokeObjectURL(url));" |
|
|
| if settings.WASM_WORKERS: |
| save_js = f'{settings.EXPORT_NAME}.js = ' |
| else: |
| save_js = '' |
|
|
| files_to_load[0] = f"binary('{settings.TARGET_JS_NAME}')" |
| if not settings.MODULARIZE: |
| then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js, |
| script_load] |
|
|
| |
| if any("binary(" in s for s in files_to_load + then_statements): |
| prefix_statements += [binary_xhr] |
| if any("script(" in s for s in files_to_load + then_statements): |
| prefix_statements += [script_xhr] |
|
|
| |
| load = '\n'.join(prefix_statements) |
| load += "Promise.all([" + ', '.join(files_to_load) + "])" |
| if len(then_statements) > 0: |
| load += '.then((r) => {\n %s\n});' % '\n '.join(then_statements) |
| return load |
|
|
|
|
| def generate_minimal_runtime_html(target, options, js_target, target_basename): |
| logger.debug('generating HTML for minimal runtime') |
| shell = utils.read_file(options.shell_html) |
| if settings.SINGLE_FILE: |
| |
| shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '') |
| else: |
| shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', generate_minimal_runtime_load_statement(target_basename)) |
|
|
| temp_files = shared.get_temp_files() |
| with temp_files.get_file(suffix='.js') as shell_temp: |
| utils.write_file(shell_temp, shell) |
| shell = building.read_and_preprocess(shell_temp) |
|
|
| if re.search(r'{{{\s*SCRIPT\s*}}}', shell): |
| utils.exit_with_error(f'--shell-file "{options.shell_html}": MINIMAL_RUNTIME uses a different kind of HTML page shell file than the traditional runtime! Please see $EMSCRIPTEN/html/shell_minimal_runtime.html for a template to use as a basis.') |
|
|
| shell = shell.replace('{{{ TARGET_BASENAME }}}', settings.TARGET_BASENAME) |
| shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME) |
| shell = shell.replace('{{{ TARGET_JS_NAME }}}', settings.TARGET_JS_NAME) |
|
|
| |
| if settings.SINGLE_FILE: |
| js_contents = utils.read_file(js_target) |
| utils.delete_file(js_target) |
| else: |
| js_contents = '' |
| shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents) |
| utils.write_file(target, shell, options.output_eol) |
|
|