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