Qt 6.x
The Qt SDK
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 <QJsonDocument>
8#include <QJsonObject>
9#include <QJsonArray>
10#include <QJsonValue>
11#include <QDebug>
12#include <QDataStream>
13#include <QXmlStreamReader>
14#include <QStandardPaths>
15#include <QUuid>
16#include <QDirIterator>
17#include <QElapsedTimer>
18#include <QRegularExpression>
19#include <QSettings>
20#include <QHash>
21#include <QSet>
22#include <QMap>
23
24#include <depfile_shared.h>
25#include <shellquote_shared.h>
26
27#include <algorithm>
28
29#if defined(Q_OS_WIN32)
30#include <qt_windows.h>
31#endif
32
33#ifdef Q_CC_MSVC
34#define popen _popen
35#define QT_POPEN_READ "rb"
36#define pclose _pclose
37#else
38#define QT_POPEN_READ "r"
39#endif
40
41using namespace Qt::StringLiterals;
42
43static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
44
46
47FILE *openProcess(const QString &command)
48{
49#if defined(Q_OS_WIN32)
50 QString processedCommand = u'\"' + command + u'\"';
51#else
52 const QString& processedCommand = command;
53#endif
54
55 return popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ);
56}
57
59{
60 QtDependency(const QString &rpath, const QString &apath) : relativePath(rpath), absolutePath(apath) {}
61
62 bool operator==(const QtDependency &other) const
63 {
64 return relativePath == other.relativePath && absolutePath == other.absolutePath;
65 }
66
69};
70
72{
74 const QString &t = QString(),
76 ) :
78 qtDirectories(dirs),
79 triple(t),
81 {}
82
86 bool enabled;
87};
88
89struct Options
90{
93 , verbose(false)
94 , timing(false)
95 , build(true)
96 , auxMode(false)
99 , digestAlg("SHA-256"_L1)
100 , sigAlg("SHA256withRSA"_L1)
107 {}
108
110 {
113 };
114
115 enum TriState {
118 True
119 };
120
123 bool timing;
124 bool build;
126 bool noRccBundleCleanup = false;
129
130 // External tools
136
137 // Build paths
146 std::vector<QString> extraPrefixDirs;
147 // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder
148 // when looking for dependencies.
149 std::vector<QString> extraLibraryDirs;
155 std::vector<QString> rootPaths;
161
162 // Versioning
167
168 // lib c++ path
171
172 // Build information
178 bool buildAAB = false;
180
181
182 // Package information
190
191 // Signing information
208
209 // Installation information
213
214 // Per architecture collected information
216 const QString &directory,
217 const QHash<QString, QString> &directories)
218 {
219 currentArchitecture = arch;
221 qtDataDirectory = directories["qtDataDirectory"_L1];
222 qtLibsDirectory = directories["qtLibsDirectory"_L1];
223 qtLibExecsDirectory = directories["qtLibExecsDirectory"_L1];
224 qtPluginsDirectory = directories["qtPluginsDirectory"_L1];
225 qtQmlDirectory = directories["qtQmlDirectory"_L1];
226 }
231 bool usesOpenGL = false;
232
233 // Per package collected information
237
238 // Override qml import scanner path
241};
242
244 {"aarch64", "arm64-v8a"},
245 {"arm", "armeabi-v7a"},
246 {"i386", "x86"},
247 {"x86_64", "x86_64"}
248};
249
250bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
251bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
252 const QString &moduleUrl);
253bool readDependenciesFromElf(Options *options, const QString &fileName,
254 QSet<QString> *usedDependencies, QSet<QString> *remainingDependencies);
255
257{
258 QRegularExpression architecture(QStringLiteral("_(armeabi-v7a|arm64-v8a|x86|x86_64).so$"));
259 auto match = architecture.match(name);
260 if (!match.hasMatch())
261 return {};
262 return match.captured(1);
263}
264
266{
267#if defined(Q_OS_WIN32)
268 path += ".exe"_L1;
269#endif
270 return path;
271}
272
274{
275#if defined(Q_OS_WIN32)
276 path += ".bat"_L1;
277#endif
278 return path;
279}
280
282{
283#ifdef Q_OS_WIN32
284 return "bin"_L1;
285#else
286 return "libexec"_L1;
287#endif
288}
289
290static QString llvmReadobjPath(const Options &options)
291{
292 return execSuffixAppended("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj"_L1
293 .arg(options.ndkPath,
294 options.toolchainPrefix,
295 options.ndkHost));
296}
297
299{
300 auto arch = architectureFromName(path);
301 if (!arch.isEmpty())
302 return arch;
303
304 QString readElf = llvmReadobjPath(options);
305 if (!QFile::exists(readElf)) {
306 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
307 return {};
308 }
309
310 readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(path));
311
312 FILE *readElfCommand = openProcess(readElf);
313 if (!readElfCommand) {
314 fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
315 return {};
316 }
317
318 char buffer[512];
319 while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
321 line = line.trimmed();
322 if (line.startsWith("Arch: ")) {
323 auto it = elfArchitectures.find(line.mid(6));
324 pclose(readElfCommand);
325 return it != elfArchitectures.constEnd() ? QString::fromLatin1(it.value()) : QString{};
326 }
327 }
328 pclose(readElfCommand);
329 return {};
330}
331
332bool checkArchitecture(const Options &options, const QString &fileName)
333{
334 return fileArchitecture(options, fileName) == options.currentArchitecture;
335}
336
337void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
338{
339 if (options.verbose)
340 fprintf(stdout, "Delete missing files %s %s\n", qPrintable(srcDir.absolutePath()), qPrintable(dstDir.absolutePath()));
341
344 for (const QFileInfo &dst : dstEntries) {
345 bool found = false;
346 for (const QFileInfo &src : srcEntries)
347 if (dst.fileName() == src.fileName()) {
348 if (dst.isDir())
349 deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath());
350 found = true;
351 break;
352 }
353
354 if (!found) {
355 if (options.verbose)
356 fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath()));
357
358 if (dst.isDir())
359 QDir{dst.absolutePath()}.removeRecursively();
360 else
361 QFile::remove(dst.absoluteFilePath());
362 }
363 }
364 fflush(stdout);
365}
366
368{
369 Options options;
370
372 for (int i=0; i<arguments.size(); ++i) {
373 const QString &argument = arguments.at(i);
374 if (argument.compare("--output"_L1, Qt::CaseInsensitive) == 0) {
375 if (i + 1 == arguments.size())
376 options.helpRequested = true;
377 else
378 options.outputDirectory = arguments.at(++i).trimmed();
379 } else if (argument.compare("--input"_L1, Qt::CaseInsensitive) == 0) {
380 if (i + 1 == arguments.size())
381 options.helpRequested = true;
382 else
383 options.inputFileName = arguments.at(++i);
384 } else if (argument.compare("--aab"_L1, Qt::CaseInsensitive) == 0) {
385 options.buildAAB = true;
386 options.build = true;
387 } else if (!options.buildAAB && argument.compare("--no-build"_L1, Qt::CaseInsensitive) == 0) {
388 options.build = false;
389 } else if (argument.compare("--install"_L1, Qt::CaseInsensitive) == 0) {
390 options.installApk = true;
391 options.uninstallApk = true;
392 } else if (argument.compare("--reinstall"_L1, Qt::CaseInsensitive) == 0) {
393 options.installApk = true;
394 options.uninstallApk = false;
395 } else if (argument.compare("--android-platform"_L1, Qt::CaseInsensitive) == 0) {
396 if (i + 1 == arguments.size())
397 options.helpRequested = true;
398 else
399 options.androidPlatform = arguments.at(++i);
400 } else if (argument.compare("--help"_L1, Qt::CaseInsensitive) == 0) {
401 options.helpRequested = true;
402 } else if (argument.compare("--verbose"_L1, Qt::CaseInsensitive) == 0) {
403 options.verbose = true;
404 } else if (argument.compare("--deployment"_L1, Qt::CaseInsensitive) == 0) {
405 if (i + 1 == arguments.size()) {
406 options.helpRequested = true;
407 } else {
408 QString deploymentMechanism = arguments.at(++i);
409 if (deploymentMechanism.compare("bundled"_L1, Qt::CaseInsensitive) == 0) {
411 } else if (deploymentMechanism.compare("unbundled"_L1,
412 Qt::CaseInsensitive) == 0) {
414 } else {
415 fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
416 options.helpRequested = true;
417 }
418 }
419 } else if (argument.compare("--device"_L1, Qt::CaseInsensitive) == 0) {
420 if (i + 1 == arguments.size())
421 options.helpRequested = true;
422 else
423 options.installLocation = arguments.at(++i);
424 } else if (argument.compare("--release"_L1, Qt::CaseInsensitive) == 0) {
425 options.releasePackage = true;
426 } else if (argument.compare("--jdk"_L1, Qt::CaseInsensitive) == 0) {
427 if (i + 1 == arguments.size())
428 options.helpRequested = true;
429 else
430 options.jdkPath = arguments.at(++i);
431 } else if (argument.compare("--apk"_L1, Qt::CaseInsensitive) == 0) {
432 if (i + 1 == arguments.size())
433 options.helpRequested = true;
434 else
435 options.apkPath = arguments.at(++i);
436 } else if (argument.compare("--depfile"_L1, Qt::CaseInsensitive) == 0) {
437 if (i + 1 == arguments.size())
438 options.helpRequested = true;
439 else
440 options.depFilePath = arguments.at(++i);
441 } else if (argument.compare("--builddir"_L1, Qt::CaseInsensitive) == 0) {
442 if (i + 1 == arguments.size())
443 options.helpRequested = true;
444 else
445 options.buildDirectory = arguments.at(++i);
446 } else if (argument.compare("--sign"_L1, Qt::CaseInsensitive) == 0) {
447 if (i + 2 >= arguments.size()) {
448 const QString keyStore = qEnvironmentVariable("QT_ANDROID_KEYSTORE_PATH");
449 const QString storeAlias = qEnvironmentVariable("QT_ANDROID_KEYSTORE_ALIAS");
450 if (keyStore.isEmpty() || storeAlias.isEmpty()) {
451 options.helpRequested = true;
452 fprintf(stderr, "Package signing path and alias values are not specified.\n");
453 } else {
454 fprintf(stdout,
455 "Using package signing path and alias values found from the "
456 "environment variables.\n");
457 options.keyStore = keyStore;
458 options.keyStoreAlias = storeAlias;
459 }
460 } else if (!arguments.at(i + 1).startsWith("--"_L1) &&
461 !arguments.at(i + 2).startsWith("--"_L1)) {
462 options.keyStore = arguments.at(++i);
463 options.keyStoreAlias = arguments.at(++i);
464 } else {
465 options.helpRequested = true;
466 fprintf(stderr, "Package signing path and alias values are not "
467 "specified.\n");
468 }
469
470 // Do not override if the passwords are provided through arguments
471 if (options.keyStorePassword.isEmpty()) {
472 fprintf(stdout, "Using package signing store password found from the environment "
473 "variable.\n");
474 options.keyStorePassword = qEnvironmentVariable("QT_ANDROID_KEYSTORE_STORE_PASS");
475 }
476 if (options.keyPass.isEmpty()) {
477 fprintf(stdout, "Using package signing key password found from the environment "
478 "variable.\n");
479 options.keyPass = qEnvironmentVariable("QT_ANDROID_KEYSTORE_KEY_PASS");
480 }
481 } else if (argument.compare("--storepass"_L1, Qt::CaseInsensitive) == 0) {
482 if (i + 1 == arguments.size())
483 options.helpRequested = true;
484 else
485 options.keyStorePassword = arguments.at(++i);
486 } else if (argument.compare("--storetype"_L1, Qt::CaseInsensitive) == 0) {
487 if (i + 1 == arguments.size())
488 options.helpRequested = true;
489 else
490 options.storeType = arguments.at(++i);
491 } else if (argument.compare("--keypass"_L1, Qt::CaseInsensitive) == 0) {
492 if (i + 1 == arguments.size())
493 options.helpRequested = true;
494 else
495 options.keyPass = arguments.at(++i);
496 } else if (argument.compare("--sigfile"_L1, Qt::CaseInsensitive) == 0) {
497 if (i + 1 == arguments.size())
498 options.helpRequested = true;
499 else
500 options.sigFile = arguments.at(++i);
501 } else if (argument.compare("--digestalg"_L1, Qt::CaseInsensitive) == 0) {
502 if (i + 1 == arguments.size())
503 options.helpRequested = true;
504 else
505 options.digestAlg = arguments.at(++i);
506 } else if (argument.compare("--sigalg"_L1, Qt::CaseInsensitive) == 0) {
507 if (i + 1 == arguments.size())
508 options.helpRequested = true;
509 else
510 options.sigAlg = arguments.at(++i);
511 } else if (argument.compare("--tsa"_L1, Qt::CaseInsensitive) == 0) {
512 if (i + 1 == arguments.size())
513 options.helpRequested = true;
514 else
515 options.tsaUrl = arguments.at(++i);
516 } else if (argument.compare("--tsacert"_L1, Qt::CaseInsensitive) == 0) {
517 if (i + 1 == arguments.size())
518 options.helpRequested = true;
519 else
520 options.tsaCert = arguments.at(++i);
521 } else if (argument.compare("--internalsf"_L1, Qt::CaseInsensitive) == 0) {
522 options.internalSf = true;
523 } else if (argument.compare("--sectionsonly"_L1, Qt::CaseInsensitive) == 0) {
524 options.sectionsOnly = true;
525 } else if (argument.compare("--protected"_L1, Qt::CaseInsensitive) == 0) {
526 options.protectedAuthenticationPath = true;
527 } else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
528 options.auxMode = true;
529 } else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
530 options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed();
531 } else if (argument.compare("--no-rcc-bundle-cleanup"_L1,
532 Qt::CaseInsensitive) == 0) {
533 options.noRccBundleCleanup = true;
534 } else if (argument.compare("--copy-dependencies-only"_L1,
535 Qt::CaseInsensitive) == 0) {
536 options.copyDependenciesOnly = true;
537 }
538 }
539
540 if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
541 options.helpRequested = true;
542
543 if (options.inputFileName.isEmpty())
544 options.inputFileName = "android-%1-deployment-settings.json"_L1.arg(QDir::current().dirName());
545
546 options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT");
547
548 if (!QDir::current().mkpath(options.outputDirectory)) {
549 fprintf(stderr, "Invalid output directory: %s\n", qPrintable(options.outputDirectory));
550 options.outputDirectory.clear();
551 } else {
553 if (!options.outputDirectory.endsWith(u'/'))
554 options.outputDirectory += u'/';
555 }
556
557 return options;
558}
559
561{
562 fprintf(stderr, R"(
563Syntax: androiddeployqt --output <destination> [options]
564
565Creates an Android package in the build directory <destination> and
566builds it into an .apk file.
567
568Optional arguments:
569 --input <inputfile>: Reads <inputfile> for options generated by
570 qmake. A default file name based on the current working
571 directory will be used if nothing else is specified.
572
573 --deployment <mechanism>: Supported deployment mechanisms:
574 bundled (default): Includes Qt files in stand-alone package.
575 unbundled: Assumes native libraries are present on the device
576 and does not include them in the APK.
577
578 --aab: Build an Android App Bundle.
579
580 --no-build: Do not build the package, it is useful to just install
581 a package previously built.
582
583 --install: Installs apk to device/emulator. By default this step is
584 not taken. If the application has previously been installed on
585 the device, it will be uninstalled first.
586
587 --reinstall: Installs apk to device/emulator. By default this step
588 is not taken. If the application has previously been installed on
589 the device, it will be overwritten, but its data will be left
590 intact.
591
592 --device [device ID]: Use specified device for deployment. Default
593 is the device selected by default by adb.
594
595 --android-platform <platform>: Builds against the given android
596 platform. By default, the highest available version will be
597 used.
598
599 --release: Builds a package ready for release. By default, the
600 package will be signed with a debug key.
601
602 --sign <url/to/keystore> <alias>: Signs the package with the
603 specified keystore, alias and store password.
604 Optional arguments for use with signing:
605 --storepass <password>: Keystore password.
606 --storetype <type>: Keystore type.
607 --keypass <password>: Password for private key (if different
608 from keystore password.)
609 --sigfile <file>: Name of .SF/.DSA file.
610 --digestalg <name>: Name of digest algorithm. Default is
611 "SHA1".
612 --sigalg <name>: Name of signature algorithm. Default is
613 "SHA1withRSA".
614 --tsa <url>: Location of the Time Stamping Authority.
615 --tsacert <alias>: Public key certificate for TSA.
616 --internalsf: Include the .SF file inside the signature block.
617 --sectionsonly: Don't compute hash of entire manifest.
618 --protected: Keystore has protected authentication path.
619 --jarsigner: Deprecated, ignored.
620
621 NOTE: To conceal the keystore information, the environment variables
622 QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to
623 set the values keysotore and alias respectively.
624 Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,
625 and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key
626 passwords respectively. This option needs only the --sign parameter.
627
628 --jdk <path/to/jdk>: Used to find the jarsigner tool when used
629 in combination with the --release argument. By default,
630 an attempt is made to detect the tool using the JAVA_HOME and
631 PATH environment variables, in that order.
632
633 --qml-import-paths: Specify additional search paths for QML
634 imports.
635
636 --verbose: Prints out information during processing.
637
638 --no-generated-assets-cache: Do not pregenerate the entry list for
639 the assets file engine.
640
641 --aux-mode: Operate in auxiliary mode. This will only copy the
642 dependencies into the build directory and update the XML templates.
643 The project will not be built or installed.
644
645 --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
646
647 --qml-importscanner-binary <path/to/qmlimportscanner>: Override the
648 default qmlimportscanner binary path. By default the
649 qmlimportscanner binary is located using the Qt directory
650 specified in the input file.
651
652 --depfile <path/to/depfile>: Output a dependency file.
653
654 --builddir <path/to/build/directory>: build directory. Necessary when
655 generating a depfile because ninja requires relative paths.
656
657 --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
658 running androiddeployqt. This option simplifies debugging of
659 the resource bundle content, but it should not be used when deploying
660 a project, since it litters the 'assets' directory.
661
662 --copy-dependencies-only: resolve application dependencies and stop
663 deploying process after all libraries and resources that the
664 application depends on have been copied.
665
666 --help: Displays this information.
667)");
668}
669
670// Since strings compared will all start with the same letters,
671// sorting by length and then alphabetically within each length
672// gives the natural order.
674{
675 QString s1 = fi1.baseName();
676 QString s2 = fi2.baseName();
677
678 if (s1.size() == s2.size())
679 return s1 > s2;
680 else
681 return s1.size() > s2.size();
682}
683
684// Files which contain templates that need to be overwritten by build data should be overwritten every
685// time.
687{
688 return (fileName.endsWith("/res/values/libs.xml"_L1)
689 || fileName.endsWith("/AndroidManifest.xml"_L1)
690 || fileName.endsWith("/res/values/strings.xml"_L1)
691 || fileName.endsWith("/src/org/qtproject/qt/android/bindings/QtActivity.java"_L1));
692}
693
694
695bool copyFileIfNewer(const QString &sourceFileName,
696 const QString &destinationFileName,
697 const Options &options,
698 bool forceOverwrite = false)
699{
700 dependenciesForDepfile << sourceFileName;
701 if (QFile::exists(destinationFileName)) {
702 QFileInfo destinationFileInfo(destinationFileName);
703 QFileInfo sourceFileInfo(sourceFileName);
704
705 if (!forceOverwrite
706 && sourceFileInfo.lastModified() <= destinationFileInfo.lastModified()
707 && !alwaysOverwritableFile(destinationFileName)) {
708 if (options.verbose)
709 fprintf(stdout, " -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName));
710 return true;
711 } else {
712 if (!QFile(destinationFileName).remove()) {
713 fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName));
714 return false;
715 }
716 }
717 }
718
719 if (!QDir().mkpath(QFileInfo(destinationFileName).path())) {
720 fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName));
721 return false;
722 }
723
724 if (!QFile::exists(destinationFileName) && !QFile::copy(sourceFileName, destinationFileName)) {
725 fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName));
726 return false;
727 } else if (options.verbose) {
728 fprintf(stdout, " -- Copied %s\n", qPrintable(destinationFileName));
729 fflush(stdout);
730 }
731 return true;
732}
733
735{
736 auto isLegalChar = [] (QChar c) -> bool {
737 ushort ch = c.unicode();
738 return (ch >= '0' && ch <= '9') ||
739 (ch >= 'A' && ch <= 'Z') ||
740 (ch >= 'a' && ch <= 'z') ||
741 ch == '.';
742 };
743 for (QChar &c : packageName) {
744 if (!isLegalChar(c))
745 c = u'_';
746 }
747
748 static QStringList keywords;
749 if (keywords.isEmpty()) {
750 keywords << "abstract"_L1 << "continue"_L1 << "for"_L1
751 << "new"_L1 << "switch"_L1 << "assert"_L1
752 << "default"_L1 << "if"_L1 << "package"_L1
753 << "synchronized"_L1 << "boolean"_L1 << "do"_L1
754 << "goto"_L1 << "private"_L1 << "this"_L1
755 << "break"_L1 << "double"_L1 << "implements"_L1
756 << "protected"_L1 << "throw"_L1 << "byte"_L1
757 << "else"_L1 << "import"_L1 << "public"_L1
758 << "throws"_L1 << "case"_L1 << "enum"_L1
759 << "instanceof"_L1 << "return"_L1 << "transient"_L1
760 << "catch"_L1 << "extends"_L1 << "int"_L1
761 << "short"_L1 << "try"_L1 << "char"_L1
762 << "final"_L1 << "interface"_L1 << "static"_L1
763 << "void"_L1 << "class"_L1 << "finally"_L1
764 << "long"_L1 << "strictfp"_L1 << "volatile"_L1
765 << "const"_L1 << "float"_L1 << "native"_L1
766 << "super"_L1 << "while"_L1;
767 }
768
769 // No keywords
770 qsizetype index = -1;
771 while (index < packageName.size()) {
772 qsizetype next = packageName.indexOf(u'.', index + 1);
773 if (next == -1)
774 next = packageName.size();
775 QString word = packageName.mid(index + 1, next - index - 1);
776 if (!word.isEmpty()) {
777 QChar c = word[0];
778 if ((c >= u'0' && c <= u'9') || c == u'_') {
779 packageName.insert(index + 1, u'a');
780 index = next + 1;
781 continue;
782 }
783 }
784 if (keywords.contains(word)) {
785 packageName.insert(next, "_"_L1);
786 index = next + 1;
787 } else {
788 index = next;
789 }
790 }
791
792 return packageName;
793}
794
796{
797 QDir dir(sdkPath + "/platforms"_L1);
798 if (!dir.exists()) {
799 fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath()));
800 return QString();
801 }
802
803 QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
804 if (fileInfos.isEmpty()) {
805 fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath()));
806 return QString();
807 }
808
809 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
810
811 QFileInfo latestPlatform = fileInfos.first();
812 return latestPlatform.baseName();
813}
814
816{
817 QFile androidManifestXml(androidManifestPath);
818 if (androidManifestXml.open(QIODevice::ReadOnly)) {
819 QXmlStreamReader reader(&androidManifestXml);
820 while (!reader.atEnd()) {
821 reader.readNext();
822 if (reader.isStartElement() && reader.name() == "manifest"_L1)
823 return cleanPackageName(reader.attributes().value("package"_L1).toString());
824 }
825 }
826 return {};
827}
828
830{
831 const QString stringValue = value.toString();
832 return (stringValue.compare(QString::fromUtf8("true"), Qt::CaseInsensitive)
833 || stringValue.compare(QString::fromUtf8("on"), Qt::CaseInsensitive)
834 || stringValue.compare(QString::fromUtf8("yes"), Qt::CaseInsensitive)
835 || stringValue.compare(QString::fromUtf8("y"), Qt::CaseInsensitive)
836 || stringValue.toInt() > 0);
837}
838
839bool readInputFileDirectory(Options *options, QJsonObject &jsonObject, const QString keyName)
840{
841 const QJsonValue qtDirectory = jsonObject.value(keyName);
842 if (qtDirectory.isUndefined()) {
843 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
844 if (keyName == "qtDataDirectory"_L1) {
845 options->architectures[it.key()].qtDirectories[keyName] = it.value().qtInstallDirectory;
846 break;
847 } else if (keyName == "qtLibsDirectory"_L1) {
848 options->architectures[it.key()].qtDirectories[keyName] = "lib"_L1;
849 break;
850 } else if (keyName == "qtLibExecsDirectory"_L1) {
851 options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
852 break;
853 } else if (keyName == "qtPluginsDirectory"_L1) {
854 options->architectures[it.key()].qtDirectories[keyName] = "plugins"_L1;
855 break;
856 } else if (keyName == "qtQmlDirectory"_L1) {
857 options->architectures[it.key()].qtDirectories[keyName] = "qml"_L1;
858 break;
859 }
860 }
861 return true;
862 }
863
864 if (qtDirectory.isObject()) {
865 const QJsonObject object = qtDirectory.toObject();
866 for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
867 if (it.value().isUndefined()) {
868 fprintf(stderr,
869 "Invalid '%s' record in deployment settings: %s\n",
871 qPrintable(it.value().toString()));
872 return false;
873 }
874 if (it.value().isNull())
875 continue;
876 if (!options->architectures.contains(it.key())) {
877 fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()),
878 qPrintable(options->architectures.keys().join(u',')));
879 return false;
880 }
881 options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
882 }
883 } else if (qtDirectory.isString()) {
884 // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch.
885 // We assume Qt > 5.14 where all architectures are in the same directory.
886 const QString directory = qtDirectory.toString();
887 options->architectures["arm64-v8a"_L1].qtDirectories[keyName] = directory;
888 options->architectures["armeabi-v7a"_L1].qtDirectories[keyName] = directory;
889 options->architectures["x86"_L1].qtDirectories[keyName] = directory;
890 options->architectures["x86_64"_L1].qtDirectories[keyName] = directory;
891 } else {
892 fprintf(stderr, "Invalid format for %s in json file %s.\n",
894 return false;
895 }
896 return true;
897}
898
899bool readInputFile(Options *options)
900{
901 QFile file(options->inputFileName);
903 fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
904 return false;
905 }
907
909 if (jsonDocument.isNull()) {
910 fprintf(stderr, "Invalid json file: %s\n", qPrintable(options->inputFileName));
911 return false;
912 }
913
914 QJsonObject jsonObject = jsonDocument.object();
915
916 {
917 QJsonValue sdkPath = jsonObject.value("sdk"_L1);
918 if (sdkPath.isUndefined()) {
919 fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName));
920 return false;
921 }
922
923 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
924
925 if (options->androidPlatform.isEmpty()) {
927 if (options->androidPlatform.isEmpty())
928 return false;
929 } else {
930 if (!QDir(options->sdkPath + "/platforms/"_L1 + options->androidPlatform).exists()) {
931 fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n",
932 qPrintable(options->androidPlatform));
933 }
934 }
935 }
936
937 {
938
939 const QJsonValue value = jsonObject.value("sdkBuildToolsRevision"_L1);
940 if (!value.isUndefined())
941 options->sdkBuildToolsVersion = value.toString();
942 }
943
944 {
945 const QJsonValue qtInstallDirectory = jsonObject.value("qt"_L1);
946 if (qtInstallDirectory.isUndefined()) {
947 fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName));
948 return false;
949 }
950
951 if (qtInstallDirectory.isObject()) {
952 const QJsonObject object = qtInstallDirectory.toObject();
953 for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
954 if (it.value().isUndefined()) {
955 fprintf(stderr,
956 "Invalid 'qt' record in deployment settings: %s\n",
957 qPrintable(it.value().toString()));
958 return false;
959 }
960 if (it.value().isNull())
961 continue;
962 options->architectures.insert(it.key(),
963 QtInstallDirectoryWithTriple(it.value().toString()));
964 }
965 } else if (qtInstallDirectory.isString()) {
966 // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch.
967 // We assume Qt > 5.14 where all architectures are in the same directory.
968 const QString directory = qtInstallDirectory.toString();
969 QtInstallDirectoryWithTriple qtInstallDirectoryWithTriple(directory);
970 options->architectures.insert("arm64-v8a"_L1, qtInstallDirectoryWithTriple);
971 options->architectures.insert("armeabi-v7a"_L1, qtInstallDirectoryWithTriple);
972 options->architectures.insert("x86"_L1, qtInstallDirectoryWithTriple);
973 options->architectures.insert("x86_64"_L1, qtInstallDirectoryWithTriple);
974 // In Qt < 6 rcc and qmlimportscanner are installed in the host and install directories
975 // In Qt >= 6 rcc and qmlimportscanner are only installed in the host directory
976 // So setting the "qtHostDir" is not necessary with Qt < 6.
977 options->qtHostDirectory = directory;
978 } else {
979 fprintf(stderr, "Invalid format for Qt install prefixes in json file %s.\n",
980 qPrintable(options->inputFileName));
981 return false;
982 }
983 }
984
985 if (!readInputFileDirectory(options, jsonObject, "qtDataDirectory"_L1) ||
986 !readInputFileDirectory(options, jsonObject, "qtLibsDirectory"_L1) ||
987 !readInputFileDirectory(options, jsonObject, "qtLibExecsDirectory"_L1) ||
988 !readInputFileDirectory(options, jsonObject, "qtPluginsDirectory"_L1) ||
989 !readInputFileDirectory(options, jsonObject, "qtQmlDirectory"_L1))
990 return false;
991
992 {
993 const QJsonValue qtHostDirectory = jsonObject.value("qtHostDir"_L1);
994 if (!qtHostDirectory.isUndefined()) {
995 if (qtHostDirectory.isString()) {
996 options->qtHostDirectory = qtHostDirectory.toString();
997 } else {
998 fprintf(stderr, "Invalid format for Qt host directory in json file %s.\n",
999 qPrintable(options->inputFileName));
1000 return false;
1001 }
1002 }
1003 }
1004
1005 {
1006 const auto extraPrefixDirs = jsonObject.value("extraPrefixDirs"_L1).toArray();
1007 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
1008 for (const QJsonValue prefix : extraPrefixDirs) {
1009 options->extraPrefixDirs.push_back(prefix.toString());
1010 }
1011 }
1012
1013 {
1014 const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray();
1015 options->extraLibraryDirs.reserve(extraLibraryDirs.size());
1016 for (const QJsonValue path : extraLibraryDirs) {
1017 options->extraLibraryDirs.push_back(path.toString());
1018 }
1019 }
1020
1021 {
1022 const QJsonValue androidSourcesDirectory = jsonObject.value("android-package-source-directory"_L1);
1023 if (!androidSourcesDirectory.isUndefined())
1024 options->androidSourceDirectory = androidSourcesDirectory.toString();
1025 }
1026
1027 {
1028 const QJsonValue applicationArguments = jsonObject.value("android-application-arguments"_L1);
1029 if (!applicationArguments.isUndefined())
1030 options->applicationArguments = applicationArguments.toString();
1031 else
1032 options->applicationArguments = QStringLiteral("");
1033 }
1034
1035 {
1036 const QJsonValue androidVersionName = jsonObject.value("android-version-name"_L1);
1037 if (!androidVersionName.isUndefined())
1038 options->versionName = androidVersionName.toString();
1039 else
1040 options->versionName = QStringLiteral("1.0");
1041 }
1042
1043 {
1044 const QJsonValue androidVersionCode = jsonObject.value("android-version-code"_L1);
1045 if (!androidVersionCode.isUndefined())
1046 options->versionCode = androidVersionCode.toString();
1047 else
1048 options->versionCode = QStringLiteral("1");
1049 }
1050
1051 {
1052 const QJsonValue ver = jsonObject.value("android-min-sdk-version"_L1);
1053 if (!ver.isUndefined())
1054 options->minSdkVersion = ver.toString().toUtf8();
1055 }
1056
1057 {
1058 const QJsonValue ver = jsonObject.value("android-target-sdk-version"_L1);
1059 if (!ver.isUndefined())
1060 options->targetSdkVersion = ver.toString().toUtf8();
1061 }
1062
1063 {
1064 const QJsonObject targetArchitectures = jsonObject.value("architectures"_L1).toObject();
1065 if (targetArchitectures.isEmpty()) {
1066 fprintf(stderr, "No target architecture defined in json file.\n");
1067 return false;
1068 }
1069 for (auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
1070 if (it.value().isUndefined()) {
1071 fprintf(stderr, "Invalid architecture.\n");
1072 return false;
1073 }
1074 if (it.value().isNull())
1075 continue;
1076 if (!options->architectures.contains(it.key())) {
1077 fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()),
1078 qPrintable(options->architectures.keys().join(u',')));
1079 return false;
1080 }
1081 options->architectures[it.key()].triple = it.value().toString();
1082 options->architectures[it.key()].enabled = true;
1083 }
1084 }
1085
1086 {
1087 const QJsonValue ndk = jsonObject.value("ndk"_L1);
1088 if (ndk.isUndefined()) {
1089 fprintf(stderr, "No NDK path defined in json file.\n");
1090 return false;
1091 }
1092 options->ndkPath = ndk.toString();
1093 const QString ndkPropertiesPath = options->ndkPath + QStringLiteral("/source.properties");
1094 const QSettings settings(ndkPropertiesPath, QSettings::IniFormat);
1095 const QString ndkVersion = settings.value(QStringLiteral("Pkg.Revision")).toString();
1096 if (ndkVersion.isEmpty()) {
1097 fprintf(stderr, "Couldn't retrieve the NDK version from \"%s\".\n",
1098 qPrintable(ndkPropertiesPath));
1099 return false;
1100 }
1101 options->ndkVersion = ndkVersion;
1102 }
1103
1104 {
1105 const QJsonValue toolchainPrefix = jsonObject.value("toolchain-prefix"_L1);
1106 if (toolchainPrefix.isUndefined()) {
1107 fprintf(stderr, "No toolchain prefix defined in json file.\n");
1108 return false;
1109 }
1110 options->toolchainPrefix = toolchainPrefix.toString();
1111 }
1112
1113 {
1114 const QJsonValue ndkHost = jsonObject.value("ndk-host"_L1);
1115 if (ndkHost.isUndefined()) {
1116 fprintf(stderr, "No NDK host defined in json file.\n");
1117 return false;
1118 }
1119 options->ndkHost = ndkHost.toString();
1120 }
1121
1122 {
1123 const QJsonValue extraLibs = jsonObject.value("android-extra-libs"_L1);
1124 if (!extraLibs.isUndefined())
1125 options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts);
1126 }
1127
1128 {
1129 const QJsonValue qmlSkipImportScanning = jsonObject.value("qml-skip-import-scanning"_L1);
1130 if (!qmlSkipImportScanning.isUndefined())
1131 options->qmlSkipImportScanning = qmlSkipImportScanning.toBool();
1132 }
1133
1134 {
1135 const QJsonValue extraPlugins = jsonObject.value("android-extra-plugins"_L1);
1136 if (!extraPlugins.isUndefined())
1137 options->extraPlugins = extraPlugins.toString().split(u',');
1138 }
1139
1140 {
1141 const QJsonValue systemLibsPath =
1142 jsonObject.value("android-system-libs-prefix"_L1);
1143 if (!systemLibsPath.isUndefined())
1144 options->systemLibsPath = systemLibsPath.toString();
1145 }
1146
1147 {
1148 const QJsonValue noDeploy = jsonObject.value("android-no-deploy-qt-libs"_L1);
1149 if (!noDeploy.isUndefined()) {
1150 bool useUnbundled = parseCmakeBoolean(noDeploy);
1151 options->deploymentMechanism = useUnbundled ? Options::Unbundled :
1153 }
1154 }
1155
1156 {
1157 const QJsonValue stdcppPath = jsonObject.value("stdcpp-path"_L1);
1158 if (stdcppPath.isUndefined()) {
1159 fprintf(stderr, "No stdcpp-path defined in json file.\n");
1160 return false;
1161 }
1162 options->stdCppPath = stdcppPath.toString();
1163 }
1164
1165 {
1166 const QJsonValue qmlRootPath = jsonObject.value("qml-root-path"_L1);
1167 if (qmlRootPath.isString()) {
1168 options->rootPaths.push_back(qmlRootPath.toString());
1169 } else if (qmlRootPath.isArray()) {
1170 auto qmlRootPaths = qmlRootPath.toArray();
1171 for (auto path : qmlRootPaths) {
1172 if (path.isString())
1173 options->rootPaths.push_back(path.toString());
1174 }
1175 } else {
1176 options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
1177 }
1178 }
1179
1180 {
1181 const QJsonValue qmlImportPaths = jsonObject.value("qml-import-paths"_L1);
1182 if (!qmlImportPaths.isUndefined())
1183 options->qmlImportPaths = qmlImportPaths.toString().split(u',');
1184 }
1185
1186 {
1187 const QJsonValue qmlImportScannerBinaryPath = jsonObject.value("qml-importscanner-binary"_L1);
1188 if (!qmlImportScannerBinaryPath.isUndefined())
1189 options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString();
1190 }
1191
1192 {
1193 const QJsonValue rccBinaryPath = jsonObject.value("rcc-binary"_L1);
1194 if (!rccBinaryPath.isUndefined())
1195 options->rccBinaryPath = rccBinaryPath.toString();
1196 }
1197
1198 {
1199 const QJsonValue applicationBinary = jsonObject.value("application-binary"_L1);
1200 if (applicationBinary.isUndefined()) {
1201 fprintf(stderr, "No application binary defined in json file.\n");
1202 return false;
1203 }
1204 options->applicationBinary = applicationBinary.toString();
1205 if (options->build) {
1206 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1207 if (!it->enabled)
1208 continue;
1209 auto appBinaryPath = "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary);
1210 if (!QFile::exists(appBinaryPath)) {
1211 fprintf(stderr, "Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
1212 return false;
1213 }
1214 }
1215 }
1216 }
1217
1218 {
1219 const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1);
1220 if (!deploymentDependencies.isUndefined()) {
1221 QString deploymentDependenciesString = deploymentDependencies.toString();
1222 const auto dependencies = QStringView{deploymentDependenciesString}.split(u',');
1223 for (const auto &dependency : dependencies) {
1225 path += dependency;
1226 if (QFileInfo(path).isDir()) {
1228 while (iterator.hasNext()) {
1229 iterator.next();
1230 if (iterator.fileInfo().isFile()) {
1231 QString subPath = iterator.filePath();
1232 auto arch = fileArchitecture(*options, subPath);
1233 if (!arch.isEmpty()) {
1234 options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.size() + 1),
1235 subPath));
1236 } else if (options->verbose) {
1237 fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(subPath));
1238 fflush(stderr);
1239 }
1240 }
1241 }
1242 } else {
1243 auto qtDependency = [options](const QStringView &dependency,
1244 const QString &arch) {
1245 const auto installDir = options->architectures[arch].qtInstallDirectory;
1246 const auto absolutePath = "%1/%2"_L1.arg(installDir, dependency.toString());
1247 return QtDependency(dependency.toString(), absolutePath);
1248 };
1249
1250 if (dependency.endsWith(QLatin1String(".so"))) {
1251 auto arch = fileArchitecture(*options, path);
1252 if (!arch.isEmpty()) {
1253 options->qtDependencies[arch].append(qtDependency(dependency, arch));
1254 } else if (options->verbose) {
1255 fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path));
1256 fflush(stderr);
1257 }
1258 } else {
1259 for (auto arch : options->architectures.keys())
1260 options->qtDependencies[arch].append(qtDependency(dependency, arch));
1261 }
1262 }
1263 }
1264 }
1265 }
1266 {
1267 const QJsonValue qrcFiles = jsonObject.value("qrcFiles"_L1);
1268 options->qrcFiles = qrcFiles.toString().split(u',', Qt::SkipEmptyParts);
1269 }
1270 {
1271 const QJsonValue zstdCompressionFlag = jsonObject.value("zstdCompression"_L1);
1272 if (zstdCompressionFlag.isBool()) {
1273 options->isZstdCompressionEnabled = zstdCompressionFlag.toBool();
1274 }
1275 }
1276 options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + "/AndroidManifest.xml"_L1);
1277 if (options->packageName.isEmpty())
1278 options->packageName = cleanPackageName("org.qtproject.example.%1"_L1.arg(options->applicationBinary));
1279
1280 return true;
1281}
1282
1283bool isDeployment(const Options *options, Options::DeploymentMechanism deployment)
1284{
1285 return options->deploymentMechanism == deployment;
1286}
1287
1288bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite = false)
1289{
1290 const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
1291 for (const QFileInfo &entry : entries) {
1292 if (entry.isDir()) {
1293 QDir dir(entry.absoluteFilePath());
1294 if (!destinationDirectory.mkpath(dir.dirName())) {
1295 fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
1296 return false;
1297 }
1298
1299 if (!copyFiles(dir, QDir(destinationDirectory.path() + u'/' + dir.dirName()), options, forceOverwrite))
1300 return false;
1301 } else {
1302 QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
1303 if (!copyFileIfNewer(entry.absoluteFilePath(), destination, options, forceOverwrite))
1304 return false;
1305 }
1306 }
1307
1308 return true;
1309}
1310
1311void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
1312{
1313 const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
1314 for (const QFileInfo &dir : dirs) {
1315 if (dir.fileName() != "libs"_L1)
1316 deleteMissingFiles(options, dir.absoluteFilePath(), QDir(dstDir + dir.fileName()));
1317 }
1318}
1319
1320void cleanAndroidFiles(const Options &options)
1321{
1322 if (!options.androidSourceDirectory.isEmpty())
1323 cleanTopFolders(options, QDir(options.androidSourceDirectory), options.outputDirectory);
1324
1325 cleanTopFolders(options,
1326 QDir(options.qtInstallDirectory + u'/' +
1327 options.qtDataDirectory + "/src/android/templates"_L1),
1328 options.outputDirectory);
1329}
1330
1331bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
1332{
1333 QDir sourceDirectory(options.qtInstallDirectory + u'/' + options.qtDataDirectory + androidTemplate);
1334 if (!sourceDirectory.exists()) {
1335 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1336 return false;
1337 }
1338
1339 QString outDir = options.outputDirectory + outDirPrefix;
1340
1341 if (!QDir::current().mkpath(outDir)) {
1342 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1343 return false;
1344 }
1345
1346 return copyFiles(sourceDirectory, QDir(outDir), options);
1347}
1348
1349bool copyGradleTemplate(const Options &options)
1350{
1351 QDir sourceDirectory(options.qtInstallDirectory + u'/' +
1352 options.qtDataDirectory + "/src/3rdparty/gradle"_L1);
1353 if (!sourceDirectory.exists()) {
1354 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1355 return false;
1356 }
1357
1358 QString outDir(options.outputDirectory);
1359 if (!QDir::current().mkpath(outDir)) {
1360 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1361 return false;
1362 }
1363
1364 return copyFiles(sourceDirectory, QDir(outDir), options);
1365}
1366
1367bool copyAndroidTemplate(const Options &options)
1368{
1369 if (options.verbose)
1370 fprintf(stdout, "Copying Android package template.\n");
1371
1372 if (!copyGradleTemplate(options))
1373 return false;
1374
1375 if (!copyAndroidTemplate(options, "/src/android/templates"_L1))
1376 return false;
1377
1378 return true;
1379}
1380
1381bool copyAndroidSources(const Options &options)
1382{
1383 if (options.androidSourceDirectory.isEmpty())
1384 return true;
1385
1386 if (options.verbose)
1387 fprintf(stdout, "Copying Android sources from project.\n");
1388
1389 QDir sourceDirectory(options.androidSourceDirectory);
1390 if (!sourceDirectory.exists()) {
1391 fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
1392 return false;
1393 }
1394
1395 return copyFiles(sourceDirectory, QDir(options.outputDirectory), options, true);
1396}
1397
1399{
1400 if (options->extraLibs.isEmpty())
1401 return true;
1402
1403 if (options->verbose) {
1404 switch (options->deploymentMechanism) {
1405 case Options::Bundled:
1406 fprintf(stdout, "Copying %zd external libraries to package.\n", size_t(options->extraLibs.size()));
1407 break;
1408 case Options::Unbundled:
1409 fprintf(stdout, "Skip copying of external libraries.\n");
1410 break;
1411 };
1412 }
1413
1414 for (const QString &extraLib : options->extraLibs) {
1415 QFileInfo extraLibInfo(extraLib);
1416 if (!extraLibInfo.exists()) {
1417 fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
1418 return false;
1419 }
1420 if (!checkArchitecture(*options, extraLibInfo.filePath())) {
1421 if (options->verbose)
1422 fprintf(stdout, "Skipping \"%s\", architecture mismatch.\n", qPrintable(extraLib));
1423 continue;
1424 }
1425 if (!extraLibInfo.fileName().startsWith("lib"_L1) || extraLibInfo.suffix() != "so"_L1) {
1426 fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
1427 qPrintable(extraLib));
1428 return false;
1429 }
1430 QString destinationFile(options->outputDirectory
1431 + "/libs/"_L1
1432 + options->currentArchitecture
1433 + u'/'
1434 + extraLibInfo.fileName());
1435
1436 if (isDeployment(options, Options::Bundled)
1437 && !copyFileIfNewer(extraLib, destinationFile, *options)) {
1438 return false;
1439 }
1440 options->archExtraLibs[options->currentArchitecture] += extraLib;
1441 }
1442
1443 return true;
1444}
1445
1446QStringList allFilesInside(const QDir& current, const QDir& rootDir)
1447{
1449 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1450 const auto files = current.entryList(QDir::Files);
1451 result.reserve(dirs.size() + files.size());
1452 for (const QString &dir : dirs) {
1453 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1454 }
1455 for (const QString &file : files) {
1456 result += rootDir.relativeFilePath(current.filePath(file));
1457 }
1458 return result;
1459}
1460
1462{
1463 if (options->extraPlugins.isEmpty())
1464 return true;
1465
1466 if (options->verbose)
1467 fprintf(stdout, "Copying %zd external resources to package.\n", size_t(options->extraPlugins.size()));
1468
1469 for (const QString &extraResource : options->extraPlugins) {
1470 QFileInfo extraResourceInfo(extraResource);
1471 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1472 fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1473 return false;
1474 }
1475
1476 QDir resourceDir(extraResource);
1477 QString assetsDir = options->outputDirectory + "/assets/"_L1 +
1478 resourceDir.dirName() + u'/';
1479 QString libsDir = options->outputDirectory + "/libs/"_L1 + options->currentArchitecture + u'/';
1480
1481 const QStringList files = allFilesInside(resourceDir, resourceDir);
1482 for (const QString &resourceFile : files) {
1483 QString originFile(resourceDir.filePath(resourceFile));
1484 QString destinationFile;
1485 if (!resourceFile.endsWith(".so"_L1)) {
1486 destinationFile = assetsDir + resourceFile;
1487 } else {
1488 if (isDeployment(options, Options::Unbundled)
1489 || !checkArchitecture(*options, originFile)) {
1490 continue;
1491 }
1492 destinationFile = libsDir + resourceFile;
1493 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1494 }
1495 if (!copyFileIfNewer(originFile, destinationFile, *options))
1496 return false;
1497 }
1498 }
1499
1500 return true;
1501}
1502
1503bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
1504{
1505 QFile inputFile(fileName);
1506 if (!inputFile.open(QIODevice::ReadOnly)) {
1507 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
1508 return false;
1509 }
1510
1511 // All the files we are doing substitutes in are quite small. If this
1512 // ever changes, this code should be updated to be more conservative.
1513 QByteArray contents = inputFile.readAll();
1514
1515 bool hasReplacements = false;
1517 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1518 if (it.key() == it.value())
1519 continue; // Nothing to actually replace
1520
1521 forever {
1522 int index = contents.indexOf(it.key().toUtf8());
1523 if (index >= 0) {
1524 contents.replace(index, it.key().size(), it.value().toUtf8());
1525 hasReplacements = true;
1526 } else {
1527 break;
1528 }
1529 }
1530 }
1531
1532 if (hasReplacements) {
1533 inputFile.close();
1534
1535 if (!inputFile.open(QIODevice::WriteOnly)) {
1536 fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
1537 return false;
1538 }
1539
1540 inputFile.write(contents);
1541 }
1542
1543 return true;
1544
1545}
1546
1548{
1549 if (options->verbose)
1550 fprintf(stdout, " -- res/values/libs.xml\n");
1551
1552 QString fileName = options->outputDirectory + "/res/values/libs.xml"_L1;
1553 if (!QFile::exists(fileName)) {
1554 fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1555 return false;
1556 }
1557
1558 QString qtLibs;
1559 QString allLocalLibs;
1560 QString extraLibs;
1561
1562 for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1563 if (!it->enabled)
1564 continue;
1565
1566 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), options->stdCppName);
1567 for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) {
1568 if (bundledFile.second.startsWith("lib/lib"_L1)) {
1569 if (!bundledFile.second.endsWith(".so"_L1)) {
1570 fprintf(stderr,
1571 "The bundled library %s doesn't end with .so. Android only supports "
1572 "versionless libraries ending with the .so suffix.\n",
1573 qPrintable(bundledFile.second));
1574 return false;
1575 }
1576 QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
1577 s.chop(sizeof(".so") - 1);
1578 qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), s);
1579 }
1580 }
1581
1582 if (!options->archExtraLibs[it.key()].isEmpty()) {
1583 for (const QString &extraLib : options->archExtraLibs[it.key()]) {
1584 QFileInfo extraLibInfo(extraLib);
1585 if (extraLibInfo.fileName().startsWith("lib"_L1)) {
1586 if (!extraLibInfo.fileName().endsWith(".so"_L1)) {
1587 fprintf(stderr,
1588 "The library %s doesn't end with .so. Android only supports "
1589 "versionless libraries ending with the .so suffix.\n",
1590 qPrintable(extraLibInfo.fileName()));
1591 return false;
1592 }
1593 QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
1594 name.chop(sizeof(".so") - 1);
1595 extraLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), name);
1596 }
1597 }
1598 }
1599
1600 QStringList localLibs;
1601 localLibs = options->localLibs[it.key()];
1602 // If .pro file overrides dependency detection, we need to see which platform plugin they picked
1603 if (localLibs.isEmpty()) {
1604 QString plugin;
1605 for (const QtDependency &qtDependency : options->qtDependencies[it.key()]) {
1606 if (qtDependency.relativePath.contains("libplugins_platforms_qtforandroid_"_L1))
1607 plugin = qtDependency.relativePath;
1608
1609 if (qtDependency.relativePath.contains(
1610 QString::asprintf("libQt%dOpenGL", QT_VERSION_MAJOR))
1611 || qtDependency.relativePath.contains(
1612 QString::asprintf("libQt%dQuick", QT_VERSION_MAJOR))) {
1613 options->usesOpenGL |= true;
1614 }
1615 }
1616
1617 if (plugin.isEmpty()) {
1618 fflush(stdout);
1619 fprintf(stderr, "No platform plugin (libplugins_platforms_qtforandroid.so) included"
1620 " in the deployment. Make sure the app links to Qt Gui library.\n");
1621 fflush(stderr);
1622 return false;
1623 }
1624
1625 localLibs.append(plugin);
1626 if (options->verbose)
1627 fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin));
1628 }
1629
1630 // remove all paths
1631 for (auto &lib : localLibs) {
1632 if (lib.endsWith(".so"_L1))
1633 lib = lib.mid(lib.lastIndexOf(u'/') + 1);
1634 }
1635 allLocalLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), localLibs.join(u':'));
1636 }
1637
1638 options->initClasses.removeDuplicates();
1639
1640 QHash<QString, QString> replacements;
1641 replacements[QStringLiteral("<!-- %%INSERT_QT_LIBS%% -->")] += qtLibs.trimmed();
1642 replacements[QStringLiteral("<!-- %%INSERT_LOCAL_LIBS%% -->")] = allLocalLibs.trimmed();
1643 replacements[QStringLiteral("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs.trimmed();
1644 const QString initClasses = options->initClasses.join(u':');
1645 replacements[QStringLiteral("<!-- %%INSERT_INIT_CLASSES%% -->")] = initClasses;
1646
1647 // Set BUNDLE_LOCAL_QT_LIBS based on the deployment used
1648 replacements[QStringLiteral("<!-- %%BUNDLE_LOCAL_QT_LIBS%% -->")]
1649 = isDeployment(options, Options::Unbundled) ? "0"_L1 : "1"_L1;
1650 replacements[QStringLiteral("<!-- %%USE_LOCAL_QT_LIBS%% -->")] = "1"_L1;
1651 replacements[QStringLiteral("<!-- %%SYSTEM_LIBS_PREFIX%% -->")] =
1653
1654 if (!updateFile(fileName, replacements))
1655 return false;
1656
1657 return true;
1658}
1659
1660bool updateStringsXml(const Options &options)
1661{
1662 if (options.verbose)
1663 fprintf(stdout, " -- res/values/strings.xml\n");
1664
1665 QHash<QString, QString> replacements;
1666 replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = options.applicationBinary;
1667
1668 QString fileName = options.outputDirectory + "/res/values/strings.xml"_L1;
1669 if (!QFile::exists(fileName)) {
1670 if (options.verbose)
1671 fprintf(stdout, " -- Create strings.xml since it's missing.\n");
1674 fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
1675 return false;
1676 }
1677 file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\" translatable=\"false\">")
1679 .append("</string></resources>\n"));
1680 return true;
1681 }
1682
1683 if (!updateFile(fileName, replacements))
1684 return false;
1685
1686 return true;
1687}
1688
1690{
1691 if (options.verbose)
1692 fprintf(stdout, " -- AndroidManifest.xml \n");
1693
1694 QHash<QString, QString> replacements;
1695 replacements[QStringLiteral("-- %%INSERT_APP_NAME%% --")] = options.applicationBinary;
1696 replacements[QStringLiteral("-- %%INSERT_APP_ARGUMENTS%% --")] = options.applicationArguments;
1697 replacements[QStringLiteral("-- %%INSERT_APP_LIB_NAME%% --")] = options.applicationBinary;
1698 replacements[QStringLiteral("-- %%INSERT_VERSION_NAME%% --")] = options.versionName;
1699 replacements[QStringLiteral("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode;
1700 replacements[QStringLiteral("package=\"org.qtproject.example\"")] = "package=\"%1\""_L1.arg(options.packageName);
1701
1702 QString permissions;
1703 for (const QString &permission : std::as_const(options.permissions))
1704 permissions += " <uses-permission android:name=\"%1\" />\n"_L1.arg(permission);
1705 replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
1706
1707 QString features;
1708 for (const QString &feature : std::as_const(options.features))
1709 features += " <uses-feature android:name=\"%1\" android:required=\"false\" />\n"_L1.arg(feature);
1710 if (options.usesOpenGL)
1711 features += " <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"_L1;
1712
1713 replacements[QStringLiteral("<!-- %%INSERT_FEATURES -->")] = features.trimmed();
1714
1715 QString androidManifestPath = options.outputDirectory + "/AndroidManifest.xml"_L1;
1716 if (!updateFile(androidManifestPath, replacements))
1717 return false;
1718
1719 // read the package, min & target sdk API levels from manifest file.
1720 bool checkOldAndroidLabelString = false;
1721 QFile androidManifestXml(androidManifestPath);
1722 if (androidManifestXml.exists()) {
1723 if (!androidManifestXml.open(QIODevice::ReadOnly)) {
1724 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
1725 return false;
1726 }
1727
1728 QXmlStreamReader reader(&androidManifestXml);
1729 while (!reader.atEnd()) {
1730 reader.readNext();
1731
1732 if (reader.isStartElement()) {
1733 if (reader.name() == "manifest"_L1) {
1734 if (!reader.attributes().hasAttribute("package"_L1)) {
1735 fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath));
1736 return false;
1737 }
1738 options.packageName = reader.attributes().value("package"_L1).toString();
1739 } else if (reader.name() == "uses-sdk"_L1) {
1740 if (reader.attributes().hasAttribute("android:minSdkVersion"_L1))
1741 if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 23) {
1742 fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 23\n");
1743 return false;
1744 }
1745 } else if ((reader.name() == "application"_L1 ||
1746 reader.name() == "activity"_L1) &&
1747 reader.attributes().hasAttribute("android:label"_L1) &&
1748 reader.attributes().value("android:label"_L1) == "@string/app_name"_L1) {
1749 checkOldAndroidLabelString = true;
1750 } else if (reader.name() == "meta-data"_L1) {
1751 const auto name = reader.attributes().value("android:name"_L1);
1752 const auto value = reader.attributes().value("android:value"_L1);
1753 if (name == "android.app.lib_name"_L1 && value.contains(u' ')) {
1754 fprintf(stderr, "The Activity's android.app.lib_name should not contain"
1755 " spaces.\n");
1756 return false;
1757 }
1758 }
1759 }
1760 }
1761
1762 if (reader.hasError()) {
1763 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
1764 return false;
1765 }
1766 } else {
1767 fprintf(stderr, "No android manifest file");
1768 return false;
1769 }
1770
1771 if (checkOldAndroidLabelString)
1772 updateStringsXml(options);
1773
1774 return true;
1775}
1776
1778{
1779 if (options.verbose)
1780 fprintf(stdout, "Updating Android package files with project settings.\n");
1781
1782 if (!updateLibsXml(&options))
1783 return false;
1784
1785 if (!updateAndroidManifest(options))
1786 return false;
1787
1788 return true;
1789}
1790
1791static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
1792{
1793 // Use extraLibraryDirs as the extra library lookup folder if it is expected to find a file in
1794 // any $prefix/lib folder.
1795 // Library directories from a build tree(extraLibraryDirs) have the higher priority.
1796 if (relativeFileName.startsWith("lib/"_L1)) {
1797 for (const auto &dir : options->extraLibraryDirs) {
1798 const QString path = dir + u'/' + relativeFileName.mid(sizeof("lib/") - 1);
1799 if (QFile::exists(path))
1800 return path;
1801 }
1802 }
1803
1804 for (const auto &prefix : options->extraPrefixDirs) {
1805 const QString path = prefix + u'/' + relativeFileName;
1806 if (QFile::exists(path))
1807 return path;
1808 }
1809
1810 if (relativeFileName.endsWith("-android-dependencies.xml"_L1)) {
1811 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
1812 u'/' + relativeFileName;
1813 }
1814
1815 if (relativeFileName.startsWith("jar/"_L1)) {
1816 return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
1817 u'/' + relativeFileName;
1818 }
1819
1820 if (relativeFileName.startsWith("lib/"_L1)) {
1821 return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
1822 u'/' + relativeFileName.mid(sizeof("lib/") - 1);
1823 }
1824 return options->qtInstallDirectory + u'/' + relativeFileName;
1825}
1826
1828{
1829 if (!info.exists())
1830 return QList<QtDependency>();
1831
1832 if (info.isDir()) {
1834
1835 QDir dir(info.filePath());
1836 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
1837
1838 for (const QString &entry : entries) {
1839 ret += findFilesRecursively(options,
1841 rootPath);
1842 }
1843
1844 return ret;
1845 } else {
1847 }
1848}
1849
1851{
1852 for (const auto &prefix : options.extraPrefixDirs) {
1853 QFileInfo info(prefix + u'/' + fileName);
1854 if (info.exists())
1855 return findFilesRecursively(options, info, prefix + u'/');
1856 }
1857 QFileInfo info(options.qtInstallDirectory + "/"_L1 + fileName);
1858 QFileInfo rootPath(options.qtInstallDirectory + "/"_L1);
1859 return findFilesRecursively(options, info, rootPath.absolutePath() + u'/');
1860}
1861
1863 const QString &moduleName,
1864 QSet<QString> *usedDependencies,
1865 QSet<QString> *remainingDependencies)
1866{
1867 QString androidDependencyName = absoluteFilePath(options, "%1-android-dependencies.xml"_L1.arg(moduleName));
1868
1869 QFile androidDependencyFile(androidDependencyName);
1870 if (androidDependencyFile.exists()) {
1871 if (options->verbose)
1872 fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
1873
1874 if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
1875 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
1876 return false;
1877 }
1878
1879 QXmlStreamReader reader(&androidDependencyFile);
1880 while (!reader.atEnd()) {
1881 reader.readNext();
1882
1883 if (reader.isStartElement()) {
1884 if (reader.name() == "bundled"_L1) {
1885 if (!reader.attributes().hasAttribute("file"_L1)) {
1886 fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
1887 return false;
1888 }
1889
1890 QString file = reader.attributes().value("file"_L1).toString();
1891
1893
1894 for (const QtDependency &fileName : fileNames) {
1895 if (usedDependencies->contains(fileName.absolutePath))
1896 continue;
1897
1898 if (fileName.absolutePath.endsWith(".so"_L1)) {
1899 QSet<QString> remainingDependencies;
1900 if (!readDependenciesFromElf(options, fileName.absolutePath,
1901 usedDependencies,
1902 &remainingDependencies)) {
1903 fprintf(stdout, "Skipping dependencies from xml: %s\n",
1904 qPrintable(fileName.relativePath));
1905 continue;
1906 }
1907 }
1908 usedDependencies->insert(fileName.absolutePath);
1909
1910 if (options->verbose)
1911 fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath));
1912
1913 options->qtDependencies[options->currentArchitecture].append(fileName);
1914 }
1915 } else if (reader.name() == "jar"_L1) {
1916 int bundling = reader.attributes().value("bundling"_L1).toInt();
1917 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
1918 if (bundling) {
1919 QtDependency dependency(fileName, absoluteFilePath(options, fileName));
1920 if (!usedDependencies->contains(dependency.absolutePath)) {
1921 options->qtDependencies[options->currentArchitecture].append(dependency);
1922 usedDependencies->insert(dependency.absolutePath);
1923 }
1924 }
1925
1926 if (reader.attributes().hasAttribute("initClass"_L1)) {
1927 options->initClasses.append(reader.attributes().value("initClass"_L1).toString());
1928 }
1929 } else if (reader.name() == "lib"_L1) {
1930 QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
1931 if (reader.attributes().hasAttribute("replaces"_L1)) {
1932 QString replaces = reader.attributes().value("replaces"_L1).toString();
1933 for (int i=0; i<options->localLibs.size(); ++i) {
1934 if (options->localLibs[options->currentArchitecture].at(i) == replaces) {
1935 options->localLibs[options->currentArchitecture][i] = fileName;
1936 break;
1937 }
1938 }
1939 } else if (!fileName.isEmpty()) {
1940 options->localLibs[options->currentArchitecture].append(fileName);
1941 }
1942 if (fileName.endsWith(".so"_L1) && checkArchitecture(*options, fileName)) {
1943 remainingDependencies->insert(fileName);
1944 }
1945 } else if (reader.name() == "permission"_L1) {
1946 QString name = reader.attributes().value("name"_L1).toString();
1947 options->permissions.append(name);
1948 } else if (reader.name() == "feature"_L1) {
1949 QString name = reader.attributes().value("name"_L1).toString();
1950 options->features.append(name);
1951 }
1952 }
1953 }
1954
1955 if (reader.hasError()) {
1956 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
1957 return false;
1958 }
1959 } else if (options->verbose) {
1960 fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
1961 }
1962 options->permissions.removeDuplicates();
1963 options->features.removeDuplicates();
1964
1965 return true;
1966}
1967
1969{
1970 QString readElf = llvmReadobjPath(options);
1971 if (!QFile::exists(readElf)) {
1972 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
1973 return QStringList();
1974 }
1975
1976 readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName));
1977
1978 FILE *readElfCommand = openProcess(readElf);
1979 if (!readElfCommand) {
1980 fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
1981 return QStringList();
1982 }
1983
1985
1986 bool readLibs = false;
1987 char buffer[512];
1988 while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
1990 QString library;
1991 line = line.trimmed();
1992 if (!readLibs) {
1993 if (line.startsWith("Arch: ")) {
1994 auto it = elfArchitectures.find(line.mid(6));
1995 if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
1996 if (options.verbose)
1997 fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
1998 return {};
1999 }
2000 }
2001 readLibs = line.startsWith("NeededLibraries");
2002 continue;
2003 }
2004 if (!line.startsWith("lib"))
2005 continue;
2006 library = QString::fromLatin1(line);
2007 QString libraryName = "lib/"_L1 + library;
2008 if (QFile::exists(absoluteFilePath(&options, libraryName)))
2009 ret += libraryName;
2010 }
2011
2012 pclose(readElfCommand);
2013
2014 return ret;
2015}
2016
2018 const QString &fileName,
2019 QSet<QString> *usedDependencies,
2020 QSet<QString> *remainingDependencies)
2021{
2022 // Get dependencies on libraries in $QTDIR/lib
2023 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
2024
2025 if (options->verbose) {
2026 fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
2027 for (const QString &dep : dependencies)
2028 fprintf(stdout, " %s\n", qPrintable(dep));
2029 }
2030 // Recursively add dependencies from ELF and supplementary XML information
2031 QList<QString> dependenciesToCheck;
2032 for (const QString &dependency : dependencies) {
2033 if (usedDependencies->contains(dependency))
2034 continue;
2035
2036 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
2037 usedDependencies->insert(dependency);
2038 if (!readDependenciesFromElf(options,
2039 absoluteDependencyPath,
2040 usedDependencies,
2041 remainingDependencies)) {
2042 return false;
2043 }
2044
2045 options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
2046 if (options->verbose)
2047 fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
2048 dependenciesToCheck.append(dependency);
2049 }
2050
2051 for (const QString &dependency : std::as_const(dependenciesToCheck)) {
2052 QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
2053 qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
2054 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
2055 return false;
2056 }
2057 }
2058
2059 return true;
2060}
2061
2062bool scanImports(Options *options, QSet<QString> *usedDependencies)
2063{
2064 if (options->verbose)
2065 fprintf(stdout, "Scanning for QML imports.\n");
2066
2067 QString qmlImportScanner;
2068 if (!options->qmlImportScannerBinaryPath.isEmpty()) {
2069 qmlImportScanner = options->qmlImportScannerBinaryPath;
2070 } else {
2071 qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
2072 "/qmlimportscanner"_L1);
2073 }
2074
2075 QStringList importPaths;
2076
2077 // In Conan's case, qtInstallDirectory will point only to qtbase installed files, which
2078 // lacks a qml directory. We don't want to pass it as an import path if it doesn't exist
2079 // because it will cause qmlimportscanner to fail.
2080 // This also covers the case when only qtbase is installed in a regular Qt build.
2081 const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
2082 if (QFile::exists(mainImportPath))
2083 importPaths += shellQuote(mainImportPath);
2084
2085 // These are usually provided by CMake in the deployment json file from paths specified
2086 // in CMAKE_FIND_ROOT_PATH. They might not have qml modules.
2087 for (const QString &prefix : options->extraPrefixDirs)
2088 if (QFile::exists(prefix + "/qml"_L1))
2089 importPaths += shellQuote(prefix + "/qml"_L1);
2090
2091 // These are provided by both CMake and qmake.
2092 for (const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
2093 if (QFile::exists(qmlImportPath)) {
2094 importPaths += shellQuote(qmlImportPath);
2095 } else {
2096 fprintf(stderr, "Warning: QML import path %s does not exist.\n",
2097 qPrintable(qmlImportPath));
2098 }
2099 }
2100
2101 bool qmlImportExists = false;
2102
2103 for (const QString &import : importPaths) {
2104 if (QDir().exists(import)) {
2105 qmlImportExists = true;
2106 break;
2107 }
2108 }
2109
2110 // Check importPaths without rootPath, since we need at least one qml plugins
2111 // folder to run a QML file
2112 if (!qmlImportExists) {
2113 fprintf(stderr, "Warning: no 'qml' directory found under Qt install directory "
2114 "or import paths. Skipping QML dependency scanning.\n");
2115 return true;
2116 }
2117
2118 if (!QFile::exists(qmlImportScanner)) {
2119 fprintf(stderr, "%s: qmlimportscanner not found at %s\n",
2120 qmlImportExists ? "Error"_L1.data() : "Warning"_L1.data(),
2121 qPrintable(qmlImportScanner));
2122 return true;
2123 }
2124
2125 for (auto rootPath : options->rootPaths) {
2126 rootPath = QFileInfo(rootPath).absoluteFilePath();
2127
2128 if (!rootPath.endsWith(u'/'))
2129 rootPath += u'/';
2130
2131 // After checking for qml folder imports we can add rootPath
2132 if (!rootPath.isEmpty())
2133 importPaths += shellQuote(rootPath);
2134
2135 qmlImportScanner += " -rootPath %1"_L1.arg(shellQuote(rootPath));
2136 }
2137
2138 if (!options->qrcFiles.isEmpty()) {
2139 qmlImportScanner += " -qrcFiles"_L1;
2140 for (const QString &qrcFile : options->qrcFiles)
2141 qmlImportScanner += u' ' + shellQuote(qrcFile);
2142 }
2143
2144 qmlImportScanner += " -importPath %1"_L1.arg(importPaths.join(u' '));
2145
2146 if (options->verbose) {
2147 fprintf(stdout, "Running qmlimportscanner with the following command: %s\n",
2148 qmlImportScanner.toLocal8Bit().constData());
2149 }
2150
2151 FILE *qmlImportScannerCommand = popen(qmlImportScanner.toLocal8Bit().constData(), QT_POPEN_READ);
2152 if (qmlImportScannerCommand == 0) {
2153 fprintf(stderr, "Couldn't run qmlimportscanner.\n");
2154 return false;
2155 }
2156
2158 char buffer[512];
2159 while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand) != 0)
2161
2163 if (jsonDocument.isNull()) {
2164 fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
2165 return false;
2166 }
2167
2168 QJsonArray jsonArray = jsonDocument.array();
2169 for (int i=0; i<jsonArray.count(); ++i) {
2170 QJsonValue value = jsonArray.at(i);
2171 if (!value.isObject()) {
2172 fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
2173 return false;
2174 }
2175
2176 QJsonObject object = value.toObject();
2177 QString path = object.value("path"_L1).toString();
2178 if (path.isEmpty()) {
2179 fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n",
2180 qPrintable(object.value("name"_L1).toString()));
2181 } else {
2182 if (options->verbose)
2183 fprintf(stdout, " -- Adding '%s' as QML dependency\n", qPrintable(path));
2184
2186
2187 // The qmlimportscanner sometimes outputs paths that do not exist.
2188 if (!info.exists()) {
2189 if (options->verbose)
2190 fprintf(stdout, " -- Skipping because path does not exist.\n");
2191 continue;
2192 }
2193
2195 if (!absolutePath.endsWith(u'/'))
2196 absolutePath += u'/';
2197
2198 const QUrl url(object.value("name"_L1).toString());
2199
2200 const QString moduleUrlPath = u"/"_s + url.toString().replace(u'.', u'/');
2201 if (checkCanImportFromRootPaths(options, info.absolutePath(), moduleUrlPath)) {
2202 if (options->verbose)
2203 fprintf(stdout, " -- Skipping because path is in QML root path.\n");
2204 continue;
2205 }
2206
2207 QString importPathOfThisImport;
2208 for (const QString &importPath : std::as_const(importPaths)) {
2209 QString cleanImportPath = QDir::cleanPath(importPath);
2210 if (QFile::exists(cleanImportPath + moduleUrlPath)) {
2211 importPathOfThisImport = importPath;
2212 break;
2213 }
2214 }
2215
2216 if (importPathOfThisImport.isEmpty()) {
2217 fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
2218 return false;
2219 }
2220
2221 importPathOfThisImport = QDir(importPathOfThisImport).absolutePath() + u'/';
2222 QList<QtDependency> qmlImportsDependencies;
2223 auto collectQmlDependency = [&usedDependencies, &qmlImportsDependencies,
2224 &importPathOfThisImport](const QString &filePath) {
2225 if (!usedDependencies->contains(filePath)) {
2226 usedDependencies->insert(filePath);
2227 qmlImportsDependencies += QtDependency(
2228 "qml/"_L1 + filePath.mid(importPathOfThisImport.size()),
2229 filePath);
2230 }
2231 };
2232
2233 QString plugin = object.value("plugin"_L1).toString();
2234 bool pluginIsOptional = object.value("pluginIsOptional"_L1).toBool();
2235 QFileInfo pluginFileInfo = QFileInfo(
2236 path + u'/' + "lib"_L1 + plugin + u'_'
2237 + options->currentArchitecture + ".so"_L1);
2238 QString pluginFilePath = pluginFileInfo.absoluteFilePath();
2239 QSet<QString> remainingDependencies;
2240 if (pluginFileInfo.exists() && checkArchitecture(*options, pluginFilePath)
2241 && readDependenciesFromElf(options, pluginFilePath, usedDependencies,
2242 &remainingDependencies)) {
2243 collectQmlDependency(pluginFilePath);
2244 } else if (!pluginIsOptional) {
2245 if (options->verbose)
2246 fprintf(stdout, " -- Skipping because the required plugin is missing.\n");
2247 continue;
2248 }
2249
2250 QFileInfo qmldirFileInfo = QFileInfo(path + u'/' + "qmldir"_L1);
2251 if (qmldirFileInfo.exists()) {
2252 collectQmlDependency(qmldirFileInfo.absoluteFilePath());
2253 }
2254
2255 QString prefer = object.value("prefer"_L1).toString();
2256 // If the preferred location of Qml files points to the Qt resources, this means
2257 // that all Qml files has been embedded into plugin and we should not copy them to the
2258 // android rcc bundle
2259 if (!prefer.startsWith(":/"_L1)) {
2260 QVariantList qmlFiles =
2261 object.value("components"_L1).toArray().toVariantList();
2262 qmlFiles.append(object.value("scripts"_L1).toArray().toVariantList());
2263 bool qmlFilesMissing = false;
2264 for (const auto &qmlFileEntry : qmlFiles) {
2265 QFileInfo fileInfo(qmlFileEntry.toString());
2266 if (!fileInfo.exists()) {
2267 qmlFilesMissing = true;
2268 break;
2269 }
2270 collectQmlDependency(fileInfo.absoluteFilePath());
2271 }
2272
2273 if (qmlFilesMissing) {
2274 if (options->verbose)
2275 fprintf(stdout,
2276 " -- Skipping because the required qml files are missing.\n");
2277 continue;
2278 }
2279 }
2280
2281 options->qtDependencies[options->currentArchitecture].append(qmlImportsDependencies);
2282 }
2283 }
2284
2285 return true;
2286}
2287
2289 const QString &moduleUrlPath)
2290{
2291 for (auto rootPath : options->rootPaths) {
2292 if ((rootPath + moduleUrlPath) == absolutePath)
2293 return true;
2294 }
2295 return false;
2296}
2297
2298bool runCommand(const Options &options, const QString &command)
2299{
2300 if (options.verbose)
2301 fprintf(stdout, "Running command '%s'\n", qPrintable(command));
2302
2303 FILE *runCommand = openProcess(command);
2304 if (runCommand == nullptr) {
2305 fprintf(stderr, "Cannot run command '%s'\n", qPrintable(command));
2306 return false;
2307 }
2308 char buffer[4096];
2309 while (fgets(buffer, sizeof(buffer), runCommand) != nullptr) {
2310 if (options.verbose)
2311 fprintf(stdout, "%s", buffer);
2312 }
2313 pclose(runCommand);
2314 fflush(stdout);
2315 fflush(stderr);
2316 return true;
2317}
2318
2319bool createRcc(const Options &options)
2320{
2321 auto assetsDir = "%1/assets"_L1.arg(options.outputDirectory);
2322 if (!QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.exists()) {
2323 fprintf(stdout, "Skipping createRCC\n");
2324 return true;
2325 }
2326
2327 if (options.verbose)
2328 fprintf(stdout, "Create rcc bundle.\n");
2329
2330
2331 QString rcc;
2332 if (!options.rccBinaryPath.isEmpty()) {
2333 rcc = options.rccBinaryPath;
2334 } else {
2335 rcc = execSuffixAppended(options.qtLibExecsDirectory + "/rcc"_L1);
2336 }
2337
2338 if (!QFile::exists(rcc)) {
2339 fprintf(stderr, "rcc not found: %s\n", qPrintable(rcc));
2340 return false;
2341 }
2342 auto currentDir = QDir::currentPath();
2343 if (!QDir::setCurrent("%1/android_rcc_bundle"_L1.arg(assetsDir))) {
2344 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable("%1/android_rcc_bundle"_L1.arg(assetsDir)));
2345 return false;
2346 }
2347
2348 bool res = runCommand(options, "%1 --project -o %2"_L1.arg(rcc, shellQuote("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir))));
2349 if (!res)
2350 return false;
2351
2352 QLatin1StringView noZstd;
2353 if (!options.isZstdCompressionEnabled)
2354 noZstd = "--no-zstd"_L1;
2355
2356 QFile::rename("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir), "%1/android_rcc_bundle/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2357
2358 res = runCommand(options, "%1 %2 %3 --binary -o %4 android_rcc_bundle.qrc"_L1.arg(rcc, shellQuote("--root=/android_rcc_bundle/"_L1),
2359 noZstd,
2360 shellQuote("%1/android_rcc_bundle.rcc"_L1.arg(assetsDir))));
2361 if (!QDir::setCurrent(currentDir)) {
2362 fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(currentDir));
2363 return false;
2364 }
2365 if (!options.noRccBundleCleanup) {
2366 QFile::remove("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir));
2367 QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.removeRecursively();
2368 }
2369 return res;
2370}
2371
2373{
2374 if (options->verbose)
2375 fprintf(stdout, "Detecting dependencies of application.\n");
2376
2377 // Override set in .pro file
2378 if (!options->qtDependencies[options->currentArchitecture].isEmpty()) {
2379 if (options->verbose)
2380 fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
2381 return true;
2382 }
2383
2384 QSet<QString> usedDependencies;
2385 QSet<QString> remainingDependencies;
2386
2387 // Add dependencies of application binary first
2388 if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies))
2389 return false;
2390
2391 while (!remainingDependencies.isEmpty()) {
2392 QSet<QString>::iterator start = remainingDependencies.begin();
2394 remainingDependencies.erase(start);
2395
2396 QStringList unmetDependencies;
2397 if (goodToCopy(options, fileName, &unmetDependencies)) {
2398 bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
2399 if (!ok)
2400 return false;
2401 } else {
2402 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2404 qPrintable(unmetDependencies.join(u',')));
2405 }
2406 }
2407
2408 QStringList::iterator it = options->localLibs[options->currentArchitecture].begin();
2409 while (it != options->localLibs[options->currentArchitecture].end()) {
2410 QStringList unmetDependencies;
2411 if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) {
2412 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
2413 qPrintable(*it),
2414 qPrintable(unmetDependencies.join(u',')));
2415 it = options->localLibs[options->currentArchitecture].erase(it);
2416 } else {
2417 ++it;
2418 }
2419 }
2420
2421 if (options->qmlSkipImportScanning
2422 || (options->rootPaths.empty() && options->qrcFiles.isEmpty()))
2423 return true;
2424 return scanImports(options, &usedDependencies);
2425}
2426
2428{
2429 if (!options->build)
2430 return true;
2431
2432 if (options->verbose)
2433 fprintf(stdout, "Checking if application binary is in package.\n");
2434
2435 QString applicationFileName = "lib%1_%2.so"_L1.arg(options->applicationBinary,
2436 options->currentArchitecture);
2437
2438 QString applicationPath = "%1/libs/%2/%3"_L1.arg(options->outputDirectory,
2439 options->currentArchitecture,
2440 applicationFileName);
2441 if (!QFile::exists(applicationPath)) {
2442#if defined(Q_OS_WIN32)
2443 const auto makeTool = "mingw32-make"_L1; // Only Mingw host builds supported on Windows currently
2444#else
2445 const auto makeTool = "make"_L1;
2446#endif
2447 fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
2448 qPrintable(applicationFileName),
2449 qPrintable(makeTool),
2450 qPrintable(options->outputDirectory));
2451 return false;
2452 }
2453 return true;
2454}
2455
2456FILE *runAdb(const Options &options, const QString &arguments)
2457{
2458 QString adb = execSuffixAppended(options.sdkPath + "/platform-tools/adb"_L1);
2459 if (!QFile::exists(adb)) {
2460 fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
2461 return 0;
2462 }
2463 QString installOption;
2464 if (!options.installLocation.isEmpty())
2465 installOption = " -s "_L1 + shellQuote(options.installLocation);
2466
2467 adb = "%1%2 %3"_L1.arg(shellQuote(adb), installOption, arguments);
2468
2469 if (options.verbose)
2470 fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
2471
2472 FILE *adbCommand = openProcess(adb);
2473 if (adbCommand == 0) {
2474 fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
2475 return 0;
2476 }
2477
2478 return adbCommand;
2479}
2480
2481bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
2482{
2483 if (!file.endsWith(".so"_L1))
2484 return true;
2485
2486 if (!checkArchitecture(*options, file))
2487 return false;
2488
2489 bool ret = true;
2490 const auto libs = getQtLibsFromElf(*options, file);
2491 for (const QString &lib : libs) {
2492 if (!options->qtDependencies[options->currentArchitecture].contains(QtDependency(lib, absoluteFilePath(options, lib)))) {
2493 ret = false;
2494 unmetDependencies->append(lib);
2495 }
2496 }
2497
2498 return ret;
2499}
2500
2501bool copyQtFiles(Options *options)
2502{
2503 if (options->verbose) {
2504 switch (options->deploymentMechanism) {
2505 case Options::Bundled:
2506 fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies[options->currentArchitecture].size()));
2507 break;
2508 case Options::Unbundled:
2509 fprintf(stdout, "Copying dependencies from Qt into the package build folder,"
2510 "skipping native libraries.\n");
2511 break;
2512 };
2513 }
2514
2515 if (!options->build)
2516 return true;
2517
2518
2519 QString libsDirectory = "libs/"_L1;
2520
2521 // Copy other Qt dependencies
2522 auto assetsDestinationDirectory = "assets/android_rcc_bundle/"_L1;
2523 for (const QtDependency &qtDependency : std::as_const(options->qtDependencies[options->currentArchitecture])) {
2524 QString sourceFileName = qtDependency.absolutePath;
2525 QString destinationFileName;
2526 bool isSharedLibrary = qtDependency.relativePath.endsWith(".so"_L1);
2527 if (isSharedLibrary) {
2528 QString garbledFileName = qtDependency.relativePath.mid(
2529 qtDependency.relativePath.lastIndexOf(u'/') + 1);
2530 destinationFileName = libsDirectory + options->currentArchitecture + u'/' + garbledFileName;
2531 } else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("jar/"_L1)) {
2532 destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
2533 } else {
2534 destinationFileName = assetsDestinationDirectory + qtDependency.relativePath;
2535 }
2536
2537 if (!QFile::exists(sourceFileName)) {
2538 fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
2539 return false;
2540 }
2541
2542 QStringList unmetDependencies;
2543 if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
2544 if (unmetDependencies.isEmpty()) {
2545 if (options->verbose) {
2546 fprintf(stdout, " -- Skipping %s, architecture mismatch.\n",
2547 qPrintable(sourceFileName));
2548 }
2549 } else {
2550 fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n",
2551 qPrintable(sourceFileName),
2552 qPrintable(unmetDependencies.join(u',')));
2553 }
2554 continue;
2555 }
2556
2557 if ((isDeployment(options, Options::Bundled) || !isSharedLibrary)
2558 && !copyFileIfNewer(sourceFileName,
2559 options->outputDirectory + u'/' + destinationFileName,
2560 *options)) {
2561 return false;
2562 }
2563 options->bundledFiles[options->currentArchitecture] += qMakePair(destinationFileName, qtDependency.relativePath);
2564 }
2565
2566 return true;
2567}
2568
2570{
2572
2573 QFile file(options.outputDirectory + "/project.properties"_L1);
2575 while (!file.atEnd()) {
2576 QByteArray line = file.readLine().trimmed();
2577 if (line.startsWith("android.library.reference")) {
2578 int equalSignIndex = line.indexOf('=');
2579 if (equalSignIndex >= 0) {
2580 QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
2581
2582 QFileInfo info(options.outputDirectory + u'/' + path);
2584 && info.exists()
2585 && info.isDir()
2588 }
2589 }
2590 }
2591 }
2592 }
2593
2594 return ret;
2595}
2596
2598{
2599 const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
2600#if defined(Q_OS_WIN32)
2601 QLatin1Char separator(';');
2602#else
2603 QLatin1Char separator(':');
2604#endif
2605
2606 const QStringList paths = path.split(separator);
2607 for (const QString &path : paths) {
2608 QFileInfo fileInfo(path + u'/' + fileName);
2609 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2610 return path + u'/' + fileName;
2611 }
2612
2613 return QString();
2614}
2615
2617
2619{
2621 QFile file(path);
2623 return properties;
2624
2625 const auto lines = file.readAll().split('\n');
2626 for (const QByteArray &line : lines) {
2627 if (line.trimmed().startsWith('#'))
2628 continue;
2629
2630 const int idx = line.indexOf('=');
2631 if (idx > -1)
2632 properties[line.left(idx).trimmed()] = line.mid(idx + 1).trimmed();
2633 }
2634 file.close();
2635 return properties;
2636}
2637
2639{
2640 const QString oldPathStr = path + u'~';
2641 QFile::remove(oldPathStr);
2642 QFile::rename(path, oldPathStr);
2643 QFile file(path);
2645 fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName()));
2646 return false;
2647 }
2648
2649 QFile oldFile(oldPathStr);
2650 if (oldFile.open(QIODevice::ReadOnly)) {
2651 while (!oldFile.atEnd()) {
2652 QByteArray line(oldFile.readLine());
2653 QList<QByteArray> prop(line.split('='));
2654 if (prop.size() > 1) {
2655 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
2656 if (it != properties.end()) {
2657 file.write(it.key() + '=' + it.value() + '\n');
2658 properties.erase(it);
2659 continue;
2660 }
2661 }
2662 file.write(line.trimmed() + '\n');
2663 }
2664 oldFile.close();
2665 QFile::remove(oldPathStr);
2666 }
2667
2668 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
2669 file.write(it.key() + '=' + it.value() + '\n');
2670
2671 file.close();
2672 return true;
2673}
2674
2675#if defined(Q_OS_WIN32)
2676void checkAndWarnGradleLongPaths(const QString &outputDirectory)
2677{
2678 QStringList longFileNames;
2679 QDirIterator it(outputDirectory, QStringList(QStringLiteral("*.java")), QDir::Files,
2681 while (it.hasNext()) {
2682 if (it.next().size() >= MAX_PATH)
2683 longFileNames.append(it.next());
2684 }
2685
2686 if (!longFileNames.isEmpty()) {
2687 fprintf(stderr,
2688 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
2689 "Consider moving your project to reduce its path length.\n"
2690 "The following files have too long paths:\n%s.\n",
2691 MAX_PATH, qPrintable(longFileNames.join(u'\n')));
2692 }
2693}
2694#endif
2695
2699};
2700
2702{
2704
2705 QFile file(path);
2707 return flags;
2708
2709 auto isComment = [](const QByteArray &line) {
2710 const auto trimmed = line.trimmed();
2711 return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
2712 };
2713
2714 const auto lines = file.readAll().split('\n');
2715 for (const auto &line : lines) {
2716 if (isComment(line))
2717 continue;
2718 if (line.contains("useLegacyPackaging")) {
2719 flags.setsLegacyPackaging = true;
2720 } else if (line.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) {
2721 flags.usesIntegerCompileSdkVersion = true;
2722 }
2723 }
2724
2725 return flags;
2726}
2727
2728bool buildAndroidProject(const Options &options)
2729{
2730 GradleProperties localProperties;
2731 localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
2732 const QString localPropertiesPath = options.outputDirectory + "local.properties"_L1;
2733 if (!mergeGradleProperties(localPropertiesPath, localProperties))
2734 return false;
2735
2736 const QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1;
2737 GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
2738
2739 const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1;
2740 GradleFlags gradleFlags = gradleBuildFlags(gradleBuildFilePath);
2741 if (!gradleFlags.setsLegacyPackaging)
2742 gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
2743
2744 gradleProperties["buildDir"] = "build";
2745 gradleProperties["qtAndroidDir"] =
2746 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
2747 "/src/android/java"_L1)
2748 .toUtf8();
2749 // The following property "qt5AndroidDir" is only for compatibility.
2750 // Projects using a custom build.gradle file may use this variable.
2751 // ### Qt7: Remove the following line
2752 gradleProperties["qt5AndroidDir"] =
2753 (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
2754 "/src/android/java"_L1)
2755 .toUtf8();
2756
2757 QByteArray sdkPlatformVersion;
2758 // Provide the integer version only if build.gradle explicitly converts to Integer,
2759 // to avoid regression to existing projects that build for sdk platform of form android-xx.
2760 if (gradleFlags.usesIntegerCompileSdkVersion) {
2761 const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit();
2762 bool ok;
2763 tmp.toInt(&ok);
2764 if (ok) {
2765 sdkPlatformVersion = tmp;
2766 } else {
2767 fprintf(stderr, "Warning: Gradle expects SDK platform version to be an integer, "
2768 "but the set version is not convertible to an integer.");
2769 }
2770 }
2771
2772 if (sdkPlatformVersion.isEmpty())
2773 sdkPlatformVersion = options.androidPlatform.toLocal8Bit();
2774
2775 gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion;
2776 gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
2777 gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
2778 gradleProperties["androidNdkVersion"] = options.ndkVersion.toUtf8();
2779 if (gradleProperties["androidBuildToolsVersion"].isEmpty())
2780 gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit();
2781 QString abiList;
2782 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
2783 if (!it->enabled)
2784 continue;
2785 if (abiList.size())
2786 abiList.append(u",");
2787 abiList.append(it.key());
2788 }
2789 gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
2790 if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
2791 return false;
2792
2793 QString gradlePath = batSuffixAppended(options.outputDirectory + "gradlew"_L1);
2794#ifndef Q_OS_WIN32
2795 {
2796 QFile f(gradlePath);
2797 if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser))
2798 fprintf(stderr, "Cannot set permissions %s\n", qPrintable(gradlePath));
2799 }
2800#endif
2801
2802 QString oldPath = QDir::currentPath();
2803 if (!QDir::setCurrent(options.outputDirectory)) {
2804 fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory));
2805 return false;
2806 }
2807
2808 QString commandLine = "%1 %2"_L1.arg(shellQuote(gradlePath), options.releasePackage ? " assembleRelease"_L1 : " assembleDebug"_L1);
2809 if (options.buildAAB)
2810 commandLine += " bundle"_L1;
2811
2812 if (options.verbose)
2813 commandLine += " --info"_L1;
2814
2815 FILE *gradleCommand = openProcess(commandLine);
2816 if (gradleCommand == 0) {
2817 fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine));
2818 return false;
2819 }
2820
2821 char buffer[512];
2822 while (fgets(buffer, sizeof(buffer), gradleCommand) != 0) {
2823 fprintf(stdout, "%s", buffer);
2824 fflush(stdout);
2825 }
2826
2827 int errorCode = pclose(gradleCommand);
2828 if (errorCode != 0) {
2829 fprintf(stderr, "Building the android package failed!\n");
2830 if (!options.verbose)
2831 fprintf(stderr, " -- For more information, run this command with --verbose.\n");
2832
2833#if defined(Q_OS_WIN32)
2834 checkAndWarnGradleLongPaths(options.outputDirectory);
2835#endif
2836 return false;
2837 }
2838
2839 if (!QDir::setCurrent(oldPath)) {
2840 fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath));
2841 return false;
2842 }
2843
2844 return true;
2845}
2846
2847bool uninstallApk(const Options &options)
2848{
2849 if (options.verbose)
2850 fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
2851
2852
2853 FILE *adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName));
2854 if (adbCommand == 0)
2855 return false;
2856
2857 if (options.verbose || mustReadOutputAnyway) {
2858 char buffer[512];
2859 while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
2860 if (options.verbose)
2861 fprintf(stdout, "%s", buffer);
2862 }
2863
2864 int returnCode = pclose(adbCommand);
2865 if (returnCode != 0) {
2866 fprintf(stderr, "Warning: Uninstall failed!\n");
2867 if (!options.verbose)
2868 fprintf(stderr, " -- Run with --verbose for more information.\n");
2869 return false;
2870 }
2871
2872 return true;
2873}
2874
2878 SignedAPK
2880
2882{
2883 QString path(options.outputDirectory);
2884 path += "/build/outputs/%1/"_L1.arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle"));
2885 QString buildType(options.releasePackage ? "release/"_L1 : "debug/"_L1);
2886 if (QDir(path + buildType).exists())
2887 path += buildType;
2888 path += QDir(options.outputDirectory).dirName() + u'-';
2889 if (options.releasePackage) {
2890 path += "release-"_L1;
2891 if (pt >= UnsignedAPK) {
2892 if (pt == UnsignedAPK)
2893 path += "un"_L1;
2894 path += "signed.apk"_L1;
2895 } else {
2896 path.chop(1);
2897 path += ".aab"_L1;
2898 }
2899 } else {
2900 path += "debug"_L1;
2901 if (pt >= UnsignedAPK) {
2902 if (pt == SignedAPK)
2903 path += "-signed"_L1;
2904 path += ".apk"_L1;
2905 } else {
2906 path += ".aab"_L1;
2907 }
2908 }
2909 return path;
2910}
2911
2912bool installApk(const Options &options)
2913{
2914 fflush(stdout);
2915 // Uninstall if necessary
2916 if (options.uninstallApk)
2917 uninstallApk(options);
2918
2919 if (options.verbose)
2920 fprintf(stdout, "Installing Android package to device.\n");
2921
2922 FILE *adbCommand = runAdb(options, " install -r "_L1
2923 + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
2924 : SignedAPK));
2925 if (adbCommand == 0)
2926 return false;
2927
2928 if (options.verbose || mustReadOutputAnyway) {
2929 char buffer[512];
2930 while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
2931 if (options.verbose)
2932 fprintf(stdout, "%s", buffer);
2933 }
2934
2935 int returnCode = pclose(adbCommand);
2936 if (returnCode != 0) {
2937 fprintf(stderr, "Installing to device failed!\n");
2938 if (!options.verbose)
2939 fprintf(stderr, " -- Run with --verbose for more information.\n");
2940 return false;
2941 }
2942
2943 return true;
2944}
2945
2946bool copyPackage(const Options &options)
2947{
2948 fflush(stdout);
2949 auto from = packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK : SignedAPK);
2950 QFile::remove(options.apkPath);
2951 return QFile::copy(from, options.apkPath);
2952}
2953
2954bool copyStdCpp(Options *options)
2955{
2956 if (isDeployment(options, Options::Unbundled))
2957 return true;
2958 if (options->verbose)
2959 fprintf(stdout, "Copying STL library\n");
2960
2961 const QString triple = options->architectures[options->currentArchitecture].triple;
2962 const QString stdCppPath = "%1/%2/lib%3.so"_L1.arg(options->stdCppPath, triple,
2963 options->stdCppName);
2964 if (!QFile::exists(stdCppPath)) {
2965 fprintf(stderr, "STL library does not exist at %s\n", qPrintable(stdCppPath));
2966 fflush(stdout);
2967 fflush(stderr);
2968 return false;
2969 }
2970
2971 const QString destinationFile = "%1/libs/%2/lib%3.so"_L1.arg(options->outputDirectory,
2972 options->currentArchitecture,
2973 options->stdCppName);
2974 return copyFileIfNewer(stdCppPath, destinationFile, *options);
2975}
2976
2977static QString zipalignPath(const Options &options, bool *ok)
2978{
2979 *ok = true;
2980 QString zipAlignTool = execSuffixAppended(options.sdkPath + "/tools/zipalign"_L1);
2981 if (!QFile::exists(zipAlignTool)) {
2982 zipAlignTool = execSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
2983 options.sdkBuildToolsVersion + "/zipalign"_L1);
2984 if (!QFile::exists(zipAlignTool)) {
2985 fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
2986 *ok = false;
2987 }
2988 }
2989
2990 return zipAlignTool;
2991}
2992
2993bool signAAB(const Options &options)
2994{
2995 if (options.verbose)
2996 fprintf(stdout, "Signing Android package.\n");
2997
2998 QString jdkPath = options.jdkPath;
2999
3000 if (jdkPath.isEmpty())
3001 jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME"));
3002
3003 QString jarSignerTool = execSuffixAppended("jarsigner"_L1);
3004 if (jdkPath.isEmpty() || !QFile::exists(jdkPath + "/bin/"_L1 + jarSignerTool))
3005 jarSignerTool = findInPath(jarSignerTool);
3006 else
3007 jarSignerTool = jdkPath + "/bin/"_L1 + jarSignerTool;
3008
3009 if (!QFile::exists(jarSignerTool)) {
3010 fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
3011 return false;
3012 }
3013
3014 jarSignerTool = "%1 -sigalg %2 -digestalg %3 -keystore %4"_L1
3015 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
3016
3017 if (!options.keyStorePassword.isEmpty())
3018 jarSignerTool += " -storepass %1"_L1.arg(shellQuote(options.keyStorePassword));
3019
3020 if (!options.storeType.isEmpty())
3021 jarSignerTool += " -storetype %1"_L1.arg(shellQuote(options.storeType));
3022
3023 if (!options.keyPass.isEmpty())
3024 jarSignerTool += " -keypass %1"_L1.arg(shellQuote(options.keyPass));
3025
3026 if (!options.sigFile.isEmpty())
3027 jarSignerTool += " -sigfile %1"_L1.arg(shellQuote(options.sigFile));
3028
3029 if (!options.signedJar.isEmpty())
3030 jarSignerTool += " -signedjar %1"_L1.arg(shellQuote(options.signedJar));
3031
3032 if (!options.tsaUrl.isEmpty())
3033 jarSignerTool += " -tsa %1"_L1.arg(shellQuote(options.tsaUrl));
3034
3035 if (!options.tsaCert.isEmpty())
3036 jarSignerTool += " -tsacert %1"_L1.arg(shellQuote(options.tsaCert));
3037
3038 if (options.internalSf)
3039 jarSignerTool += " -internalsf"_L1;
3040
3041 if (options.sectionsOnly)
3042 jarSignerTool += " -sectionsonly"_L1;
3043
3044 if (options.protectedAuthenticationPath)
3045 jarSignerTool += " -protected"_L1;
3046
3047 auto jarSignPackage = [&](const QString &file) {
3048 fprintf(stdout, "Signing file %s\n", qPrintable(file));
3049 fflush(stdout);
3050 QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file))
3051 .arg(shellQuote(options.keyStoreAlias));
3052
3053 FILE *jarSignerCommand = openProcess(command);
3054 if (jarSignerCommand == 0) {
3055 fprintf(stderr, "Couldn't run jarsigner.\n");
3056 return false;
3057 }
3058
3059 if (options.verbose) {
3060 char buffer[512];
3061 while (fgets(buffer, sizeof(buffer), jarSignerCommand) != 0)
3062 fprintf(stdout, "%s", buffer);
3063 }
3064
3065 int errorCode = pclose(jarSignerCommand);
3066 if (errorCode != 0) {
3067 fprintf(stderr, "jarsigner command failed.\n");
3068 if (!options.verbose)
3069 fprintf(stderr, " -- Run with --verbose for more information.\n");
3070 return false;
3071 }
3072 return true;
3073 };
3074
3075 if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
3076 return false;
3077 return true;
3078}
3079
3080bool signPackage(const Options &options)
3081{
3082 const QString apksignerTool = batSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
3083 options.sdkBuildToolsVersion + "/apksigner"_L1);
3084 // APKs signed with apksigner must not be changed after they're signed,
3085 // therefore we need to zipalign it before we sign it.
3086
3087 bool ok;
3088 QString zipAlignTool = zipalignPath(options, &ok);
3089 if (!ok)
3090 return false;
3091
3092 auto zipalignRunner = [](const QString &zipAlignCommandLine) {
3093 FILE *zipAlignCommand = openProcess(zipAlignCommandLine);
3094 if (zipAlignCommand == 0) {
3095 fprintf(stderr, "Couldn't run zipalign.\n");
3096 return false;
3097 }
3098
3099 char buffer[512];
3100 while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0)
3101 fprintf(stdout, "%s", buffer);
3102
3103 return pclose(zipAlignCommand) == 0;
3104 };
3105
3106 const QString verifyZipAlignCommandLine =
3107 "%1%2 -c 4 %3"_L1
3108 .arg(shellQuote(zipAlignTool),
3109 options.verbose ? " -v"_L1 : QLatin1StringView(),
3111
3112 if (zipalignRunner(verifyZipAlignCommandLine)) {
3113 if (options.verbose)
3114 fprintf(stdout, "APK already aligned, copying it for signing.\n");
3115
3116 if (QFile::exists(packagePath(options, SignedAPK)))
3118
3119 if (!QFile::copy(packagePath(options, UnsignedAPK), packagePath(options, SignedAPK))) {
3120 fprintf(stderr, "Could not copy unsigned APK.\n");
3121 return false;
3122 }
3123 } else {
3124 if (options.verbose)
3125 fprintf(stdout, "APK not aligned, aligning it for signing.\n");
3126
3127 const QString zipAlignCommandLine =
3128 "%1%2 -f 4 %3 %4"_L1
3129 .arg(shellQuote(zipAlignTool),
3130 options.verbose ? " -v"_L1 : QLatin1StringView(),
3132 shellQuote(packagePath(options, SignedAPK)));
3133
3134 if (!zipalignRunner(zipAlignCommandLine)) {
3135 fprintf(stderr, "zipalign command failed.\n");
3136 if (!options.verbose)
3137 fprintf(stderr, " -- Run with --verbose for more information.\n");
3138 return false;
3139 }
3140 }
3141
3142 QString apkSignCommand = "%1 sign --ks %2"_L1
3143 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
3144
3145 if (!options.keyStorePassword.isEmpty())
3146 apkSignCommand += " --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword));
3147
3148 if (!options.keyStoreAlias.isEmpty())
3149 apkSignCommand += " --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias));
3150
3151 if (!options.keyPass.isEmpty())
3152 apkSignCommand += " --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass));
3153
3154 if (options.verbose)
3155 apkSignCommand += " --verbose"_L1;
3156
3157 apkSignCommand += " %1"_L1.arg(shellQuote(packagePath(options, SignedAPK)));
3158
3159 auto apkSignerRunner = [](const QString &command, bool verbose) {
3160 FILE *apkSigner = openProcess(command);
3161 if (apkSigner == 0) {
3162 fprintf(stderr, "Couldn't run apksigner.\n");
3163 return false;
3164 }
3165
3166 char buffer[512];
3167 while (fgets(buffer, sizeof(buffer), apkSigner) != 0)
3168 fprintf(stdout, "%s", buffer);
3169
3170 int errorCode = pclose(apkSigner);
3171 if (errorCode != 0) {
3172 fprintf(stderr, "apksigner command failed.\n");
3173 if (!verbose)
3174 fprintf(stderr, " -- Run with --verbose for more information.\n");
3175 return false;
3176 }
3177 return true;
3178 };
3179
3180 // Sign the package
3181 if (!apkSignerRunner(apkSignCommand, options.verbose))
3182 return false;
3183
3184 const QString apkVerifyCommand =
3185 "%1 verify --verbose %2"_L1
3186 .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
3187
3188 if (options.buildAAB && !signAAB(options))
3189 return false;
3190
3191 // Verify the package and remove the unsigned apk
3192 return apkSignerRunner(apkVerifyCommand, true) && QFile::remove(packagePath(options, UnsignedAPK));
3193}
3194
3196{
3208 CannotCreateAndroidProject = 13, // Not used anymore
3214 CannotCreateRcc = 21
3216
3217bool writeDependencyFile(const Options &options)
3218{
3219 if (options.verbose)
3220 fprintf(stdout, "Writing dependency file.\n");
3221
3222 QString relativeTargetPath;
3223 if (options.copyDependenciesOnly) {
3224 // When androiddeploy Qt is running in copyDependenciesOnly mode we need to use
3225 // the timestamp file as the target to collect dependencies.
3226 QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() + "/timestamp"_L1;
3227 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath);
3228 } else {
3229 relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
3230 }
3231
3232 QFile depFile(options.depFilePath);
3233 if (depFile.open(QIODevice::WriteOnly)) {
3234 depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath));
3235 depFile.write(": ");
3236
3237 for (const auto &file : dependenciesForDepfile) {
3238 depFile.write(" \\\n ");
3240 }
3241
3242 depFile.write("\n");
3243 }
3244 return true;
3245}
3246
3247int main(int argc, char *argv[])
3248{
3249 QCoreApplication a(argc, argv);
3250
3251 Options options = parseOptions();
3252 if (options.helpRequested || options.outputDirectory.isEmpty()) {
3253 printHelp();
3255 }
3256
3257 options.timer.start();
3258
3259 if (!readInputFile(&options))
3260 return CannotReadInputFile;
3261
3262 if (Q_UNLIKELY(options.timing))
3263 fprintf(stdout, "[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed());
3264
3265 fprintf(stdout,
3266 "Generating Android Package\n"
3267 " Input file: %s\n"
3268 " Output directory: %s\n"
3269 " Application binary: %s\n"
3270 " Android build platform: %s\n"
3271 " Install to device: %s\n",
3272 qPrintable(options.inputFileName),
3273 qPrintable(options.outputDirectory),
3275 qPrintable(options.androidPlatform),
3276 options.installApk
3277 ? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation))
3278 : "No"
3279 );
3280
3281 bool androidTemplatetCopied = false;
3282
3283 for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
3284 if (!it->enabled)
3285 continue;
3286 options.setCurrentQtArchitecture(it.key(),
3287 it.value().qtInstallDirectory,
3288 it.value().qtDirectories);
3289
3290 // All architectures have a copy of the gradle files but only one set needs to be copied.
3291 if (!androidTemplatetCopied && options.build && !options.auxMode && !options.copyDependenciesOnly) {
3292 cleanAndroidFiles(options);
3293 if (Q_UNLIKELY(options.timing))
3294 fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
3295
3296 if (!copyAndroidTemplate(options))
3298
3299 if (Q_UNLIKELY(options.timing))
3300 fprintf(stdout, "[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed());
3301 androidTemplatetCopied = true;
3302 }
3303
3304 if (!readDependencies(&options))
3306
3307 if (Q_UNLIKELY(options.timing))
3308 fprintf(stdout, "[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed());
3309
3310 if (!copyQtFiles(&options))
3311 return CannotCopyQtFiles;
3312
3313 if (Q_UNLIKELY(options.timing))
3314 fprintf(stdout, "[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed());
3315
3316 if (!copyAndroidExtraLibs(&options))
3318
3319 if (Q_UNLIKELY(options.timing))
3320 fprintf(stdout, "[TIMING] %lld ms: Copied extra libs\n", options.timer.nsecsElapsed());
3321
3322 if (!copyAndroidExtraResources(&options))
3324
3325 if (Q_UNLIKELY(options.timing))
3326 fprintf(stdout, "[TIMING] %lld ns: Copied extra resources\n", options.timer.nsecsElapsed());
3327
3328 if (!options.auxMode) {
3329 if (!copyStdCpp(&options))
3330 return CannotCopyGnuStl;
3331
3332 if (Q_UNLIKELY(options.timing))
3333 fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
3334 }
3335 // If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK
3336 if (options.deploymentMechanism == Options::Unbundled) {
3337 QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1.
3338 arg(options.outputDirectory,
3339 options.currentArchitecture,
3340 options.applicationBinary);
3341 QFile::remove(appLibPath);
3342 } else if (!containsApplicationBinary(&options)) {
3344 }
3345
3346 if (Q_UNLIKELY(options.timing))
3347 fprintf(stdout, "[TIMING] %lld ns: Checked for application binary\n", options.timer.nsecsElapsed());
3348
3349 if (Q_UNLIKELY(options.timing))
3350 fprintf(stdout, "[TIMING] %lld ns: Bundled Qt libs\n", options.timer.nsecsElapsed());
3351 }
3352
3353 if (options.copyDependenciesOnly) {
3354 if (!options.depFilePath.isEmpty())
3355 writeDependencyFile(options);
3356 return 0;
3357 }
3358
3359 if (!createRcc(options))
3360 return CannotCreateRcc;
3361
3362 if (options.auxMode) {
3363 if (!updateAndroidFiles(options))
3365 return 0;
3366 }
3367
3368 if (options.build) {
3369 if (!copyAndroidSources(options))
3371
3372 if (Q_UNLIKELY(options.timing))
3373 fprintf(stdout, "[TIMING] %lld ns: Copied android sources\n", options.timer.nsecsElapsed());
3374
3375 if (!updateAndroidFiles(options))
3377
3378 if (Q_UNLIKELY(options.timing))
3379 fprintf(stdout, "[TIMING] %lld ns: Updated files\n", options.timer.nsecsElapsed());
3380
3381 if (Q_UNLIKELY(options.timing))
3382 fprintf(stdout, "[TIMING] %lld ns: Created project\n", options.timer.nsecsElapsed());
3383
3384 if (!buildAndroidProject(options))
3386
3387 if (Q_UNLIKELY(options.timing))
3388 fprintf(stdout, "[TIMING] %lld ns: Built project\n", options.timer.nsecsElapsed());
3389
3390 if (!options.keyStore.isEmpty() && !signPackage(options))
3391 return CannotSignPackage;
3392
3393 if (!options.apkPath.isEmpty() && !copyPackage(options))
3394 return CannotCopyApk;
3395
3396 if (Q_UNLIKELY(options.timing))
3397 fprintf(stdout, "[TIMING] %lld ns: Signed package\n", options.timer.nsecsElapsed());
3398 }
3399
3400 if (options.installApk && !installApk(options))
3401 return CannotInstallApk;
3402
3403 if (Q_UNLIKELY(options.timing))
3404 fprintf(stdout, "[TIMING] %lld ns: Installed APK\n", options.timer.nsecsElapsed());
3405
3406 if (!options.depFilePath.isEmpty())
3407 writeDependencyFile(options);
3408
3409 fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.);
3410
3411 if (options.installApk)
3412 fprintf(stdout, " -- It can now be run from the selected device/emulator.\n");
3413
3414 fprintf(stdout, " -- File: %s\n", qPrintable(packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
3415 : SignedAPK)));
3416 fflush(stdout);
3417 return 0;
3418}
bool startsWith(QByteArrayView other) const noexcept
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
int toInt(bool *ok=nullptr, int base=10) const
Returns the byte array converted to an int using base base, which is ten by default.
QList< QByteArray > split(char sep) const
Splits the byte array into subarrays wherever sep occurs, and returns the list of those arrays.
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:394
\inmodule QtCore
Definition qchar.h:48
static constexpr QChar fromLatin1(char c) noexcept
Converts the Latin-1 character c to its equivalent QChar.
Definition qchar.h:461
\inmodule QtCore
static QStringList arguments()
The QDirIterator class provides an iterator for directory entrylists.
\inmodule QtCore
Definition qdir.h:19
QStringList entryList(Filters filters=NoFilter, SortFlags sort=NoSort) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qdir.cpp:1364
QString dirName() const
Returns the name of the directory; this is not the same as the path, e.g.
Definition qdir.cpp:715
bool removeRecursively()
Definition qdir.cpp:1640
static bool isRelativePath(const QString &path)
Returns true if path is relative; returns false if it is absolute.
Definition qdir.cpp:2409
static QString fromNativeSeparators(const QString &pathName)
Definition qdir.cpp:962
static bool setCurrent(const QString &path)
Sets the application's current working directory to path.
Definition qdir.cpp:2027
QString path() const
Returns the path.
Definition qdir.cpp:653
static QDir current()
Returns the application's current directory.
Definition qdir.h:216
QString absolutePath() const
Returns the absolute path (a path that starts with "/" or with a drive specification),...
Definition qdir.cpp:667
QFileInfoList entryInfoList(Filters filters=NoFilter, SortFlags sort=NoSort) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qdir.cpp:1387
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qdir.cpp:1715
bool mkpath(const QString &dirPath) const
Creates the directory path dirPath.
Definition qdir.cpp:1579
QString filePath(const QString &fileName) const
Returns the path name of a file in the directory.
Definition qdir.cpp:778
static QString cleanPath(const QString &path)
Returns path with directory separators normalized (that is, platform-native separators converted to "...
Definition qdir.cpp:2395
QString relativeFilePath(const QString &fileName) const
Returns the path to fileName relative to the directory.
Definition qdir.cpp:843
QString absoluteFilePath(const QString &fileName) const
Returns the absolute path name of a file in the directory.
Definition qdir.cpp:809
static QString currentPath()
Returns the absolute path of the application's current directory.
Definition qdir.cpp:2051
@ Files
Definition qdir.h:22
@ NoDotAndDotDot
Definition qdir.h:43
@ Dirs
Definition qdir.h:21
\inmodule QtCore
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
Starts this timer.
qint64 nsecsElapsed() const noexcept
bool atEnd() const override
Returns true if the end of the file has been reached; otherwise returns false.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QDateTime lastModified() const
Returns the date and time when the file was last modified.
Definition qfileinfo.h:156
QString baseName() const
Returns the base name of the file without the path.
QString suffix() const
Returns the suffix (extension) of the file.
QString fileName() const
Returns the name of the file, excluding the path.
bool isExecutable() const
Returns true if the file is executable; otherwise returns false.
QString absoluteFilePath() const
Returns an absolute path including the file name.
bool isFile() const
Returns true if this object points to a file or to a symbolic link to a file.
bool isDir() const
Returns true if this object points to a directory or to a symbolic link to a directory.
QString absolutePath() const
Returns a file's path absolute path.
QString canonicalFilePath() const
Returns the canonical path including the file name, i.e.
QString filePath() const
Returns the file name, including the path (which may be absolute or relative).
bool exists() const
Returns true if the file exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
bool copy(const QString &newName)
Copies the file named fileName() to newName.
Definition qfile.cpp:744
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
QString fileName() const override
Returns the name set by setFileName() or to the QFile constructors.
Definition qfile.cpp:277
bool rename(const QString &newName)
Renames the file currently specified by fileName() to newName.
Definition qfile.cpp:533
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtCore
Definition qhash.h:1135
\inmodule QtCore
Definition qhash.h:818
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1202
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:925
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1209
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1258
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1205
QList< Key > keys() const
Returns a list containing all the keys in the hash, in an arbitrary order.
Definition qhash.h:1076
iterator erase(const_iterator it)
Definition qhash.h:1223
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1206
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
qint64 readLine(char *data, qint64 maxlen)
This function reads a line of ASCII characters from the device, up to a maximum of maxSize - 1 bytes,...
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
QString errorString() const
Returns a human readable description of the last error that occurred.
\inmodule QtCore\reentrant
Definition qjsonarray.h:18
qsizetype count() const
Same as size().
Definition qjsonarray.h:42
QJsonValue at(qsizetype i) const
Returns a QJsonValue representing the value for index i.
\inmodule QtCore\reentrant
bool isNull() const
returns true if this document is null.
QJsonArray array() const
Returns the QJsonArray contained in the document.
QJsonObject object() const
Returns the QJsonObject contained in the document.
static QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error=nullptr)
Parses json as a UTF-8 encoded JSON document, and creates a QJsonDocument from it.
\inmodule QtCore\reentrant
Definition qjsonobject.h:20
const_iterator constBegin() const
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the object.
const_iterator constEnd() const
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
QJsonValue value(const QString &key) const
Returns a QJsonValue representing the value for the key key.
bool isEmpty() const
Returns true if the object is empty.
\inmodule QtCore\reentrant
Definition qjsonvalue.h:24
bool isBool() const
Returns true if the value contains a boolean.
Definition qjsonvalue.h:72
bool isString() const
Returns true if the value contains a string.
Definition qjsonvalue.h:74
QJsonObject toObject() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QJsonArray toArray() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
bool toBool(bool defaultValue=false) const
Converts the value to a bool and returns it.
bool isArray() const
Returns true if the value contains an array.
Definition qjsonvalue.h:75
QString toString() const
Converts the value to a QString and returns it.
bool isObject() const
Returns true if the value contains an object.
Definition qjsonvalue.h:76
bool isUndefined() const
Returns true if the value is undefined.
Definition qjsonvalue.h:77
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & first()
Definition qlist.h:628
iterator end()
Definition qlist.h:609
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
T value(qsizetype i) const
Definition qlist.h:661
iterator begin()
Definition qlist.h:608
void append(parameter_type t)
Definition qlist.h:441
Definition qmap.h:186
\inmodule QtCore \reentrant
QRegularExpressionMatch match(const QString &subject, qsizetype offset=0, MatchType matchType=NormalMatch, MatchOptions matchOptions=NoMatchOption) const
Attempts to match the regular expression against the given subject string, starting at the position o...
Definition qset.h:18
qsizetype size() const
Definition qset.h:50
iterator begin()
Definition qset.h:136
bool isEmpty() const
Definition qset.h:52
const_iterator constEnd() const noexcept
Definition qset.h:143
iterator erase(const_iterator i)
Definition qset.h:145
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
\inmodule QtCore
Definition qsettings.h:30
QVariant value(QAnyStringView key, const QVariant &defaultValue) const
Returns the value for setting key.
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:76
bool endsWith(QStringView s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
QString toString() const
Returns a deep copy of this string view's data as a QString.
Definition qstring.h:1014
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toLatin1() const &
Definition qstring.h:559
int toInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an int using base base, which is 10 by default and must be between 2 ...
Definition qstring.h:660
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5299
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3794
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6180
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
QStringList split(const QString &sep, Qt::SplitBehavior behavior=Qt::KeepEmptyParts, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Splits the string into substrings wherever sep occurs, and returns the list of those strings.
Definition qstring.cpp:7956
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5788
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5350
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
int compare(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.cpp:6498
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3110
QByteArray toLocal8Bit() const &
Definition qstring.h:567
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.h:1217
QString & append(QChar c)
Definition qstring.cpp:3227
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
Definition qstring.cpp:5161
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
QString trimmed() const &
Definition qstring.h:380
QByteArray toUtf8() const &
Definition qstring.h:563
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7005
\inmodule QtCore
Definition qurl.h:94
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2828
QString toString() const
Returns the variant as a QString if the variant has a userType() including, but not limited to:
static QByteArray escapeAndEncodeDependencyPath(const QString &path)
int main()
[0]
list append(new Employee("Blackpool", "Stephen"))
QSet< QString >::iterator it
QList< QVariant > arguments
short next
Definition keywords.cpp:445
static const struct @480 keywords[]
@ CaseInsensitive
@ SkipEmptyParts
Definition qnamespace.h:127
size_t qstrlen(const char *str)
#define Q_UNLIKELY(x)
std::pair< T1, T2 > QPair
static const QPainterPath::ElementType * subPath(const QPainterPath::ElementType *t, const QPainterPath::ElementType *end, const qreal *points, bool *closed)
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char * destination
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define forever
Definition qforeach.h:78
return ret
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint index
[2]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLfloat GLfloat f
GLenum src
GLenum GLuint buffer
GLsizei const GLuint * paths
GLenum GLenum dst
GLbitfield flags
GLuint start
GLuint name
GLuint res
const GLubyte * c
GLuint entry
GLdouble GLdouble t
Definition qopenglext.h:243
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
static QString absolutePath(const QString &path)
static QString keyName(const QString &rKey)
#define MAX_PATH
SSL_CTX int(*) void arg)
#define qPrintable(string)
Definition qstring.h:1391
#define QStringLiteral(str)
#define s2
bool uninstallApk(const Options &options)
Definition main.cpp:2847
bool containsApplicationBinary(Options *options)
Definition main.cpp:2427
void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
Definition main.cpp:1311
bool writeDependencyFile(const Options &options)
Definition main.cpp:3217
void cleanAndroidFiles(const Options &options)
Definition main.cpp:1320
bool runCommand(const Options &options, const QString &command)
Definition main.cpp:2298
bool alwaysOverwritableFile(const QString &fileName)
Definition main.cpp:686
void printHelp()
Definition main.cpp:560
static QStringList dependenciesForDepfile
Definition main.cpp:45
bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath, const QString &moduleUrl)
Definition main.cpp:2288
QList< QtDependency > findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath)
Definition main.cpp:1827
bool checkArchitecture(const Options &options, const QString &fileName)
Definition main.cpp:332
static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
Definition main.cpp:1791
bool readAndroidDependencyXml(Options *options, const QString &moduleName, QSet< QString > *usedDependencies, QSet< QString > *remainingDependencies)
Definition main.cpp:1862
bool createRcc(const Options &options)
Definition main.cpp:2319
bool updateFile(const QString &fileName, const QHash< QString, QString > &replacements)
Definition main.cpp:1503
#define QT_POPEN_READ
Definition main.cpp:38
QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
Definition main.cpp:1968
bool updateLibsXml(Options *options)
Definition main.cpp:1547
QStringList getLibraryProjectsInOutputFolder(const Options &options)
Definition main.cpp:2569
static QString batSuffixAppended(QString path)
Definition main.cpp:273
bool copyPackage(const Options &options)
Definition main.cpp:2946
bool buildAndroidProject(const Options &options)
Definition main.cpp:2728
QString packagePath(const Options &options, PackageType pt)
Definition main.cpp:2881
bool signAAB(const Options &options)
Definition main.cpp:2993
bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2)
Definition main.cpp:673
ErrorCode
Definition main.cpp:3196
@ CannotCopyQtFiles
Definition main.cpp:3203
@ SyntaxErrorOrHelpRequested
Definition main.cpp:3198
@ CannotCopyAndroidExtraLibs
Definition main.cpp:3205
@ CannotCreateAndroidProject
Definition main.cpp:3208
@ CannotCopyGnuStl
Definition main.cpp:3202
@ CannotCopyAndroidSources
Definition main.cpp:3206
@ CannotCreateRcc
Definition main.cpp:3214
@ CannotReadDependencies
Definition main.cpp:3201
@ CannotReadInputFile
Definition main.cpp:3199
@ CannotCopyAndroidExtraResources
Definition main.cpp:3212
@ CannotUpdateAndroidFiles
Definition main.cpp:3207
@ CannotInstallApk
Definition main.cpp:3211
@ CannotFindApplicationBinary
Definition main.cpp:3204
@ CannotCopyAndroidTemplate
Definition main.cpp:3200
@ CannotBuildAndroidProject
Definition main.cpp:3209
@ CannotCopyApk
Definition main.cpp:3213
@ CannotSignPackage
Definition main.cpp:3210
@ Success
Definition main.cpp:3197
QString architectureFromName(const QString &name)
Definition main.cpp:256
bool readInputFile(Options *options)
Definition main.cpp:899
static QString zipalignPath(const Options &options, bool *ok)
Definition main.cpp:2977
GradleFlags gradleBuildFlags(const QString &path)
Definition main.cpp:2701
bool copyAndroidSources(const Options &options)
Definition main.cpp:1381
bool copyAndroidExtraResources(Options *options)
Definition main.cpp:1461
QString cleanPackageName(QString packageName)
Definition main.cpp:734
QString fileArchitecture(const Options &options, const QString &path)
Definition main.cpp:298
static const bool mustReadOutputAnyway
Definition main.cpp:43
bool isDeployment(const Options *options, Options::DeploymentMechanism deployment)
Definition main.cpp:1283
static QString execSuffixAppended(QString path)
Definition main.cpp:265
static const QHash< QByteArray, QByteArray > elfArchitectures
Definition main.cpp:243
QString packageNameFromAndroidManifest(const QString &androidManifestPath)
Definition main.cpp:815
QStringList allFilesInside(const QDir &current, const QDir &rootDir)
Definition main.cpp:1446
bool scanImports(Options *options, QSet< QString > *usedDependencies)
Definition main.cpp:2062
static bool mergeGradleProperties(const QString &path, GradleProperties properties)
Definition main.cpp:2638
QString defaultLibexecDir()
Definition main.cpp:281
bool readDependenciesFromElf(Options *options, const QString &fileName, QSet< QString > *usedDependencies, QSet< QString > *remainingDependencies)
Definition main.cpp:2017
bool copyAndroidExtraLibs(Options *options)
Definition main.cpp:1398
bool updateAndroidFiles(Options &options)
Definition main.cpp:1777
bool readInputFileDirectory(Options *options, QJsonObject &jsonObject, const QString keyName)
Definition main.cpp:839
Options parseOptions()
Definition main.cpp:367
QString findInPath(const QString &fileName)
Definition main.cpp:2597
QString detectLatestAndroidPlatform(const QString &sdkPath)
Definition main.cpp:795
bool copyStdCpp(Options *options)
Definition main.cpp:2954
FILE * runAdb(const Options &options, const QString &arguments)
Definition main.cpp:2456
static QString llvmReadobjPath(const Options &options)
Definition main.cpp:290
bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix=QString())
Definition main.cpp:1331
bool copyGradleTemplate(const Options &options)
Definition main.cpp:1349
bool updateStringsXml(const Options &options)
Definition main.cpp:1660
bool copyQtFiles(Options *options)
Definition main.cpp:2501
bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite=false)
Definition main.cpp:1288
QMap< QByteArray, QByteArray > GradleProperties
Definition main.cpp:2616
bool updateAndroidManifest(Options &options)
Definition main.cpp:1689
PackageType
Definition main.cpp:2875
@ UnsignedAPK
Definition main.cpp:2877
@ AAB
Definition main.cpp:2876
@ SignedAPK
Definition main.cpp:2878
bool parseCmakeBoolean(const QJsonValue &value)
Definition main.cpp:829
void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
Definition main.cpp:337
bool readDependencies(Options *options)
Definition main.cpp:2372
bool installApk(const Options &options)
Definition main.cpp:2912
FILE * openProcess(const QString &command)
Definition main.cpp:47
bool copyFileIfNewer(const QString &sourceFileName, const QString &destinationFileName, const Options &options, bool forceOverwrite=false)
Definition main.cpp:695
bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
Definition main.cpp:2481
bool signPackage(const Options &options)
Definition main.cpp:3080
static GradleProperties readGradleProperties(const QString &path)
Definition main.cpp:2618
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
ptrdiff_t qsizetype
Definition qtypes.h:70
unsigned short ushort
Definition qtypes.h:28
QT_BEGIN_NAMESPACE typedef uchar * output
static QString shellQuote(const QString &arg)
QFile file
[0]
QFileInfo info(fileName)
[8]
QSettings settings("MySoft", "Star Runner")
[0]
settings remove("monkey")
QUrl url("example.com")
[constructor-url-reference]
QSharedPointer< T > other(t)
[5]
QString dir
[11]
QStringList fileNames
[4]
QStringList files
[8]
QDBusArgument argument
char * toString(const MyType &t)
[31]
bool setsLegacyPackaging
Definition main.cpp:2697
bool usesIntegerCompileSdkVersion
Definition main.cpp:2698
bool internalSf
Definition main.cpp:204
QString systemLibsPath
Definition main.cpp:184
bool build
Definition main.cpp:124
QString keyStore
Definition main.cpp:193
bool installApk
Definition main.cpp:210
QString storeType
Definition main.cpp:196
QStringList extraLibs
Definition main.cpp:186
bool copyDependenciesOnly
Definition main.cpp:127
QString sigAlg
Definition main.cpp:201
std::vector< QString > rootPaths
Definition main.cpp:155
QString androidPlatform
Definition main.cpp:173
QString currentArchitecture
Definition main.cpp:175
QByteArray targetSdkVersion
Definition main.cpp:166
void setCurrentQtArchitecture(const QString &arch, const QString &directory, const QHash< QString, QString > &directories)
Definition main.cpp:215
QString rccBinaryPath
Definition main.cpp:156
DeploymentMechanism
Definition main.cpp:110
@ Unbundled
Definition main.cpp:112
@ Bundled
Definition main.cpp:111
QStringList initClasses
Definition main.cpp:234
QString qtInstallDirectory
Definition main.cpp:138
QString packageName
Definition main.cpp:185
bool auxMode
Definition main.cpp:125
DeploymentMechanism deploymentMechanism
Definition main.cpp:183
bool protectedAuthenticationPath
Definition main.cpp:206
QString inputFileName
Definition main.cpp:152
bool noRccBundleCleanup
Definition main.cpp:126
bool helpRequested
Definition main.cpp:121
bool timing
Definition main.cpp:123
bool buildAAB
Definition main.cpp:178
QString ndkVersion
Definition main.cpp:134
bool sectionsOnly
Definition main.cpp:205
QStringList qmlImportPaths
Definition main.cpp:159
QString toolchainPrefix
Definition main.cpp:176
QString jdkPath
Definition main.cpp:135
bool qmlSkipImportScanning
Definition main.cpp:240
@ False
Definition main.cpp:117
@ Auto
Definition main.cpp:116
@ True
Definition main.cpp:118
QHash< QString, QStringList > archExtraPlugins
Definition main.cpp:189
QString qtHostDirectory
Definition main.cpp:145
QString sdkBuildToolsVersion
Definition main.cpp:132
QStringList permissions
Definition main.cpp:235
bool releasePackage
Definition main.cpp:192
QByteArray minSdkVersion
Definition main.cpp:165
QString keyStoreAlias
Definition main.cpp:195
QString qtLibExecsDirectory
Definition main.cpp:142
QString buildDirectory
Definition main.cpp:158
QString digestAlg
Definition main.cpp:200
QString versionName
Definition main.cpp:163
QString directory
Definition main.cpp:175
QString stdCppPath
Definition main.cpp:169
QHash< QString, QStringList > archExtraLibs
Definition main.cpp:187
QString tsaCert
Definition main.cpp:203
QString keyPass
Definition main.cpp:197
QString applicationArguments
Definition main.cpp:154
QString depFilePath
Definition main.cpp:157
QString qtLibsDirectory
Definition main.cpp:141
QString qmlImportScannerBinaryPath
Definition main.cpp:239
QString signedJar
Definition main.cpp:199
QHash< QString, QString > qtDirectories
Definition main.cpp:139
QHash< QString, QtInstallDirectoryWithTriple > architectures
Definition main.cpp:174
QString keyStorePassword
Definition main.cpp:194
QString ndkHost
Definition main.cpp:177
QHash< QString, QList< BundledFile > > bundledFiles
Definition main.cpp:228
QString installLocation
Definition main.cpp:212
QString ndkPath
Definition main.cpp:133
QString sigFile
Definition main.cpp:198
QString sdkPath
Definition main.cpp:131
bool usesOpenGL
Definition main.cpp:231
QPair< QString, QString > BundledFile
Definition main.cpp:227
Options()
Definition main.cpp:91
QString qtDataDirectory
Definition main.cpp:140
bool verbose
Definition main.cpp:122
QStringList features
Definition main.cpp:236
QStringList extraPlugins
Definition main.cpp:188
QHash< QString, QList< QtDependency > > qtDependencies
Definition main.cpp:229
QString apkPath
Definition main.cpp:207
QString qtPluginsDirectory
Definition main.cpp:143
std::vector< QString > extraPrefixDirs
Definition main.cpp:146
QHash< QString, QStringList > localLibs
Definition main.cpp:230
QString qtQmlDirectory
Definition main.cpp:144
QString outputDirectory
Definition main.cpp:151
QString applicationBinary
Definition main.cpp:153
QString versionCode
Definition main.cpp:164
QString tsaUrl
Definition main.cpp:202
std::vector< QString > extraLibraryDirs
Definition main.cpp:149
bool isZstdCompressionEnabled
Definition main.cpp:179
QStringList qrcFiles
Definition main.cpp:160
QString androidSourceDirectory
Definition main.cpp:150
QString stdCppName
Definition main.cpp:170
QElapsedTimer timer
Definition main.cpp:128
bool uninstallApk
Definition main.cpp:211
\inmodule QtCore \reentrant
Definition qchar.h:17
bool operator==(const QtDependency &other) const
Definition main.cpp:62
QString absolutePath
Definition main.cpp:68
QtDependency(const QString &rpath, const QString &apath)
Definition main.cpp:60
QString relativePath
Definition main.cpp:67
QHash< QString, QString > qtDirectories
Definition main.cpp:84
QtInstallDirectoryWithTriple(const QString &dir=QString(), const QString &t=QString(), const QHash< QString, QString > &dirs=QHash< QString, QString >())
Definition main.cpp:73