41 QCoreApplication::setApplicationName(
"wasmdeployqt");
42 QCoreApplication::setApplicationVersion(
"1.0");
43 QCommandLineParser parser;
44 parser.setApplicationDescription(
45 QStringLiteral(
"Qt for WebAssembly deployment tool \n\n"
47 "wasmdeployqt app.wasm --qml-root-path=repo/myapp "
48 "--qt-wasm-dir=/home/user/qt/shared-qt-wasm/bin"));
49 parser.addHelpOption();
51 QStringList args = QCoreApplication::arguments();
53 parser.addPositionalArgument(
"app",
"Path to the application.");
54 QCommandLineOption libPathOption(
"lib-path",
"Colon-separated list of library directories.",
56 parser.addOption(libPathOption);
57 QCommandLineOption qtWasmDirOption(
"qt-wasm-dir",
"Path to the Qt for WebAssembly directory.",
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);
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;
72 if (!positionalArgs.isEmpty()) {
73 params.argAppPath = positionalArgs.first();
76 if (parser.isSet(libPathOption)) {
77 QStringList paths = parser.value(libPathOption).split(
';', Qt::SkipEmptyParts);
78 for (
const QString &path : paths) {
81 params.libPaths.append(dir);
83 std::cout <<
"ERROR: Directory does not exist: " << path.toStdString() << std::endl;
88 if (parser.isSet(qtWasmDirOption)) {
89 QDir dir(parser.value(qtWasmDirOption));
90 if (dir.cdUp() && dir.exists())
91 params.qtWasmDir = dir;
93 std::cout <<
"ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
98 if (parser.isSet(qtHostDirOption)) {
99 QDir dir(parser.value(qtHostDirOption));
100 if (dir.cdUp() && dir.exists())
101 params.qtHostDir = dir;
103 std::cout <<
"ERROR: Directory does not exist: " << dir.absolutePath().toStdString()
108 if (parser.isSet(qmlRootPathOption)) {
109 QDir dir(parser.value(qmlRootPathOption));
111 params.qmlRootPath = dir;
113 std::cout <<
"ERROR: Directory specified for qml-root-path does not exist: "
114 << dir.absolutePath().toStdString() <<
std::endl;
123 QDirIterator it(QDir::currentPath(), QStringList() <<
"*.html" <<
"*.wasm" <<
"*.js",
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);
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")) {
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;
151 params.appWasmPath = fileInfo.absoluteFilePath();
153 auto appName = detectAppName();
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>"
161 params.appWasmPath = QDir::current().filePath(*appName +
".wasm");
162 std::cout <<
"Automatically detected " << params.appWasmPath.toStdString() <<
std::endl;
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>"
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>. "
177 auto qtHostDir = QDir(qtHostPath);
178 if (!qtHostDir.cdUp()) {
179 std::cout <<
"ERROR: Invalid Qt host path: "
180 << qtHostDir.absolutePath().toStdString() <<
std::endl;
183 params.qtHostDir = qtHostDir;
186 params.libPaths.push_front(params.qtWasmDir->filePath(
"lib"));
187 params.libPaths.push_front(*params.qtWasmDir);
193 auto file = QFile(destPath);
197 QFileInfo destInfo(destPath);
198 if (!QDir().mkpath(destInfo.path())) {
199 std::cout <<
"ERROR: Cannot create path " << destInfo.path().toStdString() <<
std::endl;
202 if (!QFile::copy(srcPath, destPath)) {
204 std::cout <<
"ERROR: Failed to copy " << srcPath.toStdString() <<
" to "
205 << destPath.toStdString() <<
std::endl;
214 for (
auto &&depFilename : dependencies) {
215 if (params.loadedQtLibraries.contains(depFilename)) {
219 std::optional<QString> libPath;
220 for (
auto &&libDir : params.libPaths) {
221 auto path = libDir.filePath(depFilename);
222 QFileInfo file(path);
228 std::cout <<
"ERROR: Cannot find required library " << depFilename.toStdString()
232 if (!copyFile(*libPath, QDir::current().filePath(depFilename)))
235 std::cout <<
"INFO: Succesfully copied direct dependencies." <<
std::endl;
242 QDir baseDir(directory);
243 if (!baseDir.exists())
246 QDirIterator it(directory, QStringList() <<
"*.so", QDir::Files, QDirIterator::Subdirectories);
247 while (it.hasNext()) {
249 QString absPath = it.filePath();
250 QString filePath = baseDir.relativeFilePath(absPath);
251 soFiles.append(filePath);
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;
264 auto qtLibTargetDir = QDir(QDir(QDir::current().filePath(
"qt")).filePath(
"lib"));
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))
273 params.loadedQtLibraries.insert(QFileInfo(srcPath).fileName());
275 std::cout <<
"INFO: Succesfully deployed qt lib shared objects." <<
std::endl;
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;
287 auto qtPluginsTargetDir = QDir(QDir(QDir::current().filePath(
"qt")).filePath(
"plugins"));
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))
301 QSet<PreloadEntry> preload{ { {
"qt.conf" }, {
"/qt.conf" } } };
302 for (
auto &&plugin : soFiles) {
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);
309 JsonTools::savePreloadFile(preload, QDir::current().filePath(
"qt_plugins.json"));
311 QString qtconfContent =
"[Paths]\nPrefix = /qt\n";
312 QString filePath = QDir::current().filePath(
"qt.conf");
314 QFile file(filePath);
315 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
316 QTextStream out(&file);
317 out << qtconfContent;
319 std::cout <<
"ERROR: Failed flushing the file :" << file.fileName().toStdString()
325 std::cout <<
"ERROR: Failed to write to qt.conf." <<
std::endl;
328 std::cout <<
"INFO: Succesfully deployed qt plugins." <<
std::endl;
334 Q_ASSERT(params.qtWasmDir);
335 if (!params.qmlRootPath) {
336 std::cout <<
"WARNING: qml-root-path not specified. Skipping generating preloads for QML "
339 std::cout <<
"WARNING: This may lead to erronous behaviour if applications requires QML "
342 QSet<PreloadEntry> preload;
343 JsonTools::savePreloadFile(preload, QDir::current().filePath(
"qt_qml_imports.json"));
346 auto qmlImportScannerPath = params.qtHostDir
347 ? QDir(params.qtHostDir->filePath(
"libexec")).filePath(
"qmlimportscanner")
348 :
"qmlimportscanner";
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;
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;
366 QString stdoutOutput = process.readAllStandardOutput();
367 auto qmlImports = JsonTools::getPreloadsFromQmlImportScannerOutput(stdoutOutput);
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/");
376 auto srcPath = params.qtWasmDir->absoluteFilePath(relativePath);
377 auto destPath = QDir(QDir::current().filePath(
"qt")).absoluteFilePath(relativePath);
378 if (!copyFile(srcPath, destPath))
381 std::cout <<
"INFO: Succesfully deployed qml imports." <<
std::endl;