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