summaryrefslogtreecommitdiffstats
path: root/src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/CMakeLists.txt4
-rw-r--r--src/tools/configure.cmake6
-rw-r--r--src/tools/wasmdeployqt/CMakeLists.txt19
-rw-r--r--src/tools/wasmdeployqt/common.h26
-rw-r--r--src/tools/wasmdeployqt/jsontools.cpp101
-rw-r--r--src/tools/wasmdeployqt/jsontools.h19
-rw-r--r--src/tools/wasmdeployqt/main.cpp417
-rw-r--r--src/tools/wasmdeployqt/wasmbinary.cpp91
-rw-r--r--src/tools/wasmdeployqt/wasmbinary.h24
9 files changed, 707 insertions, 0 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 1ff52f8a84f..2f06cbcf67e 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -28,6 +28,10 @@ if(QT_FEATURE_macdeployqt)
add_subdirectory(macdeployqt)
endif()
+if(QT_FEATURE_wasmdeployqt)
+ add_subdirectory(wasmdeployqt)
+endif()
+
if(QT_FEATURE_windeployqt)
add_subdirectory(windeployqt)
endif()
diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake
index 6a9c1b8e3f3..27ea90b89ac 100644
--- a/src/tools/configure.cmake
+++ b/src/tools/configure.cmake
@@ -18,6 +18,12 @@ qt_feature("macdeployqt" PRIVATE
AUTODETECT CMAKE_HOST_APPLE
CONDITION MACOS AND QT_FEATURE_thread)
+qt_feature("wasmdeployqt" PRIVATE
+ SECTION "Deployment"
+ LABEL "WebAssembly deployment tool"
+ PURPOSE "The WebAssembly deployment tool is designed to automate the process of creating a deployable folder especially for dynamic linking case variant."
+ CONDITION QT_FEATURE_process)
+
qt_feature("windeployqt" PRIVATE
SECTION "Deployment"
LABEL "Windows deployment tool"
diff --git a/src/tools/wasmdeployqt/CMakeLists.txt b/src/tools/wasmdeployqt/CMakeLists.txt
new file mode 100644
index 00000000000..7305c14c269
--- /dev/null
+++ b/src/tools/wasmdeployqt/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## wasmdeployqt Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name wasmdeployqt)
+qt_internal_add_tool(${target_name}
+ TOOLS_TARGET Core
+ USER_FACING
+ INSTALL_VERSIONED_LINK
+ TARGET_DESCRIPTION "Qt WebAssembly Deployment Tool"
+ SOURCES
+ main.cpp wasmbinary.cpp jsontools.cpp
+ LIBRARIES
+ Qt::CorePrivate
+)
+qt_internal_return_unless_building_tools()
diff --git a/src/tools/wasmdeployqt/common.h b/src/tools/wasmdeployqt/common.h
new file mode 100644
index 00000000000..258d6161e67
--- /dev/null
+++ b/src/tools/wasmdeployqt/common.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <QHash>
+#include <QString>
+
+struct PreloadEntry
+{
+ QString source;
+ QString destination;
+
+ bool operator==(const PreloadEntry &other) const
+ {
+ return source == other.source && destination == other.destination;
+ }
+};
+
+inline uint qHash(const PreloadEntry &key, uint seed = 0)
+{
+ return qHash(key.source, seed) ^ qHash(key.destination, seed);
+}
+
+#endif
diff --git a/src/tools/wasmdeployqt/jsontools.cpp b/src/tools/wasmdeployqt/jsontools.cpp
new file mode 100644
index 00000000000..d76f9190b73
--- /dev/null
+++ b/src/tools/wasmdeployqt/jsontools.cpp
@@ -0,0 +1,101 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QDir>
+#include <QJsonArray>
+#include <QJsonObject>
+
+#include "jsontools.h"
+#include "common.h"
+
+#include <iostream>
+#include <optional>
+
+namespace JsonTools {
+
+bool savePreloadFile(QSet<PreloadEntry> preload, QString destFile)
+{
+
+ QJsonArray jsonArray;
+ for (const PreloadEntry &entry : preload) {
+ QJsonObject obj;
+ obj["source"] = entry.source;
+ obj["destination"] = entry.destination;
+ jsonArray.append(obj);
+ }
+ QJsonDocument doc(jsonArray);
+
+ QFile outFile(destFile);
+ if (outFile.exists()) {
+ if (!outFile.remove()) {
+ std::cout << "ERROR: Failed to delete old file: " << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ std::cout << "ERROR: Failed to open file for writing:" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ if (outFile.write(doc.toJson(QJsonDocument::Indented)) == -1) {
+ std::cout << "ERROR: Failed writing into file :" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ if (!outFile.flush()) {
+ std::cout << "ERROR: Failed flushing the file :" << outFile.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ outFile.close();
+ return true;
+}
+
+std::optional<QSet<PreloadEntry>> getPreloadsFromQmlImportScannerOutput(QString output)
+{
+ QString qtLibPath = "$QTDIR/lib";
+ QString qtQmlPath = "$QTDIR/qml";
+ QString qtDeployQmlPath = "/qt/qml";
+ QSet<PreloadEntry> res;
+ auto addImport = [&res](const PreloadEntry &entry) {
+ // qDebug() << "adding " << entry.source << "" << entry.destination;
+ res.insert(entry);
+ };
+
+ QJsonParseError parseError;
+ QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8(), &parseError);
+
+ if (parseError.error != QJsonParseError::NoError) {
+ std::cout << "ERROR: QmlImport JSON parse error: " << parseError.errorString().toStdString()
+ << std::endl;
+ return std::nullopt;
+ }
+ if (!doc.isArray()) {
+ std::cout << "ERROR: QmlImport JSON is not an array." << std::endl;
+ return std::nullopt;
+ }
+
+ QJsonArray jsonArray = doc.array();
+ for (const QJsonValue &value : jsonArray) {
+ if (value.isObject()) {
+ QJsonObject obj = value.toObject();
+ auto relativePath = obj["relativePath"].toString();
+ auto plugin = obj["plugin"].toString();
+ if (plugin.isEmpty() || relativePath.isEmpty()) {
+ continue;
+ }
+ auto pluginFilename = "lib" + plugin + ".so";
+ addImport(PreloadEntry{
+ QDir::cleanPath(qtQmlPath + "/" + relativePath + "/" + pluginFilename),
+ QDir::cleanPath(qtDeployQmlPath + "/" + relativePath + "/" + pluginFilename) });
+ addImport(PreloadEntry{
+ QDir::cleanPath(qtQmlPath + "/" + relativePath + "/" + "qmldir"),
+ QDir::cleanPath(qtDeployQmlPath + "/" + relativePath + "/" + "qmldir") });
+ }
+ }
+
+ return res;
+}
+
+}; // namespace JsonTools
diff --git a/src/tools/wasmdeployqt/jsontools.h b/src/tools/wasmdeployqt/jsontools.h
new file mode 100644
index 00000000000..a1691a2be8d
--- /dev/null
+++ b/src/tools/wasmdeployqt/jsontools.h
@@ -0,0 +1,19 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef JSONTOOLS_H
+#define JSONTOOLS_H
+
+#include <QFileInfo>
+#include <QSet>
+
+#include "common.h"
+
+#include <optional>
+
+namespace JsonTools {
+bool savePreloadFile(QSet<PreloadEntry> preload, QString destFile);
+std::optional<QSet<PreloadEntry>> getPreloadsFromQmlImportScannerOutput(QString output);
+}; // namespace JsonTools
+
+#endif
diff --git a/src/tools/wasmdeployqt/main.cpp b/src/tools/wasmdeployqt/main.cpp
new file mode 100644
index 00000000000..3bf2647cfaf
--- /dev/null
+++ b/src/tools/wasmdeployqt/main.cpp
@@ -0,0 +1,417 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "common.h"
+#include "jsontools.h"
+#include "wasmbinary.h"
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QDirListing>
+#include <QDirIterator>
+#include <QtGlobal>
+#include <QLibraryInfo>
+#include <QJsonDocument>
+#include <QStringList>
+#include <QtCore/QCommandLineOption>
+#include <QtCore/QCommandLineParser>
+#include <QtCore/QProcess>
+#include <QQueue>
+#include <QMap>
+#include <QSet>
+
+#include <optional>
+#include <iostream>
+#include <ostream>
+
+struct Parameters
+{
+ std::optional<QString> argAppPath;
+ QString appWasmPath;
+ std::optional<QDir> qtHostDir;
+ std::optional<QDir> qtWasmDir;
+ QList<QDir> libPaths;
+ std::optional<QDir> qmlRootPath;
+
+ QSet<QString> loadedQtLibraries;
+};
+
+bool parseArguments(Parameters &params)
+{
+ QCoreApplication::setApplicationName("wasmdeployqt");
+ QCoreApplication::setApplicationVersion("1.0");
+ QCommandLineParser parser;
+ parser.setApplicationDescription(
+ QStringLiteral("Qt for WebAssembly deployment tool \n\n"
+ "Example:\n"
+ "wasmdeployqt app.wasm --qml-root-path=repo/myapp "
+ "--qt-wasm-dir=/home/user/qt/shared-qt-wasm/bin"));
+ parser.addHelpOption();
+
+ QStringList args = QCoreApplication::arguments();
+
+ parser.addPositionalArgument("app", "Path to the application.");
+ QCommandLineOption libPathOption("lib-path", "Colon-separated list of library directories.",
+ "paths");
+ parser.addOption(libPathOption);
+ QCommandLineOption qtWasmDirOption("qt-wasm-dir", "Path to the Qt for WebAssembly directory.",
+ "dir");
+ parser.addOption(qtWasmDirOption);
+ QCommandLineOption qtHostDirOption("qt-host-dir", "Path to the Qt host directory.", "dir");
+ parser.addOption(qtHostDirOption);
+ QCommandLineOption qmlRootPathOption("qml-root-path", "Root directory for QML files.", "dir");
+ parser.addOption(qmlRootPathOption);
+ parser.process(args);
+
+ const QStringList positionalArgs = parser.positionalArguments();
+ if (positionalArgs.size() > 1) {
+ std::cout << "ERROR: Expected only one positional argument with path to the app. Received: "
+ << positionalArgs.join(" ").toStdString() << std::endl;
+ return false;
+ }
+ if (!positionalArgs.isEmpty()) {
+ params.argAppPath = positionalArgs.first();
+ }
+
+ if (parser.isSet(libPathOption)) {
+ QStringList paths = parser.value(libPathOption).split(';', Qt::SkipEmptyParts);
+ for (const QString &path : paths) {
+ QDir dir(path);
+ if (dir.exists()) {
+ params.libPaths.append(dir);
+ } else {
+ std::cout << "ERROR: Directory does not exist: " << path.toStdString() << std::endl;
+ return false;
+ }
+ }
+ }
+ if (parser.isSet(qtWasmDirOption)) {
+ QDir dir(parser.value(qtWasmDirOption));
+ if (dir.cdUp() && dir.exists())
+ params.qtWasmDir = dir;
+ else {
+ std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (parser.isSet(qtHostDirOption)) {
+ QDir dir(parser.value(qtHostDirOption));
+ if (dir.cdUp() && dir.exists())
+ params.qtHostDir = dir;
+ else {
+ std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
+ << std::endl;
+ return false;
+ }
+ }
+ if (parser.isSet(qmlRootPathOption)) {
+ QDir dir(parser.value(qmlRootPathOption));
+ if (dir.exists()) {
+ params.qmlRootPath = dir;
+ } else {
+ std::cout << "ERROR: Directory specified for qml-root-path does not exist: "
+ << dir.absolutePath().toStdString() << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+std::optional<QString> detectAppName()
+{
+ QDirIterator it(QDir::currentPath(), QStringList() << "*.html" << "*.wasm" << "*.js",
+ QDir::NoFilter);
+ QMap<QString, QSet<QString>> fileGroups;
+ while (it.hasNext()) {
+ QFileInfo fileInfo(it.next());
+ QString baseName = fileInfo.completeBaseName();
+ QString suffix = fileInfo.suffix();
+ fileGroups[baseName].insert(suffix);
+ }
+ for (auto it = fileGroups.constBegin(); it != fileGroups.constEnd(); ++it) {
+ const QSet<QString> &extensions = it.value();
+ if (extensions.contains("html") && extensions.contains("js")
+ && extensions.contains("wasm")) {
+ return it.key();
+ }
+ }
+ return std::nullopt;
+}
+
+bool verifyPaths(Parameters &params)
+{
+ if (params.argAppPath) {
+ QFileInfo fileInfo(*params.argAppPath);
+ if (!fileInfo.exists()) {
+ std::cout << "ERROR: Cannot find " << params.argAppPath->toStdString() << std::endl;
+ std::cout << "Make sure that the path is valid." << std::endl;
+ return false;
+ }
+ params.appWasmPath = fileInfo.absoluteFilePath();
+ } else {
+ auto appName = detectAppName();
+ if (!appName) {
+ std::cout << "ERROR: Cannot find the application in current directory. Specify the "
+ "path as an argument:"
+ "wasmdeployqt <path-to-app-wasm-binary>"
+ << std::endl;
+ return false;
+ }
+ params.appWasmPath = QDir::current().filePath(*appName + ".wasm");
+ std::cout << "Automatically detected " << params.appWasmPath.toStdString() << std::endl;
+ }
+ if (!params.qtWasmDir) {
+ std::cout << "ERROR: Please set path to Qt WebAssembly installation as "
+ "--qt-wasm-dir=<path_to_qt_wasm_bin>"
+ << std::endl;
+ return false;
+ }
+ if (!params.qtHostDir) {
+ auto qtHostPath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
+ if (qtHostPath.length() == 0) {
+ std::cout << "ERROR: Cannot read Qt host path or detect it from environment. Please "
+ "pass it explicitly with --qt-host-dir=<path>. "
+ << std::endl;
+ } else {
+ auto qtHostDir = QDir(qtHostPath);
+ if (!qtHostDir.cdUp()) {
+ std::cout << "ERROR: Invalid Qt host path: "
+ << qtHostDir.absolutePath().toStdString() << std::endl;
+ return false;
+ }
+ params.qtHostDir = qtHostDir;
+ }
+ }
+ params.libPaths.push_front(params.qtWasmDir->filePath("lib"));
+ params.libPaths.push_front(*params.qtWasmDir);
+ return true;
+}
+
+bool copyFile(QString srcPath, QString destPath)
+{
+ auto file = QFile(destPath);
+ if (file.exists()) {
+ file.remove();
+ }
+ QFileInfo destInfo(destPath);
+ if (!QDir().mkpath(destInfo.path())) {
+ std::cout << "ERROR: Cannot create path " << destInfo.path().toStdString() << std::endl;
+ return false;
+ }
+ if (!QFile::copy(srcPath, destPath)) {
+
+ std::cout << "ERROR: Failed to copy " << srcPath.toStdString() << " to "
+ << destPath.toStdString() << std::endl;
+
+ return false;
+ }
+ return true;
+}
+
+bool copyDirectDependencies(QList<QString> dependencies, const Parameters &params)
+{
+ for (auto &&depFilename : dependencies) {
+ if (params.loadedQtLibraries.contains(depFilename)) {
+ continue; // dont copy library that has been already copied
+ }
+
+ std::optional<QString> libPath;
+ for (auto &&libDir : params.libPaths) {
+ auto path = libDir.filePath(depFilename);
+ QFileInfo file(path);
+ if (file.exists()) {
+ libPath = path;
+ }
+ }
+ if (!libPath) {
+ std::cout << "ERROR: Cannot find required library " << depFilename.toStdString()
+ << std::endl;
+ return false;
+ }
+ if (!copyFile(*libPath, QDir::current().filePath(depFilename)))
+ return false;
+ }
+ std::cout << "INFO: Succesfully copied direct dependencies." << std::endl;
+ return true;
+}
+
+QStringList findSoFiles(const QString &directory)
+{
+ QStringList soFiles;
+ QDir baseDir(directory);
+ if (!baseDir.exists())
+ return soFiles;
+
+ QDirIterator it(directory, QStringList() << "*.so", QDir::Files, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ it.next();
+ QString absPath = it.filePath();
+ QString filePath = baseDir.relativeFilePath(absPath);
+ soFiles.append(filePath);
+ }
+ return soFiles;
+}
+
+bool copyQtLibs(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ auto qtLibDir = *params.qtWasmDir;
+ if (!qtLibDir.cd("lib")) {
+ std::cout << "ERROR: Cannot find lib directory in Qt installation." << std::endl;
+ return false;
+ }
+ auto qtLibTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("lib"));
+
+ auto soFiles = findSoFiles(qtLibDir.absolutePath());
+ for (auto &&soFilePath : soFiles) {
+ auto relativeFilePath = QDir("lib").filePath(soFilePath);
+ auto srcPath = qtLibDir.absoluteFilePath(soFilePath);
+ auto destPath = qtLibTargetDir.absoluteFilePath(soFilePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ params.loadedQtLibraries.insert(QFileInfo(srcPath).fileName());
+ }
+ std::cout << "INFO: Succesfully deployed qt lib shared objects." << std::endl;
+ return true;
+}
+
+bool copyPreloadPlugins(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ auto qtPluginsDir = *params.qtWasmDir;
+ if (!qtPluginsDir.cd("plugins")) {
+ std::cout << "ERROR: Cannot find plugins directory in Qt installation." << std::endl;
+ return false;
+ }
+ auto qtPluginsTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("plugins"));
+
+ // copy files
+ auto soFiles = findSoFiles(qtPluginsDir.absolutePath());
+ for (auto &&soFilePath : soFiles) {
+ auto relativeFilePath = QDir("plugins").filePath(soFilePath);
+ params.loadedQtLibraries.insert(QFileInfo(relativeFilePath).fileName());
+ auto srcPath = qtPluginsDir.absoluteFilePath(soFilePath);
+ auto destPath = qtPluginsTargetDir.absoluteFilePath(soFilePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ }
+
+ // qt_plugins.json
+ QSet<PreloadEntry> preload{ { { "qt.conf" }, { "/qt.conf" } } };
+ for (auto &&plugin : soFiles) {
+ PreloadEntry entry;
+ entry.source = QDir("$QTDIR").filePath("plugins") + QDir::separator()
+ + QDir(qtPluginsDir).relativeFilePath(plugin);
+ entry.destination = "/qt/plugins/" + QDir(qtPluginsTargetDir).relativeFilePath(plugin);
+ preload.insert(entry);
+ }
+ JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_plugins.json"));
+
+ QString qtconfContent = "[Paths]\nPrefix = /qt\n";
+ QString filePath = QDir::current().filePath("qt.conf");
+
+ QFile file(filePath);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ QTextStream out(&file);
+ out << qtconfContent;
+ if (!file.flush()) {
+ std::cout << "ERROR: Failed flushing the file :" << file.fileName().toStdString()
+ << std::endl;
+ return false;
+ }
+ file.close();
+ } else {
+ std::cout << "ERROR: Failed to write to qt.conf." << std::endl;
+ return false;
+ }
+ std::cout << "INFO: Succesfully deployed qt plugins." << std::endl;
+ return true;
+}
+
+bool copyPreloadQmlImports(Parameters &params)
+{
+ Q_ASSERT(params.qtWasmDir);
+ if (!params.qmlRootPath) {
+ std::cout << "WARNING: qml-root-path not specified. Skipping generating preloads for QML "
+ "imports."
+ << std::endl;
+ std::cout << "WARNING: This may lead to erronous behaviour if applications requires QML "
+ "imports."
+ << std::endl;
+ QSet<PreloadEntry> preload;
+ JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_qml_imports.json"));
+ return true;
+ }
+ auto qmlImportScannerPath = params.qtHostDir
+ ? QDir(params.qtHostDir->filePath("libexec")).filePath("qmlimportscanner")
+ : "qmlimportscanner";
+ QProcess process;
+ auto qmlImportPath = *params.qtWasmDir;
+ qmlImportPath.cd("qml");
+ if (!qmlImportPath.exists()) {
+ std::cout << "ERROR: Cannot find qml import path: "
+ << qmlImportPath.absolutePath().toStdString() << std::endl;
+ return -1;
+ }
+
+ QStringList args{ "-rootPath", params.qmlRootPath->absolutePath(), "-importPath",
+ qmlImportPath.absolutePath() };
+ process.start(qmlImportScannerPath, args);
+ if (!process.waitForFinished()) {
+ std::cout << "ERROR: Failed to execute qmlImportScanner." << std::endl;
+ return false;
+ }
+
+ QString stdoutOutput = process.readAllStandardOutput();
+ auto qmlImports = JsonTools::getPreloadsFromQmlImportScannerOutput(stdoutOutput);
+ if (!qmlImports) {
+ return false;
+ }
+ JsonTools::savePreloadFile(*qmlImports, QDir::current().filePath("qt_qml_imports.json"));
+ for (const PreloadEntry &import : *qmlImports) {
+ auto relativePath = import.source;
+ relativePath.remove("$QTDIR/");
+
+ auto srcPath = params.qtWasmDir->absoluteFilePath(relativePath);
+ auto destPath = QDir(QDir::current().filePath("qt")).absoluteFilePath(relativePath);
+ if (!copyFile(srcPath, destPath))
+ return false;
+ }
+ std::cout << "INFO: Succesfully deployed qml imports." << std::endl;
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ Parameters params;
+ if (!parseArguments(params)) {
+ return -1;
+ }
+ if (!verifyPaths(params)) {
+ return -1;
+ }
+ std::cout << "INFO: Target: " << params.appWasmPath.toStdString() << std::endl;
+ WasmBinary wasmBinary(params.appWasmPath);
+ if (wasmBinary.type == WasmBinary::Type::INVALID) {
+ return -1;
+ } else if (wasmBinary.type == WasmBinary::Type::STATIC) {
+ std::cout << "INFO: This is statically linked WebAssembly binary." << std::endl;
+ std::cout << "INFO: No extra steps required!" << std::endl;
+ return 0;
+ }
+ std::cout << "INFO: Verified as shared module." << std::endl;
+
+ if (!copyQtLibs(params))
+ return -1;
+ if (!copyPreloadPlugins(params))
+ return -1;
+ if (!copyPreloadQmlImports(params))
+ return -1;
+ if (!copyDirectDependencies(wasmBinary.dependencies, params))
+ return -1;
+
+ std::cout << "INFO: Deployment done!" << std::endl;
+ return 0;
+}
diff --git a/src/tools/wasmdeployqt/wasmbinary.cpp b/src/tools/wasmdeployqt/wasmbinary.cpp
new file mode 100644
index 00000000000..1a041c94066
--- /dev/null
+++ b/src/tools/wasmdeployqt/wasmbinary.cpp
@@ -0,0 +1,91 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "wasmbinary.h"
+
+#include <QFile>
+
+#include <iostream>
+
+WasmBinary::WasmBinary(QString filepath)
+{
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ std::cout << "ERROR: Cannot open the file " << filepath.toStdString() << std::endl;
+ std::cout << file.errorString().toStdString() << std::endl;
+ type = WasmBinary::Type::INVALID;
+ return;
+ }
+ auto bytes = file.readAll();
+ if (!parsePreambule(bytes)) {
+ type = WasmBinary::Type::INVALID;
+ }
+}
+
+bool WasmBinary::parsePreambule(QByteArrayView data)
+{
+ const auto preambuleSize = 24;
+ if (data.size() < preambuleSize) {
+ std::cout << "ERROR: Preambule of binary shorter than expected!" << std::endl;
+ return false;
+ }
+ uint32_t int32View[6];
+ std::memcpy(int32View, data.data(), sizeof(int32View));
+ if (int32View[0] != 0x6d736100) {
+ std::cout << "ERROR: Magic WASM number not found in binary. Binary corrupted?" << std::endl;
+ return false;
+ }
+ if (data[8] != 0) {
+ type = WasmBinary::Type::STATIC;
+ return true;
+ } else {
+ type = WasmBinary::Type::SHARED;
+ }
+ const auto sectionStart = 9;
+ size_t offset = sectionStart;
+ auto sectionSize = getLeb(data, offset);
+ auto sectionEnd = sectionStart + sectionSize;
+ auto name = getString(data, offset);
+ if (name != "dylink.0") {
+ type = WasmBinary::Type::INVALID;
+ std::cout << "ERROR: dylink.0 was not found in supposedly dynamically linked module"
+ << std::endl;
+ return false;
+ }
+
+ const auto WASM_DYLINK_NEEDED = 0x2;
+ while (offset < sectionEnd) {
+ auto subsectionType = data[offset++];
+ auto subsectionSize = getLeb(data, offset);
+ if (subsectionType == WASM_DYLINK_NEEDED) {
+ auto neededDynlibsCount = getLeb(data, offset);
+ while (neededDynlibsCount--) {
+ dependencies.append(getString(data, offset));
+ }
+ } else {
+ offset += subsectionSize;
+ }
+ }
+ return true;
+}
+
+size_t WasmBinary::getLeb(QByteArrayView data, size_t &offset)
+{
+ auto ret = 0;
+ auto mul = 1;
+ while (true) {
+ auto byte = data[offset++];
+ ret += (byte & 0x7f) * mul;
+ mul *= 0x80;
+ if (!(byte & 0x80))
+ break;
+ }
+ return ret;
+}
+
+QString WasmBinary::getString(QByteArrayView data, size_t &offset)
+{
+ auto length = getLeb(data, offset);
+ offset += length;
+ return QString::fromUtf8(data.sliced(offset - length, length));
+}
diff --git a/src/tools/wasmdeployqt/wasmbinary.h b/src/tools/wasmdeployqt/wasmbinary.h
new file mode 100644
index 00000000000..c3bb3f0eaa4
--- /dev/null
+++ b/src/tools/wasmdeployqt/wasmbinary.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef WASMBINARY_H
+#define WASMBINARY_H
+
+#include <QString>
+#include <QList>
+
+class WasmBinary
+{
+public:
+ enum class Type { INVALID, STATIC, SHARED };
+ WasmBinary(QString filepath);
+ Type type;
+ QList<QString> dependencies;
+
+private:
+ bool parsePreambule(QByteArrayView data);
+ size_t getLeb(QByteArrayView data, size_t &offset);
+ QString getString(QByteArrayView data, size_t &offset);
+};
+
+#endif