summaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2023-06-05 14:18:09 +0200
committerMorten Sørvig <morten.sorvig@qt.io>2023-07-03 19:02:48 +0200
commitbe7b748b7e049a4e42611f3e5f920a568348ff15 (patch)
treec2859802a51043ada0f763d61fd1e7595f0263d7 /util
parent73c0c798a0c28e0bbc91f80810273b66f8b89be1 (diff)
wasm: add shared library preload scripts
Add scripts which generates Qt plugins and QML imports preload lists, for use with the "preload" functionality form qtloader.js. The preload lists downlad plugins and imports from $QTDIR/ to /qt/ at application load time, where $QTDIR is configurable using the qt.qtdir qtloader configuration property (set to "qt" by default). Sample directory structure: app.html app.js qtloader.js qt_plugins.json [generated] qt_qml_imports.json [generated] qt -> /path/to/qt [symlink] The json files are generated by the scripts in this commit. app.html configures qtloader.js to use the json files as preload lists, which instructs it to preload from "qt", which again is a symlink to or a copy of the Qt installation. Pick-to: 6.6 Task-number: QTBUG-63925 Change-Id: I53bd197f22057dbb70e9a9bee43b9d9b969aa072 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'util')
-rwxr-xr-xutil/wasm/preload/preload_qml_imports.py120
-rwxr-xr-xutil/wasm/preload/preload_qt_plugins.py54
2 files changed, 174 insertions, 0 deletions
diff --git a/util/wasm/preload/preload_qml_imports.py b/util/wasm/preload/preload_qml_imports.py
new file mode 100755
index 0000000000..3e936dce22
--- /dev/null
+++ b/util/wasm/preload/preload_qml_imports.py
@@ -0,0 +1,120 @@
+#!/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
+import re
+
+# 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 find_dependencies(filepath):
+ # Very basic dependency finder which scans for ".so" strings in the file
+ try:
+ with open(filepath, "rb") as file:
+ content = file.read()
+ return [
+ m.group(0).decode("utf-8")
+ for m in re.finditer(rb"[\w\-.]+\.so", content)
+ ]
+ except IOError as e:
+ eprint(f"Error: {e}")
+ return []
+
+
+def extract_preload_files_from_imports(imports):
+ libraries = []
+ files = []
+ for qml_import in imports:
+ try:
+ relative_path = qml_import["relativePath"]
+ plugin = qml_import["plugin"]
+
+ # plugin .so
+ so_plugin_source_path = os.path.join(
+ qt_qml_path, relative_path, "lib" + plugin + ".so"
+ )
+ so_plugin_destination_path = os.path.join(
+ qt_deploy_qml_path, relative_path, "lib" + plugin + ".so"
+ )
+
+ preload_file(so_plugin_source_path, so_plugin_destination_path)
+ so_plugin_qt_install_path = os.path.join(
+ qt_wasm_path, "qml", relative_path, "lib" + plugin + ".so"
+ )
+ deps = find_dependencies(so_plugin_qt_install_path)
+ libraries.extend(deps)
+
+ # 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 files, libraries
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python make_qt_symlinks.py <qt-host-path> <qt-wasm-path>")
+ sys.exit(1)
+
+ qt_host_path = sys.argv[1]
+ qt_wasm_path = sys.argv[2]
+
+ qml_import_path = os.path.join(qt_wasm_path, "qml")
+ qmlimportsscanner_path = os.path.join(qt_host_path, "libexec/qmlimportscanner")
+
+ eprint("runing qmlimportsscanner")
+ result = subprocess.run(
+ [qmlimportsscanner_path, "-rootPath", ".", "-importPath", qml_import_path],
+ stdout=subprocess.PIPE,
+ )
+ imports = json.loads(result.stdout)
+
+ preload_files = []
+ libraries = []
+ 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))