diff options
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/tools/configure.cmake | 6 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/CMakeLists.txt | 19 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/common.h | 26 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/jsontools.cpp | 101 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/jsontools.h | 19 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/main.cpp | 417 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/wasmbinary.cpp | 91 | ||||
-rw-r--r-- | src/tools/wasmdeployqt/wasmbinary.h | 24 |
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 ¶ms) +{ + 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 ¶ms) +{ + 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 ¶ms) +{ + 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 ¶ms) +{ + 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 ¶ms) +{ + 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 ¶ms) +{ + 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 |