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) 2021 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 <QCoreApplication>
5#include <QStringList>
6#include <QDir>
7#include <QDirIterator>
8#include <QJsonDocument>
9#include <QJsonObject>
10#include <QJsonArray>
11#include <QJsonValue>
12#include <QDebug>
13#include <QDataStream>
14#include <QXmlStreamReader>
15#include <QStandardPaths>
16#include <QUuid>
17#include <QDirListing>
18#include <QElapsedTimer>
19#include <QRegularExpression>
20#include <QSettings>
21#include <QHash>
22#include <QSet>
23#include <QMap>
24#if QT_CONFIG(process)
25#include <QProcess>
26#endif
27
28#include <depfile_shared.h>
29#include <shellquote_shared.h>
30
31#include <algorithm>
32
33#if defined(Q_OS_WIN32)
34#include <qt_windows.h>
35#endif
36
37#ifdef Q_CC_MSVC
38#define popen _popen
39#define QT_POPEN_READ "rb"
40#define pclose _pclose
41#else
42#define QT_POPEN_READ "r"
43#endif
44
45using namespace Qt::StringLiterals;
46
47static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
48
50
51auto openProcess(const QString &command)
52{
53#if defined(Q_OS_WIN32)
54 QString processedCommand = u'\"' + command + u'\"';
55#else
56 const QString& processedCommand = command;
57#endif
58 struct Closer { void operator()(FILE *proc) const { if (proc) (void)pclose(proc); } };
59 using UP = std::unique_ptr<FILE, Closer>;
60 return UP{popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ)};
61}
62
64{
65 QtDependency(const QString &rpath, const QString &apath) : relativePath(rpath), absolutePath(apath) {}
66
67 bool operator==(const QtDependency &other) const
68 {
69 return relativePath == other.relativePath && absolutePath == other.absolutePath;
70 }
71
74};
75
77{
78 QtInstallDirectoryWithTriple(const QString &dir = QString(),
79 const QString &t = QString(),
80 const QHash<QString, QString> &dirs = QHash<QString, QString>()
81 ) :
84 triple(t),
85 enabled(false)
86 {}
87
91 bool enabled;
92};
93
94struct Options
95{
97 : helpRequested(false)
98 , verbose(false)
99 , timing(false)
100 , build(true)
101 , auxMode(false)
103 , releasePackage(false)
104 , digestAlg("SHA-256"_L1)
105 , sigAlg("SHA256withRSA"_L1)
106 , internalSf(false)
107 , sectionsOnly(false)
109 , installApk(false)
110 , uninstallApk(false)
112 , buildAar(false)
115 {}
116
122
128
131 bool timing;
132 bool build;
134 bool noRccBundleCleanup = false;
137
138 // External tools
144
145 // Build paths
157 // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder
158 // when looking for dependencies.
171
172 // Versioning
177
178 // lib c++ path
181
182 // Build information
188 bool buildAAB = false;
190
191
192 // Package information
202 bool useLegacyPackaging = false;
203 bool createSymlinksOnly = false;
204
205 // Signing information
222
223 // Installation information
227
228 // Per architecture collected information
229 void setCurrentQtArchitecture(const QString &arch,
230 const QString &directory,
231 const QHash<QString, QString> &directories)
232 {
233 currentArchitecture = arch;
234 qtInstallDirectory = directory;
235 qtDataDirectory = directories["qtDataDirectory"_L1];
236 qtLibsDirectory = directories["qtLibsDirectory"_L1];
237 qtLibExecsDirectory = directories["qtLibExecsDirectory"_L1];
238 qtPluginsDirectory = directories["qtPluginsDirectory"_L1];
239 qtQmlDirectory = directories["qtQmlDirectory"_L1];
240 }
245 bool usesOpenGL = false;
246
247 // Per package collected information
248 // permissions 'name' => 'optional additional attributes'
252
253 // Override qml import scanner path
260};
261
263 {"aarch64", "arm64-v8a"},
264 {"arm", "armeabi-v7a"},
265 {"i386", "x86"},
266 {"x86_64", "x86_64"}
267};
268
269bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
270bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
271 const QString &moduleUrl);
272bool readDependenciesFromElf(Options *options, const QString &fileName,
273 QSet<QString> *usedDependencies, QSet<QString> *remainingDependencies);
274
275QString architectureFromName(const QString &name)
276{
277 QRegularExpression architecture(QStringLiteral("_(armeabi-v7a|arm64-v8a|x86|x86_64).so$"));
278 auto match = architecture.match(name);
279 if (!match.hasMatch())
280 return {};
281 return match.captured(1);
282}
283
284static QString execSuffixAppended(QString path)
285{
286#if defined(Q_OS_WIN32)
287 path += ".exe"_L1;
288#endif
289 return path;
290}
291
292static QString batSuffixAppended(QString path)
293{
294#if defined(Q_OS_WIN32)
295 path += ".bat"_L1;
296#endif
297 return path;
298}
299
301{
302#ifdef Q_OS_WIN32
303 return "bin"_L1;
304#else
305 return "libexec"_L1;
306#endif
307}
308
309static QString llvmReadobjPath(const Options &options)
310{
311 return execSuffixAppended("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj"_L1
312 .arg(options.ndkPath,
313 options.toolchainPrefix,
314 options.ndkHost));
315}
316
317QString fileArchitecture(const Options &options, const QString &path)
318{
319 auto arch = architectureFromName(path);
320 if (!arch.isEmpty())
321 return arch;
322
323 QString readElf = llvmReadobjPath(options);
324 if (!QFile::exists(readElf)) {
325 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
326 return {};
327 }
328
329 readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(path));
330
331 auto readElfCommand = openProcess(readElf);
332 if (!readElfCommand) {
333 fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
334 return {};
335 }
336
337 char buffer[512];
338 while (fgets(buffer, sizeof(buffer), readElfCommand.get()) != nullptr) {
339 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
340 line = line.trimmed();
341 if (line.startsWith("Arch: ")) {
342 auto it = elfArchitectures.find(line.mid(6));
343 return it != elfArchitectures.constEnd() ? QString::fromLatin1(it.value()) : QString{};
344 }
345 }
346 return {};
347}
348
349bool checkArchitecture(const Options &options, const QString &fileName)
350{
351 return fileArchitecture(options, fileName) == options.currentArchitecture;
352}
353
354void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
355{
356 if (options.verbose)
357 fprintf(stdout, "Delete missing files %s %s\n", qPrintable(srcDir.absolutePath()), qPrintable(dstDir.absolutePath()));
358
359 const QFileInfoList srcEntries = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
360 const QFileInfoList dstEntries = dstDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
361 for (const QFileInfo &dst : dstEntries) {
362 bool found = false;
363 for (const QFileInfo &src : srcEntries)
364 if (dst.fileName() == src.fileName()) {
365 if (dst.isDir())
366 deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath());
367 found = true;
368 break;
369 }
370
371 if (!found) {
372 if (options.verbose)
373 fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath()));
374
375 if (dst.isDir())
376 QDir{dst.absolutePath()}.removeRecursively();
377 else
378 QFile::remove(dst.absoluteFilePath());
379 }
380 }
381 fflush(stdout);
382}
383
385{
386 Options options;
387
388 QStringList arguments = QCoreApplication::arguments();
389 for (int i=0; i<arguments.size(); ++i) {
390 const QString &argument = arguments.at(i);
391 if (argument.compare("--output"_L1, Qt::CaseInsensitive) == 0) {
392 if (i + 1 == arguments.size())
393 options.helpRequested = true;
394 else
395 options.outputDirectory = arguments.at(++i).trimmed();
396 } else if (argument.compare("--input"_L1, Qt::CaseInsensitive) == 0) {
397 if (i + 1 == arguments.size())
398 options.helpRequested = true;
399 else
400 options.inputFileName = arguments.at(++i);
401 } else if (argument.compare("--aab"_L1, Qt::CaseInsensitive) == 0) {
402 options.buildAAB = true;
403 options.build = true;
404 } else if (!options.buildAAB && argument.compare("--no-build"_L1, Qt::CaseInsensitive) == 0) {
405 options.build = false;
406 } else if (argument.compare("--install"_L1, Qt::CaseInsensitive) == 0) {
407 options.installApk = true;
408 options.uninstallApk = true;
409 } else if (argument.compare("--reinstall"_L1, Qt::CaseInsensitive) == 0) {
410 options.installApk = true;
411 options.uninstallApk = false;
412 } else if (argument.compare("--android-platform"_L1, Qt::CaseInsensitive) == 0) {
413 if (i + 1 == arguments.size())
414 options.helpRequested = true;
415 else
416 options.androidPlatform = arguments.at(++i);
417 } else if (argument.compare("--help"_L1, Qt::CaseInsensitive) == 0) {
418 options.helpRequested = true;
419 } else if (argument.compare("--verbose"_L1, Qt::CaseInsensitive) == 0) {
420 options.verbose = true;
421 } else if (argument.compare("--deployment"_L1, Qt::CaseInsensitive) == 0) {
422 if (i + 1 == arguments.size()) {
423 options.helpRequested = true;
424 } else {
425 QString deploymentMechanism = arguments.at(++i);
426 if (deploymentMechanism.compare("bundled"_L1, Qt::CaseInsensitive) == 0) {
428 } else if (deploymentMechanism.compare("unbundled"_L1,
429 Qt::CaseInsensitive) == 0) {
431 } else {
432 fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
433 options.helpRequested = true;
434 }
435 }
436 } else if (argument.compare("--device"_L1, Qt::CaseInsensitive) == 0) {
437 if (i + 1 == arguments.size())
438 options.helpRequested = true;
439 else
440 options.installLocation = arguments.at(++i);
441 } else if (argument.compare("--release"_L1, Qt::CaseInsensitive) == 0) {
442 options.releasePackage = true;
443 } else if (argument.compare("--jdk"_L1, Qt::CaseInsensitive) == 0) {
444 if (i + 1 == arguments.size())
445 options.helpRequested = true;
446 else
447 options.jdkPath = arguments.at(++i);
448 } else if (argument.compare("--apk"_L1, Qt::CaseInsensitive) == 0) {
449 if (i + 1 == arguments.size())
450 options.helpRequested = true;
451 else
452 options.apkPath = arguments.at(++i);
453 } else if (argument.compare("--depfile"_L1, Qt::CaseInsensitive) == 0) {
454 if (i + 1 == arguments.size())
455 options.helpRequested = true;
456 else
457 options.depFilePath = arguments.at(++i);
458 } else if (argument.compare("--builddir"_L1, Qt::CaseInsensitive) == 0) {
459 if (i + 1 == arguments.size())
460 options.helpRequested = true;
461 else
462 options.buildDirectory = arguments.at(++i);
463 } else if (argument.compare("--sign"_L1, Qt::CaseInsensitive) == 0) {
464 if (i + 2 < arguments.size() && !arguments.at(i + 1).startsWith("--"_L1) &&
465 !arguments.at(i + 2).startsWith("--"_L1)) {
466 options.keyStore = arguments.at(++i);
467 options.keyStoreAlias = arguments.at(++i);
468 } else {
469 const QString keyStore = qEnvironmentVariable("QT_ANDROID_KEYSTORE_PATH");
470 const QString storeAlias = qEnvironmentVariable("QT_ANDROID_KEYSTORE_ALIAS");
471 if (keyStore.isEmpty() || storeAlias.isEmpty()) {
472 options.helpRequested = true;
473 fprintf(stderr, "Package signing path and alias values are not specified.\n");
474 } else {
475 fprintf(stdout,
476 "Using package signing path and alias values found from the "
477 "environment variables.\n");
478 options.keyStore = keyStore;
479 options.keyStoreAlias = storeAlias;
480 }
481 }
482
483 // Do not override if the passwords are provided through arguments
484 if (options.keyStorePassword.isEmpty()) {
485 fprintf(stdout, "Using package signing store password found from the environment "
486 "variable.\n");
487 options.keyStorePassword = qEnvironmentVariable("QT_ANDROID_KEYSTORE_STORE_PASS");
488 }
489 if (options.keyPass.isEmpty()) {
490 fprintf(stdout, "Using package signing key password found from the environment "
491 "variable.\n");
492 options.keyPass = qEnvironmentVariable("QT_ANDROID_KEYSTORE_KEY_PASS");
493 }
494 } else if (argument.compare("--storepass"_L1, Qt::CaseInsensitive) == 0) {
495 if (i + 1 == arguments.size())
496 options.helpRequested = true;
497 else
498 options.keyStorePassword = arguments.at(++i);
499 } else if (argument.compare("--storetype"_L1, Qt::CaseInsensitive) == 0) {
500 if (i + 1 == arguments.size())
501 options.helpRequested = true;
502 else
503 options.storeType = arguments.at(++i);
504 } else if (argument.compare("--keypass"_L1, Qt::CaseInsensitive) == 0) {
505 if (i + 1 == arguments.size())
506 options.helpRequested = true;
507 else
508 options.keyPass = arguments.at(++i);
509 } else if (argument.compare("--sigfile"_L1, Qt::CaseInsensitive) == 0) {
510 if (i + 1 == arguments.size())
511 options.helpRequested = true;
512 else
513 options.sigFile = arguments.at(++i);
514 } else if (argument.compare("--digestalg"_L1, Qt::CaseInsensitive) == 0) {
515 if (i + 1 == arguments.size())
516 options.helpRequested = true;
517 else
518 options.digestAlg = arguments.at(++i);
519 } else if (argument.compare("--sigalg"_L1, Qt::CaseInsensitive) == 0) {
520 if (i + 1 == arguments.size())
521 options.helpRequested = true;
522 else
523 options.sigAlg = arguments.at(++i);
524 } else if (argument.compare("--tsa"_L1, Qt::CaseInsensitive) == 0) {
525 if (i + 1 == arguments.size())
526 options.helpRequested = true;
527 else
528 options.tsaUrl = arguments.at(++i);
529 } else if (argument.compare("--tsacert"_L1, Qt::CaseInsensitive) == 0) {
530 if (i + 1 == arguments.size())
531 options.helpRequested = true;
532 else
533 options.tsaCert = arguments.at(++i);
534 } else if (argument.compare("--internalsf"_L1, Qt::CaseInsensitive) == 0) {
535 options.internalSf = true;
536 } else if (argument.compare("--sectionsonly"_L1, Qt::CaseInsensitive) == 0) {
537 options.sectionsOnly = true;
538 } else if (argument.compare("--protected"_L1, Qt::CaseInsensitive) == 0) {
539 options.protectedAuthenticationPath = true;
540 } else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
541 options.auxMode = true;
542 } else if (argument.compare("--build-aar"_L1, Qt::CaseInsensitive) == 0) {
543 options.buildAar = true;
544 } else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
545 options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
546 } else if (argument.compare("--no-rcc-bundle-cleanup"_L1,
547 Qt::CaseInsensitive) == 0) {
548 options.noRccBundleCleanup = true;
549 } else if (argument.compare("--copy-dependencies-only"_L1,
550 Qt::CaseInsensitive) == 0) {
551 options.copyDependenciesOnly = true;
552 }
553 }
554
555 if (options.buildAar) {
556 if (options.installApk || options.uninstallApk) {
557 fprintf(stderr, "Warning: Skipping %s, AAR packages are not installable.\n",
558 options.uninstallApk ? "--reinstall" : "--install");
559 options.installApk = false;
560 options.uninstallApk = false;
561 }
562 if (options.buildAAB) {
563 fprintf(stderr, "Warning: Skipping -aab as --build-aar is present.\n");
564 options.buildAAB = false;
565 }
566 if (!options.keyStore.isEmpty()) {
567 fprintf(stderr, "Warning: Skipping --sign, signing AAR packages is not supported.\n");
568 options.keyStore.clear();
569 }
570 }
571
572 if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
573 options.helpRequested = true;
574
575 if (options.inputFileName.isEmpty())
576 options.inputFileName = "android-%1-deployment-settings.json"_L1.arg(QDir::current().dirName());
577
578 options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT");
579
580 if (!QDir::current().mkpath(options.outputDirectory)) {
581 fprintf(stderr, "Invalid output directory: %s\n", qPrintable(options.outputDirectory));
582 options.outputDirectory.clear();
583 } else {
584 options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath();
585 if (!options.outputDirectory.endsWith(u'/'))
586 options.outputDirectory += u'/';
587 }
588
589 return options;
590}
591
593{
594 fprintf(stderr, R"(
595Syntax: androiddeployqt --output <destination> [options]
596
597Creates an Android package in the build directory <destination> and
598builds it into an .apk file.
599
600Optional arguments:
601 --input <inputfile>: Reads <inputfile> for options generated by
602 qmake. A default file name based on the current working
603 directory will be used if nothing else is specified.
604
605 --deployment <mechanism>: Supported deployment mechanisms:
606 bundled (default): Includes Qt files in stand-alone package.
607 unbundled: Assumes native libraries are present on the device
608 and does not include them in the APK.
609
610 --aab: Build an Android App Bundle.
611
612 --no-build: Do not build the package, it is useful to just install
613 a package previously built.
614
615 --install: Installs apk to device/emulator. By default this step is
616 not taken. If the application has previously been installed on
617 the device, it will be uninstalled first.
618
619 --reinstall: Installs apk to device/emulator. By default this step
620 is not taken. If the application has previously been installed on
621 the device, it will be overwritten, but its data will be left
622 intact.
623
624 --device [device ID]: Use specified device for deployment. Default
625 is the device selected by default by adb.
626
627 --android-platform <platform>: Builds against the given android
628 platform. By default, the highest available version will be
629 used.
630
631 --release: Builds a package ready for release. By default, the
632 package will be signed with a debug key.
633
634 --sign <url/to/keystore> <alias>: Signs the package with the
635 specified keystore, alias and store password.
636 Optional arguments for use with signing:
637 --storepass <password>: Keystore password.
638 --storetype <type>: Keystore type.
639 --keypass <password>: Password for private key (if different
640 from keystore password.)
641 --sigfile <file>: Name of .SF/.DSA file.
642 --digestalg <name>: Name of digest algorithm. Default is
643 "SHA-256".
644 --sigalg <name>: Name of signature algorithm. Default is
645 "SHA256withRSA".
646 --tsa <url>: Location of the Time Stamping Authority.
647 --tsacert <alias>: Public key certificate for TSA.
648 --internalsf: Include the .SF file inside the signature block.
649 --sectionsonly: Do not compute hash of entire manifest.
650 --protected: Keystore has protected authentication path.
651 --jarsigner: Deprecated, ignored.
652
653 NOTE: To conceal the keystore information, the environment variables
654 QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to
655 set the values keysotore and alias respectively.
656 Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,
657 and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key
658 passwords respectively. This option needs only the --sign parameter.
659
660 --jdk <path/to/jdk>: Used to find the jarsigner tool when used
661 in combination with the --release argument. By default,
662 an attempt is made to detect the tool using the JAVA_HOME and
663 PATH environment variables, in that order.
664
665 --qml-import-paths: Specify additional search paths for QML
666 imports.
667
668 --verbose: Prints out information during processing.
670 --no-generated-assets-cache: Do not pregenerate the entry list for
671 the assets file engine.
672
673 --aux-mode: Operate in auxiliary mode. This will only copy the
674 dependencies into the build directory and update the XML templates.
675 The project will not be built or installed.
676
677 --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
678
679 --build-aar: Build an AAR package. This option skips --aab, --install,
680 --reinstall, and --sign options if they are provided.
681
682 --qml-importscanner-binary <path/to/qmlimportscanner>: Override the
683 default qmlimportscanner binary path. By default the
684 qmlimportscanner binary is located using the Qt directory
685 specified in the input file.
686
687 --depfile <path/to/depfile>: Output a dependency file.
688
689 --builddir <path/to/build/directory>: build directory. Necessary when
690 generating a depfile because ninja requires relative paths.
691
692 --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
693 running androiddeployqt. This option simplifies debugging of
694 the resource bundle content, but it should not be used when deploying
695 a project, since it litters the "assets" directory.
696
697 --copy-dependencies-only: resolve application dependencies and stop
698 deploying process after all libraries and resources that the
699 application depends on have been copied.
700
701 --help: Displays this information.
702)");
703}
704
705// Since strings compared will all start with the same letters,
706// sorting by length and then alphabetically within each length
707// gives the natural order.
708bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2)
709{
710 QString s1 = fi1.baseName();
711 QString s2 = fi2.baseName();
712
713 if (s1.size() == s2.size())
714 return s1 > s2;
715 else
716 return s1.size() > s2.size();
717}
718
719// Files which contain templates that need to be overwritten by build data should be overwritten every
720// time.
721bool alwaysOverwritableFile(const QString &fileName)
722{
723 return (fileName.endsWith("/res/values/libs.xml"_L1)
724 || fileName.endsWith("/AndroidManifest.xml"_L1)
725 || fileName.endsWith("/res/values/strings.xml"_L1)
726 || fileName.endsWith("/src/org/qtproject/qt/android/bindings/QtActivity.java"_L1));
727}
728
729bool copyFileIfNewer(const QString &sourceFileName,
730 const QString &destinationFileName,
731 const Options &options,
732 bool createSymlinksOnly = false,
733 bool forceOverwrite = false)
734{
735 dependenciesForDepfile << sourceFileName;
736 if (QFile::exists(destinationFileName)) {
737 QFileInfo destinationFileInfo(destinationFileName);
738 QFileInfo sourceFileInfo(sourceFileName);
739
740 if (!forceOverwrite
741 && sourceFileInfo.lastModified() <= destinationFileInfo.lastModified()
742 && !alwaysOverwritableFile(destinationFileName)) {
743 if (options.verbose)
744 fprintf(stdout, " -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName));
745 return true;
746 } else {
747 if (!QFile(destinationFileName).remove()) {
748 fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName));
749 return false;
750 }
751 }
752 }
753
754 if (!QDir().mkpath(QFileInfo(destinationFileName).path())) {
755 fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName));
756 return false;
757 }
758
759 auto copyFunction = [createSymlinksOnly, sourceFileName, destinationFileName]() {
760 if (createSymlinksOnly)
761 return QFile::link(sourceFileName, destinationFileName);
762 else
763 return QFile::copy(sourceFileName, destinationFileName);
764 };
765
766 if (!QFile::exists(destinationFileName) && !copyFunction()) {
767 qWarning() << "symlink creation failed";
768 fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName));
769 return false;
770 } else if (options.verbose) {
771 fprintf(stdout, " -- Copied %s\n", qPrintable(destinationFileName));
772 fflush(stdout);
773 }
774 return true;
775}
776
777struct GradleBuildConfigs {
778 QString appNamespace;
779 bool usesIntegerCompileSdkVersion = false;
780};
781
782GradleBuildConfigs gradleBuildConfigs(const QString &path)
783{
784 GradleBuildConfigs configs;
785
786 QFile file(path);
787 if (!file.open(QIODevice::ReadOnly))
788 return configs;
789
790 auto isComment = [](const QByteArray &trimmed) {
791 return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
792 };
793
794 auto extractValue = [](const QByteArray &trimmed) {
795 int idx = trimmed.indexOf('=');
796
797 if (idx == -1)
798 idx = trimmed.indexOf(' ');
799
800 if (idx > -1)
801 return trimmed.mid(idx + 1).trimmed();
802
803 return QByteArray();
804 };
805
806 const auto lines = file.readAll().split('\n');
807 for (const auto &line : lines) {
808 const QByteArray trimmedLine = line.trimmed();
809 if (isComment(trimmedLine))
810 continue;
811 if (trimmedLine.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) {
812 configs.usesIntegerCompileSdkVersion = true;
813 } else if (trimmedLine.contains("namespace")) {
814 const QString value = QString::fromUtf8(extractValue(trimmedLine));
815 const bool singleQuoted = value.startsWith(u'\'') && value.endsWith(u'\'');
816 const bool doubleQuoted = value.startsWith(u'\"') && value.endsWith(u'\"');
817
818 if (singleQuoted || doubleQuoted)
819 configs.appNamespace = value.mid(1, value.length() - 2);
820 else
821 configs.appNamespace = value;
822 }
823 }
824
825 return configs;
826}
827
828QString cleanPackageName(QString packageName, bool *cleaned = nullptr)
829{
830 auto isLegalChar = [] (QChar c) -> bool {
831 ushort ch = c.unicode();
832 return (ch >= '0' && ch <= '9') ||
833 (ch >= 'A' && ch <= 'Z') ||
834 (ch >= 'a' && ch <= 'z') ||
835 ch == '.' || ch == '_';
836 };
837
838 if (cleaned)
839 *cleaned = false;
840
841 for (QChar &c : packageName) {
842 if (!isLegalChar(c)) {
843 c = u'_';
844 if (cleaned)
845 *cleaned = true;
846 }
847 }
848
849 static QStringList keywords;
850 if (keywords.isEmpty()) {
851 keywords << "abstract"_L1 << "continue"_L1 << "for"_L1
852 << "new"_L1 << "switch"_L1 << "assert"_L1
853 << "default"_L1 << "if"_L1 << "package"_L1
854 << "synchronized"_L1 << "boolean"_L1 << "do"_L1
855 << "goto"_L1 << "private"_L1 << "this"_L1
856 << "break"_L1 << "double"_L1 << "implements"_L1
857 << "protected"_L1 << "throw"_L1 << "byte"_L1
858 << "else"_L1 << "import"_L1 << "public"_L1
859 << "throws"_L1 << "case"_L1 << "enum"_L1
860 << "instanceof"_L1 << "return"_L1 << "transient"_L1
861 << "catch"_L1 << "extends"_L1 << "int"_L1
862 << "short"_L1 << "try"_L1 << "char"_L1
863 << "final"_L1 << "interface"_L1 << "static"_L1
864 << "void"_L1 << "class"_L1 << "finally"_L1
865 << "long"_L1 << "strictfp"_L1 << "volatile"_L1
866 << "const"_L1 << "float"_L1 << "native"_L1
867 << "super"_L1 << "while"_L1;
868 }
869
870 // No keywords
871 qsizetype index = -1;
872 while (index < packageName.size()) {
873 qsizetype next = packageName.indexOf(u'.', index + 1);
874 if (next == -1)
875 next = packageName.size();
876 QString word = packageName.mid(index + 1, next - index - 1);
877 if (!word.isEmpty()) {
878 QChar c = word[0];
879 if ((c >= u'0' && c <= u'9') || c == u'_') {
880 packageName.insert(index + 1, u'a');
881 if (cleaned)
882 *cleaned = true;
883 index = next + 1;
884 continue;
885 }
886 }
887 if (keywords.contains(word)) {
888 packageName.insert(next, "_"_L1);
889 if (cleaned)
890 *cleaned = true;
891 index = next + 1;
892 } else {
893 index = next;
894 }
895 }
896
897 return packageName;
898}
899
900QString detectLatestAndroidPlatform(const QString &sdkPath)
901{
902 QDir dir(sdkPath + "/platforms"_L1);
903 if (!dir.exists()) {
904 fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath()));
905 return QString();
906 }
907
908 QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
909 if (fileInfos.isEmpty()) {
910 fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath()));
911 return QString();
912 }
913
914 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
915
916 const QFileInfo& latestPlatform = fileInfos.constFirst();
917 return latestPlatform.baseName();
918}
919
920QString extractPackageName(Options *options)
921{
922 {
923 const QString gradleBuildFile = options->androidSourceDirectory + "/build.gradle"_L1;
924 QString packageName = gradleBuildConfigs(gradleBuildFile).appNamespace;
925
926 if (!packageName.isEmpty() && packageName != "androidPackageName"_L1)
927 return packageName;
928 }
929
930 QFile androidManifestXml(options->androidSourceDirectory + "/AndroidManifest.xml"_L1);
931 if (androidManifestXml.open(QIODevice::ReadOnly)) {
932 QXmlStreamReader reader(&androidManifestXml);
933 while (!reader.atEnd()) {
934 reader.readNext();
935 if (reader.isStartElement() && reader.name() == "manifest"_L1) {
936 QString packageName = reader.attributes().value("package"_L1).toString();
937 if (!packageName.isEmpty() && packageName != "org.qtproject.example"_L1)
938 return packageName;
939 break;
940 }
941 }
942 }
943
944 return QString();
945}
946
947bool parseCmakeBoolean(const QJsonValue &value)
948{
949 const QString stringValue = value.toString();
950 return (stringValue.compare(QString::fromUtf8("true"), Qt::CaseInsensitive)
951 || stringValue.compare(QString::fromUtf8("on"), Qt::CaseInsensitive)
952 || stringValue.compare(QString::fromUtf8("yes"), Qt::CaseInsensitive)
953 || stringValue.compare(QString::fromUtf8("y"), Qt::CaseInsensitive)
954 || stringValue.toInt() > 0);
955}
956
957bool readInputFileDirectory(Options *options, QJsonObject &jsonObject, const QString keyName)
958{
959 const QJsonValue qtDirectory = jsonObject.value(keyName);
960 if (qtDirectory.isUndefined()) {
961 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
962 if (keyName == "qtDataDirectory"_L1) {
963 options->architectures[it.key()].qtDirectories[keyName] = "."_L1;
964 break;
965 } else if (keyName == "qtLibsDirectory"_L1) {
966 options->architectures[it.key()].qtDirectories[keyName] = "lib"_L1;
967 break;
968 } else if (keyName == "qtLibExecsDirectory"_L1) {
969 options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
970 break;
971 } else if (keyName == "qtPluginsDirectory"_L1) {
972 options->architectures[it.key()].qtDirectories[keyName] = "plugins"_L1;
973 break;
974 } else if (keyName == "qtQmlDirectory"_L1) {
975 options->architectures[it.key()].qtDirectories[keyName] = "qml"_L1;
976 break;
977 }
978 }
979 return true;
980 }
981
982 if (qtDirectory.isObject()) {
983 const QJsonObject object = qtDirectory.toObject();
984 for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
985 if (it.value().isUndefined()) {
986 fprintf(stderr,
987 "Invalid '%s' record in deployment settings: %s\n",
988 qPrintable(keyName),
989 qPrintable(it.value().toString()));
990 return false;
991 }
992 if (it.value().isNull())
993 continue;
994 if (!options->architectures.contains(it.key())) {
995 fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()),
996 qPrintable(options->architectures.keys().join(u',')));
997 return false;
998 }
999 options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
1000 }
1001 } else if (qtDirectory.isString()) {
1002 // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch.
1003 // We assume Qt > 5.14 where all architectures are in the same directory.
1004 const QString directory = qtDirectory.toString();
1005 options->architectures["arm64-v8a"_L1].qtDirectories[keyName] = directory;
1006 options->architectures["armeabi-v7a"_L1].qtDirectories[keyName] = directory;
1007 options->architectures["x86"_L1].qtDirectories[keyName] = directory;
1008 options->architectures["x86_64"_L1].qtDirectories[keyName] = directory;
1009 } else {
1010 fprintf(stderr, "Invalid format for %s in json file %s.\n",
1011 qPrintable(keyName), qPrintable(options->inputFileName));
1012 return false;
1013 }
1014 return true;
1015}
1016
1017bool readInputFile(Options *options)
1018{
1019 QFile file(options->inputFileName);
1020 if (!file.open(QIODevice::ReadOnly)) {
1021 fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
1022 return false;
1023 }
1024 dependenciesForDepfile << options->inputFileName;
1025
1026 QJsonParseError jsonParseError;
1027 QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll(), &jsonParseError);
1028 if (jsonDocument.isNull()) {
1029 fprintf(stderr, "Invalid json file: %s. Reason: %s at offset %i.\n",
1030 qPrintable(options->inputFileName),
1031 qPrintable(jsonParseError.errorString()),
1032 jsonParseError.offset);
1033 return false;
1034 }
1035
1036 QJsonObject jsonObject = jsonDocument.object();
1037
1038 {
1039 QJsonValue sdkPath = jsonObject.value("sdk"_L1);
1040 if (sdkPath.isUndefined()) {
1041 fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName));
1042 return false;
1043 }
1044
1045 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
1046
1047 }
1048
1049 {
1050 if (options->androidPlatform.isEmpty()) {
1051 const QJsonValue ver = jsonObject.value("android-compile-sdk-version"_L1);
1052 if (!ver.isUndefined()) {
1053 const auto value = ver.toString();
1054 options->androidPlatform = value.startsWith("android-"_L1) ?
1055 value : "android-%1"_L1.arg(value);
1056 }
1057
1058 if (options->androidPlatform.isEmpty()) {
1059 options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
1060 if (options->androidPlatform.isEmpty())
1061 return false;
1062 }
1063 }
1064
1065 if (!QDir(options->sdkPath + "/platforms/"_L1 + options->androidPlatform).exists()) {
1066 fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n",
1067 qPrintable(options->androidPlatform));
1068 }
1069 }
1070
1071 {
1072
1073 const QJsonValue value = jsonObject.value("sdkBuildToolsRevision"_L1);
1074 if (!value.isUndefined())
1075 options->sdkBuildToolsVersion = value.toString();
1076 }
1077
1078 {
1079 const QJsonValue qtInstallDirectory = jsonObject.value("qt"_L1);
1080 if (qtInstallDirectory.isUndefined()) {
1081 fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName));
1082 return false;
1083 }
1084
1085 if (qtInstallDirectory.isObject()) {
1086 const QJsonObject object = qtInstallDirectory.toObject();
1087 for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
1088 if (it.value().isUndefined()) {
1089 fprintf(stderr,
1090 "Invalid 'qt' record in deployment settings: %s\n",
1091 qPrintable(it.value().toString()));
1092 return false;
1093 }
1094 if (it.value().isNull())
1095 continue;
1096 options->architectures.insert(it.key(),
1097 QtInstallDirectoryWithTriple(it.value().toString()));
1098 }
1099 } else if (qtInstallDirectory.isString()) {
1100 // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch.
1101 // We assume Qt > 5.14 where all architectures are in the same directory.
1102 const QString directory = qtInstallDirectory.toString();
1103 QtInstallDirectoryWithTriple qtInstallDirectoryWithTriple(directory);
1104 options->architectures.insert("arm64-v8a"_L1, qtInstallDirectoryWithTriple);
1105 options->architectures.insert("armeabi-v7a"_L1, qtInstallDirectoryWithTriple);
1106 options->architectures.insert("x86"_L1, qtInstallDirectoryWithTriple);
1107 options->architectures.insert("x86_64"_L1, qtInstallDirectoryWithTriple);
1108 // In Qt < 6 rcc and qmlimportscanner are installed in the host and install directories
1109 // In Qt >= 6 rcc and qmlimportscanner are only installed in the host directory
1110 // So setting the "qtHostDir" is not necessary with Qt < 6.
1111 options->qtHostDirectory = directory;
1112 } else {
1113 fprintf(stderr, "Invalid format for Qt install prefixes in json file %s.\n",
1114 qPrintable(options->inputFileName));
1115 return false;
1116 }
1117 }
1118
1119 if (!readInputFileDirectory(options, jsonObject, "qtDataDirectory"_L1) ||
1120 !readInputFileDirectory(options, jsonObject, "qtLibsDirectory"_L1) ||
1121 !readInputFileDirectory(options, jsonObject, "qtLibExecsDirectory"_L1) ||
1122 !readInputFileDirectory(options, jsonObject, "qtPluginsDirectory"_L1) ||
1123 !readInputFileDirectory(options, jsonObject, "qtQmlDirectory"_L1))
1124 return false;
1125
1126 {
1127 const QJsonValue qtHostDirectory = jsonObject.value("qtHostDir"_L1);
1128 if (!qtHostDirectory.isUndefined()) {
1129 if (qtHostDirectory.isString()) {
1130 options->qtHostDirectory = qtHostDirectory.toString();
1131 } else {
1132 fprintf(stderr, "Invalid format for Qt host directory in json file %s.\n",
1133 qPrintable(options->inputFileName));
1134 return false;
1135 }
1136 }
1137 }
1138
1139 {
1140 const auto extraPrefixDirs = jsonObject.value("extraPrefixDirs"_L1).toArray();
1141 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
1142 for (const QJsonValue prefix : extraPrefixDirs) {
1143 options->extraPrefixDirs.push_back(prefix.toString());
1144 }
1145 }
1146
1147 {
1148 const auto androidDeployPlugins = jsonObject.value("android-deploy-plugins"_L1).toString();
1149 options->androidDeployPlugins = androidDeployPlugins.split(";"_L1, Qt::SkipEmptyParts);
1150 }
1151
1152 {
1153 const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray();
1154 options->extraLibraryDirs.reserve(extraLibraryDirs.size());
1155 for (const QJsonValue path : extraLibraryDirs) {
1156 options->extraLibraryDirs.push_back(path.toString());
1157 }
1158 }
1159
1160 {
1161 const QJsonValue androidSourcesDirectory = jsonObject.value("android-package-source-directory"_L1);
1162 if (!androidSourcesDirectory.isUndefined())
1163 options->androidSourceDirectory = androidSourcesDirectory.toString();
1164 }
1165
1166 {
1167 const QJsonValue applicationArguments = jsonObject.value("android-application-arguments"_L1);
1168 if (!applicationArguments.isUndefined())
1169 options->applicationArguments = applicationArguments.toString();
1170 else
1171 options->applicationArguments = QStringLiteral("");
1172 }
1173
1174 {
1175 const QJsonValue androidVersionName = jsonObject.value("android-version-name"_L1);
1176 if (!androidVersionName.isUndefined())
1177 options->versionName = androidVersionName.toString();
1178 else
1179 options->versionName = QStringLiteral("1.0");
1180 }
1181
1182 {
1183 const QJsonValue androidVersionCode = jsonObject.value("android-version-code"_L1);
1184 if (!androidVersionCode.isUndefined())
1185 options->versionCode = androidVersionCode.toString();
1186 else
1187 options->versionCode = QStringLiteral("1");
1188 }
1189
1190 {
1191 const QJsonValue ver = jsonObject.value("android-min-sdk-version"_L1);
1192 if (!ver.isUndefined())
1193 options->minSdkVersion = ver.toString().toUtf8();
1194 }
1195
1196 {
1197 const QJsonValue ver = jsonObject.value("android-target-sdk-version"_L1);
1198 if (!ver.isUndefined())
1199 options->targetSdkVersion = ver.toString().toUtf8();
1200 }
1201
1202 {
1203 if (const auto abi = jsonObject.value("abi"_L1); !abi.isUndefined())
1204 options->abi = jsonObject.value("abi"_L1).toString();
1205 }
1206
1207 {
1208 const QJsonObject targetArchitectures = jsonObject.value("architectures"_L1).toObject();
1209 if (targetArchitectures.isEmpty()) {
1210 fprintf(stderr, "No target architecture defined in json file.\n");
1211 return false;
1212 }
1213 for (auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
1214 if (it.value().isUndefined()) {
1215 fprintf(stderr, "Invalid architecture.\n");
1216 return false;
1217 }
1218 if (it.value().isNull())
1219 continue;
1220 if (!options->architectures.contains(it.key())) {
1221 fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()),
1222 qPrintable(options->architectures.keys().join(u',')));
1223 return false;
1224 }
1225 options->architectures[it.key()].triple = it.value().toString();
1226 options->architectures[it.key()].enabled = true;
1227 }
1228 }
1229
1230 {
1231 const QJsonValue ndk = jsonObject.value("ndk"_L1);
1232 if (ndk.isUndefined()) {
1233 fprintf(stderr, "No NDK path defined in json file.\n");
1234 return false;
1235 }
1236 options->ndkPath = ndk.toString();
1237 const QString ndkPropertiesPath = options->ndkPath + QStringLiteral("/source.properties");
1238 const QSettings settings(ndkPropertiesPath, QSettings::IniFormat);
1239 const QString ndkVersion = settings.value(QStringLiteral("Pkg.Revision")).toString();
1240 if (ndkVersion.isEmpty()) {
1241 fprintf(stderr, "Couldn't retrieve the NDK version from \"%s\".\n",
1242 qPrintable(ndkPropertiesPath));
1243 return false;
1244 }
1245 options->ndkVersion = ndkVersion;
1246 }
1247
1248 {
1249 const QJsonValue toolchainPrefix = jsonObject.value("toolchain-prefix"_L1);
1250 if (toolchainPrefix.isUndefined()) {
1251 fprintf(stderr, "No toolchain prefix defined in json file.\n");
1252 return false;
1253 }
1254 options->toolchainPrefix = toolchainPrefix.toString();
1255 }
1256
1257 {
1258 const QJsonValue ndkHost = jsonObject.value("ndk-host"_L1);
1259 if (ndkHost.isUndefined()) {
1260 fprintf(stderr, "No NDK host defined in json file.\n");
1261 return false;
1262 }
1263 options->ndkHost = ndkHost.toString();
1264 }
1265
1266 {
1267 const QJsonValue extraLibs = jsonObject.value("android-extra-libs"_L1);
1268 if (!extraLibs.isUndefined())
1269 options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts);
1270 }
1271
1272 {
1273 const QJsonValue qmlSkipImportScanning = jsonObject.value("qml-skip-import-scanning"_L1);
1274 if (!qmlSkipImportScanning.isUndefined())
1275 options->qmlSkipImportScanning = qmlSkipImportScanning.toBool();
1276 }
1277
1278 {
1279 const QJsonValue extraPlugins = jsonObject.value("android-extra-plugins"_L1);
1280 if (!extraPlugins.isUndefined())
1281 options->extraPlugins = extraPlugins.toString().split(u',');
1282 }
1283
1284 {
1285 const QJsonValue systemLibsPath =
1286 jsonObject.value("android-system-libs-prefix"_L1);
1287 if (!systemLibsPath.isUndefined())
1288 options->systemLibsPath = systemLibsPath.toString();
1289 }
1290
1291 {
1292 const QJsonValue noDeploy = jsonObject.value("android-no-deploy-qt-libs"_L1);
1293 if (!noDeploy.isUndefined()) {
1294 bool useUnbundled = parseCmakeBoolean(noDeploy);
1295 options->deploymentMechanism = useUnbundled ? Options::Unbundled :
1297 }
1298 }
1299
1300 {
1301 const QJsonValue stdcppPath = jsonObject.value("stdcpp-path"_L1);
1302 if (stdcppPath.isUndefined()) {
1303 fprintf(stderr, "No stdcpp-path defined in json file.\n");
1304 return false;
1305 }
1306 options->stdCppPath = stdcppPath.toString();
1307 }
1308
1309 {
1310 const QJsonValue qmlRootPath = jsonObject.value("qml-root-path"_L1);
1311 if (qmlRootPath.isString()) {
1312 options->rootPaths.push_back(qmlRootPath.toString());
1313 } else if (qmlRootPath.isArray()) {
1314 auto qmlRootPaths = qmlRootPath.toArray();
1315 for (auto path : qmlRootPaths) {
1316 if (path.isString())
1317 options->rootPaths.push_back(path.toString());
1318 }
1319 } else {
1320 options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
1321 }
1322 }
1323
1324 {
1325 const QJsonValue qmlImportPaths = jsonObject.value("qml-import-paths"_L1);
1326 if (!qmlImportPaths.isUndefined())
1327 options->qmlImportPaths = qmlImportPaths.toString().split(u',');
1328 }
1329
1330 {
1331 const QJsonValue qmlImportScannerBinaryPath = jsonObject.value("qml-importscanner-binary"_L1);
1332 if (!qmlImportScannerBinaryPath.isUndefined())
1333 options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString();
1334 }
1335
1336 {
1337 const QJsonValue rccBinaryPath = jsonObject.value("rcc-binary"_L1);
1338 if (!rccBinaryPath.isUndefined())
1339 options->rccBinaryPath = rccBinaryPath.toString();
1340 }
1341
1342 {
1343 const QJsonValue genJavaQmlComponents = jsonObject.value("generate-java-qtquickview-contents"_L1);
1344 if (!genJavaQmlComponents.isUndefined() && genJavaQmlComponents.isBool()) {
1345 options->generateJavaQmlComponents = genJavaQmlComponents.toBool(false);
1346 if (options->generateJavaQmlComponents && !options->buildAar) {
1347 fprintf(stderr,
1348 "Warning: Skipping the generation of Java QtQuickView contents from QML "
1349 "as it can be enabled only for an AAR target.\n");
1350 options->generateJavaQmlComponents = false;
1351 }
1352 }
1353 }
1354
1355 {
1356 const QJsonValue qmlDomBinaryPath = jsonObject.value("qml-dom-binary"_L1);
1357 if (!qmlDomBinaryPath.isUndefined()) {
1358 options->qmlDomBinaryPath = qmlDomBinaryPath.toString();
1359 } else if (options->generateJavaQmlComponents) {
1360 fprintf(stderr,
1361 "No qmldom binary defined in json file which is required when "
1362 "building with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n");
1363 return false;
1364 }
1365 }
1366
1367 {
1368 const QJsonValue qmlFiles = jsonObject.value("qml-files-for-code-generator"_L1);
1369 if (!qmlFiles.isUndefined() && qmlFiles.isArray()) {
1370 const QJsonArray jArray = qmlFiles.toArray();
1371 for (auto &item : jArray)
1372 options->selectedJavaQmlComponents << item.toString();
1373 }
1374 }
1375
1376 {
1377 const QJsonValue applicationBinary = jsonObject.value("application-binary"_L1);
1378 if (applicationBinary.isUndefined()) {
1379 fprintf(stderr, "No application binary defined in json file.\n");
1380 return false;
1381 }
1382 options->applicationBinary = applicationBinary.toString();
1383 if (options->build) {
1384 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1385 if (!it->enabled)
1386 continue;
1387 auto appBinaryPath = "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary);
1388 if (!QFile::exists(appBinaryPath)) {
1389 fprintf(stderr, "Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
1390 return false;
1391 }
1392 }
1393 }
1394 }
1395
1396 {
1397 const QJsonValue androidPackageName = jsonObject.value("android-package-name"_L1);
1398 const QString extractedPackageName = extractPackageName(options);
1399 if (!extractedPackageName.isEmpty())
1400 options->packageName = extractedPackageName;
1401 else if (!androidPackageName.isUndefined())
1402 options->packageName = androidPackageName.toString();
1403 else
1404 options->packageName = "org.qtproject.example.%1"_L1.arg(options->applicationBinary);
1405
1406 bool cleaned;
1407 options->packageName = cleanPackageName(options->packageName, &cleaned);
1408 if (cleaned) {
1409 fprintf(stderr, "Warning: Package name contained illegal characters and was cleaned "
1410 "to \"%s\"\n", qPrintable(options->packageName));
1411 }
1412 }
1413
1414 {
1415 const QJsonValue androidAppName = jsonObject.value("android-app-name"_L1);
1416 if (!androidAppName.isUndefined())
1417 options->appName = androidAppName.toString();
1418 else
1419 options->appName = options->applicationBinary;
1420 }
1422 {
1423 const QJsonValue androidAppIcon = jsonObject.value("android-app-icon"_L1);
1424 if (!androidAppIcon.isUndefined())
1425 options->appIcon = androidAppIcon.toString();
1427
1428 {
1429 const QJsonValue androidlegacyPackaging = jsonObject.value("android-legacy-packaging"_L1);
1430 if (!androidlegacyPackaging.isUndefined())
1431 options->useLegacyPackaging = androidlegacyPackaging.toBool();
1432 }
1433
1434 {
1435 const QJsonValue createSymlinksOnly = jsonObject.value("android-create-symlinks-only"_L1);
1436 if (!createSymlinksOnly.isUndefined())
1437 options->createSymlinksOnly = createSymlinksOnly.toBool();
1438 }
1439
1440 {
1441 using ItFlag = QDirListing::IteratorFlag;
1442 const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1);
1443 if (!deploymentDependencies.isUndefined()) {
1444 QString deploymentDependenciesString = deploymentDependencies.toString();
1445 const auto dependencies = QStringView{deploymentDependenciesString}.split(u',');
1446 for (const auto &dependency : dependencies) {
1447 QString path = options->qtInstallDirectory + QChar::fromLatin1('/');
1448 path += dependency;
1449 if (QFileInfo(path).isDir()) {
1450 for (const auto &dirEntry : QDirListing(path, ItFlag::Recursive)) {
1451 if (dirEntry.isFile()) {
1452 const QString subPath = dirEntry.filePath();
1453 auto arch = fileArchitecture(*options, subPath);
1454 if (!arch.isEmpty()) {
1455 options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.size() + 1),
1456 subPath));
1457 } else if (options->verbose) {
1458 fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(subPath));
1459 fflush(stderr);
1460 }
1461 }
1462 }
1463 } else {
1464 auto qtDependency = [options](const QStringView &dependency,
1465 const QString &arch) {
1466 const auto installDir = options->architectures[arch].qtInstallDirectory;
1467 const auto absolutePath = "%1/%2"_L1.arg(installDir, dependency.toString());
1468 return QtDependency(dependency.toString(), absolutePath);
1469 };
1470
1471 if (dependency.endsWith(QLatin1String(".so"))) {
1472 auto arch = fileArchitecture(*options, path);
1473 if (!arch.isEmpty()) {
1474 options->qtDependencies[arch].append(qtDependency(dependency, arch));
1475 } else if (options->verbose) {
1476 fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path));
1477 fflush(stderr);
1478 }
1479 } else {
1480 for (auto arch : options->architectures.keys())
1481 options->qtDependencies[arch].append(qtDependency(dependency, arch));
1482 }
1483 }
1484 }
1485 }
1486 }
1487 {
1488 const QJsonValue qrcFiles = jsonObject.value("qrcFiles"_L1);
1489 options->qrcFiles = qrcFiles.toString().split(u',', Qt::SkipEmptyParts);
1490 }
1491 {
1492 const QJsonValue zstdCompressionFlag = jsonObject.value("zstdCompression"_L1);
1493 if (zstdCompressionFlag.isBool()) {
1494 options->isZstdCompressionEnabled = zstdCompressionFlag.toBool();
1495 }
1496 }
1497
1498 {
1499 QJsonArray permissions = jsonObject.value("permissions"_L1).toArray();
1500 if (!permissions.isEmpty()) {
1501 for (const QJsonValue &value : permissions) {
1502 if (value.isObject()) {
1503 QJsonObject permissionObj = value.toObject();
1504 QString name;
1505 QString extras;
1506 for (auto it = permissionObj.begin(); it != permissionObj.end(); ++it) {
1507 if (it.key() == "name"_L1) {
1508 name = it.value().toString();
1509 } else {
1510 extras.append(" android:"_L1)
1511 .append(it.key())
1512 .append("=\""_L1)
1513 .append(it.value().toString())
1514 .append("\""_L1);
1515 }
1516 }
1517 if (name.isEmpty()) {
1518 fprintf(stderr, "Missing permission 'name' in permission specification");
1519 return false;
1520 }
1521 options->applicationPermissions.insert(name, extras);
1522 }
1523 }
1524 }
1525 }
1526 return true;
1527}
1528
1529bool isDeployment(const Options *options, Options::DeploymentMechanism deployment)
1530{
1531 return options->deploymentMechanism == deployment;
1532}
1533
1534bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite = false)
1535{
1536 const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
1537 for (const QFileInfo &entry : entries) {
1538 if (entry.isDir()) {
1539 QDir dir(entry.absoluteFilePath());
1540 const bool destinationInCopyDir = destinationDirectory.absolutePath().startsWith(dir.absolutePath());
1541 if (sourceDirectory == options.androidSourceDirectory && destinationInCopyDir)
1542 continue;
1543
1544 if (!destinationDirectory.mkpath(dir.dirName())) {
1545 fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
1546 return false;
1547 }
1549 if (!copyFiles(dir, QDir(destinationDirectory.path() + u'/' + dir.dirName()), options, forceOverwrite))
1550 return false;
1551 } else {
1552 QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
1553 if (!copyFileIfNewer(entry.absoluteFilePath(), destination,
1554 options, false, forceOverwrite)) {
1555 return false;
1556 }
1557 }
1558 }
1559
1560 return true;
1561}
1562
1563void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
1564{
1565 const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
1566 for (const QFileInfo &dir : dirs) {
1567 if (dir.fileName() != "libs"_L1)
1568 deleteMissingFiles(options, dir.absoluteFilePath(), QDir(dstDir + dir.fileName()));
1569 }
1570}
1571
1572void cleanAndroidFiles(const Options &options)
1573{
1574 if (!options.androidSourceDirectory.isEmpty())
1575 cleanTopFolders(options, QDir(options.androidSourceDirectory), options.outputDirectory);
1576
1577 cleanTopFolders(options,
1578 QDir(options.qtInstallDirectory + u'/' +
1579 options.qtDataDirectory + "/src/android/templates"_L1),
1580 options.outputDirectory);
1581}
1582
1583bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
1584{
1585 QDir sourceDirectory(options.qtInstallDirectory + u'/' + options.qtDataDirectory + androidTemplate);
1586 if (!sourceDirectory.exists()) {
1587 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1588 return false;
1589 }
1590
1591 QString outDir = options.outputDirectory + outDirPrefix;
1592
1593 if (!QDir::current().mkpath(outDir)) {
1594 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1595 return false;
1596 }
1598 return copyFiles(sourceDirectory, QDir(outDir), options);
1599}
1600
1601bool copyGradleTemplate(const Options &options)
1602{
1603 QDir sourceDirectory(options.qtInstallDirectory + u'/' +
1604 options.qtDataDirectory + "/src/3rdparty/gradle"_L1);
1605 if (!sourceDirectory.exists()) {
1606 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1607 return false;
1608 }
1609
1610 QString outDir(options.outputDirectory);
1611 if (!QDir::current().mkpath(outDir)) {
1612 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1613 return false;
1614 }
1615
1616 return copyFiles(sourceDirectory, QDir(outDir), options);
1617}
1618
1619bool copyAndroidTemplate(const Options &options)
1620{
1621 if (options.verbose)
1622 fprintf(stdout, "Copying Android package template.\n");
1623
1624 if (!options.auxMode) {
1625 // Gradle is not configured and is not running in aux mode
1626 if (!copyGradleTemplate(options))
1627 return false;
1628 }
1629
1630 if (!copyAndroidTemplate(options, "/src/android/templates"_L1))
1631 return false;
1632
1633 if (options.buildAar)
1634 return copyAndroidTemplate(options, "/src/android/templates_aar"_L1);
1635
1636 return true;
1637}
1638
1639bool copyAndroidSources(const Options &options)
1640{
1641 if (options.androidSourceDirectory.isEmpty())
1642 return true;
1643
1644 if (options.verbose)
1645 fprintf(stdout, "Copying Android sources from project.\n");
1646
1647 QDir sourceDirectory(options.androidSourceDirectory);
1648 if (!sourceDirectory.exists()) {
1649 fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
1650 return false;
1651 }
1652
1653 return copyFiles(sourceDirectory, QDir(options.outputDirectory), options, true);
1654}
1655
1656bool copyAndroidExtraLibs(Options *options)
1658 if (options->extraLibs.isEmpty())
1659 return true;
1660
1661 if (options->verbose) {
1662 switch (options->deploymentMechanism) {
1663 case Options::Bundled:
1664 fprintf(stdout, "Copying %zd external libraries to package.\n", size_t(options->extraLibs.size()));
1665 break;
1666 case Options::Unbundled:
1667 fprintf(stdout, "Skip copying of external libraries.\n");
1668 break;
1669 };
1670 }
1671
1672 for (const QString &extraLib : options->extraLibs) {
1673 QFileInfo extraLibInfo(extraLib);
1674 if (!extraLibInfo.exists()) {
1675 fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
1676 return false;
1677 }
1678 if (!checkArchitecture(*options, extraLibInfo.filePath())) {
1679 if (options->verbose)
1680 fprintf(stdout, "Skipping \"%s\", architecture mismatch.\n", qPrintable(extraLib));
1681 continue;
1682 }
1683 if (!extraLibInfo.fileName().startsWith("lib"_L1) || extraLibInfo.suffix() != "so"_L1) {
1684 fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
1685 qPrintable(extraLib));
1686 return false;
1687 }
1688 QString destinationFile(options->outputDirectory
1689 + "/libs/"_L1
1690 + options->currentArchitecture
1691 + u'/'
1692 + extraLibInfo.fileName());
1693
1694 if (isDeployment(options, Options::Bundled)
1695 && !copyFileIfNewer(extraLib, destinationFile,
1696 *options, options->createSymlinksOnly)) {
1697 return false;
1698 }
1699 options->archExtraLibs[options->currentArchitecture] += extraLib;
1700 }
1701
1702 return true;
1703}
1704
1705QStringList allFilesInside(const QDir& current, const QDir& rootDir)
1707 QStringList result;
1708 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1709 const auto files = current.entryList(QDir::Files);
1710 result.reserve(dirs.size() + files.size());
1711 for (const QString &dir : dirs) {
1712 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1713 }
1714 for (const QString &file : files) {
1715 result += rootDir.relativeFilePath(current.filePath(file));
1716 }
1717 return result;
1718}
1719
1720bool copyAndroidExtraResources(Options *options)
1721{
1722 if (options->extraPlugins.isEmpty())
1723 return true;
1724
1725 if (options->verbose)
1726 fprintf(stdout, "Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1727
1728 for (const QString &extraResource : options->extraPlugins) {
1729 QFileInfo extraResourceInfo(extraResource);
1730 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1731 fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1732 return false;
1733 }
1734
1735 QDir resourceDir(extraResource);
1736 QString assetsDir = options->outputDirectory + "/assets/"_L1 +
1737 resourceDir.dirName() + u'/';
1738 QString libsDir = options->outputDirectory + "/libs/"_L1 + options->currentArchitecture + u'/';
1739
1740 const QStringList files = allFilesInside(resourceDir, resourceDir);
1741 for (const QString &resourceFile : files) {
1742 QString originFile(resourceDir.filePath(resourceFile));
1743 QString destinationFile;
1744 if (!resourceFile.endsWith(".so"_L1)) {
1745 destinationFile = assetsDir + resourceFile;
1746 } else {
1747 if (isDeployment(options, Options::Unbundled)
1748 || !checkArchitecture(*options, originFile)) {
1749 continue;
1750 }
1751 destinationFile = libsDir + resourceFile;
1752 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1753 }
1754
1755 if (!copyFileIfNewer(originFile, destinationFile,
1756 *options, options->createSymlinksOnly)) {
1757 return false;
1758 }
1759 }
1760 }
1761
1762 return true;
1763}
1764
1765bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
1766{
1767 QFile inputFile(fileName);
1768 if (!inputFile.open(QIODevice::ReadOnly)) {
1769 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
1770 return false;
1771 }
1772
1773 // All the files we are doing substitutes in are quite small. If this
1774 // ever changes, this code should be updated to be more conservative.
1775 QByteArray contents = inputFile.readAll();
1776
1777 bool hasReplacements = false;
1778 QHash<QString, QString>::const_iterator it;
1779 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1780 if (it.key() == it.value())
1781 continue; // Nothing to actually replace
1782
1783 forever {
1784 int index = contents.indexOf(it.key().toUtf8());
1785 if (index >= 0) {
1786 contents.replace(index, it.key().size(), it.value().toUtf8());
1787 hasReplacements = true;
1788 } else {
1789 break;
1790 }
1791 }
1792 }
1793
1794 if (hasReplacements) {
1795 inputFile.close();
1796
1797 if (!inputFile.open(QIODevice::WriteOnly)) {
1798 fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
1799 return false;
1800 }
1801
1802 // Remove leftover empty lines after replacements, for example,
1803 // in case of setting the app icon.
1804 QRegularExpression emptyLinesRegex("\\n\\s+\\n"_L1);
1805 contents = QString::fromUtf8(contents).replace(emptyLinesRegex, "\n"_L1).toUtf8();
1806
1807 inputFile.write(contents);
1808 }
1809
1810 return true;
1811
1812}
1813
1814bool updateLibsXml(Options *options)
1815{
1816 if (options->verbose)
1817 fprintf(stdout, " -- res/values/libs.xml\n");
1818
1819 QString fileName = options->outputDirectory + "/res/values/libs.xml"_L1;
1820 if (!QFile::exists(fileName)) {
1821 fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1822 return false;
1823 }
1824
1825 QString qtLibs;
1826 QString allLocalLibs;
1827 QString extraLibs;
1828
1829 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1830 if (!it->enabled)
1831 continue;
1832
1833 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), options->stdCppName);
1834 for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) {
1835 if (bundledFile.second.startsWith("lib/lib"_L1)) {
1836 if (!bundledFile.second.endsWith(".so"_L1)) {
1837 fprintf(stderr,
1838 "The bundled library %s doesn't end with .so. Android only supports "
1839 "versionless libraries ending with the .so suffix.\n",
1840 qPrintable(bundledFile.second));
1841 return false;
1842 }
1843 QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
1844 s.chop(sizeof(".so") - 1);
1845 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), s);
1846 }
1847 }
1848
1849 if (!options->archExtraLibs[it.key()].isEmpty()) {
1850 for (const QString &extraLib : options->archExtraLibs[it.key()]) {
1851 QFileInfo extraLibInfo(extraLib);
1852 if (extraLibInfo.fileName().startsWith("lib"_L1)) {
1853 if (!extraLibInfo.fileName().endsWith(".so"_L1)) {
1854 fprintf(stderr,
1855 "The library %s doesn't end with .so. Android only supports "
1856 "versionless libraries ending with the .so suffix.\n",
1857 qPrintable(extraLibInfo.fileName()));
1858 return false;
1859 }
1860 QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
1861 name.chop(sizeof(".so") - 1);
1862 extraLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), name);
1863 }
1864 }
1865 }
1866
1867 QStringList localLibs;
1868 localLibs = options->localLibs[it.key()];
1869 const QString archSuffix = it.key() + ".so"_L1;
1870
1871 const QList<QtDependency>& deps = options->qtDependencies[it.key()];
1872 auto notExistsInDependencies = [&deps, archSuffix] (const QString &libName) {
1873 QString lib = QFileInfo(libName).fileName();
1874 if (lib.endsWith(archSuffix))
1875 lib.chop(archSuffix.length());
1876 return std::none_of(deps.begin(), deps.end(), [&lib] (const QtDependency &dep) {
1877 return QFileInfo(dep.absolutePath).fileName().contains(lib);
1878 });
1879 };
1880
1881 // Clean up localLibs: remove libs that were not added to qtDependecies
1882 localLibs.erase(std::remove_if(localLibs.begin(), localLibs.end(), notExistsInDependencies),
1883 localLibs.end());
1884
1885 // If .pro file overrides dependency detection, we need to see which platform plugin they picked
1886 if (localLibs.isEmpty()) {
1887 QString plugin;
1888 for (const QtDependency &qtDependency : deps) {
1889 if (qtDependency.relativePath.contains("libplugins_platforms_qtforandroid_"_L1))
1890 plugin = qtDependency.relativePath;
1891
1892 if (qtDependency.relativePath.contains(
1893 QString::asprintf("libQt%dOpenGL", QT_VERSION_MAJOR))
1894 || qtDependency.relativePath.contains(
1895 QString::asprintf("libQt%dQuick", QT_VERSION_MAJOR))) {
1896 options->usesOpenGL |= true;
1897 }
1898 }
1899
1900 if (plugin.isEmpty()) {
1901 fflush(stdout);
1902 fprintf(stderr, "No platform plugin (libplugins_platforms_qtforandroid.so) included"
1903 " in the deployment. Make sure the app links to Qt Gui library.\n");
1904 fflush(stderr);
1905 return false;
1906 }
1907
1908 localLibs.append(plugin);
1909 if (options->verbose)
1910 fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin));
1911 }
1912
1913 // remove all paths
1914 for (auto &lib : localLibs) {
1915 if (lib.endsWith(".so"_L1))
1916 lib = lib.mid(lib.lastIndexOf(u'/') + 1);
1917 }
1918 allLocalLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), localLibs.join(u':'));
1919 }
1920
1921 QHash<QString, QString> replacements;
1922 replacements[QStringLiteral("<!-- %%INSERT_QT_LIBS%% -->")] += qtLibs.trimmed();
1923 replacements[QStringLiteral("<!-- %%INSERT_LOCAL_LIBS%% -->")] = allLocalLibs.trimmed();
1924 replacements[QStringLiteral("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs.trimmed();
1925
1926 // Set BUNDLE_LOCAL_QT_LIBS based on the deployment used
1927 replacements[QStringLiteral("<!-- %%BUNDLE_LOCAL_QT_LIBS%% -->")]
1928 = isDeployment(options, Options::Unbundled) ? "0"_L1 : "1"_L1;
1929 replacements[QStringLiteral("<!-- %%USE_LOCAL_QT_LIBS%% -->")] = "1"_L1;
1930 replacements[QStringLiteral("<!-- %%SYSTEM_LIBS_PREFIX%% -->")] =
1931 isDeployment(options, Options::Unbundled) ? options->systemLibsPath : QStringLiteral("");
1932
1933 if (!updateFile(fileName, replacements))
1934 return false;
1935
1936 return true;
1937}
1938
1939bool updateStringsXml(const Options &options)
1940{
1941 if (options.verbose)
1942 fprintf(stdout, " -- res/values/strings.xml\n");
1943
1944 QHash<QString, QString> replacements;
1945 replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = options.applicationBinary;
1946
1947 QString fileName = options.outputDirectory + "/res/values/strings.xml"_L1;
1948 if (!QFile::exists(fileName)) {
1949 if (options.verbose)
1950 fprintf(stdout, " -- Create strings.xml since it's missing.\n");
1951 QFile file(fileName);
1952 if (!file.open(QIODevice::WriteOnly)) {
1953 fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
1954 return false;
1955 }
1956 file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\" translatable=\"false\">")
1957 .append(options.applicationBinary.toLatin1())
1958 .append("</string></resources>\n"));
1959 return true;
1960 }
1961
1962 if (!updateFile(fileName, replacements))
1963 return false;
1964
1965 return true;
1966}
1967
1968bool updateAndroidManifest(Options &options)
1969{
1970 if (options.verbose)
1971 fprintf(stdout, " -- AndroidManifest.xml \n");
1972
1973 QHash<QString, QString> replacements;
1974 replacements[QStringLiteral("-- %%INSERT_APP_NAME%% --")] = options.appName;
1975 replacements[QStringLiteral("-- %%INSERT_APP_ARGUMENTS%% --")] = options.applicationArguments;
1976 replacements[QStringLiteral("-- %%INSERT_APP_LIB_NAME%% --")] = options.applicationBinary;
1977 replacements[QStringLiteral("-- %%INSERT_VERSION_NAME%% --")] = options.versionName;
1978 replacements[QStringLiteral("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode;
1979 replacements[QStringLiteral("package=\"org.qtproject.example\"")] = "package=\"%1\""_L1.arg(options.packageName);
1980
1981 const QString iconAttribute = "android:icon=\"%1\""_L1;
1982 replacements[iconAttribute.arg("-- %%INSERT_APP_ICON%% --"_L1)] = options.appIcon.isEmpty() ?
1983 ""_L1 : iconAttribute.arg(options.appIcon);
1984
1985 const QString androidManifestPath = options.outputDirectory + "/AndroidManifest.xml"_L1;
1986 QFile androidManifestXml(androidManifestPath);
1987 // User may have manually defined permissions in the AndroidManifest.xml
1988 // Read these permissions in order to remove any duplicates, as otherwise the
1989 // application build would fail.
1990 if (androidManifestXml.exists() && androidManifestXml.open(QIODevice::ReadOnly)) {
1991 QXmlStreamReader reader(&androidManifestXml);
1992 while (!reader.atEnd()) {
1993 reader.readNext();
1994 if (reader.isStartElement() && reader.name() == "uses-permission"_L1) {
1995 options.modulePermissions.remove(
1996 QString(reader.attributes().value("android:name"_L1)));
1997 options.applicationPermissions.remove(
1998 QString(reader.attributes().value("android:name"_L1)));
1999 }
2000 }
2001 androidManifestXml.close();
2002 }
2003
2004 // Application may define permissions in its CMakeLists.txt, give them the priority
2005 QMap<QString, QString> resolvedPermissions = options.modulePermissions;
2006 for (auto [name, extras] : options.applicationPermissions.asKeyValueRange())
2007 resolvedPermissions.insert(name, extras);
2008
2009 QString permissions;
2010 for (auto [name, extras] : resolvedPermissions.asKeyValueRange())
2011 permissions += " <uses-permission android:name=\"%1\" %2 />\n"_L1.arg(name).arg(extras);
2012 replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
2013
2014 QString features;
2015 for (const QString &feature : std::as_const(options.features))
2016 features += " <uses-feature android:name=\"%1\" android:required=\"false\" />\n"_L1.arg(feature);
2017 if (options.usesOpenGL)
2018 features += " <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"_L1;
2019
2020 replacements[QStringLiteral("<!-- %%INSERT_FEATURES -->")] = features.trimmed();
2021
2022 if (!updateFile(androidManifestPath, replacements))
2023 return false;
2024
2025 // read the package, min & target sdk API levels from manifest file.
2026 bool checkOldAndroidLabelString = false;
2027 if (androidManifestXml.exists()) {
2028 if (!androidManifestXml.open(QIODevice::ReadOnly)) {
2029 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
2030 return false;
2031 }
2032
2033 QXmlStreamReader reader(&androidManifestXml);
2034 while (!reader.atEnd()) {
2035 reader.readNext();
2036
2037 if (reader.isStartElement()) {
2038 if (reader.name() == "uses-sdk"_L1) {
2039 if (reader.attributes().hasAttribute("android:minSdkVersion"_L1))
2040 if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 28) {
2041 fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 28\n");
2042 return false;
2043 }
2044 } else if ((reader.name() == "application"_L1 ||
2045 reader.name() == "activity"_L1) &&
2046 reader.attributes().hasAttribute("android:label"_L1) &&
2047 reader.attributes().value("android:label"_L1) == "@string/app_name"_L1) {
2048 checkOldAndroidLabelString = true;
2049 } else if (reader.name() == "meta-data"_L1) {
2050 const auto name = reader.attributes().value("android:name"_L1);
2051 const auto value = reader.attributes().value("android:value"_L1);
2052 if (name == "android.app.lib_name"_L1 && value.contains(u' ')) {
2053 fprintf(stderr, "The Activity's android.app.lib_name should not contain"
2054 " spaces.\n");
2055 return false;
2056 }
2057 }
2058 }
2059 }
2060
2061 if (reader.hasError()) {
2062 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
2063 return false;
2064 }
2065 } else {
2066 fprintf(stderr, "No android manifest file");
2067 return false;
2068 }
2069
2070 if (checkOldAndroidLabelString)
2071 updateStringsXml(options);
2072
2073 return true;
2074}
2076bool updateAndroidFiles(Options &options)
2077{
2078 if (options.verbose)
2079 fprintf(stdout, "Updating Android package files with project settings.\n");
2080
2081 if (!updateLibsXml(&options))
2082 return false;
2083
2084 if (!updateAndroidManifest(options))
2085 return false;
2086
2087 return true;
2088}
2089
2090static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
2091{
2092 // Use extraLibraryDirs as the extra library lookup folder if it is expected to find a file in
2093 // any $prefix/lib folder.
2094 // Library directories from a build tree(extraLibraryDirs) have the higher priority.
2095 if (relativeFileName.startsWith("lib/"_L1)) {
2096 for (const auto &dir : options->extraLibraryDirs) {
2097 const QString path = dir + u'/' + relativeFileName.mid(sizeof("lib/") - 1);
2098 if (QFile::exists(path))
2099 return path;
2100 }
2102
2103 for (const auto &prefix : options->extraPrefixDirs) {
2104 const QString path = prefix + u'/' + relativeFileName;
2105 if (QFile::exists(path))
2106 return path;
2107 }
2108
2109 if (relativeFileName.endsWith("-android-dependencies.xml"_L1)) {
2110 for (const auto &dir : options->extraLibraryDirs) {
2111 const QString path = dir + u'/' + relativeFileName;
2112 if (QFile::exists(path))
2113 return path;
2114 }
2115 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2116 u'/' + relativeFileName;
2117 }
2118
2119 if (relativeFileName.startsWith("jar/"_L1)) {
2120 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2121 u'/' + relativeFileName;
2122 }
2123
2124 if (relativeFileName.startsWith("lib/"_L1)) {
2125 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2126 u'/' + relativeFileName.mid(sizeof("lib/") - 1);
2127 }
2128 return options->qtInstallDirectory + u'/' + relativeFileName;
2129}
2130
2131QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath)
2132{
2133 if (!info.exists())
2134 return QList<QtDependency>();
2135
2136 if (info.isDir()) {
2137 QList<QtDependency> ret;
2138
2139 QDir dir(info.filePath());
2140 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2141
2142 for (const QString &entry : entries) {
2143 ret += findFilesRecursively(options,
2144 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2145 rootPath);
2146 }
2147
2148 return ret;
2149 } else {
2150 return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2151 }
2152}
2153
2154QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName)
2155{
2156 // We try to find the fileName in extraPrefixDirs first. The function behaves differently
2157 // depending on what the fileName points to. If fileName is a file then we try to find the
2158 // first occurrence in extraPrefixDirs and return this file. If fileName is directory function
2159 // iterates over it and looks for deployment artifacts in each 'extraPrefixDirs' entry.
2160 // Also we assume that if the fileName is recognized as a directory once it will be directory
2161 // for every 'extraPrefixDirs' entry.
2162 QList<QtDependency> deps;
2163 for (const auto &prefix : options.extraPrefixDirs) {
2164 QFileInfo info(prefix + u'/' + fileName);
2165 if (info.exists()) {
2166 if (info.isDir())
2167 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2168 else
2169 return findFilesRecursively(options, info, prefix + u'/');
2170 }
2171 }
2172
2173 // Usually android deployment settings contain Qt install directory in extraPrefixDirs.
2174 if (std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2175 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2176 QFileInfo info(options.qtInstallDirectory + "/"_L1 + fileName);
2177 QFileInfo rootPath(options.qtInstallDirectory + "/"_L1);
2178 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2179 }
2180 return deps;
2181}
2182
2183void readDependenciesFromFiles(Options *options, const QList<QtDependency> &files,
2184 QSet<QString> &usedDependencies,
2185 QSet<QString> &remainingDependencies)
2186{
2187 for (const QtDependency &fileName : files) {
2188 if (usedDependencies.contains(fileName.absolutePath))
2189 continue;
2190
2191 if (fileName.absolutePath.endsWith(".so"_L1)) {
2192 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2193 &remainingDependencies)) {
2194 fprintf(stdout, "Skipping file dependency: %s\n",
2195 qPrintable(fileName.relativePath));
2196 continue;
2197 }
2198 }
2199 usedDependencies.insert(fileName.absolutePath);
2200
2201 if (options->verbose) {
2202 fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath));
2203 }
2204
2205 options->qtDependencies[options->currentArchitecture].append(fileName);
2206 }
2207}
2208
2209bool readAndroidDependencyXml(Options *options,
2210 const QString &moduleName,
2211 QSet<QString> *usedDependencies,
2212 QSet<QString> *remainingDependencies)
2213{
2214 QString androidDependencyName = absoluteFilePath(options, "%1-android-dependencies.xml"_L1.arg(moduleName));
2215
2216 QFile androidDependencyFile(androidDependencyName);
2217 if (androidDependencyFile.exists()) {
2218 if (options->verbose)
2219 fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
2220
2221 if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
2222 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
2223 return false;
2224 }
2225
2226 QXmlStreamReader reader(&androidDependencyFile);
2227 while (!reader.atEnd()) {
2228 reader.readNext();
2229
2230 if (reader.isStartElement()) {
2231 if (reader.name() == "bundled"_L1) {
2232 if (!reader.attributes().hasAttribute("file"_L1)) {
2233 fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
2234 return false;
2235 }
2236
2237 QString file = reader.attributes().value("file"_L1).toString();
2238
2239 if (reader.attributes().hasAttribute("type"_L1)
2240 && reader.attributes().value("type"_L1) == "plugin_dir"_L1
2241 && !options->androidDeployPlugins.isEmpty()) {
2242 continue;
2243 }
2244
2245 const QList<QtDependency> fileNames = findFilesRecursively(*options, file);
2246 readDependenciesFromFiles(options, fileNames, *usedDependencies,
2247 *remainingDependencies);
2248 } else if (reader.name() == "jar"_L1) {
2249 int bundling = reader.attributes().value("bundling"_L1).toInt();
2250 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
2251 if (bundling) {
2252 QtDependency dependency(fileName, absoluteFilePath(options, fileName));
2253 if (!usedDependencies->contains(dependency.absolutePath)) {
2254 options->qtDependencies[options->currentArchitecture].append(dependency);
2255 usedDependencies->insert(dependency.absolutePath);
2256 }
2257 }
2258 } else if (reader.name() == "lib"_L1) {
2259 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
2260 if (reader.attributes().hasAttribute("replaces"_L1)) {
2261 QString replaces = reader.attributes().value("replaces"_L1).toString();
2262 for (int i=0; i<options->localLibs.size(); ++i) {
2263 if (options->localLibs[options->currentArchitecture].at(i) == replaces) {
2264 options->localLibs[options->currentArchitecture][i] = fileName;
2265 break;
2266 }
2267 }
2268 } else if (!fileName.isEmpty()) {
2269 options->localLibs[options->currentArchitecture].append(fileName);
2270 }
2271 if (fileName.endsWith(".so"_L1) && checkArchitecture(*options, fileName)) {
2272 remainingDependencies->insert(fileName);
2273 }
2274 } else if (reader.name() == "permission"_L1) {
2275 QString name = reader.attributes().value("name"_L1).toString();
2276 QString extras = reader.attributes().value("extras"_L1).toString();
2277 // With duplicate permissions prioritize the one without any attributes,
2278 // as that is likely the most permissive
2279 if (!options->modulePermissions.contains(name)
2280 || !options->modulePermissions.value(name).isEmpty()) {
2281 options->modulePermissions.insert(name, extras);
2282 }
2283 } else if (reader.name() == "feature"_L1) {
2284 QString name = reader.attributes().value("name"_L1).toString();
2285 options->features.append(name);
2287 }
2288 }
2289
2290 if (reader.hasError()) {
2291 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2292 return false;
2293 }
2294 } else if (options->verbose) {
2295 fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
2296 }
2297 options->features.removeDuplicates();
2298
2299 return true;
2300}
2301
2302QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
2303{
2304 QString readElf = llvmReadobjPath(options);
2305 if (!QFile::exists(readElf)) {
2306 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
2307 return QStringList();
2308 }
2309
2310 readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2311
2312 auto readElfCommand = openProcess(readElf);
2313 if (!readElfCommand) {
2314 fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
2315 return QStringList();
2316 }
2317
2318 QStringList ret;
2319
2320 bool readLibs = false;
2321 char buffer[512];
2322 while (fgets(buffer, sizeof(buffer), readElfCommand.get()) != nullptr) {
2323 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2324 QString library;
2325 line = line.trimmed();
2326 if (!readLibs) {
2327 if (line.startsWith("Arch: ")) {
2328 auto it = elfArchitectures.find(line.mid(6));
2329 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2330 if (options.verbose)
2331 fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2332 return {};
2333 }
2334 }
2335 readLibs = line.startsWith("NeededLibraries");
2336 continue;
2337 }
2338 if (!line.startsWith("lib"))
2339 continue;
2340 library = QString::fromLatin1(line);
2341 QString libraryName = "lib/"_L1 + library;
2342 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2343 ret += libraryName;
2344 }
2345
2346 return ret;
2347}
2348
2349bool readDependenciesFromElf(Options *options,
2350 const QString &fileName,
2351 QSet<QString> *usedDependencies,
2352 QSet<QString> *remainingDependencies)
2353{
2354 // Get dependencies on libraries in $QTDIR/lib
2355 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2356
2357 if (options->verbose) {
2358 fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
2359 for (const QString &dep : dependencies)
2360 fprintf(stdout, " %s\n", qPrintable(dep));
2361 }
2362 // Recursively add dependencies from ELF and supplementary XML information
2363 QList<QString> dependenciesToCheck;
2364 for (const QString &dependency : dependencies) {
2365 if (usedDependencies->contains(dependency))
2366 continue;
2367
2368 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2369 usedDependencies->insert(dependency);
2370 if (!readDependenciesFromElf(options,
2371 absoluteDependencyPath,
2372 usedDependencies,
2373 remainingDependencies)) {
2374 return false;
2375 }
2376
2377 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2378 if (options->verbose)
2379 fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
2380 dependenciesToCheck.append(dependency);
2381 }
2382
2383 for (const QString &dependency : std::as_const(dependenciesToCheck)) {
2384 QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
2385 qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
2386 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2387 return false;
2388 }
2389 }
2390
2391 return true;
2392}
2393
2394bool scanImports(Options *options, QSet<QString> *usedDependencies)
2395{
2396 if (options->verbose)
2397 fprintf(stdout, "Scanning for QML imports.\n");
2398
2399 QString qmlImportScanner;
2400 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2401 qmlImportScanner = options->qmlImportScannerBinaryPath;
2402 } else {
2403 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2404 "/qmlimportscanner"_L1);
2405 }
2406
2407 QStringList importPaths;
2408
2409 // In Conan's case, qtInstallDirectory will point only to qtbase installed files, which
2410 // lacks a qml directory. We don't want to pass it as an import path if it doesn't exist
2411 // because it will cause qmlimportscanner to fail.
2412 // This also covers the case when only qtbase is installed in a regular Qt build.
2413 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2414 if (QFile::exists(mainImportPath))
2415 importPaths += shellQuote(mainImportPath);
2416
2417 // These are usually provided by CMake in the deployment json file from paths specified
2418 // in CMAKE_FIND_ROOT_PATH. They might not have qml modules.
2419 for (const QString &prefix : options->extraPrefixDirs)
2420 if (QFile::exists(prefix + "/qml"_L1))
2421 importPaths += shellQuote(prefix + "/qml"_L1);
2422
2423 // These are provided by both CMake and qmake.
2424 for (const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2425 if (QFile::exists(qmlImportPath)) {
2426 importPaths += shellQuote(qmlImportPath);
2427 } else {
2428 fprintf(stderr, "Warning: QML import path %s does not exist.\n",
2429 qPrintable(qmlImportPath));
2430 }
2431 }
2432
2433 bool qmlImportExists = false;
2434
2435 for (const QString &import : importPaths) {
2436 if (QDir().exists(import)) {
2437 qmlImportExists = true;
2438 break;
2439 }
2440 }
2441
2442 // Check importPaths without rootPath, since we need at least one qml plugins
2443 // folder to run a QML file
2444 if (!qmlImportExists) {
2445 fprintf(stderr, "Warning: no 'qml' directory found under Qt install directory "
2446 "or import paths. Skipping QML dependency scanning.\n");
2447 return true;
2448 }
2449
2450 if (!QFile::exists(qmlImportScanner)) {
2451 fprintf(stderr, "%s: qmlimportscanner not found at %s\n",
2452 qmlImportExists ? "Error"_L1.data() : "Warning"_L1.data(),
2453 qPrintable(qmlImportScanner));
2454 return true;
2455 }
2456
2457 for (auto rootPath : options->rootPaths) {
2458 rootPath = QFileInfo(rootPath).absoluteFilePath();
2459
2460 if (!rootPath.endsWith(u'/'))
2461 rootPath += u'/';
2462
2463 // After checking for qml folder imports we can add rootPath
2464 if (!rootPath.isEmpty())
2465 importPaths += shellQuote(rootPath);
2466
2467 qmlImportScanner += " -rootPath %1"_L1.arg(shellQuote(rootPath));
2468 }
2469
2470 if (!options->qrcFiles.isEmpty()) {
2471 qmlImportScanner += " -qrcFiles"_L1;
2472 for (const QString &qrcFile : options->qrcFiles)
2473 qmlImportScanner += u' ' + shellQuote(qrcFile);
2474 }
2475
2476 qmlImportScanner += " -importPath %1"_L1.arg(importPaths.join(u' '));
2477
2478 if (options->verbose) {
2479 fprintf(stdout, "Running qmlimportscanner with the following command: %s\n",
2480 qmlImportScanner.toLocal8Bit().constData());
2481 }
2482
2483 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2484 if (qmlImportScannerCommand == 0) {
2485 fprintf(stderr, "Couldn't run qmlimportscanner.\n");
2486 return false;
2487 }
2488
2489 QByteArray output;
2490 char buffer[512];
2491 while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand.get()) != nullptr)
2492 output += QByteArray(buffer, qstrlen(buffer));
2493
2494 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2495 if (jsonDocument.isNull()) {
2496 fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
2497 return false;
2498 }
2499
2500 QJsonArray jsonArray = jsonDocument.array();
2501 for (int i=0; i<jsonArray.count(); ++i) {
2502 QJsonValue value = jsonArray.at(i);
2503 if (!value.isObject()) {
2504 fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
2505 return false;
2506 }
2507
2508 QJsonObject object = value.toObject();
2509 QString path = object.value("path"_L1).toString();
2510 if (path.isEmpty()) {
2511 fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n",
2512 qPrintable(object.value("name"_L1).toString()));
2513 } else if (object.value("type"_L1).toString() == "module"_L1) {
2514 if (options->verbose)
2515 fprintf(stdout, " -- Adding '%s' as QML dependency\n", qPrintable(path));
2516
2517 QFileInfo info(path);
2518
2519 // The qmlimportscanner sometimes outputs paths that do not exist.
2520 if (!info.exists()) {
2521 if (options->verbose)
2522 fprintf(stdout, " -- Skipping because path does not exist.\n");
2523 continue;
2524 }
2526 QString absolutePath = info.absolutePath();
2527 if (!absolutePath.endsWith(u'/'))
2528 absolutePath += u'/';
2529
2530 const QUrl url(object.value("name"_L1).toString());
2531
2532 const QString moduleUrlPath = u"/"_s + url.toString().replace(u'.', u'/');
2533 if (checkCanImportFromRootPaths(options, info.absolutePath(), moduleUrlPath)) {
2534 if (options->verbose)
2535 fprintf(stdout, " -- Skipping because path is in QML root path.\n");
2536 continue;
2537 }
2538
2539 QString importPathOfThisImport;
2540 for (const QString &importPath : std::as_const(importPaths)) {
2541 QString cleanImportPath = QDir::cleanPath(importPath);
2542 if (QFile::exists(cleanImportPath + moduleUrlPath)) {
2543 importPathOfThisImport = importPath;
2544 break;
2545 }
2547
2548 if (importPathOfThisImport.isEmpty()) {
2549 fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
2550 return false;
2551 }
2552
2553 importPathOfThisImport = QDir(importPathOfThisImport).absolutePath() + u'/';
2554 QList<QtDependency> qmlImportsDependencies;
2555 auto collectQmlDependency = [&usedDependencies, &qmlImportsDependencies,
2556 &importPathOfThisImport](const QString &filePath) {
2557 if (!usedDependencies->contains(filePath)) {
2558 usedDependencies->insert(filePath);
2559 qmlImportsDependencies += QtDependency(
2560 "qml/"_L1 + filePath.mid(importPathOfThisImport.size()),
2561 filePath);
2562 }
2563 };
2564
2565 QString plugin = object.value("plugin"_L1).toString();
2566 bool pluginIsOptional = object.value("pluginIsOptional"_L1).toBool();
2567 QFileInfo pluginFileInfo = QFileInfo(
2568 path + u'/' + "lib"_L1 + plugin + u'_'
2569 + options->currentArchitecture + ".so"_L1);
2570 QString pluginFilePath = pluginFileInfo.absoluteFilePath();
2571 QSet<QString> remainingDependencies;
2572 if (pluginFileInfo.exists() && checkArchitecture(*options, pluginFilePath)
2573 && readDependenciesFromElf(options, pluginFilePath, usedDependencies,
2574 &remainingDependencies)) {
2575 collectQmlDependency(pluginFilePath);
2576 } else if (!pluginIsOptional) {
2577 if (options->verbose)
2578 fprintf(stdout, " -- Skipping because the required plugin is missing.\n");
2579 continue;
2580 }
2581
2582 QFileInfo qmldirFileInfo = QFileInfo(path + u'/' + "qmldir"_L1);
2583 if (qmldirFileInfo.exists()) {
2584 collectQmlDependency(qmldirFileInfo.absoluteFilePath());
2585 }
2586
2587 QString prefer = object.value("prefer"_L1).toString();
2588 // If the preferred location of Qml files points to the Qt resources, this means
2589 // that all Qml files has been embedded into plugin and we should not copy them to the
2590 // android rcc bundle
2591 if (!prefer.startsWith(":/"_L1)) {
2592 QVariantList qmlFiles =
2593 object.value("components"_L1).toArray().toVariantList();
2594 qmlFiles.append(object.value("scripts"_L1).toArray().toVariantList());
2595 bool qmlFilesMissing = false;
2596 for (const auto &qmlFileEntry : qmlFiles) {
2597 QFileInfo fileInfo(qmlFileEntry.toString());
2598 if (!fileInfo.exists()) {
2599 qmlFilesMissing = true;
2600 break;
2601 }
2602 collectQmlDependency(fileInfo.absoluteFilePath());
2603 }
2604
2605 if (qmlFilesMissing) {
2606 if (options->verbose)
2607 fprintf(stdout,
2608 " -- Skipping because the required qml files are missing.\n");
2609 continue;
2610 }
2611 }
2612
2613 options->qtDependencies[options->currentArchitecture].append(qmlImportsDependencies);
2614 } else {
2615 // We don't need to handle file and directory imports. Generally those should be
2616 // considered as part of the application and are therefore scanned separately.
2617 }
2618 }
2619
2620 return true;
2621}
2622
2623bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
2624 const QString &moduleUrlPath)
2625{
2626 for (auto rootPath : options->rootPaths) {
2627 if ((rootPath + moduleUrlPath) == absolutePath)
2628 return true;
2629 }
2630 return false;
2631}
2632
2633bool runCommand(const Options &options, const QString &command)
2634{
2635 if (options.verbose)
2636 fprintf(stdout, "Running command '%s'\n", qPrintable(command));
2637
2638 auto runCommand = openProcess(command);
2639 if (runCommand == nullptr) {
2640 fprintf(stderr, "Cannot run command '%s'\n", qPrintable(command));
2641 return false;
2642 }
2643 char buffer[4096];
2644 while (fgets(buffer, sizeof(buffer), runCommand.get()) != nullptr) {
2645 if (options.verbose)
2646 fprintf(stdout, "%s", buffer);
2647 }
2648 runCommand.reset();
2649 fflush(stdout);
2650 fflush(stderr);
2651 return true;
2652}
2653
2654bool createRcc(const Options &options)
2655{
2656 auto assetsDir = "%1/assets"_L1.arg(options.outputDirectory);
2657 if (!QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.exists()) {
2658 fprintf(stdout, "Skipping createRCC\n");
2659 return true;
2660 }
2661
2662 if (options.verbose)
2663 fprintf(stdout, "Create rcc bundle.\n");
2664
2665
2666 QString rcc;
2667 if (!options.rccBinaryPath.isEmpty()) {
2668 rcc = options.rccBinaryPath;
2669 } else {
2670 rcc = execSuffixAppended(options.qtLibExecsDirectory + "/rcc"_L1);
2671 }
2672
2673 if (!QFile::exists(rcc)) {
2674 fprintf(stderr, "rcc not found: %s\n", qPrintable(rcc));
2675 return false;
2676 }
2677 auto currentDir = QDir::currentPath();
2678 if (!QDir::setCurrent("%1/android_rcc_bundle"_L1.arg(assetsDir))) {
2679 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable("%1/android_rcc_bundle"_L1.arg(assetsDir)));
2680 return false;
2681 }
2682
2683 bool res = runCommand(options, "%1 --project -o %2"_L1.arg(rcc, shellQuote("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir))));
2684 if (!res)
2685 return false;
2686
2687 QLatin1StringView noZstd;
2688 if (!options.isZstdCompressionEnabled)
2689 noZstd = "--no-zstd"_L1;
2690
2691 QFile::rename("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir), "%1/android_rcc_bundle/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2692
2693 res = runCommand(options, "%1 %2 %3 --binary -o %4 android_rcc_bundle.qrc"_L1.arg(rcc, shellQuote("--root=/android_rcc_bundle/"_L1),
2694 noZstd,
2695 shellQuote("%1/android_rcc_bundle.rcc"_L1.arg(assetsDir))));
2696 if (!QDir::setCurrent(currentDir)) {
2697 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(currentDir));
2698 return false;
2699 }
2700 if (!options.noRccBundleCleanup) {
2701 QFile::remove("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2702 QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.removeRecursively();
2703 }
2704 return res;
2705}
2706
2707bool readDependencies(Options *options)
2708{
2709 if (options->verbose)
2710 fprintf(stdout, "Detecting dependencies of application.\n");
2711
2712 // Override set in .pro file
2713 if (!options->qtDependencies[options->currentArchitecture].isEmpty()) {
2714 if (options->verbose)
2715 fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
2716 return true;
2718
2719 QSet<QString> usedDependencies;
2720 QSet<QString> remainingDependencies;
2721
2722 // Add dependencies of application binary first
2723 if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies))
2724 return false;
2725
2726 QList<QtDependency> pluginDeps;
2727 for (const auto &pluginPath : options->androidDeployPlugins) {
2728 pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath),
2729 options->qtInstallDirectory + "/"_L1));
2730 }
2731
2732 readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies);
2733
2734 while (!remainingDependencies.isEmpty()) {
2735 QSet<QString>::iterator start = remainingDependencies.begin();
2736 QString fileName = absoluteFilePath(options, *start);
2737 remainingDependencies.erase(start);
2738
2739 QStringList unmetDependencies;
2740 if (goodToCopy(options, fileName, &unmetDependencies)) {
2741 bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
2742 if (!ok)
2743 return false;
2744 } else {
2745 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2746 qPrintable(fileName),
2747 qPrintable(unmetDependencies.join(u',')));
2748 }
2749 }
2750
2751 QStringList::iterator it = options->localLibs[options->currentArchitecture].begin();
2752 while (it != options->localLibs[options->currentArchitecture].end()) {
2753 QStringList unmetDependencies;
2754 if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) {
2755 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2756 qPrintable(*it),
2757 qPrintable(unmetDependencies.join(u',')));
2758 it = options->localLibs[options->currentArchitecture].erase(it);
2759 } else {
2760 ++it;
2761 }
2762 }
2763
2764 if (options->qmlSkipImportScanning
2765 || (options->rootPaths.empty() && options->qrcFiles.isEmpty()))
2766 return true;
2767 return scanImports(options, &usedDependencies);
2768}
2769
2770bool containsApplicationBinary(Options *options)
2771{
2772 if (!options->build)
2773 return true;
2774
2775 if (options->verbose)
2776 fprintf(stdout, "Checking if application binary is in package.\n");
2777
2778 QString applicationFileName = "lib%1_%2.so"_L1.arg(options->applicationBinary,
2779 options->currentArchitecture);
2780
2781 QString applicationPath = "%1/libs/%2/%3"_L1.arg(options->outputDirectory,
2782 options->currentArchitecture,
2783 applicationFileName);
2784 if (!QFile::exists(applicationPath)) {
2785#if defined(Q_OS_WIN32)
2786 const auto makeTool = "mingw32-make"_L1; // Only Mingw host builds supported on Windows currently
2787#else
2788 const auto makeTool = "make"_L1;
2789#endif
2790 fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
2791 qPrintable(applicationFileName),
2792 qPrintable(makeTool),
2793 qPrintable(options->outputDirectory));
2794 return false;
2795 }
2796 return true;
2797}
2798
2799auto runAdb(const Options &options, const QString &arguments)
2800 -> decltype(openProcess({}))
2801{
2802 QString adb = execSuffixAppended(options.sdkPath + "/platform-tools/adb"_L1);
2803 if (!QFile::exists(adb)) {
2804 fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
2805 return 0;
2806 }
2807 QString installOption;
2808 if (!options.installLocation.isEmpty())
2809 installOption = " -s "_L1 + shellQuote(options.installLocation);
2810
2811 adb = "%1%2 %3"_L1.arg(shellQuote(adb), installOption, arguments);
2812
2813 if (options.verbose)
2814 fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
2815
2816 auto adbCommand = openProcess(adb);
2817 if (adbCommand == 0) {
2818 fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
2819 return 0;
2820 }
2821
2822 return adbCommand;
2823}
2824
2825bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
2826{
2827 if (!file.endsWith(".so"_L1))
2828 return true;
2829
2830 if (!checkArchitecture(*options, file))
2831 return false;
2832
2833 if (!options->abi.isEmpty() && options->abi != options->currentArchitecture)
2834 return true;
2835
2836 bool ret = true;
2837 const auto libs = getQtLibsFromElf(*options, file);
2838 for (const QString &lib : libs) {
2839 if (!options->qtDependencies[options->currentArchitecture].contains(QtDependency(lib, absoluteFilePath(options, lib)))) {
2840 ret = false;
2841 unmetDependencies->append(lib);
2842 }
2843 }
2844
2845 return ret;
2846}
2847
2848bool copyQtFiles(Options *options)
2849{
2850 if (options->verbose) {
2851 switch (options->deploymentMechanism) {
2852 case Options::Bundled:
2853 fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies[options->currentArchitecture].size()));
2854 break;
2855 case Options::Unbundled:
2856 fprintf(stdout, "Copying dependencies from Qt into the package build folder,"
2857 "skipping native libraries.\n");
2858 break;
2860 }
2862 if (!options->build)
2863 return true;
2864
2865
2866 QString libsDirectory = "libs/"_L1;
2867
2868 // Copy other Qt dependencies
2869 auto assetsDestinationDirectory = "assets/android_rcc_bundle/"_L1;
2870 for (const QtDependency &qtDependency : std::as_const(options->qtDependencies[options->currentArchitecture])) {
2871 QString sourceFileName = qtDependency.absolutePath;
2872 QString destinationFileName;
2873 bool isSharedLibrary = qtDependency.relativePath.endsWith(".so"_L1);
2874 bool createSymlinksOnly = options->createSymlinksOnly;
2875 if (isSharedLibrary) {
2876 QString garbledFileName = qtDependency.relativePath.mid(
2877 qtDependency.relativePath.lastIndexOf(u'/') + 1);
2878 destinationFileName = libsDirectory + options->currentArchitecture + u'/' + garbledFileName;
2879 } else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("jar/"_L1)) {
2880 destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
2881 } else {
2882 // rcc resouces compilation doesn't support using symlinks
2883 createSymlinksOnly = false;
2884 destinationFileName = assetsDestinationDirectory + qtDependency.relativePath;
2885 }
2886
2887 if (!QFile::exists(sourceFileName)) {
2888 fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
2889 return false;
2890 }
2891
2892 QStringList unmetDependencies;
2893 if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
2894 if (unmetDependencies.isEmpty()) {
2895 if (options->verbose) {
2896 fprintf(stdout, " -- Skipping %s, architecture mismatch.\n",
2897 qPrintable(sourceFileName));
2898 }
2899 } else {
2900 fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n",
2901 qPrintable(sourceFileName),
2902 qPrintable(unmetDependencies.join(u',')));
2903 }
2904 continue;
2905 }
2906
2907 if ((isDeployment(options, Options::Bundled) || !isSharedLibrary)
2908 && !copyFileIfNewer(sourceFileName,
2909 options->outputDirectory + u'/' + destinationFileName,
2910 *options, createSymlinksOnly)) {
2911 return false;
2912 }
2913 options->bundledFiles[options->currentArchitecture] += std::make_pair(destinationFileName, qtDependency.relativePath);
2914 }
2915
2916 return true;
2917}
2918
2919QStringList getLibraryProjectsInOutputFolder(const Options &options)
2920{
2921 QStringList ret;
2922
2923 QFile file(options.outputDirectory + "/project.properties"_L1);
2924 if (file.open(QIODevice::ReadOnly)) {
2925 QByteArray lineArray;
2926 while (file.readLineInto(&lineArray)) {
2927 QByteArrayView line = QByteArrayView(lineArray).trimmed();
2928 if (line.startsWith("android.library.reference")) {
2929 int equalSignIndex = line.indexOf('=');
2930 if (equalSignIndex >= 0) {
2931 QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
2932
2933 QFileInfo info(options.outputDirectory + u'/' + path);
2934 if (QDir::isRelativePath(path)
2935 && info.exists()
2936 && info.isDir()
2937 && info.canonicalFilePath().startsWith(options.outputDirectory)) {
2938 ret += info.canonicalFilePath();
2940 }
2941 }
2942 }
2943 }
2944
2945 return ret;
2946}
2947
2948QString findInPath(const QString &fileName)
2949{
2950 const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
2951#if defined(Q_OS_WIN32)
2952 QLatin1Char separator(';');
2953#else
2954 QLatin1Char separator(':');
2955#endif
2956
2957 const QStringList paths = path.split(separator);
2958 for (const QString &path : paths) {
2959 QFileInfo fileInfo(path + u'/' + fileName);
2960 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2961 return path + u'/' + fileName;
2962 }
2963
2964 return QString();
2965}
2966
2967typedef QMap<QByteArray, QByteArray> GradleProperties;
2968
2969static GradleProperties readGradleProperties(const QString &path)
2970{
2971 GradleProperties properties;
2972 QFile file(path);
2973 if (!file.open(QIODevice::ReadOnly))
2974 return properties;
2975
2976 const auto lines = file.readAll().split('\n');
2977 for (const QByteArray &line : lines) {
2978 if (line.trimmed().startsWith('#'))
2979 continue;
2980
2981 const int idx = line.indexOf('=');
2982 if (idx > -1)
2983 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
2984 }
2985 file.close();
2986 return properties;
2987}
2988
2989static bool mergeGradleProperties(const QString &path, GradleProperties properties)
2990{
2991 const QString oldPathStr = path + u'~';
2992 QFile::remove(oldPathStr);
2993 QFile::rename(path, oldPathStr);
2994 QFile file(path);
2995 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
2996 fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName()));
2997 return false;
2998 }
2999
3000 QFile oldFile(oldPathStr);
3001 if (oldFile.open(QIODevice::ReadOnly)) {
3002 QByteArray line;
3003 while (oldFile.readLineInto(&line)) {
3004 QList<QByteArray> prop(line.split('='));
3005 if (prop.size() > 1) {
3006 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3007 if (it != properties.end()) {
3008 file.write(it.key() + '=' + it.value() + '\n');
3009 properties.erase(it);
3010 continue;
3011 }
3012 }
3013 file.write(line.trimmed() + '\n');
3014 }
3015 oldFile.close();
3016 QFile::remove(oldPathStr);
3017 }
3018
3019 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3020 file.write(it.key() + '=' + it.value() + '\n');
3021
3022 file.close();
3023 return true;
3024}
3025
3026#if defined(Q_OS_WIN32)
3027void checkAndWarnGradleLongPaths(const QString &outputDirectory)
3028{
3029 QStringList longFileNames;
3030 using F = QDirListing::IteratorFlag;
3031 for (const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3032 F::FilesOnly | F::Recursive)) {
3033 if (dirEntry.size() >= MAX_PATH)
3034 longFileNames.append(dirEntry.filePath());
3035 }
3036
3037 if (!longFileNames.isEmpty()) {
3038 fprintf(stderr,
3039 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3040 "Consider moving your project to reduce its path length.\n"
3041 "The following files have too long paths:\n%s.\n",
3042 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3043 }
3044}
3045#endif
3046
3047bool buildAndroidProject(const Options &options)
3048{
3049 GradleProperties localProperties;
3050 localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3051 const QString localPropertiesPath = options.outputDirectory + "local.properties"_L1;
3052 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3053 return false;
3054
3055 const QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1;
3056 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3057
3058 const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1;
3059 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3060
3061 gradleProperties["buildDir"] = "build";
3062 gradleProperties["qtAndroidDir"] =
3063 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3064 "/src/android/java"_L1)
3065 .toUtf8();
3066 // The following property "qt5AndroidDir" is only for compatibility.
3067 // Projects using a custom build.gradle file may use this variable.
3068 // ### Qt7: Remove the following line
3069 gradleProperties["qt5AndroidDir"] =
3070 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3071 "/src/android/java"_L1)
3072 .toUtf8();
3073
3074 QByteArray sdkPlatformVersion;
3075 // Provide the integer version only if build.gradle explicitly converts to Integer,
3076 // to avoid regression to existing projects that build for sdk platform of form android-xx.
3077 if (gradleConfigs.usesIntegerCompileSdkVersion) {
3078 const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit();
3079 bool ok;
3080 tmp.toInt(&ok);
3081 if (ok) {
3082 sdkPlatformVersion = tmp;
3083 } else {
3084 fprintf(stderr, "Warning: Gradle expects SDK platform version to be an integer, "
3085 "but the set version is not convertible to an integer.");
3086 }
3087 }
3088
3089 if (sdkPlatformVersion.isEmpty())
3090 sdkPlatformVersion = options.androidPlatform.toLocal8Bit();
3092 gradleProperties["androidPackageName"] = options.packageName.toLocal8Bit();
3093 gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion;
3094 gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
3095 gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
3096 gradleProperties["androidNdkVersion"] = options.ndkVersion.toUtf8();
3097 if (gradleProperties["androidBuildToolsVersion"].isEmpty())
3098 gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit();
3099 gradleProperties["legacyPackaging"] = options.useLegacyPackaging ? "true" : "false";
3100 QString abiList;
3101 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
3102 if (!it->enabled)
3103 continue;
3104 if (abiList.size())
3105 abiList.append(u",");
3106 abiList.append(it.key());
3107 }
3108 gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
3109 gradleProperties["qtGradlePluginType"] = options.buildAar
3110 ? "com.android.library"
3111 : "com.android.application";
3112 if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
3113 return false;
3114
3115 QString gradlePath = batSuffixAppended(options.outputDirectory + "gradlew"_L1);
3116#ifndef Q_OS_WIN32
3117 {
3118 QFile f(gradlePath);
3119 if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser))
3120 fprintf(stderr, "Cannot set permissions %s\n", qPrintable(gradlePath));
3121 }
3122#endif
3123
3124 QString oldPath = QDir::currentPath();
3125 if (!QDir::setCurrent(options.outputDirectory)) {
3126 fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory));
3127 return false;
3128 }
3129
3130 QString commandLine = "%1 %2"_L1.arg(shellQuote(gradlePath), options.releasePackage ? " assembleRelease"_L1 : " assembleDebug"_L1);
3131 if (options.buildAAB)
3132 commandLine += " bundle"_L1;
3133
3134 if (options.verbose)
3135 commandLine += " --info"_L1;
3136
3137 auto gradleCommand = openProcess(commandLine);
3138 if (gradleCommand == 0) {
3139 fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine));
3140 return false;
3141 }
3142
3143 char buffer[512];
3144 while (fgets(buffer, sizeof(buffer), gradleCommand.get()) != nullptr) {
3145 fprintf(stdout, "%s", buffer);
3146 fflush(stdout);
3147 }
3148
3149 const int errorCode = pclose(gradleCommand.release());
3150 if (errorCode != 0) {
3151 fprintf(stderr, "Building the android package failed!\n");
3152 if (!options.verbose)
3153 fprintf(stderr, " -- For more information, run this command with --verbose.\n");
3154
3155#if defined(Q_OS_WIN32)
3156 checkAndWarnGradleLongPaths(options.outputDirectory);
3157#endif
3158 return false;
3159 }
3160
3161 if (!QDir::setCurrent(oldPath)) {
3162 fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath));
3163 return false;
3165
3166 return true;
3167}
3168
3169bool uninstallApk(const Options &options)
3170{
3171 if (options.verbose)
3172 fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
3173
3174
3175 auto adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName));
3176 if (adbCommand == 0)
3177 return false;
3178
3179 if (options.verbose || mustReadOutputAnyway) {
3180 char buffer[512];
3181 while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
3182 if (options.verbose)
3183 fprintf(stdout, "%s", buffer);
3184 }
3185
3186 const int returnCode = pclose(adbCommand.release());
3187 if (returnCode != 0) {
3188 fprintf(stderr, "Warning: Uninstall failed!\n");
3189 if (!options.verbose)
3190 fprintf(stderr, " -- Run with --verbose for more information.\n");
3191 return false;
3192 }
3193
3194 return true;
3196
3197enum PackageType {
3198 AAB,
3199 AAR,
3200 UnsignedAPK,
3201 SignedAPK
3202};
3203
3204QString packagePath(const Options &options, PackageType packageType)
3205{
3206 // The package type is always AAR if option.buildAar has been set
3207 if (options.buildAar)
3208 packageType = AAR;
3209
3210 static const QHash<PackageType, QLatin1StringView> packageTypeToPath{
3211 { AAB, "bundle"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
3212 };
3213 static const QHash<PackageType, QLatin1StringView> packageTypeToExtension{
3214 { AAB, "aab"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
3215 };
3216
3217 const QString buildType(options.releasePackage ? "release"_L1 : "debug"_L1);
3218 QString signedSuffix;
3219 if (packageType == SignedAPK)
3220 signedSuffix = "-signed"_L1;
3221 else if (packageType == UnsignedAPK && options.releasePackage)
3222 signedSuffix = "-unsigned"_L1;
3223
3224 QString dirPath(options.outputDirectory);
3225 dirPath += "/build/outputs/%1/"_L1.arg(packageTypeToPath[packageType]);
3226 if (QDir(dirPath + buildType).exists())
3227 dirPath += buildType;
3228
3229 const QString fileName = "/%1-%2%3.%4"_L1.arg(
3230 QDir(options.outputDirectory).dirName(),
3231 buildType,
3232 signedSuffix,
3233 packageTypeToExtension[packageType]);
3234
3235 return dirPath + fileName;
3236}
3237
3238bool installApk(const Options &options)
3239{
3240 fflush(stdout);
3241 // Uninstall if necessary
3242 if (options.uninstallApk)
3243 uninstallApk(options);
3244
3245 if (options.verbose)
3246 fprintf(stdout, "Installing Android package to device.\n");
3247
3248 auto adbCommand = runAdb(options, " install -r "_L1
3249 + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
3250 : SignedAPK));
3251 if (adbCommand == 0)
3252 return false;
3253
3254 if (options.verbose || mustReadOutputAnyway) {
3255 char buffer[512];
3256 while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
3257 if (options.verbose)
3258 fprintf(stdout, "%s", buffer);
3259 }
3260
3261 const int returnCode = pclose(adbCommand.release());
3262 if (returnCode != 0) {
3263 fprintf(stderr, "Installing to device failed!\n");
3264 if (!options.verbose)
3265 fprintf(stderr, " -- Run with --verbose for more information.\n");
3266 return false;
3267 }
3268
3269 return true;
3270}
3271
3272bool copyPackage(const Options &options)
3273{
3274 fflush(stdout);
3275 auto from = packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK : SignedAPK);
3276 QFile::remove(options.apkPath);
3277 return QFile::copy(from, options.apkPath);
3278}
3279
3280bool copyStdCpp(Options *options)
3281{
3282 if (isDeployment(options, Options::Unbundled))
3283 return true;
3284 if (options->verbose)
3285 fprintf(stdout, "Copying STL library\n");
3286
3287 const QString triple = options->architectures[options->currentArchitecture].triple;
3288 const QString stdCppPath = "%1/%2/lib%3.so"_L1.arg(options->stdCppPath, triple,
3289 options->stdCppName);
3290 if (!QFile::exists(stdCppPath)) {
3291 fprintf(stderr, "STL library does not exist at %s\n", qPrintable(stdCppPath));
3292 fflush(stdout);
3293 fflush(stderr);
3294 return false;
3295 }
3296
3297 const QString destinationFile = "%1/libs/%2/lib%3.so"_L1.arg(options->outputDirectory,
3298 options->currentArchitecture,
3299 options->stdCppName);
3300 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3301}
3302
3303static QString zipalignPath(const Options &options, bool *ok)
3304{
3305 *ok = true;
3306 QString zipAlignTool = execSuffixAppended(options.sdkPath + "/tools/zipalign"_L1);
3307 if (!QFile::exists(zipAlignTool)) {
3308 zipAlignTool = execSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
3309 options.sdkBuildToolsVersion + "/zipalign"_L1);
3310 if (!QFile::exists(zipAlignTool)) {
3311 fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3312 *ok = false;
3313 }
3314 }
3315
3316 return zipAlignTool;
3317}
3318
3319bool signAAB(const Options &options)
3320{
3321 if (options.verbose)
3322 fprintf(stdout, "Signing Android package.\n");
3323
3324 QString jdkPath = options.jdkPath;
3325
3326 if (jdkPath.isEmpty())
3327 jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME"));
3328
3329 QString jarSignerTool = execSuffixAppended("jarsigner"_L1);
3330 if (jdkPath.isEmpty() || !QFile::exists(jdkPath + "/bin/"_L1 + jarSignerTool))
3331 jarSignerTool = findInPath(jarSignerTool);
3332 else
3333 jarSignerTool = jdkPath + "/bin/"_L1 + jarSignerTool;
3334
3335 if (!QFile::exists(jarSignerTool)) {
3336 fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3337 return false;
3338 }
3339
3340 jarSignerTool = "%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3341 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3342
3343 if (!options.keyStorePassword.isEmpty())
3344 jarSignerTool += " -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3345
3346 if (!options.storeType.isEmpty())
3347 jarSignerTool += " -storetype %1"_L1.arg(shellQuote(options.storeType));
3348
3349 if (!options.keyPass.isEmpty())
3350 jarSignerTool += " -keypass %1"_L1.arg(shellQuote(options.keyPass));
3351
3352 if (!options.sigFile.isEmpty())
3353 jarSignerTool += " -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3354
3355 if (!options.signedJar.isEmpty())
3356 jarSignerTool += " -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3357
3358 if (!options.tsaUrl.isEmpty())
3359 jarSignerTool += " -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3360
3361 if (!options.tsaCert.isEmpty())
3362 jarSignerTool += " -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3363
3364 if (options.internalSf)
3365 jarSignerTool += " -internalsf"_L1;
3366
3367 if (options.sectionsOnly)
3368 jarSignerTool += " -sectionsonly"_L1;
3369
3370 if (options.protectedAuthenticationPath)
3371 jarSignerTool += " -protected"_L1;
3372
3373 auto jarSignPackage = [&](const QString &file) {
3374 fprintf(stdout, "Signing file %s\n", qPrintable(file));
3375 fflush(stdout);
3376 QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file))
3377 .arg(shellQuote(options.keyStoreAlias));
3378
3379 auto jarSignerCommand = openProcess(command);
3380 if (jarSignerCommand == 0) {
3381 fprintf(stderr, "Couldn't run jarsigner.\n");
3382 return false;
3383 }
3384
3385 if (options.verbose) {
3386 char buffer[512];
3387 while (fgets(buffer, sizeof(buffer), jarSignerCommand.get()) != nullptr)
3388 fprintf(stdout, "%s", buffer);
3389 }
3390
3391 const int errorCode = pclose(jarSignerCommand.release());
3392 if (errorCode != 0) {
3393 fprintf(stderr, "jarsigner command failed.\n");
3394 if (!options.verbose)
3395 fprintf(stderr, " -- Run with --verbose for more information.\n");
3396 return false;
3397 }
3398 return true;
3399 };
3400
3401 if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3402 return false;
3403 return true;
3404}
3405
3406bool signPackage(const Options &options)
3407{
3408 const QString apksignerTool = batSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
3409 options.sdkBuildToolsVersion + "/apksigner"_L1);
3410 // APKs signed with apksigner must not be changed after they're signed,
3411 // therefore we need to zipalign it before we sign it.
3412
3413 bool ok;
3414 QString zipAlignTool = zipalignPath(options, &ok);
3415 if (!ok)
3416 return false;
3418 auto zipalignRunner = [](const QString &zipAlignCommandLine) {
3419 auto zipAlignCommand = openProcess(zipAlignCommandLine);
3420 if (zipAlignCommand == 0) {
3421 fprintf(stderr, "Couldn't run zipalign.\n");
3422 return false;
3425 char buffer[512];
3426 while (fgets(buffer, sizeof(buffer), zipAlignCommand.get()) != nullptr)
3427 fprintf(stdout, "%s", buffer);
3429 return pclose(zipAlignCommand.release()) == 0;
3432 const QString verifyZipAlignCommandLine =
3433 "%1%2 -c 4 %3"_L1
3434 .arg(shellQuote(zipAlignTool),
3435 options.verbose ? " -v"_L1 : QLatin1StringView(),
3436 shellQuote(packagePath(options, UnsignedAPK)));
3437
3438 if (zipalignRunner(verifyZipAlignCommandLine)) {
3439 if (options.verbose)
3440 fprintf(stdout, "APK already aligned, copying it for signing.\n");
3441
3442 if (QFile::exists(packagePath(options, SignedAPK)))
3443 QFile::remove(packagePath(options, SignedAPK));
3444
3445 if (!QFile::copy(packagePath(options, UnsignedAPK), packagePath(options, SignedAPK))) {
3446 fprintf(stderr, "Could not copy unsigned APK.\n");
3447 return false;
3448 }
3449 } else {
3450 if (options.verbose)
3451 fprintf(stdout, "APK not aligned, aligning it for signing.\n");
3452
3453 const QString zipAlignCommandLine =
3454 "%1%2 -f 4 %3 %4"_L1
3455 .arg(shellQuote(zipAlignTool),
3456 options.verbose ? " -v"_L1 : QLatin1StringView(),
3457 shellQuote(packagePath(options, UnsignedAPK)),
3458 shellQuote(packagePath(options, SignedAPK)));
3459
3460 if (!zipalignRunner(zipAlignCommandLine)) {
3461 fprintf(stderr, "zipalign command failed.\n");
3462 if (!options.verbose)
3463 fprintf(stderr, " -- Run with --verbose for more information.\n");
3464 return false;
3465 }
3467
3468 QString apkSignCommand = "%1 sign --ks %2"_L1
3469 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3470
3471 if (!options.keyStorePassword.isEmpty())
3472 apkSignCommand += " --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3473
3474 if (!options.keyStoreAlias.isEmpty())
3475 apkSignCommand += " --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3476
3477 if (!options.keyPass.isEmpty())
3478 apkSignCommand += " --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3479
3480 if (options.verbose)
3481 apkSignCommand += " --verbose"_L1;
3482
3483 apkSignCommand += " %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3484
3485 auto apkSignerRunner = [](const QString &command, bool verbose) {
3486 auto apkSigner = openProcess(command);
3487 if (apkSigner == 0) {
3488 fprintf(stderr, "Couldn't run apksigner.\n");
3489 return false;
3490 }
3491
3492 char buffer[512];
3493 while (fgets(buffer, sizeof(buffer), apkSigner.get()) != nullptr)
3494 fprintf(stdout, "%s", buffer);
3495
3496 const int errorCode = pclose(apkSigner.release());
3497 if (errorCode != 0) {
3498 fprintf(stderr, "apksigner command failed.\n");
3499 if (!verbose)
3500 fprintf(stderr, " -- Run with --verbose for more information.\n");
3501 return false;
3502 }
3503 return true;
3504 };
3505
3506 // Sign the package
3507 if (!apkSignerRunner(apkSignCommand, options.verbose))
3508 return false;
3509
3510 const QString apkVerifyCommand =
3511 "%1 verify --verbose %2"_L1
3512 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3513
3514 if (options.buildAAB && !signAAB(options))
3515 return false;
3516
3517 // Verify the package and remove the unsigned apk
3518 return apkSignerRunner(apkVerifyCommand, true) && QFile::remove(packagePath(options, UnsignedAPK));
3519}
3520
3521enum ErrorCode
3522{
3523 Success,
3524 SyntaxErrorOrHelpRequested = 1,
3525 CannotReadInputFile = 2,
3526 CannotCopyAndroidTemplate = 3,
3527 CannotReadDependencies = 4,
3528 CannotCopyGnuStl = 5,
3529 CannotCopyQtFiles = 6,
3530 CannotFindApplicationBinary = 7,
3531 CannotCopyAndroidExtraLibs = 10,
3532 CannotCopyAndroidSources = 11,
3533 CannotUpdateAndroidFiles = 12,
3534 CannotCreateAndroidProject = 13, // Not used anymore
3535 CannotBuildAndroidProject = 14,
3536 CannotSignPackage = 15,
3537 CannotInstallApk = 16,
3538 CannotCopyAndroidExtraResources = 19,
3539 CannotCopyApk = 20,
3540 CannotCreateRcc = 21,
3541 CannotGenerateJavaQmlComponents = 22
3542};
3543
3544bool writeDependencyFile(const Options &options)
3545{
3546 if (options.verbose)
3547 fprintf(stdout, "Writing dependency file.\n");
3548
3549 QString relativeTargetPath;
3550 if (options.copyDependenciesOnly) {
3551 // When androiddeploy Qt is running in copyDependenciesOnly mode we need to use
3552 // the timestamp file as the target to collect dependencies.
3553 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() + "/timestamp"_L1;
3554 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3555 } else {
3556 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3557 }
3558
3559 QFile depFile(options.depFilePath);
3560 if (depFile.open(QIODevice::WriteOnly)) {
3561 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3562 depFile.write(": ");
3563
3564 for (const auto &file : dependenciesForDepfile) {
3565 depFile.write(" \\\n ");
3566 depFile.write(escapeAndEncodeDependencyPath(file));
3567 }
3568
3569 depFile.write("\n");
3570 }
3571 return true;
3572}
3573
3574int generateJavaQmlComponents(const Options &options)
3575{
3576 const auto firstCharToUpper = [](const QString &str) -> QString {
3577 if (str.isEmpty())
3578 return str;
3579 return str.left(1).toUpper() + str.mid(1);
3580 };
3581
3582 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3583 if (str.isEmpty())
3584 return str;
3585
3586 str[0] = str[0].toUpper();
3587
3588 for (int i = 0; i < str.size(); ++i) {
3589 if (str[i] == "."_L1) {
3590 // Move to the next character after the dot
3591 int j = i + 1;
3592 if (j < str.size()) {
3593 str[j] = str[j].toUpper();
3594 }
3595 }
3596 }
3597 return str;
3598 };
3599
3600 const auto getImportPaths = [options](const QString &buildPath, const QString &libName,
3601 QStringList &appImports, QStringList &externalImports) -> bool {
3602 QFile confRspFile("%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3603 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3604 return false;
3605 QTextStream rspStream(&confRspFile);
3606 while (!rspStream.atEnd()) {
3607 QString currentLine = rspStream.readLine();
3608 if (currentLine.compare("-importPath"_L1) == 0) {
3609 currentLine = rspStream.readLine();
3610 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3611 appImports << currentLine;
3612 else
3613 externalImports << currentLine;
3614 }
3615 }
3616
3617 // Find inner qmldir files
3618 QSet<QString> qmldirDirectories;
3619 for (const QString &path : appImports) {
3620 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3621 while (it.hasNext()) {
3622 const QDir dir(it.next());
3623 const QString absolutePath = dir.absolutePath();
3624 if (!absolutePath.startsWith(options.outputDirectory)
3625 && dir.exists("qmldir"_L1)) {
3626 qmldirDirectories.insert(absolutePath);
3627 }
3628 }
3629 }
3630 appImports << qmldirDirectories.values();
3631 appImports.removeDuplicates();
3632
3633 return appImports.count() + externalImports.count();
3634 };
3635
3636 struct ComponentInfo {
3637 QString name;
3638 QString path;
3639 };
3640
3641 struct ModuleInfo
3642 {
3643 QString moduleName;
3644 QString preferPath;
3645 QList<ComponentInfo> qmlComponents;
3646 bool isValid() { return qmlComponents.size() && moduleName.size(); }
3647 };
3648
3649 const auto getModuleInfo = [](const QString &qmldirPath) -> ModuleInfo {
3650 QFile qmlDirFile(qmldirPath + "/qmldir"_L1);
3651 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3652 return ModuleInfo();
3653 ModuleInfo moduleInfo;
3654 QSet<QString> qmlComponentNames;
3655 QTextStream qmldirStream(&qmlDirFile);
3656 while (!qmldirStream.atEnd()) {
3657 const QString currentLine = qmldirStream.readLine();
3658 if (currentLine.size() && currentLine[0].isLower()) {
3659 // TODO QTBUG-125891: Handling of QML modules with dotted URI
3660 if (currentLine.startsWith("module "_L1))
3661 moduleInfo.moduleName = currentLine.split(" "_L1)[1];
3662 else if (currentLine.startsWith("prefer "_L1))
3663 moduleInfo.preferPath = currentLine.split(" "_L1)[1];
3664 } else if (currentLine.size()
3665 && (currentLine[0].isUpper() || currentLine.startsWith("singleton"_L1))) {
3666 const QStringList parts = currentLine.split(" "_L1);
3667 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3668 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3669 qmlComponentNames.insert(parts.first());
3670 }
3671 }
3672 }
3673 return moduleInfo;
3674 };
3675
3676 const auto extractDomInfo = [](const QString &qmlDomExecPath, const QString &qmldirPath,
3677 const QString &qmlFile,
3678 const QStringList &otherImportPaths) -> QJsonObject {
3679 QByteArray domInfo;
3680#if QT_CONFIG(process)
3681 QStringList qmlDomArgs {"-d"_L1, "-D"_L1, "required"_L1, "-f"_L1, "+:propertyInfos"_L1 };
3682 for (auto &importPath : otherImportPaths)
3683 qmlDomArgs << "-I"_L1 << importPath;
3684 qmlDomArgs << "%1/%2"_L1.arg(qmldirPath, qmlFile);
3685 const QString qmlDomCmd = "%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3686 QProcess process;
3687 process.start(qmlDomExecPath, qmlDomArgs);
3688 if (!process.waitForStarted()) {
3689 fprintf(stderr, "Cannot execute command %s\n", qPrintable(qmlDomCmd));
3690 return QJsonObject();
3691 }
3692 // Wait, maximum 30 seconds
3693 if (!process.waitForFinished(30000)) {
3694 fprintf(stderr, "Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3695 return QJsonObject();
3696 }
3697 domInfo = process.readAllStandardOutput();
3698
3699 QJsonParseError jsonError;
3700 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3701 if (jsonError.error != QJsonParseError::NoError)
3702 fprintf(stderr, "Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3703 return jsonDoc.object();
3704#else
3705#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3706 return QJsonObject();
3707#endif
3708 };
3709
3710 const auto getComponent = [](const QJsonObject &dom) -> QJsonObject {
3711 if (dom.isEmpty())
3712 return QJsonObject();
3713
3714 const QJsonObject currentItem = dom.value("currentItem"_L1).toObject();
3715 if (!currentItem.value("isValid"_L1).toBool(false))
3716 return QJsonObject();
3717
3718 const QJsonArray components =
3719 currentItem.value("components"_L1).toObject().value(""_L1).toArray();
3720 if (components.isEmpty())
3721 return QJsonObject();
3722 return components.constBegin()->toObject();
3723 };
3724
3725 const auto getProperties = [](const QJsonObject &component) -> QJsonArray {
3726 QJsonArray properties;
3727 const QJsonArray objects = component.value("objects"_L1).toArray();
3728 if (objects.isEmpty())
3729 return QJsonArray();
3730 const QJsonObject propertiesObject =
3731 objects[0].toObject().value("propertyInfos"_L1).toObject();
3732 for (const auto &jsonProperty : propertiesObject) {
3733 const QJsonArray propertyDefs =
3734 jsonProperty.toObject().value("propertyDefs"_L1).toArray();
3735 if (propertyDefs.isEmpty())
3736 continue;
3737
3738 properties.append(propertyDefs[0].toObject());
3739 }
3740 return properties;
3741 };
3742
3743 const auto getMethods = [](const QJsonObject &component) -> QJsonArray {
3744 QJsonArray methods;
3745 const QJsonArray objects = component.value("objects"_L1).toArray();
3746 if (objects.isEmpty())
3747 return QJsonArray();
3748 const QJsonObject methodsObject = objects[0].toObject().value("methods"_L1).toObject();
3749 for (const auto &jsonMethod : methodsObject) {
3750 const QJsonArray overloads = jsonMethod.toArray();
3751 for (const auto &m : overloads)
3752 methods.append(m);
3753 }
3754 return methods;
3755 };
3756
3757 const static QHash<QString, QString> qmlToJavaType = {
3758 { "real"_L1, "Double"_L1 }, { "double"_L1, "Double"_L1 }, { "int"_L1, "Integer"_L1 },
3759 { "float"_L1, "Float"_L1 }, { "bool"_L1, "Boolean"_L1 }, { "string"_L1, "String"_L1 },
3760 { "void"_L1, "Void"_L1 }
3761 };
3762
3763 const auto endBlock = [](QTextStream &stream, int indentWidth = 0) {
3764 stream << QString(indentWidth, u' ') << "}\n";
3765 };
3766
3767 const auto createHeaderBlock = [](QTextStream &stream, const QString &javaPackage) {
3768 stream << "/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3769 << "package %1;\n\n"_L1.arg(javaPackage)
3770 << "import org.qtproject.qt.android.QtSignalListener;\n"
3771 << "import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3772 };
3773
3774 const auto beginComponentBlock = [](QTextStream &stream, const QString &libName,
3775 const QString &moduleName, const QString &preferPath,
3776 const ComponentInfo &componentInfo, int indentWidth = 8) {
3777 const QString indent(indentWidth, u' ');
3778
3779 stream << indent
3780 << "public final class %1 extends QtQuickViewContent {\n"_L1
3781 .arg(componentInfo.name)
3782 << indent << " @Override public String getLibraryName() {\n"_L1
3783 << indent << " return \"%1\";\n"_L1.arg(libName)
3784 << indent << " }\n"_L1
3785 << indent << " @Override public String getModuleName() {\n"_L1
3786 << indent << " return \"%1\";\n"_L1.arg(moduleName)
3787 << indent << " }\n"_L1
3788 << indent << " @Override public String getFilePath() {\n"_L1
3789 << indent << " return \"qrc%1%2\";\n"_L1.arg(preferPath)
3790 .arg(componentInfo.path)
3791 << indent << " }\n"_L1;
3792 };
3793
3794 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3795 const QJsonObject &propertyData,
3796 int indentWidth = 8) {
3797 const QString indent(indentWidth, u' ');
3798 const QString propertyName = propertyData["name"_L1].toString();
3799 if (propertyName.isEmpty())
3800 return;
3801 const QString upperPropertyName = firstCharToUpper(propertyName);
3802 const QString typeName = propertyData["typeName"_L1].toString();
3803 const bool isReadyonly = propertyData["isReadonly"_L1].toBool();
3804
3805 const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3806
3807 if (!isReadyonly) {
3808 stream << indent
3809 << "public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3810 upperPropertyName, javaTypeName, propertyName);
3811 }
3812
3813 stream << indent
3814 << "public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3815 .arg(upperPropertyName, javaTypeName, propertyName)
3816 << indent
3817 << "public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3818 .arg(upperPropertyName, javaTypeName)
3819 << indent
3820 << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3821 propertyName, javaTypeName)
3822 << indent << "}\n";
3823 };
3824
3825 enum class MethodType { Signal = 0, Function = 1 };
3826
3827 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3828 const QJsonObject &methodData,
3829 int indentWidth = 8) {
3830 const QString indent(indentWidth, u' ');
3831 if (MethodType(methodData["methodType"_L1].toInt()) != MethodType::Signal)
3832 return;
3833 const QJsonArray parameters = methodData["parameters"_L1].toArray();
3834
3835 const QString methodName = methodData["name"_L1].toString();
3836 if (methodName.isEmpty())
3837 return;
3838
3839 const QString upperMethodName = firstCharToUpper(methodName);
3840 if (parameters.size() <= 1) { // Generate a QtSignalListener<T> API for this property/signal
3841 const QString typeName = !parameters.isEmpty()
3842 ? parameters[0].toObject()["typeName"_L1].toString()
3843 : "void"_L1;
3844 const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3845 stream << indent
3846 << "public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3847 .arg(upperMethodName, javaTypeName)
3848 << indent
3849 << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3850 .arg(methodName, javaTypeName)
3851 << indent << "}\n";
3852 } else { // Multi-arg signal; Generate a custom listener interface for this signal
3853 // Returns a comma-separated parameter list of java types deduced from the QML DOM array
3854 const auto getJavaArgsString = [&parameters]() -> QString {
3855 QList<QString> javaArgsList;
3856 for (const auto param : parameters) {
3857 const auto typeName = param["typeName"_L1].toString();
3858 const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3859 const auto qmlParamName = param["name"_L1].toString();
3860
3861 javaArgsList.emplace_back(
3862 QStringLiteral("%1%2").arg(javaTypeName, " %1"_L1.arg(qmlParamName)));
3863 }
3864 return javaArgsList.join(", "_L1);
3865 };
3866 // Returns a comma-separated parameter list of java classes deduced from QML DOM array
3867 const auto getJavaClassesString = [&parameters]() -> QString {
3868 QList<QString> javaArgsList;
3869 for (const auto param : parameters) {
3870 const auto typeName = param["typeName"_L1].toString();
3871 const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3872
3873 javaArgsList.emplace_back(
3874 QStringLiteral("%1%2").arg(javaTypeName, ".class"_L1));
3875 }
3876 return javaArgsList.join(", "_L1);
3877 };
3878
3879 const auto javaParamsString = getJavaArgsString();
3880 const auto javaParamsClassesString = getJavaClassesString();
3881
3882 // e.g. "{(String) args[0], (Integer) args[1], (Boolean) args[2]}"
3883 QList<QString> objectToTypeConversion;
3884 for (auto i = 0; i < parameters.size(); ++i) {
3885 const auto typeName = parameters.at(i).toObject().value("typeName"_L1).toString();
3886 objectToTypeConversion.emplace_back("(%1) args[%2]"_L1.arg(
3887 qmlToJavaType.value(typeName, "Object"_L1), QString::number(i)));
3888 }
3889
3890 // Generate new interface type for this signal
3891 const auto signalInterfaceName = "%1Listener"_L1.arg(methodName);
3892 const auto objectToTypeConversionString = objectToTypeConversion.join(", "_L1);
3893 stream << indent << "@FunctionalInterface\n"
3894 << indent << "public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3895 << " default void onSignalEmitted(Object[] args) {\n"
3896 << indent
3897 << " on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3898 << indent << " }\n"
3899 << indent
3900 << " void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3901 stream << indent << "}\n"_L1;
3902
3903 // Generate the connection function with this new interface type
3904 stream << indent
3905 << "public int connect%1(%2 signalListener) {\n"_L1.arg(
3906 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3907 << indent
3908 << " return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3909 .arg(methodName, javaParamsClassesString)
3910 << indent << "}\n\n";
3911 }
3912 };
3913
3914 const auto writeFunctionBlock = [](QTextStream &stream, const QJsonObject &methodData,
3915 int indentWidth = 8) {
3916 const QString indent(indentWidth, u' ');
3917 if (MethodType(methodData["methodType"_L1].toInt()) != MethodType::Function)
3918 return;
3919
3920 const QJsonArray params = methodData["parameters"_L1].toArray();
3921 const QString functionName = methodData["name"_L1].toString();
3922
3923 QList<QString> javaFunctionParams; // e.g. { "Object param", "String thing" }
3924 QList<QString> javaParams; // e.g. "param, thing"
3925 for (const auto &value : params) {
3926 const auto object = value.toObject();
3927 if (!object.contains("typeName"_L1)) {
3928 qWarning() << " -- Skipping function" << functionName
3929 << "due to untyped function parameter detected while generating Java "
3930 "code for QML methods.";
3931 return;
3932 }
3933
3934 const auto qmlParamType = object["typeName"_L1].toString();
3935 if (!qmlToJavaType.contains(qmlParamType)) {
3936 qWarning() << " -- Skipping function" << functionName
3937 << "due to unsupported type detected in parameters:" << qmlParamType;
3938 return;
3939 }
3940
3941 const auto javaTypeName{ qmlToJavaType.value(object["typeName"_L1].toString(),
3942 "Object"_L1) };
3943 const auto javaParamName = object["name"_L1].toString();
3944 javaFunctionParams.push_back(
3945 QString{ "%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
3946 javaParams.append(javaParamName);
3947 }
3948
3949 const auto functionSignature {
3950 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(", "_L1))
3951 };
3952 const auto functionCallParams {
3953 javaParams.isEmpty() ? ""_L1 : ", new Object[] { %1 }"_L1.arg(javaParams.join(", "_L1))
3954 };
3955
3956 stream << indent << functionSignature
3957 << indent << " invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
3958 .arg(functionCallParams)
3959 << indent << "}\n";
3960 };
3961
3962 constexpr static auto markerFileName = "qml_java_contents"_L1;
3963 const QString libName(options.applicationBinary);
3964 QString javaPackageBase = options.packageName;
3965 const QString expectedBaseLeaf = ".%1"_L1.arg(libName);
3966 if (!javaPackageBase.endsWith(expectedBaseLeaf))
3967 javaPackageBase += expectedBaseLeaf;
3968 const QString baseSourceDir = "%1/src/%2"_L1.arg(options.outputDirectory,
3969 QString(javaPackageBase).replace(u'.', u'/'));
3970 const QString buildPath(QDir(options.buildDirectory).absolutePath());
3971 const QString domBinaryPath(options.qmlDomBinaryPath);
3973 fprintf(stdout, "Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
3974 if (!QDir().current().mkpath(baseSourceDir)) {
3975 fprintf(stderr, "Cannot create %s directory\n", qPrintable(baseSourceDir));
3976 return false;
3977 }
3978
3979 QStringList appImports;
3980 QStringList externalImports;
3981 if (!getImportPaths(buildPath, libName, appImports, externalImports))
3982 return false;
3983
3984 // Remove previous directories generated by this code generator
3985 {
3986 const QString srcDir = "%1/src"_L1.arg(options.outputDirectory);
3987 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
3988 while (iter.hasNext())
3989 iter.nextFileInfo().dir().removeRecursively();
3990 }
3991
3992 int generatedComponents = 0;
3993 for (const auto &importPath : appImports) {
3994 ModuleInfo moduleInfo = getModuleInfo(importPath);
3995 if (!moduleInfo.isValid())
3996 continue;
3997
3998 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
3999 if (moduleInfo.moduleName == libName) {
4000 fprintf(stderr,
4001 "A QML module name (%s) cannot be the same as the target name when building "
4002 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4003 qPrintable(moduleInfo.moduleName));
4004 return false;
4005 }
4006
4007 const QString javaPackage = "%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4008 const QString outputDir =
4009 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4010 if (!QDir().current().mkpath(outputDir)) {
4011 fprintf(stderr, "Cannot create %s directory\n", qPrintable(outputDir));
4012 return false;
4013 }
4014
4015 // Add a marker file to indicate this as a module package source directory
4016 {
4017 QFile markerFile("%1/%2"_L1.arg(outputDir, markerFileName));
4018 if (!markerFile.open(QFile::WriteOnly)) {
4019 fprintf(stderr, "Cannot create %s file\n", qPrintable(markerFile.fileName()));
4020 return false;
4021 }
4022 }
4023
4024 int indentBase = 0;
4025
4026 for (const auto &qmlComponent : moduleInfo.qmlComponents) {
4027 const bool isSelected = options.selectedJavaQmlComponents.contains(
4028 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4029 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4030 continue;
4031
4032 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4033 externalImports + appImports);
4034 QJsonObject component = getComponent(domInfo);
4035 if (component.isEmpty())
4036 continue;
4037
4038 QByteArray componentClassBody;
4039 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4040
4041 createHeaderBlock(outputStream, javaPackage);
4042
4043 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4044 qmlComponent, indentBase);
4045 indentBase += 4;
4046
4047 const QJsonArray properties = getProperties(component);
4048 for (const QJsonValue &p : std::as_const(properties))
4049 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4050
4051 const QJsonArray methods = getMethods(component);
4052 for (const QJsonValue &m : std::as_const(methods))
4053 beginSignalBlock(outputStream, m.toObject(), indentBase);
4054
4055 for (const QJsonValue &m : std::as_const(methods))
4056 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4057
4058 indentBase -= 4;
4059 endBlock(outputStream, indentBase);
4060 outputStream.flush();
4061
4062 // Write component class body to file
4063 QFile outputFile("%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4064 if (outputFile.exists())
4065 outputFile.remove();
4066 if (!outputFile.open(QFile::WriteOnly)) {
4067 fprintf(stderr, "Cannot open %s file to write.\n",
4068 qPrintable(outputFile.fileName()));
4069 return false;
4070 }
4071 outputFile.write(componentClassBody);
4072 outputFile.close();
4073
4074 generatedComponents++;
4075 }
4076 }
4077 return generatedComponents;
4078}
4079
4080int main(int argc, char *argv[])
4081{
4082 QCoreApplication a(argc, argv);
4083
4084 Options options = parseOptions();
4085 if (options.helpRequested || options.outputDirectory.isEmpty()) {
4087 return SyntaxErrorOrHelpRequested;
4088 }
4089
4090 options.timer.start();
4091
4092 if (!readInputFile(&options))
4093 return CannotReadInputFile;
4094
4095 if (Q_UNLIKELY(options.timing))
4096 fprintf(stdout, "[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4097
4098 fprintf(stdout,
4099 "Generating Android Package\n"
4100 " Input file: %s\n"
4101 " Output directory: %s\n"
4102 " Application binary: %s\n"
4103 " Android build platform: %s\n"
4104 " Install to device: %s\n",
4105 qPrintable(options.inputFileName),
4106 qPrintable(options.outputDirectory),
4107 qPrintable(options.applicationBinary),
4108 qPrintable(options.androidPlatform),
4109 options.installApk
4110 ? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation))
4111 : "No"
4112 );
4113
4114 bool androidTemplatetCopied = false;
4115
4116 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4117 if (!it->enabled)
4118 continue;
4119 options.setCurrentQtArchitecture(it.key(),
4120 it.value().qtInstallDirectory,
4121 it.value().qtDirectories);
4122
4123 // All architectures have a copy of the gradle files but only one set needs to be copied.
4124 if (!androidTemplatetCopied && options.build && !options.copyDependenciesOnly) {
4125 cleanAndroidFiles(options);
4126 if (Q_UNLIKELY(options.timing))
4127 fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4128
4129 if (!copyAndroidTemplate(options))
4130 return CannotCopyAndroidTemplate;
4131
4132 if (Q_UNLIKELY(options.timing))
4133 fprintf(stdout, "[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4134 androidTemplatetCopied = true;
4135 }
4136
4137 if (!readDependencies(&options))
4138 return CannotReadDependencies;
4139
4140 if (Q_UNLIKELY(options.timing))
4141 fprintf(stdout, "[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4142
4143 if (!copyQtFiles(&options))
4144 return CannotCopyQtFiles;
4145
4146 if (Q_UNLIKELY(options.timing))
4147 fprintf(stdout, "[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4148
4149 if (!copyAndroidExtraLibs(&options))
4150 return CannotCopyAndroidExtraLibs;
4151
4152 if (Q_UNLIKELY(options.timing))
4153 fprintf(stdout, "[TIMING] %lld ms: Copied extra libs\n", options.timer.nsecsElapsed());
4154
4155 if (!copyAndroidExtraResources(&options))
4156 return CannotCopyAndroidExtraResources;
4157
4158 if (Q_UNLIKELY(options.timing))
4159 fprintf(stdout, "[TIMING] %lld ns: Copied extra resources\n", options.timer.nsecsElapsed());
4160
4161 if (!copyStdCpp(&options))
4162 return CannotCopyGnuStl;
4163
4164 if (Q_UNLIKELY(options.timing))
4165 fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
4166
4167 if (options.generateJavaQmlComponents) {
4168 if (!generateJavaQmlComponents(options))
4169 return CannotGenerateJavaQmlComponents;
4170 }
4171
4172 if (Q_UNLIKELY(options.timing)) {
4173 fprintf(stdout, "[TIMING] %lld ns: Generate Java QtQuickViewContents.\n",
4174 options.timer.nsecsElapsed());
4175 }
4176
4177 // If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK
4179 QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1.
4180 arg(options.outputDirectory,
4181 options.currentArchitecture,
4182 options.applicationBinary);
4183 QFile::remove(appLibPath);
4184 } else if (!containsApplicationBinary(&options)) {
4185 return CannotFindApplicationBinary;
4186 }
4187
4188 if (Q_UNLIKELY(options.timing))
4189 fprintf(stdout, "[TIMING] %lld ns: Checked for application binary\n", options.timer.nsecsElapsed());
4190
4191 if (Q_UNLIKELY(options.timing))
4192 fprintf(stdout, "[TIMING] %lld ns: Bundled Qt libs\n", options.timer.nsecsElapsed());
4193 }
4194
4195 if (options.copyDependenciesOnly) {
4196 if (!options.depFilePath.isEmpty())
4197 writeDependencyFile(options);
4198 return 0;
4199 }
4200
4201 if (!createRcc(options))
4202 return CannotCreateRcc;
4203
4204 if (options.auxMode || options.build) {
4205 if (!copyAndroidSources(options))
4206 return CannotCopyAndroidSources;
4207
4208 if (Q_UNLIKELY(options.timing))
4209 fprintf(stdout, "[TIMING] %lld ns: Copied android sources\n", options.timer.nsecsElapsed());
4210
4211 if (!updateAndroidFiles(options))
4212 return CannotUpdateAndroidFiles;
4213
4214 if (Q_UNLIKELY(options.timing))
4215 fprintf(stdout, "[TIMING] %lld ns: Updated files\n", options.timer.nsecsElapsed());
4216 }
4217
4218 if (options.auxMode)
4219 return 0;
4220
4221 if (options.build) {
4222 if (Q_UNLIKELY(options.timing))
4223 fprintf(stdout, "[TIMING] %lld ns: Created project\n", options.timer.nsecsElapsed());
4224
4225 if (!buildAndroidProject(options))
4226 return CannotBuildAndroidProject;
4227
4228 if (Q_UNLIKELY(options.timing))
4229 fprintf(stdout, "[TIMING] %lld ns: Built project\n", options.timer.nsecsElapsed());
4230
4231 if (!options.keyStore.isEmpty() && !signPackage(options))
4232 return CannotSignPackage;
4233
4234 if (!options.apkPath.isEmpty() && !copyPackage(options))
4235 return CannotCopyApk;
4236
4237 if (Q_UNLIKELY(options.timing))
4238 fprintf(stdout, "[TIMING] %lld ns: Signed package\n", options.timer.nsecsElapsed());
4239 }
4240
4241 if (options.installApk && !installApk(options))
4242 return CannotInstallApk;
4243
4244 if (Q_UNLIKELY(options.timing))
4245 fprintf(stdout, "[TIMING] %lld ns: Installed APK\n", options.timer.nsecsElapsed());
4246
4247 if (!options.depFilePath.isEmpty())
4248 writeDependencyFile(options);
4249
4250 fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.);
4251
4252 if (options.installApk)
4253 fprintf(stdout, " -- It can now be run from the selected device/emulator.\n");
4254
4255 fprintf(stdout, " -- File: %s\n", qPrintable(packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
4256 : SignedAPK)));
4257 fflush(stdout);
4258 return 0;
4259}
static const bool mustReadOutputAnyway
Definition main.cpp:47
static QStringList dependenciesForDepfile
Definition main.cpp:49
bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath, const QString &moduleUrl)
Definition main.cpp:2515
bool checkArchitecture(const Options &options, const QString &fileName)
Definition main.cpp:349
#define QT_POPEN_READ
Definition main.cpp:42
static const QHash< QByteArray, QByteArray > elfArchitectures
Definition main.cpp:262
static QString batSuffixAppended(QString path)
Definition main.cpp:292
QString architectureFromName(const QString &name)
Definition main.cpp:275
QString fileArchitecture(const Options &options, const QString &path)
Definition main.cpp:317
static QString execSuffixAppended(QString path)
Definition main.cpp:284
QString defaultLibexecDir()
Definition main.cpp:300
Options parseOptions()
Definition main.cpp:384
bool readDependenciesFromElf(Options *options, const QString &fileName, QSet< QString > *usedDependencies, QSet< QString > *remainingDependencies)
Definition main.cpp:2241
void printHelp()
Definition main.cpp:592
static QString llvmReadobjPath(const Options &options)
Definition main.cpp:309
auto openProcess(const QString &command)
Definition main.cpp:51
void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
Definition main.cpp:354
bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
Definition main.cpp:2717
int main(int argc, char *argv[])
[ctor_close]
bool internalSf
Definition main.cpp:218
bool createSymlinksOnly
Definition main.cpp:203
QString systemLibsPath
Definition main.cpp:194
bool build
Definition main.cpp:132
QString keyStore
Definition main.cpp:207
QHash< QString, QStringList > archExtraLibs
Definition main.cpp:199
bool installApk
Definition main.cpp:224
QString storeType
Definition main.cpp:210
QMap< QString, QString > applicationPermissions
Definition main.cpp:250
QStringList extraLibs
Definition main.cpp:198
bool copyDependenciesOnly
Definition main.cpp:135
QString sigAlg
Definition main.cpp:215
bool generateJavaQmlComponents
Definition main.cpp:258
QString androidPlatform
Definition main.cpp:183
QString currentArchitecture
Definition main.cpp:185
QByteArray targetSdkVersion
Definition main.cpp:176
void setCurrentQtArchitecture(const QString &arch, const QString &directory, const QHash< QString, QString > &directories)
Definition main.cpp:229
QHash< QString, QString > qtDirectories
Definition main.cpp:148
QString rccBinaryPath
Definition main.cpp:166
DeploymentMechanism
Definition main.cpp:118
@ Bundled
Definition main.cpp:119
@ Unbundled
Definition main.cpp:120
QString qtInstallDirectory
Definition main.cpp:147
bool useLegacyPackaging
Definition main.cpp:202
QString packageName
Definition main.cpp:195
bool auxMode
Definition main.cpp:133
DeploymentMechanism deploymentMechanism
Definition main.cpp:193
bool protectedAuthenticationPath
Definition main.cpp:220
QString inputFileName
Definition main.cpp:162
bool noRccBundleCleanup
Definition main.cpp:134
bool helpRequested
Definition main.cpp:129
bool timing
Definition main.cpp:131
bool buildAAB
Definition main.cpp:188
QString ndkVersion
Definition main.cpp:142
bool sectionsOnly
Definition main.cpp:219
QStringList qmlImportPaths
Definition main.cpp:169
QString toolchainPrefix
Definition main.cpp:186
QString jdkPath
Definition main.cpp:143
bool qmlSkipImportScanning
Definition main.cpp:255
@ True
Definition main.cpp:126
@ False
Definition main.cpp:125
@ Auto
Definition main.cpp:124
QString qtHostDirectory
Definition main.cpp:154
QString sdkBuildToolsVersion
Definition main.cpp:140
bool releasePackage
Definition main.cpp:206
QByteArray minSdkVersion
Definition main.cpp:175
QString keyStoreAlias
Definition main.cpp:209
QString qtLibExecsDirectory
Definition main.cpp:151
QString buildDirectory
Definition main.cpp:168
QHash< QString, QtInstallDirectoryWithTriple > architectures
Definition main.cpp:184
QString digestAlg
Definition main.cpp:214
QString versionName
Definition main.cpp:173
std::vector< QString > rootPaths
Definition main.cpp:165
QString stdCppPath
Definition main.cpp:179
QString tsaCert
Definition main.cpp:217
QString keyPass
Definition main.cpp:211
std::vector< QString > extraLibraryDirs
Definition main.cpp:159
QString applicationArguments
Definition main.cpp:164
QString depFilePath
Definition main.cpp:167
QString qtLibsDirectory
Definition main.cpp:150
QString qmlImportScannerBinaryPath
Definition main.cpp:254
QString signedJar
Definition main.cpp:213
QString keyStorePassword
Definition main.cpp:208
QStringList androidDeployPlugins
Definition main.cpp:156
QString ndkHost
Definition main.cpp:187
QMap< QString, QString > modulePermissions
Definition main.cpp:249
QString installLocation
Definition main.cpp:226
QString abi
Definition main.cpp:146
QString ndkPath
Definition main.cpp:141
QString sigFile
Definition main.cpp:212
QString sdkPath
Definition main.cpp:139
bool usesOpenGL
Definition main.cpp:245
Options()
Definition main.cpp:96
QString qtDataDirectory
Definition main.cpp:149
bool verbose
Definition main.cpp:130
QStringList features
Definition main.cpp:251
QStringList extraPlugins
Definition main.cpp:200
QString appName
Definition main.cpp:196
QString apkPath
Definition main.cpp:221
QHash< QString, QStringList > localLibs
Definition main.cpp:244
QString qtPluginsDirectory
Definition main.cpp:152
QHash< QString, QStringList > archExtraPlugins
Definition main.cpp:201
QString qtQmlDirectory
Definition main.cpp:153
bool buildAar
Definition main.cpp:256
QString outputDirectory
Definition main.cpp:161
QString applicationBinary
Definition main.cpp:163
QString qmlDomBinaryPath
Definition main.cpp:257
QString versionCode
Definition main.cpp:174
std::vector< QString > extraPrefixDirs
Definition main.cpp:155
QString tsaUrl
Definition main.cpp:216
bool isZstdCompressionEnabled
Definition main.cpp:189
QStringList qrcFiles
Definition main.cpp:170
QString androidSourceDirectory
Definition main.cpp:160
QString appIcon
Definition main.cpp:197
QString stdCppName
Definition main.cpp:180
QElapsedTimer timer
Definition main.cpp:136
QSet< QString > selectedJavaQmlComponents
Definition main.cpp:259
bool uninstallApk
Definition main.cpp:225
bool operator==(const QtDependency &other) const
Definition main.cpp:67
QString absolutePath
Definition main.cpp:73
QtDependency(const QString &rpath, const QString &apath)
Definition main.cpp:65
QString relativePath
Definition main.cpp:72
QHash< QString, QString > qtDirectories
Definition main.cpp:89
QtInstallDirectoryWithTriple(const QString &dir=QString(), const QString &t=QString(), const QHash< QString, QString > &dirs=QHash< QString, QString >())
Definition main.cpp:78
QString findInPath(const QString &file)
Definition main.cpp:2840