diff options
Diffstat (limited to 'util/wasm')
-rw-r--r-- | util/wasm/batchedtestrunner/batchedtestrunner.html | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/batchedtestrunner.js | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/emrunadapter.js | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/qtestoutputreporter.css | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/qtestoutputreporter.js | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/qwasmjsruntime.js | 44 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/qwasmtestmain.js | 2 | ||||
-rw-r--r-- | util/wasm/batchedtestrunner/util.js | 2 | ||||
-rwxr-xr-x | util/wasm/preload/preload_qml_imports.py | 101 | ||||
-rwxr-xr-x | util/wasm/preload/preload_qt_plugins.py | 54 | ||||
-rw-r--r-- | util/wasm/preload/wasm_binary_tools.py | 78 |
11 files changed, 266 insertions, 25 deletions
diff --git a/util/wasm/batchedtestrunner/batchedtestrunner.html b/util/wasm/batchedtestrunner/batchedtestrunner.html index 0b85e48691..147cf34376 100644 --- a/util/wasm/batchedtestrunner/batchedtestrunner.html +++ b/util/wasm/batchedtestrunner/batchedtestrunner.html @@ -1,6 +1,6 @@ <!-- Copyright (C) 2022 The Qt Company Ltd. -SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 --> <!doctype html> diff --git a/util/wasm/batchedtestrunner/batchedtestrunner.js b/util/wasm/batchedtestrunner/batchedtestrunner.js index 7cd5f2d350..453865a935 100644 --- a/util/wasm/batchedtestrunner/batchedtestrunner.js +++ b/util/wasm/batchedtestrunner/batchedtestrunner.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import { AbortedError, diff --git a/util/wasm/batchedtestrunner/emrunadapter.js b/util/wasm/batchedtestrunner/emrunadapter.js index 96c5624131..5b4284e18f 100644 --- a/util/wasm/batchedtestrunner/emrunadapter.js +++ b/util/wasm/batchedtestrunner/emrunadapter.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import { RunnerStatus, TestStatus } from './batchedtestrunner.js'; diff --git a/util/wasm/batchedtestrunner/qtestoutputreporter.css b/util/wasm/batchedtestrunner/qtestoutputreporter.css index 3cf312b11a..aefb867b81 100644 --- a/util/wasm/batchedtestrunner/qtestoutputreporter.css +++ b/util/wasm/batchedtestrunner/qtestoutputreporter.css @@ -1,6 +1,6 @@ /* Copyright (C) 2022 The Qt Company Ltd. - SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 */ :root { diff --git a/util/wasm/batchedtestrunner/qtestoutputreporter.js b/util/wasm/batchedtestrunner/qtestoutputreporter.js index ad8a373540..7af288b8f0 100644 --- a/util/wasm/batchedtestrunner/qtestoutputreporter.js +++ b/util/wasm/batchedtestrunner/qtestoutputreporter.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import { RunnerStatus, TestStatus } from './batchedtestrunner.js' diff --git a/util/wasm/batchedtestrunner/qwasmjsruntime.js b/util/wasm/batchedtestrunner/qwasmjsruntime.js index b8603f2618..3f2d421181 100644 --- a/util/wasm/batchedtestrunner/qwasmjsruntime.js +++ b/util/wasm/batchedtestrunner/qwasmjsruntime.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // Exposes platform capabilities as static properties @@ -109,18 +109,19 @@ export class CompiledModule { this.#resourceLocator = resourceLocator; } - static make(js, wasm, resourceLocator - ) { + static make(js, wasm, entryFunctionName, resourceLocator) + { const exports = {}; + const module = {}; eval(js); - if (!exports.createQtAppInstance) { + if (!module.exports) { throw new Error( - 'createQtAppInstance has not been exported by the main script' + '${entryFunctionName} has not been exported by the main script' ); } return new CompiledModule( - exports.createQtAppInstance, js, wasm, resourceLocator + module.exports, js, wasm, resourceLocator ); } @@ -128,16 +129,9 @@ export class CompiledModule { return await new Promise(async (resolve, reject) => { let instance = undefined; let result = undefined; - const continuation = () => { - if (!(instance && result)) - return; - resolve({ - stdout: result.stdout, - exitCode: result.exitCode, - instance, - }); - }; + let testFinished = false; + const testFinishedEvent = new CustomEvent('testFinished'); instance = await this.#createQtAppInstanceFn((() => { const params = this.#makeDefaultExecParams({ onInstantiationError: (error) => { reject(error); }, @@ -153,12 +147,26 @@ export class CompiledModule { params.quit = (code, exception) => { if (exception && exception.name !== 'ExitStatus') reject(exception); + }; + params.notifyTestFinished = (code) => { result = { stdout: data, exitCode: code }; - continuation(); + testFinished = true; + window.dispatchEvent(testFinishedEvent); }; return params; })()); - continuation(); + if (!testFinished) { + await new Promise((resolve) => { + window.addEventListener('testFinished', () => { + resolve(); + }); + }); + } + resolve({ + stdout: result.stdout, + exitCode: result.exitCode, + instance, + }); }); } @@ -218,6 +226,6 @@ export class ModuleLoader { ); const [js, wasm] = await Promise.all([jsLoadPromise, wasmLoadPromise]); - return CompiledModule.make(js, wasm, this.#resourceLocator); + return CompiledModule.make(js, wasm, `${moduleName}_entry`, this.#resourceLocator); } } diff --git a/util/wasm/batchedtestrunner/qwasmtestmain.js b/util/wasm/batchedtestrunner/qwasmtestmain.js index 497f5309b1..a92a3a4b30 100644 --- a/util/wasm/batchedtestrunner/qwasmtestmain.js +++ b/util/wasm/batchedtestrunner/qwasmtestmain.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import { BatchedTestRunner } from './batchedtestrunner.js' import { EmrunAdapter, EmrunCommunication } from './emrunadapter.js' diff --git a/util/wasm/batchedtestrunner/util.js b/util/wasm/batchedtestrunner/util.js index 07a0e73e1a..a297baf6b2 100644 --- a/util/wasm/batchedtestrunner/util.js +++ b/util/wasm/batchedtestrunner/util.js @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 export function parseQuery() { const trimmed = window.location.search.substring(1); diff --git a/util/wasm/preload/preload_qml_imports.py b/util/wasm/preload/preload_qml_imports.py new file mode 100755 index 0000000000..9af4fa2a28 --- /dev/null +++ b/util/wasm/preload/preload_qml_imports.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import subprocess +import json + +# Paths to shared libraries and qml imports on the Qt installation on the web server. +# "$QTDIR" is replaced by qtloader.js at load time (defaults to "qt"), and makes +# possible to relocate the application build relative to the Qt build on the web server. +qt_lib_path = "$QTDIR/lib" +qt_qml_path = "$QTDIR/qml" + +# Path to QML imports on the in-memory file system provided by Emscripten. This script emits +# preload commands which copies QML imports to this directory. In addition, preload_qt_plugins.py +# creates (and preloads) a qt.conf file which makes Qt load QML plugins from this location. +qt_deploy_qml_path = "/qt/qml" + + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def preload_file(source, destination): + preload_files.append({"source": source, "destination": destination}) + + +def extract_preload_files_from_imports(imports): + libraries = [] + for qml_import in imports: + try: + relative_path = qml_import["relativePath"] + plugin = qml_import["plugin"] + + # plugin .so + plugin_filename = "lib" + plugin + ".so" + so_plugin_source_path = os.path.join( + qt_qml_path, relative_path, plugin_filename + ) + so_plugin_destination_path = os.path.join( + qt_deploy_qml_path, relative_path, plugin_filename + ) + + preload_file(so_plugin_source_path, so_plugin_destination_path) + so_plugin_qt_install_path = os.path.join( + qt_wasm_path, "qml", relative_path, plugin_filename + ) + + # qmldir file + qmldir_source_path = os.path.join(qt_qml_path, relative_path, "qmldir") + qmldir_destination_path = os.path.join( + qt_deploy_qml_path, relative_path, "qmldir" + ) + preload_file(qmldir_source_path, qmldir_destination_path) + except Exception as e: + eprint(e) + continue + return libraries + + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("Usage: python preload_qml_imports.py <qml-source-path> <qt-host-path> <qt-wasm-path>") + sys.exit(1) + + qml_source_path = sys.argv[1] + qt_host_path = sys.argv[2] + qt_wasm_path = sys.argv[3] + + qml_import_path = os.path.join(qt_wasm_path, "qml") + qmlimportsscanner_path = os.path.join(qt_host_path, "libexec/qmlimportscanner") + + eprint("runing qmlimportsscanner") + command = [qmlimportsscanner_path, "-rootPath", qml_source_path, "-importPath", qml_import_path] + result = subprocess.run(command, stdout=subprocess.PIPE) + imports = json.loads(result.stdout) + + preload_files = [] + libraries = extract_preload_files_from_imports(imports) + + # Deploy plugin dependencies, that is, shared libraries used by the plugins. + # Skip some of the obvious libraries which will be + skip_libraries = [ + "libQt6Core.so", + "libQt6Gui.so", + "libQt6Quick.so", + "libQt6Qml.so" "libQt6Network.so", + "libQt6OpenGL.so", + ] + + libraries = set(libraries) - set(skip_libraries) + for library in libraries: + source = os.path.join(qt_lib_path, library) + # Emscripten looks for shared libraries on "/", shared libraries + # most be deployed there instead of at /qt/lib + destination = os.path.join("/", library) + preload_file(source, destination) + + print(json.dumps(preload_files, indent=2)) diff --git a/util/wasm/preload/preload_qt_plugins.py b/util/wasm/preload/preload_qt_plugins.py new file mode 100755 index 0000000000..362d129732 --- /dev/null +++ b/util/wasm/preload/preload_qt_plugins.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import json + +# Path to plugins on the Qt installation on the web server. "$QTPATH" is replaced by qtloader.js +# at load time (defaults to "qt"), which makes it possible to relocate the application build relative +# to the Qt build on the web server. +qt_plugins_path = "$QTDIR/plugins" + +# Path to plugins on the in-memory file system provided by Emscripten. This script emits +# preload commands which copies plugins to this directory. +qt_deploy_plugins_path = "/qt/plugins" + + +def find_so_files(directory): + so_files = [] + for root, dirs, files in os.walk(directory): + for file in files: + if file.endswith(".so"): + relative_path = os.path.relpath(os.path.join(root, file), directory) + so_files.append(relative_path) + return so_files + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python make_qt_symlinks.py <qt-wasm-path>") + sys.exit(1) + + qt_wasm_path = sys.argv[1] + + # preload all plugins + plugins = find_so_files(os.path.join(qt_wasm_path, "plugins")) + preload = [ + { + "source": os.path.join(qt_plugins_path, plugin), + "destination": os.path.join(qt_deploy_plugins_path, plugin), + } + for plugin in plugins + ] + + # Create and preload qt.conf which will tell Qt to look for plugins + # and QML imports in /qt/plugins and /qt/qml. The qt.conf file is + # written to the current directory. + qtconf = "[Paths]\nPrefix = /qt\n" + with open("qt.conf", "w") as f: + f.write(qtconf) + preload.append({"source": "qt.conf", "destination": "/qt.conf"}) + + print(json.dumps(preload, indent=2)) diff --git a/util/wasm/preload/wasm_binary_tools.py b/util/wasm/preload/wasm_binary_tools.py new file mode 100644 index 0000000000..9a2150b964 --- /dev/null +++ b/util/wasm/preload/wasm_binary_tools.py @@ -0,0 +1,78 @@ + +#!/usr/bin/env python3 +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import sys +import struct + + +class WasmBinary: + """For reference of binary format see Emscripten source code, especially library_dylink.js.""" + + def __init__(self, filepath): + self._offset = 0 + self._end = 0 + self._dependencies = [] + with open(filepath, 'rb') as file: + self._binary = file.read() + self._check_preamble() + self._parse_subsections() + + def get_dependencies(self): + return self._dependencies + + def _get_leb(self): + ret = 0 + mul = 1 + while True: + byte = self._binary[self._offset] + self._offset += 1 + ret += (byte & 0x7f) * mul + mul *= 0x80 + if not (byte & 0x80): + break + return ret + + def _get_string(self): + length = self._get_leb() + self._offset += length + return self._binary[self._offset - length:self._offset].decode('utf-8') + + def _check_preamble(self): + preamble = memoryview(self._binary)[:24] + int32View = struct.unpack('<6I', preamble) + assert int32View[0] == 0x6d736100, "magic number not found" + assert self._binary[8] == 0, "dynlink section needs to be first" + self._offset = 9 + section_size = self._get_leb() + self._end = self._offset + section_size + name = self._get_string() + assert name == "dylink.0", "section dylink.0 not found" + + def _parse_subsections(self): + WASM_DYLINK_NEEDED = 0x2 + + while self._offset < self._end: + subsection_type = self._binary[self._offset] + self._offset += 1 + subsection_size = self._get_leb() + + if subsection_type == WASM_DYLINK_NEEDED: + needed_dynlibs_count = self._get_leb() + for _ in range(needed_dynlibs_count): + self._dependencies.append(self._get_string()) + else: + self._offset += subsection_size # we don't care about other sections for now + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python wasm_binary_tools.py <shared_object>") + sys.exit(1) + + file_path = sys.argv[1] + binary = WasmBinary(file_path) + dependencies = binary.get_dependencies() + for d in dependencies: + print(d) |