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