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();
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}
1534bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite = false, const QSet<QString> &excludedAbsolutePaths = {})
1535{
1536 const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
1537 for (const QFileInfo &entry : entries) {
1538 if (excludedAbsolutePaths.contains(entry.absoluteFilePath()))
1539 continue;
1540 if (entry.isDir()) {
1541 QDir dir(entry.absoluteFilePath());
1542 const bool destinationInCopyDir = destinationDirectory.absolutePath().startsWith(dir.absolutePath());
1543 if (sourceDirectory == options.androidSourceDirectory && destinationInCopyDir)
1544 continue;
1545
1546 if (!destinationDirectory.mkpath(dir.dirName())) {
1547 fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
1548 return false;
1549 }
1550
1551 if (!copyFiles(dir, QDir(destinationDirectory.path() + u'/' + dir.dirName()), options, forceOverwrite, excludedAbsolutePaths))
1552 return false;
1553 } else {
1554 QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
1555 if (!copyFileIfNewer(entry.absoluteFilePath(), destination,
1556 options, false, forceOverwrite)) {
1557 return false;
1558 }
1559 }
1560 }
1561
1562 return true;
1563}
1565void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
1566{
1567 const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
1568 for (const QFileInfo &dir : dirs) {
1569 if (dir.fileName() != "libs"_L1)
1570 deleteMissingFiles(options, dir.absoluteFilePath(), QDir(dstDir + dir.fileName()));
1571 }
1572}
1573
1574void cleanAndroidFiles(const Options &options)
1575{
1576 if (!options.androidSourceDirectory.isEmpty())
1577 cleanTopFolders(options, QDir(options.androidSourceDirectory), options.outputDirectory);
1578
1579 cleanTopFolders(options,
1580 QDir(options.qtInstallDirectory + u'/' +
1581 options.qtDataDirectory + "/src/android/templates"_L1),
1582 options.outputDirectory);
1583}
1584
1585bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
1586{
1587 QDir sourceDirectory(options.qtInstallDirectory + u'/' + options.qtDataDirectory + androidTemplate);
1588 if (!sourceDirectory.exists()) {
1589 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1590 return false;
1591 }
1592
1593 QString outDir = options.outputDirectory + outDirPrefix;
1594
1595 if (!QDir::current().mkpath(outDir)) {
1596 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1597 return false;
1598 }
1599
1600 return copyFiles(sourceDirectory, QDir(outDir), options);
1601}
1602
1603bool copyGradleTemplate(const Options &options)
1604{
1605 QDir sourceDirectory(options.qtInstallDirectory + u'/' +
1606 options.qtDataDirectory + "/src/3rdparty/gradle"_L1);
1607 if (!sourceDirectory.exists()) {
1608 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1609 return false;
1610 }
1611
1612 QString outDir(options.outputDirectory);
1613 if (!QDir::current().mkpath(outDir)) {
1614 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1615 return false;
1616 }
1617
1618 return copyFiles(sourceDirectory, QDir(outDir), options);
1619}
1620
1621bool copyAndroidTemplate(const Options &options)
1622{
1623 if (options.verbose)
1624 fprintf(stdout, "Copying Android package template.\n");
1625
1626 if (!options.auxMode) {
1627 // Gradle is not configured and is not running in aux mode
1628 if (!copyGradleTemplate(options))
1629 return false;
1630 }
1631
1632 if (!copyAndroidTemplate(options, "/src/android/templates"_L1))
1633 return false;
1634
1635 if (options.buildAar)
1636 return copyAndroidTemplate(options, "/src/android/templates_aar"_L1);
1637
1638 return true;
1639}
1640
1641bool copyAndroidSources(const Options &options)
1642{
1643 if (options.androidSourceDirectory.isEmpty())
1644 return true;
1645
1646 if (options.verbose)
1647 fprintf(stdout, "Copying Android sources from project.\n");
1648
1649 QDir sourceDirectory(options.androidSourceDirectory);
1650 if (!sourceDirectory.exists()) {
1651 fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
1652 return false;
1653 }
1654
1655 QSet<QString> excludedAbsolutePaths;
1656 const QString providerPaths = sourceDirectory.absoluteFilePath(
1657 QStringLiteral("res/xml/qtprovider_paths.xml"));
1658 if (QFileInfo::exists(providerPaths)) {
1659 fprintf(stderr,
1660 "Warning: %s in the package source directory is being excluded. Qt "
1661 "now bundles its own copy of it under the Android sources directory, "
1662 "and a duplicate would fail the Gradle build. Remove this file from "
1663 "your package source directory to silence this warning.\n",
1664 qPrintable(providerPaths));
1665 excludedAbsolutePaths.insert(providerPaths);
1666 }
1667
1668 return copyFiles(sourceDirectory, QDir(options.outputDirectory), options, true,
1669 excludedAbsolutePaths);
1670}
1671
1672bool copyAndroidExtraLibs(Options *options)
1674 if (options->extraLibs.isEmpty())
1675 return true;
1676
1677 if (options->verbose) {
1678 switch (options->deploymentMechanism) {
1679 case Options::Bundled:
1680 fprintf(stdout, "Copying %zd external libraries to package.\n", size_t(options->extraLibs.size()));
1681 break;
1682 case Options::Unbundled:
1683 fprintf(stdout, "Skip copying of external libraries.\n");
1684 break;
1685 };
1686 }
1687
1688 for (const QString &extraLib : options->extraLibs) {
1689 QFileInfo extraLibInfo(extraLib);
1690 if (!extraLibInfo.exists()) {
1691 fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
1692 return false;
1693 }
1694 if (!checkArchitecture(*options, extraLibInfo.filePath())) {
1695 if (options->verbose)
1696 fprintf(stdout, "Skipping \"%s\", architecture mismatch.\n", qPrintable(extraLib));
1697 continue;
1698 }
1699 if (!extraLibInfo.fileName().startsWith("lib"_L1) || extraLibInfo.suffix() != "so"_L1) {
1700 fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
1701 qPrintable(extraLib));
1702 return false;
1703 }
1704 QString destinationFile(options->outputDirectory
1705 + "/libs/"_L1
1706 + options->currentArchitecture
1707 + u'/'
1708 + extraLibInfo.fileName());
1709
1710 if (isDeployment(options, Options::Bundled)
1711 && !copyFileIfNewer(extraLib, destinationFile,
1712 *options, options->createSymlinksOnly)) {
1713 return false;
1714 }
1715 options->archExtraLibs[options->currentArchitecture] += extraLib;
1716 }
1717
1718 return true;
1719}
1720
1721QStringList allFilesInside(const QDir& current, const QDir& rootDir)
1723 QStringList result;
1724 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1725 const auto files = current.entryList(QDir::Files);
1726 result.reserve(dirs.size() + files.size());
1727 for (const QString &dir : dirs) {
1728 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1729 }
1730 for (const QString &file : files) {
1731 result += rootDir.relativeFilePath(current.filePath(file));
1732 }
1733 return result;
1734}
1735
1736bool copyAndroidExtraResources(Options *options)
1737{
1738 if (options->extraPlugins.isEmpty())
1739 return true;
1740
1741 if (options->verbose)
1742 fprintf(stdout, "Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1743
1744 for (const QString &extraResource : options->extraPlugins) {
1745 QFileInfo extraResourceInfo(extraResource);
1746 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1747 fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1748 return false;
1749 }
1750
1751 QDir resourceDir(extraResource);
1752 QString assetsDir = options->outputDirectory + "/assets/"_L1 +
1753 resourceDir.dirName() + u'/';
1754 QString libsDir = options->outputDirectory + "/libs/"_L1 + options->currentArchitecture + u'/';
1755
1756 const QStringList files = allFilesInside(resourceDir, resourceDir);
1757 for (const QString &resourceFile : files) {
1758 QString originFile(resourceDir.filePath(resourceFile));
1759 QString destinationFile;
1760 if (!resourceFile.endsWith(".so"_L1)) {
1761 destinationFile = assetsDir + resourceFile;
1762 } else {
1763 if (isDeployment(options, Options::Unbundled)
1764 || !checkArchitecture(*options, originFile)) {
1765 continue;
1766 }
1767 destinationFile = libsDir + resourceFile;
1768 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1769 }
1770
1771 if (!copyFileIfNewer(originFile, destinationFile,
1772 *options, options->createSymlinksOnly)) {
1773 return false;
1774 }
1775 }
1776 }
1777
1778 return true;
1779}
1780
1781bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
1782{
1783 QFile inputFile(fileName);
1784 if (!inputFile.open(QIODevice::ReadOnly)) {
1785 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
1786 return false;
1787 }
1788
1789 // All the files we are doing substitutes in are quite small. If this
1790 // ever changes, this code should be updated to be more conservative.
1791 QByteArray contents = inputFile.readAll();
1792
1793 bool hasReplacements = false;
1794 QHash<QString, QString>::const_iterator it;
1795 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1796 if (it.key() == it.value())
1797 continue; // Nothing to actually replace
1798
1799 forever {
1800 int index = contents.indexOf(it.key().toUtf8());
1801 if (index >= 0) {
1802 contents.replace(index, it.key().size(), it.value().toUtf8());
1803 hasReplacements = true;
1804 } else {
1805 break;
1806 }
1807 }
1808 }
1809
1810 if (hasReplacements) {
1811 inputFile.close();
1812
1813 if (!inputFile.open(QIODevice::WriteOnly)) {
1814 fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
1815 return false;
1816 }
1817
1818 // Remove leftover empty lines after replacements, for example,
1819 // in case of setting the app icon.
1820 QRegularExpression emptyLinesRegex("\\n\\s+\\n"_L1);
1821 contents = QString::fromUtf8(contents).replace(emptyLinesRegex, "\n"_L1).toUtf8();
1822
1823 inputFile.write(contents);
1824 }
1825
1826 return true;
1827
1828}
1829
1830bool updateLibsXml(Options *options)
1831{
1832 if (options->verbose)
1833 fprintf(stdout, " -- res/values/libs.xml\n");
1834
1835 QString fileName = options->outputDirectory + "/res/values/libs.xml"_L1;
1836 if (!QFile::exists(fileName)) {
1837 fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1838 return false;
1839 }
1840
1841 QString qtLibs;
1842 QString allLocalLibs;
1843 QString extraLibs;
1844
1845 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1846 if (!it->enabled)
1847 continue;
1848
1849 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), options->stdCppName);
1850 for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) {
1851 if (bundledFile.second.startsWith("lib/lib"_L1)) {
1852 if (!bundledFile.second.endsWith(".so"_L1)) {
1853 fprintf(stderr,
1854 "The bundled library %s doesn't end with .so. Android only supports "
1855 "versionless libraries ending with the .so suffix.\n",
1856 qPrintable(bundledFile.second));
1857 return false;
1858 }
1859 QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
1860 s.chop(sizeof(".so") - 1);
1861 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), s);
1862 }
1863 }
1864
1865 if (!options->archExtraLibs[it.key()].isEmpty()) {
1866 for (const QString &extraLib : options->archExtraLibs[it.key()]) {
1867 QFileInfo extraLibInfo(extraLib);
1868 if (extraLibInfo.fileName().startsWith("lib"_L1)) {
1869 if (!extraLibInfo.fileName().endsWith(".so"_L1)) {
1870 fprintf(stderr,
1871 "The library %s doesn't end with .so. Android only supports "
1872 "versionless libraries ending with the .so suffix.\n",
1873 qPrintable(extraLibInfo.fileName()));
1874 return false;
1875 }
1876 QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
1877 name.chop(sizeof(".so") - 1);
1878 extraLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), name);
1879 }
1880 }
1881 }
1882
1883 QStringList localLibs;
1884 localLibs = options->localLibs[it.key()];
1885 const QString archSuffix = it.key() + ".so"_L1;
1886
1887 const QList<QtDependency>& deps = options->qtDependencies[it.key()];
1888 auto notExistsInDependencies = [&deps, archSuffix] (const QString &libName) {
1889 QString lib = QFileInfo(libName).fileName();
1890 if (lib.endsWith(archSuffix))
1891 lib.chop(archSuffix.length());
1892 return std::none_of(deps.begin(), deps.end(), [&lib] (const QtDependency &dep) {
1893 return QFileInfo(dep.absolutePath).fileName().contains(lib);
1894 });
1895 };
1896
1897 // Clean up localLibs: remove libs that were not added to qtDependecies
1898 localLibs.erase(std::remove_if(localLibs.begin(), localLibs.end(), notExistsInDependencies),
1899 localLibs.end());
1900
1901 // If .pro file overrides dependency detection, we need to see which platform plugin they picked
1902 if (localLibs.isEmpty()) {
1903 QString plugin;
1904 for (const QtDependency &qtDependency : deps) {
1905 if (qtDependency.relativePath.contains("libplugins_platforms_qtforandroid_"_L1))
1906 plugin = qtDependency.relativePath;
1907
1908 if (qtDependency.relativePath.contains(
1909 QString::asprintf("libQt%dOpenGL", QT_VERSION_MAJOR))
1910 || qtDependency.relativePath.contains(
1911 QString::asprintf("libQt%dQuick", QT_VERSION_MAJOR))) {
1912 options->usesOpenGL |= true;
1913 }
1914 }
1915
1916 if (plugin.isEmpty()) {
1917 fflush(stdout);
1918 fprintf(stderr, "No platform plugin (libplugins_platforms_qtforandroid.so) included"
1919 " in the deployment. Make sure the app links to Qt Gui library.\n");
1920 fflush(stderr);
1921 return false;
1922 }
1923
1924 localLibs.append(plugin);
1925 if (options->verbose)
1926 fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin));
1927 }
1928
1929 // remove all paths
1930 for (auto &lib : localLibs) {
1931 if (lib.endsWith(".so"_L1))
1932 lib = lib.mid(lib.lastIndexOf(u'/') + 1);
1933 }
1934 allLocalLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), localLibs.join(u':'));
1935 }
1936
1937 QHash<QString, QString> replacements;
1938 replacements[QStringLiteral("<!-- %%INSERT_QT_LIBS%% -->")] += qtLibs.trimmed();
1939 replacements[QStringLiteral("<!-- %%INSERT_LOCAL_LIBS%% -->")] = allLocalLibs.trimmed();
1940 replacements[QStringLiteral("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs.trimmed();
1941
1942 // Set BUNDLE_LOCAL_QT_LIBS based on the deployment used
1943 replacements[QStringLiteral("<!-- %%BUNDLE_LOCAL_QT_LIBS%% -->")]
1944 = isDeployment(options, Options::Unbundled) ? "0"_L1 : "1"_L1;
1945 replacements[QStringLiteral("<!-- %%USE_LOCAL_QT_LIBS%% -->")] = "1"_L1;
1946 replacements[QStringLiteral("<!-- %%SYSTEM_LIBS_PREFIX%% -->")] =
1947 isDeployment(options, Options::Unbundled) ? options->systemLibsPath : QStringLiteral("");
1948
1949 if (!updateFile(fileName, replacements))
1950 return false;
1951
1952 return true;
1953}
1954
1955bool updateStringsXml(const Options &options)
1956{
1957 if (options.verbose)
1958 fprintf(stdout, " -- res/values/strings.xml\n");
1959
1960 QHash<QString, QString> replacements;
1961 replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = options.applicationBinary;
1962
1963 QString fileName = options.outputDirectory + "/res/values/strings.xml"_L1;
1964 if (!QFile::exists(fileName)) {
1965 if (options.verbose)
1966 fprintf(stdout, " -- Create strings.xml since it's missing.\n");
1967 QFile file(fileName);
1968 if (!file.open(QIODevice::WriteOnly)) {
1969 fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
1970 return false;
1971 }
1972 file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\" translatable=\"false\">")
1973 .append(options.applicationBinary.toLatin1())
1974 .append("</string></resources>\n"));
1975 return true;
1976 }
1977
1978 if (!updateFile(fileName, replacements))
1979 return false;
1980
1981 return true;
1982}
1983
1984bool updateAndroidManifest(Options &options)
1985{
1986 if (options.verbose)
1987 fprintf(stdout, " -- AndroidManifest.xml \n");
1988
1989 QHash<QString, QString> replacements;
1990 replacements[QStringLiteral("-- %%INSERT_APP_NAME%% --")] = options.appName;
1991 replacements[QStringLiteral("-- %%INSERT_APP_ARGUMENTS%% --")] = options.applicationArguments;
1992 replacements[QStringLiteral("-- %%INSERT_APP_LIB_NAME%% --")] = options.applicationBinary;
1993 replacements[QStringLiteral("-- %%INSERT_VERSION_NAME%% --")] = options.versionName;
1994 replacements[QStringLiteral("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode;
1995 replacements[QStringLiteral("package=\"org.qtproject.example\"")] = "package=\"%1\""_L1.arg(options.packageName);
1996
1997 const QString iconAttribute = "android:icon=\"%1\""_L1;
1998 replacements[iconAttribute.arg("-- %%INSERT_APP_ICON%% --"_L1)] = options.appIcon.isEmpty() ?
1999 ""_L1 : iconAttribute.arg(options.appIcon);
2000
2001 const QString androidManifestPath = options.outputDirectory + "/AndroidManifest.xml"_L1;
2002 QFile androidManifestXml(androidManifestPath);
2003 // User may have manually defined permissions in the AndroidManifest.xml
2004 // Read these permissions in order to remove any duplicates, as otherwise the
2005 // application build would fail.
2006 if (androidManifestXml.exists() && androidManifestXml.open(QIODevice::ReadOnly)) {
2007 QXmlStreamReader reader(&androidManifestXml);
2008 while (!reader.atEnd()) {
2009 reader.readNext();
2010 if (reader.isStartElement() && reader.name() == "uses-permission"_L1) {
2011 options.modulePermissions.remove(
2012 QString(reader.attributes().value("android:name"_L1)));
2013 options.applicationPermissions.remove(
2014 QString(reader.attributes().value("android:name"_L1)));
2015 }
2016 }
2017 androidManifestXml.close();
2018 }
2019
2020 // Application may define permissions in its CMakeLists.txt, give them the priority
2021 QMap<QString, QString> resolvedPermissions = options.modulePermissions;
2022 for (auto [name, extras] : options.applicationPermissions.asKeyValueRange())
2023 resolvedPermissions.insert(name, extras);
2024
2025 QString permissions;
2026 for (auto [name, extras] : resolvedPermissions.asKeyValueRange())
2027 permissions += " <uses-permission android:name=\"%1\" %2 />\n"_L1.arg(name).arg(extras);
2028 replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
2029
2030 QString features;
2031 for (const QString &feature : std::as_const(options.features))
2032 features += " <uses-feature android:name=\"%1\" android:required=\"false\" />\n"_L1.arg(feature);
2033 if (options.usesOpenGL)
2034 features += " <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"_L1;
2035
2036 replacements[QStringLiteral("<!-- %%INSERT_FEATURES -->")] = features.trimmed();
2037
2038 if (!updateFile(androidManifestPath, replacements))
2039 return false;
2040
2041 // read the package, min & target sdk API levels from manifest file.
2042 bool checkOldAndroidLabelString = false;
2043 if (androidManifestXml.exists()) {
2044 if (!androidManifestXml.open(QIODevice::ReadOnly)) {
2045 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
2046 return false;
2047 }
2048
2049 QXmlStreamReader reader(&androidManifestXml);
2050 while (!reader.atEnd()) {
2051 reader.readNext();
2052
2053 if (reader.isStartElement()) {
2054 if (reader.name() == "uses-sdk"_L1) {
2055 if (reader.attributes().hasAttribute("android:minSdkVersion"_L1))
2056 if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 28) {
2057 fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 28\n");
2058 return false;
2059 }
2060 } else if ((reader.name() == "application"_L1 ||
2061 reader.name() == "activity"_L1) &&
2062 reader.attributes().hasAttribute("android:label"_L1) &&
2063 reader.attributes().value("android:label"_L1) == "@string/app_name"_L1) {
2064 checkOldAndroidLabelString = true;
2065 } else if (reader.name() == "meta-data"_L1) {
2066 const auto name = reader.attributes().value("android:name"_L1);
2067 const auto value = reader.attributes().value("android:value"_L1);
2068 if (name == "android.app.lib_name"_L1 && value.contains(u' ')) {
2069 fprintf(stderr, "The Activity's android.app.lib_name should not contain"
2070 " spaces.\n");
2071 return false;
2072 }
2073 }
2074 }
2075 }
2076
2077 if (reader.hasError()) {
2078 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
2079 return false;
2080 }
2081 } else {
2082 fprintf(stderr, "No android manifest file");
2083 return false;
2085
2086 if (checkOldAndroidLabelString)
2087 updateStringsXml(options);
2088
2089 return true;
2090}
2091
2092bool updateAndroidFiles(Options &options)
2093{
2094 if (options.verbose)
2095 fprintf(stdout, "Updating Android package files with project settings.\n");
2096
2097 if (!updateLibsXml(&options))
2098 return false;
2099
2100 if (!updateAndroidManifest(options))
2101 return false;
2102
2103 return true;
2104}
2105
2106static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
2107{
2108 if (QDir::isAbsolutePath(relativeFileName))
2109 return relativeFileName;
2110
2111 // Use extraLibraryDirs as the extra library lookup folder if it is expected to find a file in
2112 // any $prefix/lib folder.
2113 // Library directories from a build tree(extraLibraryDirs) have the higher priority.
2114 if (relativeFileName.startsWith("lib/"_L1)) {
2115 for (const auto &dir : options->extraLibraryDirs) {
2116 const QString path = dir + u'/' + relativeFileName.mid(sizeof("lib/") - 1);
2117 if (QFile::exists(path))
2118 return path;
2119 }
2120 }
2121
2122 for (const auto &prefix : options->extraPrefixDirs) {
2123 const QString path = prefix + u'/' + relativeFileName;
2124 if (QFile::exists(path))
2125 return path;
2126 }
2127
2128 if (relativeFileName.endsWith("-android-dependencies.xml"_L1)) {
2129 for (const auto &dir : options->extraLibraryDirs) {
2130 const QString path = dir + u'/' + relativeFileName;
2131 if (QFile::exists(path))
2132 return path;
2133 }
2134 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2135 u'/' + relativeFileName;
2136 }
2137
2138 if (relativeFileName.startsWith("jar/"_L1)) {
2139 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
2140 u'/' + relativeFileName;
2141 }
2142
2143 if (relativeFileName.startsWith("lib/"_L1)) {
2144 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
2145 u'/' + relativeFileName.mid(sizeof("lib/") - 1);
2146 }
2147 return options->qtInstallDirectory + u'/' + relativeFileName;
2148}
2149
2150// Returns the entry as recorded, or with the ABI suffix added or stripped, whichever is on disk.
2151static QString resolveLibDependency(const Options *options, const QString &fileName)
2152{
2153 if (QFile::exists(absoluteFilePath(options, fileName)))
2154 return fileName;
2155
2156 const QString abiSuffix = u'_' + options->currentArchitecture + ".so"_L1;
2157 QString alternative;
2158 if (fileName.endsWith(abiSuffix))
2159 alternative = fileName.chopped(abiSuffix.size()) + ".so"_L1;
2160 else
2161 alternative = fileName.chopped(sizeof(".so") - 1) + abiSuffix;
2162
2163 if (QFile::exists(absoluteFilePath(options, alternative)))
2164 return alternative;
2165
2166 return fileName;
2167}
2168
2169QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath)
2170{
2171 if (!info.exists())
2172 return QList<QtDependency>();
2173
2174 if (info.isDir()) {
2175 QList<QtDependency> ret;
2176
2177 QDir dir(info.filePath());
2178 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
2179
2180 for (const QString &entry : entries) {
2181 ret += findFilesRecursively(options,
2182 QFileInfo(info.absoluteFilePath() + QChar(u'/') + entry),
2183 rootPath);
2184 }
2185
2186 return ret;
2187 } else {
2188 return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
2189 }
2190}
2191
2192QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName)
2193{
2194 // We try to find the fileName in extraPrefixDirs first. The function behaves differently
2195 // depending on what the fileName points to. If fileName is a file then we try to find the
2196 // first occurrence in extraPrefixDirs and return this file. If fileName is directory function
2197 // iterates over it and looks for deployment artifacts in each 'extraPrefixDirs' entry.
2198 // Also we assume that if the fileName is recognized as a directory once it will be directory
2199 // for every 'extraPrefixDirs' entry.
2200 QList<QtDependency> deps;
2201 for (const auto &prefix : options.extraPrefixDirs) {
2202 QFileInfo info(prefix + u'/' + fileName);
2203 if (info.exists()) {
2204 if (info.isDir())
2205 deps.append(findFilesRecursively(options, info, prefix + u'/'));
2206 else
2207 return findFilesRecursively(options, info, prefix + u'/');
2208 }
2209 }
2210
2211 // Usually android deployment settings contain Qt install directory in extraPrefixDirs.
2212 if (std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
2213 options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
2214 QFileInfo info(options.qtInstallDirectory + "/"_L1 + fileName);
2215 QFileInfo rootPath(options.qtInstallDirectory + "/"_L1);
2216 deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
2217 }
2218 return deps;
2219}
2220
2221void readDependenciesFromFiles(Options *options, const QList<QtDependency> &files,
2222 QSet<QString> &usedDependencies,
2223 QSet<QString> &remainingDependencies)
2224{
2225 for (const QtDependency &fileName : files) {
2226 if (usedDependencies.contains(fileName.absolutePath))
2227 continue;
2228
2229 if (fileName.absolutePath.endsWith(".so"_L1)) {
2230 if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
2231 &remainingDependencies)) {
2232 fprintf(stdout, "Skipping file dependency: %s\n",
2233 qPrintable(fileName.relativePath));
2234 continue;
2236 }
2237 usedDependencies.insert(fileName.absolutePath);
2238
2239 if (options->verbose) {
2240 fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath));
2241 }
2242
2243 options->qtDependencies[options->currentArchitecture].append(fileName);
2244 }
2245}
2246
2247bool readAndroidDependencyXml(Options *options,
2248 const QString &moduleName,
2249 QSet<QString> *usedDependencies,
2250 QSet<QString> *remainingDependencies)
2251{
2252 QString androidDependencyName = absoluteFilePath(options, "%1-android-dependencies.xml"_L1.arg(moduleName));
2253
2254 QFile androidDependencyFile(androidDependencyName);
2255 if (androidDependencyFile.exists()) {
2256 if (options->verbose)
2257 fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
2258
2259 if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
2260 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
2261 return false;
2262 }
2263
2264 QXmlStreamReader reader(&androidDependencyFile);
2265 while (!reader.atEnd()) {
2266 reader.readNext();
2267
2268 if (reader.isStartElement()) {
2269 if (reader.name() == "bundled"_L1) {
2270 if (!reader.attributes().hasAttribute("file"_L1)) {
2271 fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
2272 return false;
2273 }
2274
2275 QString file = reader.attributes().value("file"_L1).toString();
2276
2277 if (reader.attributes().hasAttribute("type"_L1)
2278 && reader.attributes().value("type"_L1) == "plugin_dir"_L1
2279 && !options->androidDeployPlugins.isEmpty()) {
2280 continue;
2281 }
2283 const QList<QtDependency> fileNames = findFilesRecursively(*options, file);
2284 readDependenciesFromFiles(options, fileNames, *usedDependencies,
2285 *remainingDependencies);
2286 } else if (reader.name() == "jar"_L1) {
2287 int bundling = reader.attributes().value("bundling"_L1).toInt();
2288 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
2289 if (bundling) {
2290 QtDependency dependency(fileName, absoluteFilePath(options, fileName));
2291 if (!usedDependencies->contains(dependency.absolutePath)) {
2292 options->qtDependencies[options->currentArchitecture].append(dependency);
2293 usedDependencies->insert(dependency.absolutePath);
2294 }
2295 }
2296 } else if (reader.name() == "lib"_L1) {
2297 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
2298 if (fileName.endsWith(".so"_L1))
2299 fileName = resolveLibDependency(options, fileName);
2300 if (reader.attributes().hasAttribute("replaces"_L1)) {
2301 QString replaces = reader.attributes().value("replaces"_L1).toString();
2302 for (int i=0; i<options->localLibs.size(); ++i) {
2303 if (options->localLibs[options->currentArchitecture].at(i) == replaces) {
2304 options->localLibs[options->currentArchitecture][i] = fileName;
2305 break;
2306 }
2307 }
2308 } else if (!fileName.isEmpty()) {
2309 options->localLibs[options->currentArchitecture].append(fileName);
2310 }
2311 if (fileName.endsWith(".so"_L1)) {
2312 if (checkArchitecture(*options, absoluteFilePath(options, fileName)))
2313 remainingDependencies->insert(fileName);
2314 }
2315 } else if (reader.name() == "permission"_L1) {
2316 QString name = reader.attributes().value("name"_L1).toString();
2317 QString extras = reader.attributes().value("extras"_L1).toString();
2318 // With duplicate permissions prioritize the one without any attributes,
2319 // as that is likely the most permissive
2320 if (!options->modulePermissions.contains(name)
2321 || !options->modulePermissions.value(name).isEmpty()) {
2322 options->modulePermissions.insert(name, extras);
2323 }
2324 } else if (reader.name() == "feature"_L1) {
2325 QString name = reader.attributes().value("name"_L1).toString();
2326 options->features.append(name);
2328 }
2329 }
2330
2331 if (reader.hasError()) {
2332 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
2333 return false;
2334 }
2335 } else if (options->verbose) {
2336 fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
2337 }
2338 options->features.removeDuplicates();
2339
2340 return true;
2341}
2342
2343QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
2344{
2345 QString readElf = llvmReadobjPath(options);
2346 if (!QFile::exists(readElf)) {
2347 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
2348 return QStringList();
2349 }
2350
2351 readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
2352
2353 auto readElfCommand = openProcess(readElf);
2354 if (!readElfCommand) {
2355 fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
2356 return QStringList();
2357 }
2358
2359 QStringList ret;
2360
2361 bool readLibs = false;
2362 char buffer[512];
2363 while (fgets(buffer, sizeof(buffer), readElfCommand.get()) != nullptr) {
2364 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
2365 QString library;
2366 line = line.trimmed();
2367 if (!readLibs) {
2368 if (line.startsWith("Arch: ")) {
2369 auto it = elfArchitectures.find(line.mid(6));
2370 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
2371 if (options.verbose)
2372 fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
2373 return {};
2374 }
2375 }
2376 readLibs = line.startsWith("NeededLibraries");
2377 continue;
2378 }
2379 if (!line.startsWith("lib"))
2380 continue;
2381 library = QString::fromLatin1(line);
2382 QString libraryName = "lib/"_L1 + library;
2383 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2384 ret += libraryName;
2385 }
2386
2387 return ret;
2388}
2389
2390bool readDependenciesFromElf(Options *options,
2391 const QString &fileName,
2392 QSet<QString> *usedDependencies,
2393 QSet<QString> *remainingDependencies)
2394{
2395 // Get dependencies on libraries in $QTDIR/lib
2396 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2397
2398 if (options->verbose) {
2399 fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
2400 for (const QString &dep : dependencies)
2401 fprintf(stdout, " %s\n", qPrintable(dep));
2402 }
2403 // Recursively add dependencies from ELF and supplementary XML information
2404 QList<QString> dependenciesToCheck;
2405 for (const QString &dependency : dependencies) {
2406 if (usedDependencies->contains(dependency))
2407 continue;
2408
2409 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2410 usedDependencies->insert(dependency);
2411 if (!readDependenciesFromElf(options,
2412 absoluteDependencyPath,
2413 usedDependencies,
2414 remainingDependencies)) {
2415 return false;
2416 }
2417
2418 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2419 if (options->verbose)
2420 fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
2421 dependenciesToCheck.append(dependency);
2422 }
2423
2424 for (const QString &dependency : std::as_const(dependenciesToCheck)) {
2425 QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
2426 qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
2427 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2428 return false;
2429 }
2430 }
2431
2432 return true;
2433}
2434
2435bool scanImports(Options *options, QSet<QString> *usedDependencies)
2436{
2437 if (options->verbose)
2438 fprintf(stdout, "Scanning for QML imports.\n");
2439
2440 QString qmlImportScanner;
2441 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2442 qmlImportScanner = options->qmlImportScannerBinaryPath;
2443 } else {
2444 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2445 "/qmlimportscanner"_L1);
2446 }
2447
2448 QStringList importPaths;
2449
2450 // In Conan's case, qtInstallDirectory will point only to qtbase installed files, which
2451 // lacks a qml directory. We don't want to pass it as an import path if it doesn't exist
2452 // because it will cause qmlimportscanner to fail.
2453 // This also covers the case when only qtbase is installed in a regular Qt build.
2454 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2455 if (QFile::exists(mainImportPath))
2456 importPaths += shellQuote(mainImportPath);
2457
2458 // These are usually provided by CMake in the deployment json file from paths specified
2459 // in CMAKE_FIND_ROOT_PATH. They might not have qml modules.
2460 for (const QString &prefix : options->extraPrefixDirs)
2461 if (QFile::exists(prefix + "/qml"_L1))
2462 importPaths += shellQuote(prefix + "/qml"_L1);
2463
2464 // These are provided by both CMake and qmake.
2465 for (const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2466 if (QFile::exists(qmlImportPath)) {
2467 importPaths += shellQuote(qmlImportPath);
2468 } else {
2469 fprintf(stderr, "Warning: QML import path %s does not exist.\n",
2470 qPrintable(qmlImportPath));
2471 }
2472 }
2473
2474 bool qmlImportExists = false;
2475
2476 for (const QString &import : importPaths) {
2477 if (QDir().exists(import)) {
2478 qmlImportExists = true;
2479 break;
2480 }
2481 }
2482
2483 // Check importPaths without rootPath, since we need at least one qml plugins
2484 // folder to run a QML file
2485 if (!qmlImportExists) {
2486 fprintf(stderr, "Warning: no 'qml' directory found under Qt install directory "
2487 "or import paths. Skipping QML dependency scanning.\n");
2488 return true;
2489 }
2490
2491 if (!QFile::exists(qmlImportScanner)) {
2492 fprintf(stderr, "%s: qmlimportscanner not found at %s\n",
2493 qmlImportExists ? "Error"_L1.data() : "Warning"_L1.data(),
2494 qPrintable(qmlImportScanner));
2495 return true;
2496 }
2497
2498 for (auto rootPath : options->rootPaths) {
2499 rootPath = QFileInfo(rootPath).absoluteFilePath();
2500
2501 if (!rootPath.endsWith(u'/'))
2502 rootPath += u'/';
2503
2504 // After checking for qml folder imports we can add rootPath
2505 if (!rootPath.isEmpty())
2506 importPaths += shellQuote(rootPath);
2507
2508 qmlImportScanner += " -rootPath %1"_L1.arg(shellQuote(rootPath));
2509 }
2510
2511 if (!options->qrcFiles.isEmpty()) {
2512 qmlImportScanner += " -qrcFiles"_L1;
2513 for (const QString &qrcFile : options->qrcFiles)
2514 qmlImportScanner += u' ' + shellQuote(qrcFile);
2515 }
2516
2517 qmlImportScanner += " -importPath %1"_L1.arg(importPaths.join(u' '));
2518
2519 if (options->verbose) {
2520 fprintf(stdout, "Running qmlimportscanner with the following command: %s\n",
2521 qmlImportScanner.toLocal8Bit().constData());
2522 }
2523
2524 auto qmlImportScannerCommand = openProcess(qmlImportScanner);
2525 if (qmlImportScannerCommand == 0) {
2526 fprintf(stderr, "Couldn't run qmlimportscanner.\n");
2527 return false;
2528 }
2529
2530 QByteArray output;
2531 char buffer[512];
2532 while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand.get()) != nullptr)
2533 output += QByteArray(buffer, qstrlen(buffer));
2534
2535 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
2536 if (jsonDocument.isNull()) {
2537 fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
2538 return false;
2539 }
2540
2541 QJsonArray jsonArray = jsonDocument.array();
2542 for (int i=0; i<jsonArray.count(); ++i) {
2543 QJsonValue value = jsonArray.at(i);
2544 if (!value.isObject()) {
2545 fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
2546 return false;
2547 }
2548
2549 QJsonObject object = value.toObject();
2550 QString path = object.value("path"_L1).toString();
2551 if (path.isEmpty()) {
2552 fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n",
2553 qPrintable(object.value("name"_L1).toString()));
2554 } else if (object.value("type"_L1).toString() == "module"_L1) {
2555 if (options->verbose)
2556 fprintf(stdout, " -- Adding '%s' as QML dependency\n", qPrintable(path));
2557
2558 QFileInfo info(path);
2559
2560 // The qmlimportscanner sometimes outputs paths that do not exist.
2561 if (!info.exists()) {
2562 if (options->verbose)
2563 fprintf(stdout, " -- Skipping because path does not exist.\n");
2564 continue;
2565 }
2567 QString absolutePath = info.absolutePath();
2568 if (!absolutePath.endsWith(u'/'))
2569 absolutePath += u'/';
2570
2571 const QUrl url(object.value("name"_L1).toString());
2572
2573 const QString moduleUrlPath = u"/"_s + url.toString().replace(u'.', u'/');
2574 if (checkCanImportFromRootPaths(options, info.absolutePath(), moduleUrlPath)) {
2575 if (options->verbose)
2576 fprintf(stdout, " -- Skipping because path is in QML root path.\n");
2577 continue;
2578 }
2579
2580 QString importPathOfThisImport;
2581 for (const QString &importPath : std::as_const(importPaths)) {
2582 QString cleanImportPath = QDir::cleanPath(importPath);
2583 if (QFile::exists(cleanImportPath + moduleUrlPath)) {
2584 importPathOfThisImport = importPath;
2585 break;
2586 }
2588
2589 if (importPathOfThisImport.isEmpty()) {
2590 fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
2591 return false;
2592 }
2593
2594 importPathOfThisImport = QDir(importPathOfThisImport).absolutePath() + u'/';
2595 QList<QtDependency> qmlImportsDependencies;
2596 auto collectQmlDependency = [&usedDependencies, &qmlImportsDependencies,
2597 &importPathOfThisImport](const QString &filePath) {
2598 if (!usedDependencies->contains(filePath)) {
2599 usedDependencies->insert(filePath);
2600 qmlImportsDependencies += QtDependency(
2601 "qml/"_L1 + filePath.mid(importPathOfThisImport.size()),
2602 filePath);
2603 }
2604 };
2605
2606 QString plugin = object.value("plugin"_L1).toString();
2607 bool pluginIsOptional = object.value("pluginIsOptional"_L1).toBool();
2608 QFileInfo pluginFileInfo = QFileInfo(
2609 path + u'/' + "lib"_L1 + plugin + u'_'
2610 + options->currentArchitecture + ".so"_L1);
2611 QString pluginFilePath = pluginFileInfo.absoluteFilePath();
2612 QSet<QString> remainingDependencies;
2613 if (pluginFileInfo.exists() && checkArchitecture(*options, pluginFilePath)
2614 && readDependenciesFromElf(options, pluginFilePath, usedDependencies,
2615 &remainingDependencies)) {
2616 collectQmlDependency(pluginFilePath);
2617 } else if (!pluginIsOptional) {
2618 if (options->verbose)
2619 fprintf(stdout, " -- Skipping because the required plugin is missing.\n");
2620 continue;
2621 }
2622
2623 QFileInfo qmldirFileInfo = QFileInfo(path + u'/' + "qmldir"_L1);
2624 if (qmldirFileInfo.exists()) {
2625 collectQmlDependency(qmldirFileInfo.absoluteFilePath());
2626 }
2627
2628 QString prefer = object.value("prefer"_L1).toString();
2629 // If the preferred location of Qml files points to the Qt resources, this means
2630 // that all Qml files has been embedded into plugin and we should not copy them to the
2631 // android rcc bundle
2632 if (!prefer.startsWith(":/"_L1)) {
2633 QVariantList qmlFiles =
2634 object.value("components"_L1).toArray().toVariantList();
2635 qmlFiles.append(object.value("scripts"_L1).toArray().toVariantList());
2636 bool qmlFilesMissing = false;
2637 for (const auto &qmlFileEntry : qmlFiles) {
2638 QFileInfo fileInfo(qmlFileEntry.toString());
2639 if (!fileInfo.exists()) {
2640 qmlFilesMissing = true;
2641 break;
2642 }
2643 collectQmlDependency(fileInfo.absoluteFilePath());
2644 }
2645
2646 if (qmlFilesMissing) {
2647 if (options->verbose)
2648 fprintf(stdout,
2649 " -- Skipping because the required qml files are missing.\n");
2650 continue;
2651 }
2652 }
2653
2654 options->qtDependencies[options->currentArchitecture].append(qmlImportsDependencies);
2655 } else {
2656 // We don't need to handle file and directory imports. Generally those should be
2657 // considered as part of the application and are therefore scanned separately.
2658 }
2659 }
2660
2661 return true;
2662}
2663
2664bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
2665 const QString &moduleUrlPath)
2666{
2667 for (auto rootPath : options->rootPaths) {
2668 if ((rootPath + moduleUrlPath) == absolutePath)
2669 return true;
2670 }
2671 return false;
2672}
2673
2674bool runCommand(const Options &options, const QString &command)
2675{
2676 if (options.verbose)
2677 fprintf(stdout, "Running command '%s'\n", qPrintable(command));
2678
2679 auto runCommand = openProcess(command);
2680 if (runCommand == nullptr) {
2681 fprintf(stderr, "Cannot run command '%s'\n", qPrintable(command));
2682 return false;
2683 }
2684 char buffer[4096];
2685 while (fgets(buffer, sizeof(buffer), runCommand.get()) != nullptr) {
2686 if (options.verbose)
2687 fprintf(stdout, "%s", buffer);
2688 }
2689 runCommand.reset();
2690 fflush(stdout);
2691 fflush(stderr);
2692 return true;
2693}
2694
2695bool createRcc(const Options &options)
2696{
2697 auto assetsDir = "%1/assets"_L1.arg(options.outputDirectory);
2698 if (!QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.exists()) {
2699 fprintf(stdout, "Skipping createRCC\n");
2700 return true;
2701 }
2702
2703 if (options.verbose)
2704 fprintf(stdout, "Create rcc bundle.\n");
2705
2706
2707 QString rcc;
2708 if (!options.rccBinaryPath.isEmpty()) {
2709 rcc = options.rccBinaryPath;
2710 } else {
2711 rcc = execSuffixAppended(options.qtLibExecsDirectory + "/rcc"_L1);
2712 }
2713
2714 if (!QFile::exists(rcc)) {
2715 fprintf(stderr, "rcc not found: %s\n", qPrintable(rcc));
2716 return false;
2717 }
2718 auto currentDir = QDir::currentPath();
2719 if (!QDir::setCurrent("%1/android_rcc_bundle"_L1.arg(assetsDir))) {
2720 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable("%1/android_rcc_bundle"_L1.arg(assetsDir)));
2721 return false;
2722 }
2723
2724 bool res = runCommand(options, "%1 --project -o %2"_L1.arg(rcc, shellQuote("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir))));
2725 if (!res)
2726 return false;
2727
2728 QLatin1StringView noZstd;
2729 if (!options.isZstdCompressionEnabled)
2730 noZstd = "--no-zstd"_L1;
2731
2732 QFile::rename("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir), "%1/android_rcc_bundle/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2733
2734 res = runCommand(options, "%1 %2 %3 --binary -o %4 android_rcc_bundle.qrc"_L1.arg(rcc, shellQuote("--root=/android_rcc_bundle/"_L1),
2735 noZstd,
2736 shellQuote("%1/android_rcc_bundle.rcc"_L1.arg(assetsDir))));
2737 if (!QDir::setCurrent(currentDir)) {
2738 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(currentDir));
2739 return false;
2740 }
2741 if (!options.noRccBundleCleanup) {
2742 QFile::remove("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2743 QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.removeRecursively();
2744 }
2745 return res;
2746}
2747
2748bool readDependencies(Options *options)
2749{
2750 if (options->verbose)
2751 fprintf(stdout, "Detecting dependencies of application.\n");
2752
2753 // Override set in .pro file
2754 if (!options->qtDependencies[options->currentArchitecture].isEmpty()) {
2755 if (options->verbose)
2756 fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
2757 return true;
2759
2760 QSet<QString> usedDependencies;
2761 QSet<QString> remainingDependencies;
2762
2763 // Add dependencies of application binary first
2764 if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies))
2765 return false;
2766
2767 QList<QtDependency> pluginDeps;
2768 for (const auto &pluginPath : options->androidDeployPlugins) {
2769 pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath),
2770 options->qtInstallDirectory + "/"_L1));
2771 }
2772
2773 readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies);
2774
2775 while (!remainingDependencies.isEmpty()) {
2776 QSet<QString>::iterator start = remainingDependencies.begin();
2777 QString fileName = absoluteFilePath(options, *start);
2778 remainingDependencies.erase(start);
2779
2780 QStringList unmetDependencies;
2781 if (goodToCopy(options, fileName, &unmetDependencies)) {
2782 bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
2783 if (!ok)
2784 return false;
2785 } else {
2786 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2787 qPrintable(fileName),
2788 qPrintable(unmetDependencies.join(u',')));
2789 }
2790 }
2791
2792 QStringList::iterator it = options->localLibs[options->currentArchitecture].begin();
2793 while (it != options->localLibs[options->currentArchitecture].end()) {
2794 QStringList unmetDependencies;
2795 if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) {
2796 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2797 qPrintable(*it),
2798 qPrintable(unmetDependencies.join(u',')));
2799 it = options->localLibs[options->currentArchitecture].erase(it);
2800 } else {
2801 ++it;
2802 }
2803 }
2804
2805 if (options->qmlSkipImportScanning
2806 || (options->rootPaths.empty() && options->qrcFiles.isEmpty()))
2807 return true;
2808 return scanImports(options, &usedDependencies);
2809}
2810
2811bool containsApplicationBinary(Options *options)
2812{
2813 if (!options->build)
2814 return true;
2815
2816 if (options->verbose)
2817 fprintf(stdout, "Checking if application binary is in package.\n");
2818
2819 QString applicationFileName = "lib%1_%2.so"_L1.arg(options->applicationBinary,
2820 options->currentArchitecture);
2821
2822 QString applicationPath = "%1/libs/%2/%3"_L1.arg(options->outputDirectory,
2823 options->currentArchitecture,
2824 applicationFileName);
2825 if (!QFile::exists(applicationPath)) {
2826#if defined(Q_OS_WIN32)
2827 const auto makeTool = "mingw32-make"_L1; // Only Mingw host builds supported on Windows currently
2828#else
2829 const auto makeTool = "make"_L1;
2830#endif
2831 fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
2832 qPrintable(applicationFileName),
2833 qPrintable(makeTool),
2834 qPrintable(options->outputDirectory));
2835 return false;
2836 }
2837 return true;
2838}
2839
2840auto runAdb(const Options &options, const QString &arguments)
2841 -> decltype(openProcess({}))
2842{
2843 QString adb = execSuffixAppended(options.sdkPath + "/platform-tools/adb"_L1);
2844 if (!QFile::exists(adb)) {
2845 fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
2846 return 0;
2847 }
2848 QString installOption;
2849 if (!options.installLocation.isEmpty())
2850 installOption = " -s "_L1 + shellQuote(options.installLocation);
2851
2852 adb = "%1%2 %3"_L1.arg(shellQuote(adb), installOption, arguments);
2853
2854 if (options.verbose)
2855 fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
2856
2857 auto adbCommand = openProcess(adb);
2858 if (adbCommand == 0) {
2859 fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
2860 return 0;
2861 }
2862
2863 return adbCommand;
2864}
2865
2866bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
2867{
2868 if (!file.endsWith(".so"_L1))
2869 return true;
2870
2871 if (!checkArchitecture(*options, file))
2872 return false;
2873
2874 if (!options->abi.isEmpty() && options->abi != options->currentArchitecture)
2875 return true;
2876
2877 bool ret = true;
2878 const auto libs = getQtLibsFromElf(*options, file);
2879 for (const QString &lib : libs) {
2880 if (!options->qtDependencies[options->currentArchitecture].contains(QtDependency(lib, absoluteFilePath(options, lib)))) {
2881 ret = false;
2882 unmetDependencies->append(lib);
2883 }
2884 }
2885
2886 return ret;
2887}
2888
2889bool copyQtFiles(Options *options)
2890{
2891 if (options->verbose) {
2892 switch (options->deploymentMechanism) {
2893 case Options::Bundled:
2894 fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies[options->currentArchitecture].size()));
2895 break;
2896 case Options::Unbundled:
2897 fprintf(stdout, "Copying dependencies from Qt into the package build folder,"
2898 "skipping native libraries.\n");
2899 break;
2901 }
2903 if (!options->build)
2904 return true;
2905
2906
2907 QString libsDirectory = "libs/"_L1;
2908
2909 // Copy other Qt dependencies
2910 auto assetsDestinationDirectory = "assets/android_rcc_bundle/"_L1;
2911 for (const QtDependency &qtDependency : std::as_const(options->qtDependencies[options->currentArchitecture])) {
2912 QString sourceFileName = qtDependency.absolutePath;
2913 QString destinationFileName;
2914 bool isSharedLibrary = qtDependency.relativePath.endsWith(".so"_L1);
2915 bool createSymlinksOnly = options->createSymlinksOnly;
2916 if (isSharedLibrary) {
2917 QString garbledFileName = qtDependency.relativePath.mid(
2918 qtDependency.relativePath.lastIndexOf(u'/') + 1);
2919 destinationFileName = libsDirectory + options->currentArchitecture + u'/' + garbledFileName;
2920 } else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("jar/"_L1)) {
2921 destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
2922 } else {
2923 // rcc resouces compilation doesn't support using symlinks
2924 createSymlinksOnly = false;
2925 destinationFileName = assetsDestinationDirectory + qtDependency.relativePath;
2926 }
2927
2928 if (!QFile::exists(sourceFileName)) {
2929 fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
2930 return false;
2931 }
2932
2933 QStringList unmetDependencies;
2934 if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
2935 if (unmetDependencies.isEmpty()) {
2936 if (options->verbose) {
2937 fprintf(stdout, " -- Skipping %s, architecture mismatch.\n",
2938 qPrintable(sourceFileName));
2939 }
2940 } else {
2941 fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n",
2942 qPrintable(sourceFileName),
2943 qPrintable(unmetDependencies.join(u',')));
2944 }
2945 continue;
2946 }
2947
2948 if ((isDeployment(options, Options::Bundled) || !isSharedLibrary)
2949 && !copyFileIfNewer(sourceFileName,
2950 options->outputDirectory + u'/' + destinationFileName,
2951 *options, createSymlinksOnly)) {
2952 return false;
2953 }
2954 options->bundledFiles[options->currentArchitecture] += std::make_pair(destinationFileName, qtDependency.relativePath);
2955 }
2956
2957 return true;
2958}
2959
2960QStringList getLibraryProjectsInOutputFolder(const Options &options)
2961{
2962 QStringList ret;
2963
2964 QFile file(options.outputDirectory + "/project.properties"_L1);
2965 if (file.open(QIODevice::ReadOnly)) {
2966 QByteArray lineArray;
2967 while (file.readLineInto(&lineArray)) {
2968 QByteArrayView line = QByteArrayView(lineArray).trimmed();
2969 if (line.startsWith("android.library.reference")) {
2970 int equalSignIndex = line.indexOf('=');
2971 if (equalSignIndex >= 0) {
2972 QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
2973
2974 QFileInfo info(options.outputDirectory + u'/' + path);
2975 if (QDir::isRelativePath(path)
2976 && info.exists()
2977 && info.isDir()
2978 && info.canonicalFilePath().startsWith(options.outputDirectory)) {
2979 ret += info.canonicalFilePath();
2981 }
2982 }
2983 }
2984 }
2985
2986 return ret;
2987}
2988
2989QString findInPath(const QString &fileName)
2990{
2991 const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
2992#if defined(Q_OS_WIN32)
2993 QLatin1Char separator(';');
2994#else
2995 QLatin1Char separator(':');
2996#endif
2997
2998 const QStringList paths = path.split(separator);
2999 for (const QString &path : paths) {
3000 QFileInfo fileInfo(path + u'/' + fileName);
3001 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
3002 return path + u'/' + fileName;
3003 }
3004
3005 return QString();
3006}
3007
3008typedef QMap<QByteArray, QByteArray> GradleProperties;
3009
3010static GradleProperties readGradleProperties(const QString &path)
3011{
3012 GradleProperties properties;
3013 QFile file(path);
3014 if (!file.open(QIODevice::ReadOnly))
3015 return properties;
3016
3017 const auto lines = file.readAll().split('\n');
3018 for (const QByteArray &line : lines) {
3019 if (line.trimmed().startsWith('#'))
3020 continue;
3021
3022 const int idx = line.indexOf('=');
3023 if (idx > -1)
3024 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
3025 }
3026 file.close();
3027 return properties;
3028}
3029
3030static bool mergeGradleProperties(const QString &path, GradleProperties properties)
3031{
3032 const QString oldPathStr = path + u'~';
3033 QFile::remove(oldPathStr);
3034 QFile::rename(path, oldPathStr);
3035 QFile file(path);
3036 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
3037 fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName()));
3038 return false;
3039 }
3040
3041 QFile oldFile(oldPathStr);
3042 if (oldFile.open(QIODevice::ReadOnly)) {
3043 QByteArray line;
3044 while (oldFile.readLineInto(&line)) {
3045 QList<QByteArray> prop(line.split('='));
3046 if (prop.size() > 1) {
3047 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
3048 if (it != properties.end()) {
3049 file.write(it.key() + '=' + it.value() + '\n');
3050 properties.erase(it);
3051 continue;
3052 }
3053 }
3054 file.write(line.trimmed() + '\n');
3055 }
3056 oldFile.close();
3057 QFile::remove(oldPathStr);
3058 }
3059
3060 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
3061 file.write(it.key() + '=' + it.value() + '\n');
3062
3063 file.close();
3064 return true;
3065}
3066
3067#if defined(Q_OS_WIN32)
3068void checkAndWarnGradleLongPaths(const QString &outputDirectory)
3069{
3070 QStringList longFileNames;
3071 using F = QDirListing::IteratorFlag;
3072 for (const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
3073 F::FilesOnly | F::Recursive)) {
3074 if (dirEntry.size() >= MAX_PATH)
3075 longFileNames.append(dirEntry.filePath());
3076 }
3077
3078 if (!longFileNames.isEmpty()) {
3079 fprintf(stderr,
3080 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
3081 "Consider moving your project to reduce its path length.\n"
3082 "The following files have too long paths:\n%s.\n",
3083 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
3084 }
3085}
3086#endif
3087
3088bool buildAndroidProject(const Options &options)
3089{
3090 GradleProperties localProperties;
3091 localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
3092 const QString localPropertiesPath = options.outputDirectory + "local.properties"_L1;
3093 if (!mergeGradleProperties(localPropertiesPath, localProperties))
3094 return false;
3095
3096 const QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1;
3097 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
3098
3099 const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1;
3100 GradleBuildConfigs gradleConfigs = gradleBuildConfigs(gradleBuildFilePath);
3101
3102 gradleProperties["buildDir"] = "build";
3103 gradleProperties["qtAndroidDir"] =
3104 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3105 "/src/android/java"_L1)
3106 .toUtf8();
3107 // The following property "qt5AndroidDir" is only for compatibility.
3108 // Projects using a custom build.gradle file may use this variable.
3109 // ### Qt7: Remove the following line
3110 gradleProperties["qt5AndroidDir"] =
3111 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
3112 "/src/android/java"_L1)
3113 .toUtf8();
3114
3115 QByteArray sdkPlatformVersion;
3116 // Provide the integer version only if build.gradle explicitly converts to Integer,
3117 // to avoid regression to existing projects that build for sdk platform of form android-xx.
3118 if (gradleConfigs.usesIntegerCompileSdkVersion) {
3119 const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit();
3120 bool ok;
3121 tmp.toInt(&ok);
3122 if (ok) {
3123 sdkPlatformVersion = tmp;
3124 } else {
3125 fprintf(stderr, "Warning: Gradle expects SDK platform version to be an integer, "
3126 "but the set version is not convertible to an integer.");
3127 }
3128 }
3129
3130 if (sdkPlatformVersion.isEmpty())
3131 sdkPlatformVersion = options.androidPlatform.toLocal8Bit();
3133 gradleProperties["androidPackageName"] = options.packageName.toLocal8Bit();
3134 gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion;
3135 gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
3136 gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
3137 gradleProperties["androidNdkVersion"] = options.ndkVersion.toUtf8();
3138 if (gradleProperties["androidBuildToolsVersion"].isEmpty())
3139 gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit();
3140 gradleProperties["legacyPackaging"] = options.useLegacyPackaging ? "true" : "false";
3141 QString abiList;
3142 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
3143 if (!it->enabled)
3144 continue;
3145 if (abiList.size())
3146 abiList.append(u",");
3147 abiList.append(it.key());
3148 }
3149 gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
3150 gradleProperties["qtGradlePluginType"] = options.buildAar
3151 ? "com.android.library"
3152 : "com.android.application";
3153 if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
3154 return false;
3155
3156 QString gradlePath = batSuffixAppended(options.outputDirectory + "gradlew"_L1);
3157#ifndef Q_OS_WIN32
3158 {
3159 QFile f(gradlePath);
3160 if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser))
3161 fprintf(stderr, "Cannot set permissions %s\n", qPrintable(gradlePath));
3162 }
3163#endif
3164
3165 QString oldPath = QDir::currentPath();
3166 if (!QDir::setCurrent(options.outputDirectory)) {
3167 fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory));
3168 return false;
3169 }
3170
3171 QString commandLine = "%1 %2"_L1.arg(shellQuote(gradlePath), options.releasePackage ? " assembleRelease"_L1 : " assembleDebug"_L1);
3172 if (options.buildAAB)
3173 commandLine += " bundle"_L1;
3174
3175 if (options.verbose)
3176 commandLine += " --info"_L1;
3177
3178 auto gradleCommand = openProcess(commandLine);
3179 if (gradleCommand == 0) {
3180 fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine));
3181 return false;
3182 }
3183
3184 char buffer[512];
3185 while (fgets(buffer, sizeof(buffer), gradleCommand.get()) != nullptr) {
3186 fprintf(stdout, "%s", buffer);
3187 fflush(stdout);
3188 }
3189
3190 const int errorCode = pclose(gradleCommand.release());
3191 if (errorCode != 0) {
3192 fprintf(stderr, "Building the android package failed!\n");
3193 if (!options.verbose)
3194 fprintf(stderr, " -- For more information, run this command with --verbose.\n");
3195
3196#if defined(Q_OS_WIN32)
3197 checkAndWarnGradleLongPaths(options.outputDirectory);
3198#endif
3199 return false;
3200 }
3201
3202 if (!QDir::setCurrent(oldPath)) {
3203 fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath));
3204 return false;
3206
3207 return true;
3208}
3209
3210bool uninstallApk(const Options &options)
3211{
3212 if (options.verbose)
3213 fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
3214
3215
3216 auto adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName));
3217 if (adbCommand == 0)
3218 return false;
3219
3220 if (options.verbose || mustReadOutputAnyway) {
3221 char buffer[512];
3222 while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
3223 if (options.verbose)
3224 fprintf(stdout, "%s", buffer);
3225 }
3226
3227 const int returnCode = pclose(adbCommand.release());
3228 if (returnCode != 0) {
3229 fprintf(stderr, "Warning: Uninstall failed!\n");
3230 if (!options.verbose)
3231 fprintf(stderr, " -- Run with --verbose for more information.\n");
3232 return false;
3233 }
3234
3235 return true;
3237
3238enum PackageType {
3239 AAB,
3240 AAR,
3241 UnsignedAPK,
3242 SignedAPK
3243};
3244
3245QString packagePath(const Options &options, PackageType packageType)
3246{
3247 // The package type is always AAR if option.buildAar has been set
3248 if (options.buildAar)
3249 packageType = AAR;
3250
3251 static const QHash<PackageType, QLatin1StringView> packageTypeToPath{
3252 { AAB, "bundle"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
3253 };
3254 static const QHash<PackageType, QLatin1StringView> packageTypeToExtension{
3255 { AAB, "aab"_L1 }, { AAR, "aar"_L1 }, { UnsignedAPK, "apk"_L1 }, { SignedAPK, "apk"_L1 }
3256 };
3257
3258 const QString buildType(options.releasePackage ? "release"_L1 : "debug"_L1);
3259 QString signedSuffix;
3260 if (packageType == SignedAPK)
3261 signedSuffix = "-signed"_L1;
3262 else if (packageType == UnsignedAPK && options.releasePackage)
3263 signedSuffix = "-unsigned"_L1;
3264
3265 QString dirPath(options.outputDirectory);
3266 dirPath += "/build/outputs/%1/"_L1.arg(packageTypeToPath[packageType]);
3267 if (QDir(dirPath + buildType).exists())
3268 dirPath += buildType;
3269
3270 const QString fileName = "/%1-%2%3.%4"_L1.arg(
3271 QDir(options.outputDirectory).dirName(),
3272 buildType,
3273 signedSuffix,
3274 packageTypeToExtension[packageType]);
3275
3276 return dirPath + fileName;
3277}
3278
3279bool installApk(const Options &options)
3280{
3281 fflush(stdout);
3282 // Uninstall if necessary
3283 if (options.uninstallApk)
3284 uninstallApk(options);
3285
3286 if (options.verbose)
3287 fprintf(stdout, "Installing Android package to device.\n");
3288
3289 auto adbCommand = runAdb(options, " install -r "_L1
3290 + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
3291 : SignedAPK));
3292 if (adbCommand == 0)
3293 return false;
3294
3295 if (options.verbose || mustReadOutputAnyway) {
3296 char buffer[512];
3297 while (fgets(buffer, sizeof(buffer), adbCommand.get()) != nullptr)
3298 if (options.verbose)
3299 fprintf(stdout, "%s", buffer);
3300 }
3301
3302 const int returnCode = pclose(adbCommand.release());
3303 if (returnCode != 0) {
3304 fprintf(stderr, "Installing to device failed!\n");
3305 if (!options.verbose)
3306 fprintf(stderr, " -- Run with --verbose for more information.\n");
3307 return false;
3308 }
3309
3310 return true;
3311}
3312
3313bool copyPackage(const Options &options)
3314{
3315 fflush(stdout);
3316 auto from = packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK : SignedAPK);
3317 QFile::remove(options.apkPath);
3318 return QFile::copy(from, options.apkPath);
3319}
3320
3321bool copyStdCpp(Options *options)
3322{
3323 if (isDeployment(options, Options::Unbundled))
3324 return true;
3325 if (options->verbose)
3326 fprintf(stdout, "Copying STL library\n");
3327
3328 const QString triple = options->architectures[options->currentArchitecture].triple;
3329 const QString stdCppPath = "%1/%2/lib%3.so"_L1.arg(options->stdCppPath, triple,
3330 options->stdCppName);
3331 if (!QFile::exists(stdCppPath)) {
3332 fprintf(stderr, "STL library does not exist at %s\n", qPrintable(stdCppPath));
3333 fflush(stdout);
3334 fflush(stderr);
3335 return false;
3336 }
3337
3338 const QString destinationFile = "%1/libs/%2/lib%3.so"_L1.arg(options->outputDirectory,
3339 options->currentArchitecture,
3340 options->stdCppName);
3341 return copyFileIfNewer(stdCppPath, destinationFile, *options, options->createSymlinksOnly);
3342}
3343
3344static QString zipalignPath(const Options &options, bool *ok)
3345{
3346 *ok = true;
3347 QString zipAlignTool = execSuffixAppended(options.sdkPath + "/tools/zipalign"_L1);
3348 if (!QFile::exists(zipAlignTool)) {
3349 zipAlignTool = execSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
3350 options.sdkBuildToolsVersion + "/zipalign"_L1);
3351 if (!QFile::exists(zipAlignTool)) {
3352 fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
3353 *ok = false;
3354 }
3355 }
3356
3357 return zipAlignTool;
3358}
3359
3360bool signAAB(const Options &options)
3361{
3362 if (options.verbose)
3363 fprintf(stdout, "Signing Android package.\n");
3364
3365 QString jdkPath = options.jdkPath;
3366
3367 if (jdkPath.isEmpty())
3368 jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME"));
3369
3370 QString jarSignerTool = execSuffixAppended("jarsigner"_L1);
3371 if (jdkPath.isEmpty() || !QFile::exists(jdkPath + "/bin/"_L1 + jarSignerTool))
3372 jarSignerTool = findInPath(jarSignerTool);
3373 else
3374 jarSignerTool = jdkPath + "/bin/"_L1 + jarSignerTool;
3375
3376 if (!QFile::exists(jarSignerTool)) {
3377 fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3378 return false;
3379 }
3380
3381 jarSignerTool = "%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3382 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3383
3384 if (!options.keyStorePassword.isEmpty())
3385 jarSignerTool += " -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3386
3387 if (!options.storeType.isEmpty())
3388 jarSignerTool += " -storetype %1"_L1.arg(shellQuote(options.storeType));
3389
3390 if (!options.keyPass.isEmpty())
3391 jarSignerTool += " -keypass %1"_L1.arg(shellQuote(options.keyPass));
3392
3393 if (!options.sigFile.isEmpty())
3394 jarSignerTool += " -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3395
3396 if (!options.signedJar.isEmpty())
3397 jarSignerTool += " -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3398
3399 if (!options.tsaUrl.isEmpty())
3400 jarSignerTool += " -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3401
3402 if (!options.tsaCert.isEmpty())
3403 jarSignerTool += " -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3404
3405 if (options.internalSf)
3406 jarSignerTool += " -internalsf"_L1;
3407
3408 if (options.sectionsOnly)
3409 jarSignerTool += " -sectionsonly"_L1;
3410
3411 if (options.protectedAuthenticationPath)
3412 jarSignerTool += " -protected"_L1;
3413
3414 auto jarSignPackage = [&](const QString &file) {
3415 fprintf(stdout, "Signing file %s\n", qPrintable(file));
3416 fflush(stdout);
3417 QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file))
3418 .arg(shellQuote(options.keyStoreAlias));
3419
3420 auto jarSignerCommand = openProcess(command);
3421 if (jarSignerCommand == 0) {
3422 fprintf(stderr, "Couldn't run jarsigner.\n");
3423 return false;
3424 }
3425
3426 if (options.verbose) {
3427 char buffer[512];
3428 while (fgets(buffer, sizeof(buffer), jarSignerCommand.get()) != nullptr)
3429 fprintf(stdout, "%s", buffer);
3430 }
3431
3432 const int errorCode = pclose(jarSignerCommand.release());
3433 if (errorCode != 0) {
3434 fprintf(stderr, "jarsigner command failed.\n");
3435 if (!options.verbose)
3436 fprintf(stderr, " -- Run with --verbose for more information.\n");
3437 return false;
3438 }
3439 return true;
3440 };
3441
3442 if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3443 return false;
3444 return true;
3445}
3446
3447bool signPackage(const Options &options)
3448{
3449 const QString apksignerTool = batSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
3450 options.sdkBuildToolsVersion + "/apksigner"_L1);
3451 // APKs signed with apksigner must not be changed after they're signed,
3452 // therefore we need to zipalign it before we sign it.
3453
3454 bool ok;
3455 QString zipAlignTool = zipalignPath(options, &ok);
3456 if (!ok)
3457 return false;
3459 auto zipalignRunner = [](const QString &zipAlignCommandLine) {
3460 auto zipAlignCommand = openProcess(zipAlignCommandLine);
3461 if (zipAlignCommand == 0) {
3462 fprintf(stderr, "Couldn't run zipalign.\n");
3463 return false;
3466 char buffer[512];
3467 while (fgets(buffer, sizeof(buffer), zipAlignCommand.get()) != nullptr)
3468 fprintf(stdout, "%s", buffer);
3470 return pclose(zipAlignCommand.release()) == 0;
3473 const QString verifyZipAlignCommandLine =
3474 "%1%2 -c 4 %3"_L1
3475 .arg(shellQuote(zipAlignTool),
3476 options.verbose ? " -v"_L1 : QLatin1StringView(),
3477 shellQuote(packagePath(options, UnsignedAPK)));
3478
3479 if (zipalignRunner(verifyZipAlignCommandLine)) {
3480 if (options.verbose)
3481 fprintf(stdout, "APK already aligned, copying it for signing.\n");
3482
3483 if (QFile::exists(packagePath(options, SignedAPK)))
3484 QFile::remove(packagePath(options, SignedAPK));
3485
3486 if (!QFile::copy(packagePath(options, UnsignedAPK), packagePath(options, SignedAPK))) {
3487 fprintf(stderr, "Could not copy unsigned APK.\n");
3488 return false;
3489 }
3490 } else {
3491 if (options.verbose)
3492 fprintf(stdout, "APK not aligned, aligning it for signing.\n");
3493
3494 const QString zipAlignCommandLine =
3495 "%1%2 -f 4 %3 %4"_L1
3496 .arg(shellQuote(zipAlignTool),
3497 options.verbose ? " -v"_L1 : QLatin1StringView(),
3498 shellQuote(packagePath(options, UnsignedAPK)),
3499 shellQuote(packagePath(options, SignedAPK)));
3500
3501 if (!zipalignRunner(zipAlignCommandLine)) {
3502 fprintf(stderr, "zipalign command failed.\n");
3503 if (!options.verbose)
3504 fprintf(stderr, " -- Run with --verbose for more information.\n");
3505 return false;
3506 }
3508
3509 QString apkSignCommand = "%1 sign --ks %2"_L1
3510 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3511
3512 if (!options.keyStorePassword.isEmpty())
3513 apkSignCommand += " --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3514
3515 if (!options.keyStoreAlias.isEmpty())
3516 apkSignCommand += " --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3517
3518 if (!options.keyPass.isEmpty())
3519 apkSignCommand += " --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3520
3521 if (options.verbose)
3522 apkSignCommand += " --verbose"_L1;
3523
3524 apkSignCommand += " %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3525
3526 auto apkSignerRunner = [](const QString &command, bool verbose) {
3527 auto apkSigner = openProcess(command);
3528 if (apkSigner == 0) {
3529 fprintf(stderr, "Couldn't run apksigner.\n");
3530 return false;
3531 }
3532
3533 char buffer[512];
3534 while (fgets(buffer, sizeof(buffer), apkSigner.get()) != nullptr)
3535 fprintf(stdout, "%s", buffer);
3536
3537 const int errorCode = pclose(apkSigner.release());
3538 if (errorCode != 0) {
3539 fprintf(stderr, "apksigner command failed.\n");
3540 if (!verbose)
3541 fprintf(stderr, " -- Run with --verbose for more information.\n");
3542 return false;
3543 }
3544 return true;
3545 };
3546
3547 // Sign the package
3548 if (!apkSignerRunner(apkSignCommand, options.verbose))
3549 return false;
3550
3551 const QString apkVerifyCommand =
3552 "%1 verify --verbose %2"_L1
3553 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3554
3555 if (options.buildAAB && !signAAB(options))
3556 return false;
3557
3558 // Verify the package and remove the unsigned apk
3559 return apkSignerRunner(apkVerifyCommand, true) && QFile::remove(packagePath(options, UnsignedAPK));
3560}
3561
3562enum ErrorCode
3563{
3564 Success,
3565 SyntaxErrorOrHelpRequested = 1,
3566 CannotReadInputFile = 2,
3567 CannotCopyAndroidTemplate = 3,
3568 CannotReadDependencies = 4,
3569 CannotCopyGnuStl = 5,
3570 CannotCopyQtFiles = 6,
3571 CannotFindApplicationBinary = 7,
3572 CannotCopyAndroidExtraLibs = 10,
3573 CannotCopyAndroidSources = 11,
3574 CannotUpdateAndroidFiles = 12,
3575 CannotCreateAndroidProject = 13, // Not used anymore
3576 CannotBuildAndroidProject = 14,
3577 CannotSignPackage = 15,
3578 CannotInstallApk = 16,
3579 CannotCopyAndroidExtraResources = 19,
3580 CannotCopyApk = 20,
3581 CannotCreateRcc = 21,
3582 CannotGenerateJavaQmlComponents = 22
3583};
3584
3585bool writeDependencyFile(const Options &options)
3586{
3587 if (options.verbose)
3588 fprintf(stdout, "Writing dependency file.\n");
3589
3590 QString relativeTargetPath;
3591 if (options.copyDependenciesOnly) {
3592 // When androiddeploy Qt is running in copyDependenciesOnly mode we need to use
3593 // the timestamp file as the target to collect dependencies.
3594 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() + "/timestamp"_L1;
3595 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3596 } else {
3597 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3598 }
3599
3600 QFile depFile(options.depFilePath);
3601 if (depFile.open(QIODevice::WriteOnly)) {
3602 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3603 depFile.write(": ");
3604
3605 for (const auto &file : dependenciesForDepfile) {
3606 depFile.write(" \\\n ");
3607 depFile.write(escapeAndEncodeDependencyPath(file));
3608 }
3609
3610 depFile.write("\n");
3611 }
3612 return true;
3613}
3614
3615int generateJavaQmlComponents(const Options &options)
3616{
3617 const auto firstCharToUpper = [](const QString &str) -> QString {
3618 if (str.isEmpty())
3619 return str;
3620 return str.left(1).toUpper() + str.mid(1);
3621 };
3622
3623 const auto upperFirstAndAfterDot = [](QString str) -> QString {
3624 if (str.isEmpty())
3625 return str;
3626
3627 str[0] = str[0].toUpper();
3628
3629 for (int i = 0; i < str.size(); ++i) {
3630 if (str[i] == "."_L1) {
3631 // Move to the next character after the dot
3632 int j = i + 1;
3633 if (j < str.size()) {
3634 str[j] = str[j].toUpper();
3635 }
3636 }
3637 }
3638 return str;
3639 };
3640
3641 const auto getImportPaths = [options](const QString &buildPath, const QString &libName,
3642 QStringList &appImports, QStringList &externalImports) -> bool {
3643 QFile confRspFile("%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
3644 if (!confRspFile.exists() || !confRspFile.open(QFile::ReadOnly))
3645 return false;
3646 QTextStream rspStream(&confRspFile);
3647 while (!rspStream.atEnd()) {
3648 QString currentLine = rspStream.readLine();
3649 if (currentLine.compare("-importPath"_L1) == 0) {
3650 currentLine = rspStream.readLine();
3651 if (QDir::cleanPath(currentLine).startsWith(QDir::cleanPath(buildPath)))
3652 appImports << currentLine;
3653 else
3654 externalImports << currentLine;
3655 }
3656 }
3657
3658 // Find inner qmldir files
3659 QSet<QString> qmldirDirectories;
3660 for (const QString &path : appImports) {
3661 QDirIterator it(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
3662 while (it.hasNext()) {
3663 const QDir dir(it.next());
3664 const QString absolutePath = dir.absolutePath();
3665 if (!absolutePath.startsWith(options.outputDirectory)
3666 && dir.exists("qmldir"_L1)) {
3667 qmldirDirectories.insert(absolutePath);
3668 }
3669 }
3670 }
3671 appImports << qmldirDirectories.values();
3672 appImports.removeDuplicates();
3673
3674 return appImports.count() + externalImports.count();
3675 };
3676
3677 struct ComponentInfo {
3678 QString name;
3679 QString path;
3680 };
3681
3682 struct ModuleInfo
3683 {
3684 QString moduleName;
3685 QString preferPath;
3686 QList<ComponentInfo> qmlComponents;
3687 bool isValid() { return qmlComponents.size() && moduleName.size(); }
3688 };
3689
3690 const auto getModuleInfo = [](const QString &qmldirPath) -> ModuleInfo {
3691 QFile qmlDirFile(qmldirPath + "/qmldir"_L1);
3692 if (!qmlDirFile.exists() || !qmlDirFile.open(QFile::ReadOnly))
3693 return ModuleInfo();
3694 ModuleInfo moduleInfo;
3695 QSet<QString> qmlComponentNames;
3696 QTextStream qmldirStream(&qmlDirFile);
3697 while (!qmldirStream.atEnd()) {
3698 const QString currentLine = qmldirStream.readLine();
3699 if (currentLine.size() && currentLine[0].isLower()) {
3700 // TODO QTBUG-125891: Handling of QML modules with dotted URI
3701 if (currentLine.startsWith("module "_L1))
3702 moduleInfo.moduleName = currentLine.split(" "_L1)[1];
3703 else if (currentLine.startsWith("prefer "_L1))
3704 moduleInfo.preferPath = currentLine.split(" "_L1)[1];
3705 } else if (currentLine.size()
3706 && (currentLine[0].isUpper() || currentLine.startsWith("singleton"_L1))) {
3707 const QStringList parts = currentLine.split(" "_L1);
3708 if (parts.size() > 2 && !qmlComponentNames.contains(parts.first())) {
3709 moduleInfo.qmlComponents.append({ parts.first(), parts.last() });
3710 qmlComponentNames.insert(parts.first());
3711 }
3712 }
3713 }
3714 return moduleInfo;
3715 };
3716
3717 const auto extractDomInfo = [](const QString &qmlDomExecPath, const QString &qmldirPath,
3718 const QString &qmlFile,
3719 const QStringList &otherImportPaths) -> QJsonObject {
3720 QByteArray domInfo;
3721#if QT_CONFIG(process)
3722 QStringList qmlDomArgs {"-d"_L1, "-D"_L1, "required"_L1, "-f"_L1, "+:propertyInfos"_L1 };
3723 for (auto &importPath : otherImportPaths)
3724 qmlDomArgs << "-I"_L1 << importPath;
3725 qmlDomArgs << "%1/%2"_L1.arg(qmldirPath, qmlFile);
3726 const QString qmlDomCmd = "%1 %2"_L1.arg(qmlDomExecPath, qmlDomArgs.join(u' '));
3727 QProcess process;
3728 process.start(qmlDomExecPath, qmlDomArgs);
3729 if (!process.waitForStarted()) {
3730 fprintf(stderr, "Cannot execute command %s\n", qPrintable(qmlDomCmd));
3731 return QJsonObject();
3732 }
3733 // Wait, maximum 30 seconds
3734 if (!process.waitForFinished(30000)) {
3735 fprintf(stderr, "Execution of command %s timed out.\n", qPrintable(qmlDomCmd));
3736 return QJsonObject();
3737 }
3738 domInfo = process.readAllStandardOutput();
3739
3740 QJsonParseError jsonError;
3741 const QJsonDocument jsonDoc = QJsonDocument::fromJson(domInfo, &jsonError);
3742 if (jsonError.error != QJsonParseError::NoError)
3743 fprintf(stderr, "Output of %s is not valid JSON document.", qPrintable(qmlDomCmd));
3744 return jsonDoc.object();
3745#else
3746#warning Generating QtQuickView Java Contents is not possible with missing QProcess feature.
3747 return QJsonObject();
3748#endif
3749 };
3750
3751 const auto getComponent = [](const QJsonObject &dom) -> QJsonObject {
3752 if (dom.isEmpty())
3753 return QJsonObject();
3754
3755 const QJsonObject currentItem = dom.value("currentItem"_L1).toObject();
3756 if (!currentItem.value("isValid"_L1).toBool(false))
3757 return QJsonObject();
3758
3759 const QJsonArray components =
3760 currentItem.value("components"_L1).toObject().value(""_L1).toArray();
3761 if (components.isEmpty())
3762 return QJsonObject();
3763 return components.constBegin()->toObject();
3764 };
3765
3766 const auto getProperties = [](const QJsonObject &component) -> QJsonArray {
3767 QJsonArray properties;
3768 const QJsonArray objects = component.value("objects"_L1).toArray();
3769 if (objects.isEmpty())
3770 return QJsonArray();
3771 const QJsonObject propertiesObject =
3772 objects[0].toObject().value("propertyInfos"_L1).toObject();
3773 for (const auto &jsonProperty : propertiesObject) {
3774 const QJsonArray propertyDefs =
3775 jsonProperty.toObject().value("propertyDefs"_L1).toArray();
3776 if (propertyDefs.isEmpty())
3777 continue;
3778
3779 properties.append(propertyDefs[0].toObject());
3780 }
3781 return properties;
3782 };
3783
3784 const auto getMethods = [](const QJsonObject &component) -> QJsonArray {
3785 QJsonArray methods;
3786 const QJsonArray objects = component.value("objects"_L1).toArray();
3787 if (objects.isEmpty())
3788 return QJsonArray();
3789 const QJsonObject methodsObject = objects[0].toObject().value("methods"_L1).toObject();
3790 for (const auto &jsonMethod : methodsObject) {
3791 const QJsonArray overloads = jsonMethod.toArray();
3792 for (const auto &m : overloads)
3793 methods.append(m);
3794 }
3795 return methods;
3796 };
3797
3798 const static QHash<QString, QString> qmlToJavaType = {
3799 { "real"_L1, "Double"_L1 }, { "double"_L1, "Double"_L1 }, { "int"_L1, "Integer"_L1 },
3800 { "float"_L1, "Float"_L1 }, { "bool"_L1, "Boolean"_L1 }, { "string"_L1, "String"_L1 },
3801 { "void"_L1, "Void"_L1 }
3802 };
3803
3804 const auto endBlock = [](QTextStream &stream, int indentWidth = 0) {
3805 stream << QString(indentWidth, u' ') << "}\n";
3806 };
3807
3808 const auto createHeaderBlock = [](QTextStream &stream, const QString &javaPackage) {
3809 stream << "/* This file is autogenerated by androiddeployqt. Do not edit */\n\n"
3810 << "package %1;\n\n"_L1.arg(javaPackage)
3811 << "import org.qtproject.qt.android.QtSignalListener;\n"
3812 << "import org.qtproject.qt.android.QtQuickViewContent;\n\n";
3813 };
3814
3815 const auto beginComponentBlock = [](QTextStream &stream, const QString &libName,
3816 const QString &moduleName, const QString &preferPath,
3817 const ComponentInfo &componentInfo, int indentWidth = 8) {
3818 const QString indent(indentWidth, u' ');
3819
3820 stream << indent
3821 << "public final class %1 extends QtQuickViewContent {\n"_L1
3822 .arg(componentInfo.name)
3823 << indent << " @Override public String getLibraryName() {\n"_L1
3824 << indent << " return \"%1\";\n"_L1.arg(libName)
3825 << indent << " }\n"_L1
3826 << indent << " @Override public String getModuleName() {\n"_L1
3827 << indent << " return \"%1\";\n"_L1.arg(moduleName)
3828 << indent << " }\n"_L1
3829 << indent << " @Override public String getFilePath() {\n"_L1
3830 << indent << " return \"qrc%1%2\";\n"_L1.arg(preferPath)
3831 .arg(componentInfo.path)
3832 << indent << " }\n"_L1;
3833 };
3834
3835 const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
3836 const QJsonObject &propertyData,
3837 int indentWidth = 8) {
3838 const QString indent(indentWidth, u' ');
3839 const QString propertyName = propertyData["name"_L1].toString();
3840 if (propertyName.isEmpty())
3841 return;
3842 const QString upperPropertyName = firstCharToUpper(propertyName);
3843 const QString typeName = propertyData["typeName"_L1].toString();
3844 const bool isReadyonly = propertyData["isReadonly"_L1].toBool();
3845
3846 const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3847
3848 if (!isReadyonly) {
3849 stream << indent
3850 << "public void set%1(%2 %3) { setProperty(\"%3\", %3); }\n"_L1.arg(
3851 upperPropertyName, javaTypeName, propertyName);
3852 }
3853
3854 stream << indent
3855 << "public %2 get%1() { return this.<%2>getProperty(\"%3\"); }\n"_L1
3856 .arg(upperPropertyName, javaTypeName, propertyName)
3857 << indent
3858 << "public int connect%1ChangeListener(QtSignalListener<%2> signalListener) {\n"_L1
3859 .arg(upperPropertyName, javaTypeName)
3860 << indent
3861 << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg(
3862 propertyName, javaTypeName)
3863 << indent << "}\n";
3864 };
3865
3866 enum class MethodType { Signal = 0, Function = 1 };
3867
3868 const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
3869 const QJsonObject &methodData,
3870 int indentWidth = 8) {
3871 const QString indent(indentWidth, u' ');
3872 if (MethodType(methodData["methodType"_L1].toInt()) != MethodType::Signal)
3873 return;
3874 const QJsonArray parameters = methodData["parameters"_L1].toArray();
3875
3876 const QString methodName = methodData["name"_L1].toString();
3877 if (methodName.isEmpty())
3878 return;
3879
3880 const QString upperMethodName = firstCharToUpper(methodName);
3881 if (parameters.size() <= 1) { // Generate a QtSignalListener<T> API for this property/signal
3882 const QString typeName = !parameters.isEmpty()
3883 ? parameters[0].toObject()["typeName"_L1].toString()
3884 : "void"_L1;
3885 const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3886 stream << indent
3887 << "public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1
3888 .arg(upperMethodName, javaTypeName)
3889 << indent
3890 << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1
3891 .arg(methodName, javaTypeName)
3892 << indent << "}\n";
3893 } else { // Multi-arg signal; Generate a custom listener interface for this signal
3894 // Returns a comma-separated parameter list of java types deduced from the QML DOM array
3895 const auto getJavaArgsString = [&parameters]() -> QString {
3896 QList<QString> javaArgsList;
3897 for (const auto param : parameters) {
3898 const auto typeName = param["typeName"_L1].toString();
3899 const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3900 const auto qmlParamName = param["name"_L1].toString();
3901
3902 javaArgsList.emplace_back(
3903 QStringLiteral("%1%2").arg(javaTypeName, " %1"_L1.arg(qmlParamName)));
3904 }
3905 return javaArgsList.join(", "_L1);
3906 };
3907 // Returns a comma-separated parameter list of java classes deduced from QML DOM array
3908 const auto getJavaClassesString = [&parameters]() -> QString {
3909 QList<QString> javaArgsList;
3910 for (const auto param : parameters) {
3911 const auto typeName = param["typeName"_L1].toString();
3912 const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1);
3913
3914 javaArgsList.emplace_back(
3915 QStringLiteral("%1%2").arg(javaTypeName, ".class"_L1));
3916 }
3917 return javaArgsList.join(", "_L1);
3918 };
3919
3920 const auto javaParamsString = getJavaArgsString();
3921 const auto javaParamsClassesString = getJavaClassesString();
3922
3923 // e.g. "{(String) args[0], (Integer) args[1], (Boolean) args[2]}"
3924 QList<QString> objectToTypeConversion;
3925 for (auto i = 0; i < parameters.size(); ++i) {
3926 const auto typeName = parameters.at(i).toObject().value("typeName"_L1).toString();
3927 objectToTypeConversion.emplace_back("(%1) args[%2]"_L1.arg(
3928 qmlToJavaType.value(typeName, "Object"_L1), QString::number(i)));
3929 }
3930
3931 // Generate new interface type for this signal
3932 const auto signalInterfaceName = "%1Listener"_L1.arg(methodName);
3933 const auto objectToTypeConversionString = objectToTypeConversion.join(", "_L1);
3934 stream << indent << "@FunctionalInterface\n"
3935 << indent << "public interface %1 {\n"_L1.arg(signalInterfaceName) << indent
3936 << " default void onSignalEmitted(Object[] args) {\n"
3937 << indent
3938 << " on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString)
3939 << indent << " }\n"
3940 << indent
3941 << " void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString);
3942 stream << indent << "}\n"_L1;
3943
3944 // Generate the connection function with this new interface type
3945 stream << indent
3946 << "public int connect%1(%2 signalListener) {\n"_L1.arg(
3947 firstCharToUpper(signalInterfaceName), signalInterfaceName)
3948 << indent
3949 << " return connectSignalListener(\"%1\", new Class<?>[]{ %2 }, signalListener);\n"_L1
3950 .arg(methodName, javaParamsClassesString)
3951 << indent << "}\n\n";
3952 }
3953 };
3954
3955 const auto writeFunctionBlock = [](QTextStream &stream, const QJsonObject &methodData,
3956 int indentWidth = 8) {
3957 const QString indent(indentWidth, u' ');
3958 if (MethodType(methodData["methodType"_L1].toInt()) != MethodType::Function)
3959 return;
3960
3961 const QJsonArray params = methodData["parameters"_L1].toArray();
3962 const QString functionName = methodData["name"_L1].toString();
3963
3964 QList<QString> javaFunctionParams; // e.g. { "Object param", "String thing" }
3965 QList<QString> javaParams; // e.g. "param, thing"
3966 for (const auto &value : params) {
3967 const auto object = value.toObject();
3968 if (!object.contains("typeName"_L1)) {
3969 qWarning() << " -- Skipping function" << functionName
3970 << "due to untyped function parameter detected while generating Java "
3971 "code for QML methods.";
3972 return;
3973 }
3974
3975 const auto qmlParamType = object["typeName"_L1].toString();
3976 if (!qmlToJavaType.contains(qmlParamType)) {
3977 qWarning() << " -- Skipping function" << functionName
3978 << "due to unsupported type detected in parameters:" << qmlParamType;
3979 return;
3980 }
3981
3982 const auto javaTypeName{ qmlToJavaType.value(object["typeName"_L1].toString(),
3983 "Object"_L1) };
3984 const auto javaParamName = object["name"_L1].toString();
3985 javaFunctionParams.push_back(
3986 QString{ "%1 %2"_L1 }.arg(javaTypeName).arg(javaParamName));
3987 javaParams.append(javaParamName);
3988 }
3989
3990 const auto functionSignature {
3991 "public void %1(%2) {\n"_L1.arg(functionName).arg(javaFunctionParams.join(", "_L1))
3992 };
3993 const auto functionCallParams {
3994 javaParams.isEmpty() ? ""_L1 : ", new Object[] { %1 }"_L1.arg(javaParams.join(", "_L1))
3995 };
3996
3997 stream << indent << functionSignature
3998 << indent << " invokeMethod(\"%1\"%2);\n"_L1.arg(functionName)
3999 .arg(functionCallParams)
4000 << indent << "}\n";
4001 };
4002
4003 constexpr static auto markerFileName = "qml_java_contents"_L1;
4004 const QString libName(options.applicationBinary);
4005 QString javaPackageBase = options.packageName;
4006 const QString expectedBaseLeaf = ".%1"_L1.arg(libName);
4007 if (!javaPackageBase.endsWith(expectedBaseLeaf))
4008 javaPackageBase += expectedBaseLeaf;
4009 const QString baseSourceDir = "%1/src/%2"_L1.arg(options.outputDirectory,
4010 QString(javaPackageBase).replace(u'.', u'/'));
4011 const QString buildPath(QDir(options.buildDirectory).absolutePath());
4012 const QString domBinaryPath(options.qmlDomBinaryPath);
4014 fprintf(stdout, "Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
4015 if (!QDir().current().mkpath(baseSourceDir)) {
4016 fprintf(stderr, "Cannot create %s directory\n", qPrintable(baseSourceDir));
4017 return false;
4018 }
4019
4020 QStringList appImports;
4021 QStringList externalImports;
4022 if (!getImportPaths(buildPath, libName, appImports, externalImports))
4023 return false;
4024
4025 // Remove previous directories generated by this code generator
4026 {
4027 const QString srcDir = "%1/src"_L1.arg(options.outputDirectory);
4028 QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
4029 while (iter.hasNext())
4030 iter.nextFileInfo().dir().removeRecursively();
4031 }
4032
4033 int generatedComponents = 0;
4034 for (const auto &importPath : appImports) {
4035 ModuleInfo moduleInfo = getModuleInfo(importPath);
4036 if (!moduleInfo.isValid())
4037 continue;
4038
4039 const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
4040 if (moduleInfo.moduleName == libName) {
4041 fprintf(stderr,
4042 "A QML module name (%s) cannot be the same as the target name when building "
4043 "with QT_ANDROID_GENERATE_JAVA_QTQUICKVIEW_CONTENTS flag.\n",
4044 qPrintable(moduleInfo.moduleName));
4045 return false;
4046 }
4047
4048 const QString javaPackage = "%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
4049 const QString outputDir =
4050 "%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
4051 if (!QDir().current().mkpath(outputDir)) {
4052 fprintf(stderr, "Cannot create %s directory\n", qPrintable(outputDir));
4053 return false;
4054 }
4055
4056 // Add a marker file to indicate this as a module package source directory
4057 {
4058 QFile markerFile("%1/%2"_L1.arg(outputDir, markerFileName));
4059 if (!markerFile.open(QFile::WriteOnly)) {
4060 fprintf(stderr, "Cannot create %s file\n", qPrintable(markerFile.fileName()));
4061 return false;
4062 }
4063 }
4064
4065 int indentBase = 0;
4066
4067 for (const auto &qmlComponent : moduleInfo.qmlComponents) {
4068 const bool isSelected = options.selectedJavaQmlComponents.contains(
4069 "%1.%2"_L1.arg(moduleInfo.moduleName, qmlComponent.name));
4070 if (!options.selectedJavaQmlComponents.isEmpty() && !isSelected)
4071 continue;
4072
4073 QJsonObject domInfo = extractDomInfo(domBinaryPath, importPath, qmlComponent.path,
4074 externalImports + appImports);
4075 QJsonObject component = getComponent(domInfo);
4076 if (component.isEmpty())
4077 continue;
4078
4079 QByteArray componentClassBody;
4080 QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
4081
4082 createHeaderBlock(outputStream, javaPackage);
4083
4084 beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
4085 qmlComponent, indentBase);
4086 indentBase += 4;
4087
4088 const QJsonArray properties = getProperties(component);
4089 for (const QJsonValue &p : std::as_const(properties))
4090 beginPropertyBlock(outputStream, p.toObject(), indentBase);
4091
4092 const QJsonArray methods = getMethods(component);
4093 for (const QJsonValue &m : std::as_const(methods))
4094 beginSignalBlock(outputStream, m.toObject(), indentBase);
4095
4096 for (const QJsonValue &m : std::as_const(methods))
4097 writeFunctionBlock(outputStream, m.toObject(), indentBase);
4098
4099 indentBase -= 4;
4100 endBlock(outputStream, indentBase);
4101 outputStream.flush();
4102
4103 // Write component class body to file
4104 QFile outputFile("%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
4105 if (outputFile.exists())
4106 outputFile.remove();
4107 if (!outputFile.open(QFile::WriteOnly)) {
4108 fprintf(stderr, "Cannot open %s file to write.\n",
4109 qPrintable(outputFile.fileName()));
4110 return false;
4111 }
4112 outputFile.write(componentClassBody);
4113 outputFile.close();
4114
4115 generatedComponents++;
4116 }
4117 }
4118 return generatedComponents;
4119}
4120
4121int main(int argc, char *argv[])
4122{
4123 QCoreApplication a(argc, argv);
4124
4125 Options options = parseOptions();
4126 if (options.helpRequested || options.outputDirectory.isEmpty()) {
4128 return SyntaxErrorOrHelpRequested;
4129 }
4130
4131 options.timer.start();
4132
4133 if (!readInputFile(&options))
4134 return CannotReadInputFile;
4135
4136 if (Q_UNLIKELY(options.timing))
4137 fprintf(stdout, "[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
4138
4139 fprintf(stdout,
4140 "Generating Android Package\n"
4141 " Input file: %s\n"
4142 " Output directory: %s\n"
4143 " Application binary: %s\n"
4144 " Android build platform: %s\n"
4145 " Install to device: %s\n",
4146 qPrintable(options.inputFileName),
4147 qPrintable(options.outputDirectory),
4148 qPrintable(options.applicationBinary),
4149 qPrintable(options.androidPlatform),
4150 options.installApk
4151 ? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation))
4152 : "No"
4153 );
4154
4155 bool androidTemplatetCopied = false;
4156
4157 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
4158 if (!it->enabled)
4159 continue;
4160 options.setCurrentQtArchitecture(it.key(),
4161 it.value().qtInstallDirectory,
4162 it.value().qtDirectories);
4163
4164 // All architectures have a copy of the gradle files but only one set needs to be copied.
4165 if (!androidTemplatetCopied && options.build && !options.copyDependenciesOnly) {
4166 cleanAndroidFiles(options);
4167 if (Q_UNLIKELY(options.timing))
4168 fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
4169
4170 if (!copyAndroidTemplate(options))
4171 return CannotCopyAndroidTemplate;
4172
4173 if (Q_UNLIKELY(options.timing))
4174 fprintf(stdout, "[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
4175 androidTemplatetCopied = true;
4176 }
4177
4178 if (!readDependencies(&options))
4179 return CannotReadDependencies;
4180
4181 if (Q_UNLIKELY(options.timing))
4182 fprintf(stdout, "[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
4183
4184 if (!copyQtFiles(&options))
4185 return CannotCopyQtFiles;
4186
4187 if (Q_UNLIKELY(options.timing))
4188 fprintf(stdout, "[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
4189
4190 if (!copyAndroidExtraLibs(&options))
4191 return CannotCopyAndroidExtraLibs;
4192
4193 if (Q_UNLIKELY(options.timing))
4194 fprintf(stdout, "[TIMING] %lld ms: Copied extra libs\n", options.timer.nsecsElapsed());
4195
4196 if (!copyAndroidExtraResources(&options))
4197 return CannotCopyAndroidExtraResources;
4198
4199 if (Q_UNLIKELY(options.timing))
4200 fprintf(stdout, "[TIMING] %lld ns: Copied extra resources\n", options.timer.nsecsElapsed());
4201
4202 if (!copyStdCpp(&options))
4203 return CannotCopyGnuStl;
4204
4205 if (Q_UNLIKELY(options.timing))
4206 fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
4207
4208 if (options.generateJavaQmlComponents) {
4209 if (!generateJavaQmlComponents(options))
4210 return CannotGenerateJavaQmlComponents;
4211 }
4212
4213 if (Q_UNLIKELY(options.timing)) {
4214 fprintf(stdout, "[TIMING] %lld ns: Generate Java QtQuickViewContents.\n",
4215 options.timer.nsecsElapsed());
4216 }
4217
4218 // If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK
4220 QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1.
4221 arg(options.outputDirectory,
4222 options.currentArchitecture,
4223 options.applicationBinary);
4224 QFile::remove(appLibPath);
4225 } else if (!containsApplicationBinary(&options)) {
4226 return CannotFindApplicationBinary;
4227 }
4228
4229 if (Q_UNLIKELY(options.timing))
4230 fprintf(stdout, "[TIMING] %lld ns: Checked for application binary\n", options.timer.nsecsElapsed());
4231
4232 if (Q_UNLIKELY(options.timing))
4233 fprintf(stdout, "[TIMING] %lld ns: Bundled Qt libs\n", options.timer.nsecsElapsed());
4234 }
4235
4236 if (options.copyDependenciesOnly) {
4237 if (!options.depFilePath.isEmpty())
4238 writeDependencyFile(options);
4239 return 0;
4240 }
4241
4242 if (!createRcc(options))
4243 return CannotCreateRcc;
4244
4245 if (options.auxMode || options.build) {
4246 if (!copyAndroidSources(options))
4247 return CannotCopyAndroidSources;
4248
4249 if (Q_UNLIKELY(options.timing))
4250 fprintf(stdout, "[TIMING] %lld ns: Copied android sources\n", options.timer.nsecsElapsed());
4251
4252 if (!updateAndroidFiles(options))
4253 return CannotUpdateAndroidFiles;
4254
4255 if (Q_UNLIKELY(options.timing))
4256 fprintf(stdout, "[TIMING] %lld ns: Updated files\n", options.timer.nsecsElapsed());
4257 }
4258
4259 if (options.auxMode)
4260 return 0;
4261
4262 if (options.build) {
4263 if (Q_UNLIKELY(options.timing))
4264 fprintf(stdout, "[TIMING] %lld ns: Created project\n", options.timer.nsecsElapsed());
4265
4266 if (!buildAndroidProject(options))
4267 return CannotBuildAndroidProject;
4268
4269 if (Q_UNLIKELY(options.timing))
4270 fprintf(stdout, "[TIMING] %lld ns: Built project\n", options.timer.nsecsElapsed());
4271
4272 if (!options.keyStore.isEmpty() && !signPackage(options))
4273 return CannotSignPackage;
4274
4275 if (!options.apkPath.isEmpty() && !copyPackage(options))
4276 return CannotCopyApk;
4277
4278 if (Q_UNLIKELY(options.timing))
4279 fprintf(stdout, "[TIMING] %lld ns: Signed package\n", options.timer.nsecsElapsed());
4280 }
4281
4282 if (options.installApk && !installApk(options))
4283 return CannotInstallApk;
4284
4285 if (Q_UNLIKELY(options.timing))
4286 fprintf(stdout, "[TIMING] %lld ns: Installed APK\n", options.timer.nsecsElapsed());
4287
4288 if (!options.depFilePath.isEmpty())
4289 writeDependencyFile(options);
4290
4291 fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.);
4292
4293 if (options.installApk)
4294 fprintf(stdout, " -- It can now be run from the selected device/emulator.\n");
4295
4296 fprintf(stdout, " -- File: %s\n", qPrintable(packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
4297 : SignedAPK)));
4298 fflush(stdout);
4299 return 0;
4300}
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:2556
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:2282
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:2758
QString findInPath(const QString &file)
Definition main.cpp:2881
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