Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "common.h"
5#include "jsontools.h"
6#include "wasmbinary.h"
7
8#include <QCoreApplication>
9#include <QDir>
10#include <QDirListing>
11#include <QDirIterator>
12#include <QtGlobal>
13#include <QLibraryInfo>
14#include <QJsonDocument>
15#include <QStringList>
16#include <QtCore/QCommandLineOption>
17#include <QtCore/QCommandLineParser>
18#include <QtCore/QProcess>
19#include <QQueue>
20#include <QMap>
21#include <QSet>
22
23#include <optional>
24#include <iostream>
25#include <ostream>
26
38
40{
41 QCoreApplication::setApplicationName("wasmdeployqt");
42 QCoreApplication::setApplicationVersion("1.0");
43 QCommandLineParser parser;
44 parser.setApplicationDescription(
45 QStringLiteral("Qt for WebAssembly deployment tool \n\n"
46 "Example:\n"
47 "wasmdeployqt app.wasm --qml-root-path=repo/myapp "
48 "--qt-wasm-dir=/home/user/qt/shared-qt-wasm/bin"));
49 parser.addHelpOption();
50
51 QStringList args = QCoreApplication::arguments();
52
53 parser.addPositionalArgument("app", "Path to the application.");
54 QCommandLineOption libPathOption("lib-path", "Colon-separated list of library directories.",
55 "paths");
56 parser.addOption(libPathOption);
57 QCommandLineOption qtWasmDirOption("qt-wasm-dir", "Path to the Qt for WebAssembly directory.",
58 "dir");
59 parser.addOption(qtWasmDirOption);
60 QCommandLineOption qtHostDirOption("qt-host-dir", "Path to the Qt host directory.", "dir");
61 parser.addOption(qtHostDirOption);
62 QCommandLineOption qmlRootPathOption("qml-root-path", "Root directory for QML files.", "dir");
63 parser.addOption(qmlRootPathOption);
64 parser.process(args);
65
66 const QStringList positionalArgs = parser.positionalArguments();
67 if (positionalArgs.size() > 1) {
68 std::cout << "ERROR: Expected only one positional argument with path to the app. Received: "
69 << positionalArgs.join(" ").toStdString() << std::endl;
70 return false;
71 }
72 if (!positionalArgs.isEmpty()) {
73 params.argAppPath = positionalArgs.first();
74 }
75
76 if (parser.isSet(libPathOption)) {
77 QStringList paths = parser.value(libPathOption).split(';', Qt::SkipEmptyParts);
78 for (const QString &path : paths) {
79 QDir dir(path);
80 if (dir.exists()) {
81 params.libPaths.append(dir);
82 } else {
83 std::cout << "ERROR: Directory does not exist: " << path.toStdString() << std::endl;
84 return false;
85 }
86 }
87 }
88 if (parser.isSet(qtWasmDirOption)) {
89 QDir dir(parser.value(qtWasmDirOption));
90 if (dir.cdUp() && dir.exists())
91 params.qtWasmDir = dir;
92 else {
93 std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
94 << std::endl;
95 return false;
96 }
97 }
98 if (parser.isSet(qtHostDirOption)) {
99 QDir dir(parser.value(qtHostDirOption));
100 if (dir.cdUp() && dir.exists())
101 params.qtHostDir = dir;
102 else {
103 std::cout << "ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
104 << std::endl;
105 return false;
106 }
107 }
108 if (parser.isSet(qmlRootPathOption)) {
109 QDir dir(parser.value(qmlRootPathOption));
110 if (dir.exists()) {
111 params.qmlRootPath = dir;
112 } else {
113 std::cout << "ERROR: Directory specified for qml-root-path does not exist: "
114 << dir.absolutePath().toStdString() << std::endl;
115 return false;
116 }
117 }
118 return true;
119}
120
122{
123 QDirIterator it(QDir::currentPath(), QStringList() << "*.html" << "*.wasm" << "*.js",
124 QDir::NoFilter);
125 QMap<QString, QSet<QString>> fileGroups;
126 while (it.hasNext()) {
127 QFileInfo fileInfo(it.next());
128 QString baseName = fileInfo.completeBaseName();
129 QString suffix = fileInfo.suffix();
130 fileGroups[baseName].insert(suffix);
131 }
132 for (auto it = fileGroups.constBegin(); it != fileGroups.constEnd(); ++it) {
133 const QSet<QString> &extensions = it.value();
134 if (extensions.contains("html") && extensions.contains("js")
135 && extensions.contains("wasm")) {
136 return it.key();
137 }
138 }
139 return std::nullopt;
140}
141
143{
144 if (params.argAppPath) {
145 QFileInfo fileInfo(*params.argAppPath);
146 if (!fileInfo.exists()) {
147 std::cout << "ERROR: Cannot find " << params.argAppPath->toStdString() << std::endl;
148 std::cout << "Make sure that the path is valid." << std::endl;
149 return false;
150 }
151 params.appWasmPath = fileInfo.absoluteFilePath();
152 } else {
153 auto appName = detectAppName();
154 if (!appName) {
155 std::cout << "ERROR: Cannot find the application in current directory. Specify the "
156 "path as an argument:"
157 "wasmdeployqt <path-to-app-wasm-binary>"
158 << std::endl;
159 return false;
160 }
161 params.appWasmPath = QDir::current().filePath(*appName + ".wasm");
162 std::cout << "Automatically detected " << params.appWasmPath.toStdString() << std::endl;
163 }
164 if (!params.qtWasmDir) {
165 std::cout << "ERROR: Please set path to Qt WebAssembly installation as "
166 "--qt-wasm-dir=<path_to_qt_wasm_bin>"
167 << std::endl;
168 return false;
169 }
170 if (!params.qtHostDir) {
171 auto qtHostPath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
172 if (qtHostPath.length() == 0) {
173 std::cout << "ERROR: Cannot read Qt host path or detect it from environment. Please "
174 "pass it explicitly with --qt-host-dir=<path>. "
175 << std::endl;
176 } else {
177 auto qtHostDir = QDir(qtHostPath);
178 if (!qtHostDir.cdUp()) {
179 std::cout << "ERROR: Invalid Qt host path: "
180 << qtHostDir.absolutePath().toStdString() << std::endl;
181 return false;
182 }
183 params.qtHostDir = qtHostDir;
184 }
185 }
186 params.libPaths.push_front(params.qtWasmDir->filePath("lib"));
187 params.libPaths.push_front(*params.qtWasmDir);
188 return true;
189}
190
191bool copyFile(QString srcPath, QString destPath)
192{
193 auto file = QFile(destPath);
194 if (file.exists()) {
195 file.remove();
196 }
197 QFileInfo destInfo(destPath);
198 if (!QDir().mkpath(destInfo.path())) {
199 std::cout << "ERROR: Cannot create path " << destInfo.path().toStdString() << std::endl;
200 return false;
201 }
202 if (!QFile::copy(srcPath, destPath)) {
203
204 std::cout << "ERROR: Failed to copy " << srcPath.toStdString() << " to "
205 << destPath.toStdString() << std::endl;
206
207 return false;
208 }
209 return true;
210}
211
212bool copyDirectDependencies(QList<QString> dependencies, const Parameters &params)
213{
214 for (auto &&depFilename : dependencies) {
215 if (params.loadedQtLibraries.contains(depFilename)) {
216 continue; // dont copy library that has been already copied
217 }
218
219 std::optional<QString> libPath;
220 for (auto &&libDir : params.libPaths) {
221 auto path = libDir.filePath(depFilename);
222 QFileInfo file(path);
223 if (file.exists()) {
224 libPath = path;
225 }
226 }
227 if (!libPath) {
228 std::cout << "ERROR: Cannot find required library " << depFilename.toStdString()
229 << std::endl;
230 return false;
231 }
232 if (!copyFile(*libPath, QDir::current().filePath(depFilename)))
233 return false;
234 }
235 std::cout << "INFO: Succesfully copied direct dependencies." << std::endl;
236 return true;
237}
238
239QStringList findSoFiles(const QString &directory)
240{
241 QStringList soFiles;
242 QDir baseDir(directory);
243 if (!baseDir.exists())
244 return soFiles;
245
246 QDirIterator it(directory, QStringList() << "*.so", QDir::Files, QDirIterator::Subdirectories);
247 while (it.hasNext()) {
248 it.next();
249 QString absPath = it.filePath();
250 QString filePath = baseDir.relativeFilePath(absPath);
251 soFiles.append(filePath);
252 }
253 return soFiles;
254}
255
257{
258 Q_ASSERT(params.qtWasmDir);
259 auto qtLibDir = *params.qtWasmDir;
260 if (!qtLibDir.cd("lib")) {
261 std::cout << "ERROR: Cannot find lib directory in Qt installation." << std::endl;
262 return false;
263 }
264 auto qtLibTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("lib"));
265
266 auto soFiles = findSoFiles(qtLibDir.absolutePath());
267 for (auto &&soFilePath : soFiles) {
268 auto relativeFilePath = QDir("lib").filePath(soFilePath);
269 auto srcPath = qtLibDir.absoluteFilePath(soFilePath);
270 auto destPath = qtLibTargetDir.absoluteFilePath(soFilePath);
271 if (!copyFile(srcPath, destPath))
272 return false;
273 params.loadedQtLibraries.insert(QFileInfo(srcPath).fileName());
274 }
275 std::cout << "INFO: Succesfully deployed qt lib shared objects." << std::endl;
276 return true;
277}
278
280{
281 Q_ASSERT(params.qtWasmDir);
282 auto qtPluginsDir = *params.qtWasmDir;
283 if (!qtPluginsDir.cd("plugins")) {
284 std::cout << "ERROR: Cannot find plugins directory in Qt installation." << std::endl;
285 return false;
286 }
287 auto qtPluginsTargetDir = QDir(QDir(QDir::current().filePath("qt")).filePath("plugins"));
288
289 // copy files
290 auto soFiles = findSoFiles(qtPluginsDir.absolutePath());
291 for (auto &&soFilePath : soFiles) {
292 auto relativeFilePath = QDir("plugins").filePath(soFilePath);
293 params.loadedQtLibraries.insert(QFileInfo(relativeFilePath).fileName());
294 auto srcPath = qtPluginsDir.absoluteFilePath(soFilePath);
295 auto destPath = qtPluginsTargetDir.absoluteFilePath(soFilePath);
296 if (!copyFile(srcPath, destPath))
297 return false;
298 }
299
300 // qt_plugins.json
301 QSet<PreloadEntry> preload{ { { "qt.conf" }, { "/qt.conf" } } };
302 for (auto &&plugin : soFiles) {
303 PreloadEntry entry;
304 entry.source = QDir("$QTDIR").filePath("plugins") + QDir::separator()
305 + QDir(qtPluginsDir).relativeFilePath(plugin);
306 entry.destination = "/qt/plugins/" + QDir(qtPluginsTargetDir).relativeFilePath(plugin);
307 preload.insert(entry);
308 }
309 JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_plugins.json"));
310
311 QString qtconfContent = "[Paths]\nPrefix = /qt\n";
312 QString filePath = QDir::current().filePath("qt.conf");
313
314 QFile file(filePath);
315 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
316 QTextStream out(&file);
317 out << qtconfContent;
318 if (!file.flush()) {
319 std::cout << "ERROR: Failed flushing the file :" << file.fileName().toStdString()
320 << std::endl;
321 return false;
322 }
323 file.close();
324 } else {
325 std::cout << "ERROR: Failed to write to qt.conf." << std::endl;
326 return false;
327 }
328 std::cout << "INFO: Succesfully deployed qt plugins." << std::endl;
329 return true;
330}
331
333{
334 Q_ASSERT(params.qtWasmDir);
335 if (!params.qmlRootPath) {
336 std::cout << "WARNING: qml-root-path not specified. Skipping generating preloads for QML "
337 "imports."
338 << std::endl;
339 std::cout << "WARNING: This may lead to erronous behaviour if applications requires QML "
340 "imports."
341 << std::endl;
342 QSet<PreloadEntry> preload;
343 JsonTools::savePreloadFile(preload, QDir::current().filePath("qt_qml_imports.json"));
344 return true;
345 }
346 auto qmlImportScannerPath = params.qtHostDir
347 ? QDir(params.qtHostDir->filePath("libexec")).filePath("qmlimportscanner")
348 : "qmlimportscanner";
349 QProcess process;
350 auto qmlImportPath = *params.qtWasmDir;
351 qmlImportPath.cd("qml");
352 if (!qmlImportPath.exists()) {
353 std::cout << "ERROR: Cannot find qml import path: "
354 << qmlImportPath.absolutePath().toStdString() << std::endl;
355 return false;
356 }
357
358 QStringList args{ "-rootPath", params.qmlRootPath->absolutePath(), "-importPath",
359 qmlImportPath.absolutePath() };
360 process.start(qmlImportScannerPath, args);
361 if (!process.waitForFinished()) {
362 std::cout << "ERROR: Failed to execute qmlImportScanner." << std::endl;
363 return false;
364 }
365
366 QString stdoutOutput = process.readAllStandardOutput();
367 auto qmlImports = JsonTools::getPreloadsFromQmlImportScannerOutput(stdoutOutput);
368 if (!qmlImports) {
369 return false;
370 }
371 JsonTools::savePreloadFile(*qmlImports, QDir::current().filePath("qt_qml_imports.json"));
372 for (const PreloadEntry &import : *qmlImports) {
373 auto relativePath = import.source;
374 relativePath.remove("$QTDIR/");
375
376 auto srcPath = params.qtWasmDir->absoluteFilePath(relativePath);
377 auto destPath = QDir(QDir::current().filePath("qt")).absoluteFilePath(relativePath);
378 if (!copyFile(srcPath, destPath))
379 return false;
380 }
381 std::cout << "INFO: Succesfully deployed qml imports." << std::endl;
382 return true;
383}
384
385int main(int argc, char **argv)
386{
387 QCoreApplication app(argc, argv);
388 Parameters params;
389 if (!parseArguments(params)) {
390 return -1;
391 }
392 if (!verifyPaths(params)) {
393 return -1;
394 }
395 std::cout << "INFO: Target: " << params.appWasmPath.toStdString() << std::endl;
396 WasmBinary wasmBinary(params.appWasmPath);
397 if (wasmBinary.type == WasmBinary::Type::INVALID) {
398 return -1;
399 } else if (wasmBinary.type == WasmBinary::Type::STATIC) {
400 std::cout << "INFO: This is statically linked WebAssembly binary." << std::endl;
401 std::cout << "INFO: No extra steps required!" << std::endl;
402 return 0;
403 }
404 std::cout << "INFO: Verified as shared module." << std::endl;
405
406 if (!copyQtLibs(params))
407 return -1;
408 if (!copyPreloadPlugins(params))
409 return -1;
410 if (!copyPreloadQmlImports(params))
411 return -1;
412 if (!copyDirectDependencies(wasmBinary.dependencies, params))
413 return -1;
414
415 std::cout << "INFO: Deployment done!" << std::endl;
416 return 0;
417}
bool copyPreloadPlugins(Parameters &params)
Definition main.cpp:279
bool copyDirectDependencies(QList< QString > dependencies, const Parameters &params)
Definition main.cpp:212
QStringList findSoFiles(const QString &directory)
Definition main.cpp:239
bool parseArguments(Parameters &params)
Definition main.cpp:39
bool copyPreloadQmlImports(Parameters &params)
Definition main.cpp:332
bool copyFile(QString srcPath, QString destPath)
Definition main.cpp:191
std::optional< QString > detectAppName()
Definition main.cpp:121
bool verifyPaths(Parameters &params)
Definition main.cpp:142
bool copyQtLibs(Parameters &params)
Definition main.cpp:256
int main(int argc, char *argv[])
[ctor_close]
A class for parsing and managing a function parameter list.
Definition main.cpp:28
std::optional< QDir > qmlRootPath
Definition main.cpp:34
std::optional< QDir > qtWasmDir
Definition main.cpp:32
std::optional< QDir > qtHostDir
Definition main.cpp:31
QString appWasmPath
Definition main.cpp:30
std::optional< QString > argAppPath
Definition main.cpp:29
QList< QDir > libPaths
Definition main.cpp:33
QSet< QString > loadedQtLibraries
Definition main.cpp:36