1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include <QCoreApplication>
30 #include <QStringList>
31 #include <QDir>
32 #include <QJsonDocument>
33 #include <QJsonObject>
34 #include <QJsonArray>
35 #include <QJsonValue>
36 #include <QDebug>
37 #include <QDataStream>
38 #include <QXmlStreamReader>
39 #include <QDateTime>
40 #include <QStandardPaths>
41 #include <QUuid>
42 #include <QDirIterator>
43 #include <QRegExp>
44 
45 #include <algorithm>
46 
47 #if defined(Q_OS_WIN32)
48 #include <qt_windows.h>
49 #endif
50 
51 #ifdef Q_CC_MSVC
52 #define popen _popen
53 #define QT_POPEN_READ "rb"
54 #define pclose _pclose
55 #else
56 #define QT_POPEN_READ "r"
57 #endif
58 
59 class ActionTimer
60 {
61     qint64 started;
62 public:
63     ActionTimer() = default;
start()64     void start()
65     {
66         started = QDateTime::currentMSecsSinceEpoch();
67     }
elapsed()68     int elapsed()
69     {
70         return int(QDateTime::currentMSecsSinceEpoch() - started);
71     }
72 };
73 
74 static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
75 
deleteRecursively(const QString & dirName)76 void deleteRecursively(const QString &dirName)
77 {
78     QDir dir(dirName);
79     if (!dir.exists())
80         return;
81 
82     const QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
83     for (const QFileInfo &entry : entries) {
84         if (entry.isDir())
85             deleteRecursively(entry.absoluteFilePath());
86         else
87             QFile::remove(entry.absoluteFilePath());
88     }
89 
90     QDir().rmdir(dirName);
91 }
92 
openProcess(const QString & command)93 FILE *openProcess(const QString &command)
94 {
95 #if defined(Q_OS_WIN32)
96     QString processedCommand = QLatin1Char('\"') + command + QLatin1Char('\"');
97 #else
98     const QString& processedCommand = command;
99 #endif
100 
101     return popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ);
102 }
103 
104 struct QtDependency
105 {
QtDependencyQtDependency106     QtDependency(const QString &rpath, const QString &apath) : relativePath(rpath), absolutePath(apath) {}
107 
operator ==QtDependency108     bool operator==(const QtDependency &other) const
109     {
110         return relativePath == other.relativePath && absolutePath == other.absolutePath;
111     }
112 
113     QString relativePath;
114     QString absolutePath;
115 };
116 
117 struct Options
118 {
OptionsOptions119     Options()
120         : helpRequested(false)
121         , verbose(false)
122         , timing(false)
123         , build(true)
124         , auxMode(false)
125         , deploymentMechanism(Bundled)
126         , releasePackage(false)
127         , digestAlg(QLatin1String("SHA-256"))
128         , sigAlg(QLatin1String("SHA256withRSA"))
129         , internalSf(false)
130         , sectionsOnly(false)
131         , protectedAuthenticationPath(false)
132         , jarSigner(false)
133         , installApk(false)
134         , uninstallApk(false)
135     {}
136 
137     enum DeploymentMechanism
138     {
139         Bundled,
140         Ministro
141     };
142 
143     enum TriState {
144         Auto,
145         False,
146         True
147     };
148 
149     bool helpRequested;
150     bool verbose;
151     bool timing;
152     bool build;
153     bool auxMode;
154     ActionTimer timer;
155 
156     // External tools
157     QString sdkPath;
158     QString sdkBuildToolsVersion;
159     QString ndkPath;
160     QString jdkPath;
161 
162     // Build paths
163     QString qtInstallDirectory;
164     std::vector<QString> extraPrefixDirs;
165     QString androidSourceDirectory;
166     QString outputDirectory;
167     QString inputFileName;
168     QString applicationBinary;
169     QString rootPath;
170     QStringList qmlImportPaths;
171     QStringList qrcFiles;
172 
173     // Versioning
174     QString versionName;
175     QString versionCode;
176     QByteArray minSdkVersion{"21"};
177     QByteArray targetSdkVersion{"29"};
178 
179     // lib c++ path
180     QString stdCppPath;
181     QString stdCppName = QStringLiteral("c++_shared");
182 
183     // Build information
184     QString androidPlatform;
185     QHash<QString, QString> architectures;
186     QString currentArchitecture;
187     QString toolchainPrefix;
188     QString ndkHost;
189     bool buildAAB = false;
190 
191 
192     // Package information
193     DeploymentMechanism deploymentMechanism;
194     QString packageName;
195     QStringList extraLibs;
196     QHash<QString, QStringList> archExtraLibs;
197     QStringList extraPlugins;
198     QHash<QString, QStringList> archExtraPlugins;
199 
200     // Signing information
201     bool releasePackage;
202     QString keyStore;
203     QString keyStorePassword;
204     QString keyStoreAlias;
205     QString storeType;
206     QString keyPass;
207     QString sigFile;
208     QString signedJar;
209     QString digestAlg;
210     QString sigAlg;
211     QString tsaUrl;
212     QString tsaCert;
213     bool internalSf;
214     bool sectionsOnly;
215     bool protectedAuthenticationPath;
216     bool jarSigner;
217     QString apkPath;
218 
219     // Installation information
220     bool installApk;
221     bool uninstallApk;
222     QString installLocation;
223 
224     // Per architecture collected information
clearOptions225     void clear(const QString &arch)
226     {
227         currentArchitecture = arch;
228     }
229     typedef QPair<QString, QString> BundledFile;
230     QHash<QString, QList<BundledFile>> bundledFiles;
231     QHash<QString, QList<QtDependency>> qtDependencies;
232     QHash<QString, QStringList> localLibs;
233     bool usesOpenGL = false;
234 
235     // Per package collected information
236     QStringList localJars;
237     QStringList initClasses;
238     QStringList permissions;
239     QStringList features;
240 };
241 
242 static const QHash<QByteArray, QByteArray> elfArchitecures = {
243     {"aarch64", "arm64-v8a"},
244     {"arm", "armeabi-v7a"},
245     {"i386", "x86"},
246     {"x86_64", "x86_64"}
247 };
248 
249 // Copy-pasted from qmake/library/ioutil.cpp
hasSpecialChars(const QString & arg,const uchar (& iqm)[16])250 inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
251 {
252     for (int x = arg.length() - 1; x >= 0; --x) {
253         ushort c = arg.unicode()[x].unicode();
254         if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
255             return true;
256     }
257     return false;
258 }
259 
shellQuoteUnix(const QString & arg)260 static QString shellQuoteUnix(const QString &arg)
261 {
262     // Chars that should be quoted (TM). This includes:
263     static const uchar iqm[] = {
264         0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
265         0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
266     }; // 0-32 \'"$`<>|;&(){}*?#!~[]
267 
268     if (!arg.length())
269         return QLatin1String("\"\"");
270 
271     QString ret(arg);
272     if (hasSpecialChars(ret, iqm)) {
273         ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
274         ret.prepend(QLatin1Char('\''));
275         ret.append(QLatin1Char('\''));
276     }
277     return ret;
278 }
279 
shellQuoteWin(const QString & arg)280 static QString shellQuoteWin(const QString &arg)
281 {
282     // Chars that should be quoted (TM). This includes:
283     // - control chars & space
284     // - the shell meta chars "&()<>^|
285     // - the potential separators ,;=
286     static const uchar iqm[] = {
287         0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
288         0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
289     };
290 
291     if (!arg.length())
292         return QLatin1String("\"\"");
293 
294     QString ret(arg);
295     if (hasSpecialChars(ret, iqm)) {
296         // Quotes are escaped and their preceding backslashes are doubled.
297         // It's impossible to escape anything inside a quoted string on cmd
298         // level, so the outer quoting must be "suspended".
299         ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
300         // The argument must not end with a \ since this would be interpreted
301         // as escaping the quote -- rather put the \ behind the quote: e.g.
302         // rather use "foo"\ than "foo\"
303         int i = ret.length();
304         while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
305             --i;
306         ret.insert(i, QLatin1Char('"'));
307         ret.prepend(QLatin1Char('"'));
308     }
309     return ret;
310 }
311 
shellQuote(const QString & arg)312 static QString shellQuote(const QString &arg)
313 {
314     if (QDir::separator() == QLatin1Char('\\'))
315         return shellQuoteWin(arg);
316     else
317         return shellQuoteUnix(arg);
318 }
319 
architecureFromName(const QString & name)320 QString architecureFromName(const QString &name)
321 {
322     QRegExp architecture(QStringLiteral(".*_(armeabi-v7a|arm64-v8a|x86|x86_64).so"));
323     if (!architecture.exactMatch(name))
324         return {};
325     return architecture.capturedTexts().last();
326 }
327 
fileArchitecture(const Options & options,const QString & path)328 QString fileArchitecture(const Options &options, const QString &path)
329 {
330     auto arch = architecureFromName(path);
331     if (!arch.isEmpty())
332         return arch;
333 
334     QString readElf = QLatin1String("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj").arg(options.ndkPath,
335                                                                                           options.toolchainPrefix,
336                                                                                           options.ndkHost);
337 #if defined(Q_OS_WIN32)
338     readElf += QLatin1String(".exe");
339 #endif
340 
341     if (!QFile::exists(readElf)) {
342         fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
343         return {};
344     }
345 
346     readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(path));
347 
348     FILE *readElfCommand = openProcess(readElf);
349     if (!readElfCommand) {
350         fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
351         return {};
352     }
353 
354     char buffer[512];
355     while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
356         QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
357         QString library;
358         line = line.trimmed();
359         if (line.startsWith("Arch: ")) {
360             auto it = elfArchitecures.find(line.mid(6));
361             pclose(readElfCommand);
362             return it != elfArchitecures.constEnd() ? QString::fromLatin1(it.value()) : QString{};
363         }
364     }
365     pclose(readElfCommand);
366     return {};
367 }
368 
checkArchitecture(const Options & options,const QString & fileName)369 bool checkArchitecture(const Options &options, const QString &fileName)
370 {
371     return fileArchitecture(options, fileName) == options.currentArchitecture;
372 }
373 
deleteMissingFiles(const Options & options,const QDir & srcDir,const QDir & dstDir)374 void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
375 {
376     if (options.verbose)
377         fprintf(stdout, "Delete missing files %s %s\n", qPrintable(srcDir.absolutePath()), qPrintable(dstDir.absolutePath()));
378 
379     const QFileInfoList srcEntries = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
380     const QFileInfoList dstEntries = dstDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
381     for (const QFileInfo &dst : dstEntries) {
382         bool found = false;
383         for (const QFileInfo &src : srcEntries)
384             if (dst.fileName() == src.fileName()) {
385                 if (dst.isDir())
386                     deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath());
387                 found = true;
388                 break;
389             }
390 
391         if (!found) {
392             if (options.verbose)
393                 fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath()));
394 
395             if (dst.isDir())
396                 deleteRecursively(dst.absolutePath());
397             else
398                 QFile::remove(dst.absoluteFilePath());
399         }
400     }
401     fflush(stdout);
402 }
403 
404 
parseOptions()405 Options parseOptions()
406 {
407     Options options;
408 
409     QStringList arguments = QCoreApplication::arguments();
410     for (int i=0; i<arguments.size(); ++i) {
411         const QString &argument = arguments.at(i);
412         if (argument.compare(QLatin1String("--output"), Qt::CaseInsensitive) == 0) {
413             if (i + 1 == arguments.size())
414                 options.helpRequested = true;
415             else
416                 options.outputDirectory = arguments.at(++i).trimmed();
417         } else if (argument.compare(QLatin1String("--input"), Qt::CaseInsensitive) == 0) {
418             if (i + 1 == arguments.size())
419                 options.helpRequested = true;
420             else
421                 options.inputFileName = arguments.at(++i);
422         } else if (argument.compare(QLatin1String("--aab"), Qt::CaseInsensitive) == 0) {
423             options.buildAAB = true;
424             options.build = true;
425             options.jarSigner = true;
426         } else if (!options.buildAAB && argument.compare(QLatin1String("--no-build"), Qt::CaseInsensitive) == 0) {
427             options.build = false;
428         } else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) {
429             options.installApk = true;
430             options.uninstallApk = true;
431         } else if (argument.compare(QLatin1String("--reinstall"), Qt::CaseInsensitive) == 0) {
432             options.installApk = true;
433             options.uninstallApk = false;
434         } else if (argument.compare(QLatin1String("--android-platform"), Qt::CaseInsensitive) == 0) {
435             if (i + 1 == arguments.size())
436                 options.helpRequested = true;
437             else
438                 options.androidPlatform = arguments.at(++i);
439         } else if (argument.compare(QLatin1String("--help"), Qt::CaseInsensitive) == 0) {
440             options.helpRequested = true;
441         } else if (argument.compare(QLatin1String("--verbose"), Qt::CaseInsensitive) == 0) {
442             options.verbose = true;
443         } else if (argument.compare(QLatin1String("--deployment"), Qt::CaseInsensitive) == 0) {
444             if (i + 1 == arguments.size()) {
445                 options.helpRequested = true;
446             } else {
447                 QString deploymentMechanism = arguments.at(++i);
448                 if (deploymentMechanism.compare(QLatin1String("ministro"), Qt::CaseInsensitive) == 0) {
449                     options.deploymentMechanism = Options::Ministro;
450                 } else if (deploymentMechanism.compare(QLatin1String("bundled"), Qt::CaseInsensitive) == 0) {
451                     options.deploymentMechanism = Options::Bundled;
452                 } else {
453                     fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
454                     options.helpRequested = true;
455                 }
456             }
457         } else if (argument.compare(QLatin1String("--device"), Qt::CaseInsensitive) == 0) {
458             if (i + 1 == arguments.size())
459                 options.helpRequested = true;
460             else
461                 options.installLocation = arguments.at(++i);
462         } else if (argument.compare(QLatin1String("--release"), Qt::CaseInsensitive) == 0) {
463             options.releasePackage = true;
464         } else if (argument.compare(QLatin1String("--jdk"), Qt::CaseInsensitive) == 0) {
465             if (i + 1 == arguments.size())
466                 options.helpRequested = true;
467             else
468                 options.jdkPath = arguments.at(++i);
469         } else if (argument.compare(QLatin1String("--apk"), Qt::CaseInsensitive) == 0) {
470             if (i + 1 == arguments.size())
471                 options.helpRequested = true;
472             else
473                 options.apkPath = arguments.at(++i);
474         } else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) {
475             if (i + 2 >= arguments.size()) {
476                 options.helpRequested = true;
477             } else {
478                 options.releasePackage = true;
479                 options.keyStore = arguments.at(++i);
480                 options.keyStoreAlias = arguments.at(++i);
481             }
482         } else if (argument.compare(QLatin1String("--storepass"), Qt::CaseInsensitive) == 0) {
483             if (i + 1 == arguments.size())
484                 options.helpRequested = true;
485             else
486                 options.keyStorePassword = arguments.at(++i);
487         } else if (argument.compare(QLatin1String("--storetype"), Qt::CaseInsensitive) == 0) {
488             if (i + 1 == arguments.size())
489                 options.helpRequested = true;
490             else
491                 options.storeType = arguments.at(++i);
492         } else if (argument.compare(QLatin1String("--keypass"), Qt::CaseInsensitive) == 0) {
493             if (i + 1 == arguments.size())
494                 options.helpRequested = true;
495             else
496                 options.keyPass = arguments.at(++i);
497         } else if (argument.compare(QLatin1String("--sigfile"), Qt::CaseInsensitive) == 0) {
498             if (i + 1 == arguments.size())
499                 options.helpRequested = true;
500             else
501                 options.sigFile = arguments.at(++i);
502         } else if (argument.compare(QLatin1String("--digestalg"), Qt::CaseInsensitive) == 0) {
503             if (i + 1 == arguments.size())
504                 options.helpRequested = true;
505             else
506                 options.digestAlg = arguments.at(++i);
507         } else if (argument.compare(QLatin1String("--sigalg"), Qt::CaseInsensitive) == 0) {
508             if (i + 1 == arguments.size())
509                 options.helpRequested = true;
510             else
511                 options.sigAlg = arguments.at(++i);
512         } else if (argument.compare(QLatin1String("--tsa"), Qt::CaseInsensitive) == 0) {
513             if (i + 1 == arguments.size())
514                 options.helpRequested = true;
515             else
516                 options.tsaUrl = arguments.at(++i);
517         } else if (argument.compare(QLatin1String("--tsacert"), Qt::CaseInsensitive) == 0) {
518             if (i + 1 == arguments.size())
519                 options.helpRequested = true;
520             else
521                 options.tsaCert = arguments.at(++i);
522         } else if (argument.compare(QLatin1String("--internalsf"), Qt::CaseInsensitive) == 0) {
523             options.internalSf = true;
524         } else if (argument.compare(QLatin1String("--sectionsonly"), Qt::CaseInsensitive) == 0) {
525             options.sectionsOnly = true;
526         } else if (argument.compare(QLatin1String("--protected"), Qt::CaseInsensitive) == 0) {
527             options.protectedAuthenticationPath = true;
528         } else if (argument.compare(QLatin1String("--jarsigner"), Qt::CaseInsensitive) == 0) {
529             options.jarSigner = true;
530         } else if (argument.compare(QLatin1String("--aux-mode"), Qt::CaseInsensitive) == 0) {
531             options.auxMode = true;
532         }
533     }
534 
535     if (options.inputFileName.isEmpty())
536         options.inputFileName = QLatin1String("android-lib%1.so-deployment-settings.json").arg(QDir::current().dirName());
537 
538     options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT");
539 
540     if (!QDir::current().mkpath(options.outputDirectory)) {
541         fprintf(stderr, "Invalid output directory: %s\n", qPrintable(options.outputDirectory));
542         options.outputDirectory.clear();
543     } else {
544         options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath();
545         if (!options.outputDirectory.endsWith(QLatin1Char('/')))
546             options.outputDirectory += QLatin1Char('/');
547     }
548 
549     return options;
550 }
551 
printHelp()552 void printHelp()
553 {//                 "012345678901234567890123456789012345678901234567890123456789012345678901"
554     fprintf(stderr, "Syntax: %s --output <destination> [options]\n"
555                     "\n"
556                     "  Creates an Android package in the build directory <destination> and\n"
557                     "  builds it into an .apk file.\n\n"
558                     "  Optional arguments:\n"
559                     "    --input <inputfile>: Reads <inputfile> for options generated by\n"
560                     "       qmake. A default file name based on the current working\n"
561                     "       directory will be used if nothing else is specified.\n"
562                     "    --deployment <mechanism>: Supported deployment mechanisms:\n"
563                     "       bundled (default): Include Qt files in stand-alone package.\n"
564                     "       ministro: Use the Ministro service to manage Qt files.\n"
565                     "    --aab: Build an Android App Bundle.\n"
566                     "    --no-build: Do not build the package, it is useful to just install\n"
567                     "       a package previously built.\n"
568                     "    --install: Installs apk to device/emulator. By default this step is\n"
569                     "       not taken. If the application has previously been installed on\n"
570                     "       the device, it will be uninstalled first.\n"
571                     "    --reinstall: Installs apk to device/emulator. By default this step\n"
572                     "       is not taken. If the application has previously been installed on\n"
573                     "       the device, it will be overwritten, but its data will be left\n"
574                     "       intact.\n"
575                     "    --device [device ID]: Use specified device for deployment. Default\n"
576                     "       is the device selected by default by adb.\n"
577                     "    --android-platform <platform>: Builds against the given android\n"
578                     "       platform. By default, the highest available version will be\n"
579                     "       used.\n"
580                     "    --release: Builds a package ready for release. By default, the\n"
581                     "       package will be signed with a debug key.\n"
582                     "    --sign <url/to/keystore> <alias>: Signs the package with the\n"
583                     "       specified keystore, alias and store password. Also implies the\n"
584                     "       --release option.\n"
585                     "       Optional arguments for use with signing:\n"
586                     "         --storepass <password>: Keystore password.\n"
587                     "         --storetype <type>: Keystore type.\n"
588                     "         --keypass <password>: Password for private key (if different\n"
589                     "           from keystore password.)\n"
590                     "         --sigfile <file>: Name of .SF/.DSA file.\n"
591                     "         --digestalg <name>: Name of digest algorithm. Default is\n"
592                     "           \"SHA1\".\n"
593                     "         --sigalg <name>: Name of signature algorithm. Default is\n"
594                     "           \"SHA1withRSA\".\n"
595                     "         --tsa <url>: Location of the Time Stamping Authority.\n"
596                     "         --tsacert <alias>: Public key certificate for TSA.\n"
597                     "         --internalsf: Include the .SF file inside the signature block.\n"
598                     "         --sectionsonly: Don't compute hash of entire manifest.\n"
599                     "         --protected: Keystore has protected authentication path.\n"
600                     "         --jarsigner: Force jarsigner usage, otherwise apksigner will be\n"
601                     "           used if available.\n"
602                     "    --jdk <path/to/jdk>: Used to find the jarsigner tool when used\n"
603                     "       in combination with the --release argument. By default,\n"
604                     "       an attempt is made to detect the tool using the JAVA_HOME and\n"
605                     "       PATH environment variables, in that order.\n"
606                     "    --qml-import-paths: Specify additional search paths for QML\n"
607                     "       imports.\n"
608                     "    --verbose: Prints out information during processing.\n"
609                     "    --no-generated-assets-cache: Do not pregenerate the entry list for\n"
610                     "       the assets file engine.\n"
611                     "    --aux-mode: Operate in auxiliary mode. This will only copy the\n"
612                     "       dependencies into the build directory and update the XML templates.\n"
613                     "       The project will not be built or installed.\n"
614                     "    --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.\n"
615                     "    --help: Displays this information.\n\n",
616                     qPrintable(QCoreApplication::arguments().at(0))
617             );
618 }
619 
620 // Since strings compared will all start with the same letters,
621 // sorting by length and then alphabetically within each length
622 // gives the natural order.
quasiLexicographicalReverseLessThan(const QFileInfo & fi1,const QFileInfo & fi2)623 bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2)
624 {
625     QString s1 = fi1.baseName();
626     QString s2 = fi2.baseName();
627 
628     if (s1.length() == s2.length())
629         return s1 > s2;
630     else
631         return s1.length() > s2.length();
632 }
633 
634 // Files which contain templates that need to be overwritten by build data should be overwritten every
635 // time.
alwaysOverwritableFile(const QString & fileName)636 bool alwaysOverwritableFile(const QString &fileName)
637 {
638     return (fileName.endsWith(QLatin1String("/res/values/libs.xml"))
639             || fileName.endsWith(QLatin1String("/AndroidManifest.xml"))
640             || fileName.endsWith(QLatin1String("/res/values/strings.xml"))
641             || fileName.endsWith(QLatin1String("/src/org/qtproject/qt5/android/bindings/QtActivity.java")));
642 }
643 
644 
copyFileIfNewer(const QString & sourceFileName,const QString & destinationFileName,const Options & options,bool forceOverwrite=false)645 bool copyFileIfNewer(const QString &sourceFileName,
646                      const QString &destinationFileName,
647                      const Options &options,
648                      bool forceOverwrite = false)
649 {
650     if (QFile::exists(destinationFileName)) {
651         QFileInfo destinationFileInfo(destinationFileName);
652         QFileInfo sourceFileInfo(sourceFileName);
653 
654         if (!forceOverwrite
655                 && sourceFileInfo.lastModified() <= destinationFileInfo.lastModified()
656                 && !alwaysOverwritableFile(destinationFileName)) {
657             if (options.verbose)
658                 fprintf(stdout, "  -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName));
659             return true;
660         } else {
661             if (!QFile(destinationFileName).remove()) {
662                 fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName));
663                 return false;
664             }
665         }
666     }
667 
668     if (!QDir().mkpath(QFileInfo(destinationFileName).path())) {
669         fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName));
670         return false;
671     }
672 
673     if (!QFile::exists(destinationFileName) && !QFile::copy(sourceFileName, destinationFileName)) {
674         fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName));
675         return false;
676     } else if (options.verbose) {
677         fprintf(stdout, "  -- Copied %s\n", qPrintable(destinationFileName));
678         fflush(stdout);
679     }
680     return true;
681 }
682 
cleanPackageName(QString packageName)683 QString cleanPackageName(QString packageName)
684 {
685     QRegExp legalChars(QLatin1String("[a-zA-Z0-9_\\.]"));
686 
687     for (int i = 0; i < packageName.length(); ++i) {
688         if (!legalChars.exactMatch(packageName.mid(i, 1)))
689             packageName[i] = QLatin1Char('_');
690     }
691 
692     static QStringList keywords;
693     if (keywords.isEmpty()) {
694         keywords << QLatin1String("abstract") << QLatin1String("continue") << QLatin1String("for")
695                  << QLatin1String("new") << QLatin1String("switch") << QLatin1String("assert")
696                  << QLatin1String("default") << QLatin1String("if") << QLatin1String("package")
697                  << QLatin1String("synchronized") << QLatin1String("boolean") << QLatin1String("do")
698                  << QLatin1String("goto") << QLatin1String("private") << QLatin1String("this")
699                  << QLatin1String("break") << QLatin1String("double") << QLatin1String("implements")
700                  << QLatin1String("protected") << QLatin1String("throw") << QLatin1String("byte")
701                  << QLatin1String("else") << QLatin1String("import") << QLatin1String("public")
702                  << QLatin1String("throws") << QLatin1String("case") << QLatin1String("enum")
703                  << QLatin1String("instanceof") << QLatin1String("return") << QLatin1String("transient")
704                  << QLatin1String("catch") << QLatin1String("extends") << QLatin1String("int")
705                  << QLatin1String("short") << QLatin1String("try") << QLatin1String("char")
706                  << QLatin1String("final") << QLatin1String("interface") << QLatin1String("static")
707                  << QLatin1String("void") << QLatin1String("class") << QLatin1String("finally")
708                  << QLatin1String("long") << QLatin1String("strictfp") << QLatin1String("volatile")
709                  << QLatin1String("const") << QLatin1String("float") << QLatin1String("native")
710                  << QLatin1String("super") << QLatin1String("while");
711     }
712 
713     // No keywords
714     int index = -1;
715     while (index < packageName.length()) {
716         int next = packageName.indexOf(QLatin1Char('.'), index + 1);
717         if (next == -1)
718             next = packageName.length();
719         QString word = packageName.mid(index + 1, next - index - 1);
720         if (!word.isEmpty()) {
721             QChar c = word[0];
722             if ((c >= QChar(QLatin1Char('0')) && c<= QChar(QLatin1Char('9')))
723                    || c == QLatin1Char('_')) {
724                 packageName.insert(index + 1, QLatin1Char('a'));
725                 index = next + 1;
726                 continue;
727             }
728         }
729         if (keywords.contains(word)) {
730             packageName.insert(next, QLatin1String("_"));
731             index = next + 1;
732         } else {
733             index = next;
734         }
735     }
736 
737     return packageName;
738 }
739 
detectLatestAndroidPlatform(const QString & sdkPath)740 QString detectLatestAndroidPlatform(const QString &sdkPath)
741 {
742     QDir dir(sdkPath + QLatin1String("/platforms"));
743     if (!dir.exists()) {
744         fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath()));
745         return QString();
746     }
747 
748     QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
749     if (fileInfos.isEmpty()) {
750         fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath()));
751         return QString();
752     }
753 
754     std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
755 
756     QFileInfo latestPlatform = fileInfos.first();
757     return latestPlatform.baseName();
758 }
759 
packageNameFromAndroidManifest(const QString & androidManifestPath)760 QString packageNameFromAndroidManifest(const QString &androidManifestPath)
761 {
762     QFile androidManifestXml(androidManifestPath);
763     if (androidManifestXml.open(QIODevice::ReadOnly)) {
764         QXmlStreamReader reader(&androidManifestXml);
765         while (!reader.atEnd()) {
766             reader.readNext();
767             if (reader.isStartElement() && reader.name() == QLatin1String("manifest"))
768                 return cleanPackageName(
769                             reader.attributes().value(QLatin1String("package")).toString());
770         }
771     }
772     return {};
773 }
774 
readInputFile(Options * options)775 bool readInputFile(Options *options)
776 {
777     QFile file(options->inputFileName);
778     if (!file.open(QIODevice::ReadOnly)) {
779         fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
780         return false;
781     }
782 
783     QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll());
784     if (jsonDocument.isNull()) {
785         fprintf(stderr, "Invalid json file: %s\n", qPrintable(options->inputFileName));
786         return false;
787     }
788 
789     QJsonObject jsonObject = jsonDocument.object();
790 
791     {
792         QJsonValue sdkPath = jsonObject.value(QLatin1String("sdk"));
793         if (sdkPath.isUndefined()) {
794             fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName));
795             return false;
796         }
797 
798         options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
799 
800         if (options->androidPlatform.isEmpty()) {
801             options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
802             if (options->androidPlatform.isEmpty())
803                 return false;
804         } else {
805             if (!QDir(options->sdkPath + QLatin1String("/platforms/") + options->androidPlatform).exists()) {
806                 fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n",
807                         qPrintable(options->androidPlatform));
808             }
809         }
810     }
811 
812     {
813 
814         const QJsonValue value = jsonObject.value(QLatin1String("sdkBuildToolsRevision"));
815         if (!value.isUndefined())
816             options->sdkBuildToolsVersion = value.toString();
817     }
818 
819     {
820         const QJsonValue qtInstallDirectory = jsonObject.value(QLatin1String("qt"));
821         if (qtInstallDirectory.isUndefined()) {
822             fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName));
823             return false;
824         }
825         options->qtInstallDirectory = qtInstallDirectory.toString();
826     }
827 
828     {
829         const auto extraPrefixDirs = jsonObject.value(QLatin1String("extraPrefixDirs")).toArray();
830         options->extraPrefixDirs.reserve(extraPrefixDirs.size());
831         for (const auto &prefix : extraPrefixDirs) {
832             options->extraPrefixDirs.push_back(prefix.toString());
833         }
834     }
835 
836     {
837         const QJsonValue androidSourcesDirectory = jsonObject.value(QLatin1String("android-package-source-directory"));
838         if (!androidSourcesDirectory.isUndefined())
839             options->androidSourceDirectory = androidSourcesDirectory.toString();
840     }
841 
842     {
843         const QJsonValue androidVersionName = jsonObject.value(QLatin1String("android-version-name"));
844         if (!androidVersionName.isUndefined())
845             options->versionName = androidVersionName.toString();
846         else
847             options->versionName = QStringLiteral("1.0");
848     }
849 
850     {
851         const QJsonValue androidVersionCode = jsonObject.value(QLatin1String("android-version-code"));
852         if (!androidVersionCode.isUndefined())
853             options->versionCode = androidVersionCode.toString();
854         else
855             options->versionCode = QStringLiteral("1");
856     }
857 
858     {
859         const QJsonValue ver = jsonObject.value(QLatin1String("android-min-sdk-version"));
860         if (!ver.isUndefined())
861             options->minSdkVersion = ver.toString().toUtf8();
862     }
863 
864     {
865         const QJsonValue ver = jsonObject.value(QLatin1String("android-target-sdk-version"));
866         if (!ver.isUndefined())
867             options->targetSdkVersion = ver.toString().toUtf8();
868     }
869 
870     {
871         const QJsonObject targetArchitectures = jsonObject.value(QLatin1String("architectures")).toObject();
872         if (targetArchitectures.isEmpty()) {
873             fprintf(stderr, "No target architecture defined in json file.\n");
874             return false;
875         }
876         for (auto it = targetArchitectures.constBegin(); it != targetArchitectures.constEnd(); ++it) {
877             if (it.value().isUndefined()) {
878                 fprintf(stderr, "Invalid architecure.\n");
879                 return false;
880             }
881             if (it.value().isNull())
882                 continue;
883             options->architectures.insert(it.key(), it.value().toString());
884         }
885     }
886 
887     {
888         const QJsonValue ndk = jsonObject.value(QLatin1String("ndk"));
889         if (ndk.isUndefined()) {
890             fprintf(stderr, "No NDK path defined in json file.\n");
891             return false;
892         }
893         options->ndkPath = ndk.toString();
894     }
895 
896     {
897         const QJsonValue toolchainPrefix = jsonObject.value(QLatin1String("toolchain-prefix"));
898         if (toolchainPrefix.isUndefined()) {
899             fprintf(stderr, "No toolchain prefix defined in json file.\n");
900             return false;
901         }
902         options->toolchainPrefix = toolchainPrefix.toString();
903     }
904 
905     {
906         const QJsonValue ndkHost = jsonObject.value(QLatin1String("ndk-host"));
907         if (ndkHost.isUndefined()) {
908             fprintf(stderr, "No NDK host defined in json file.\n");
909             return false;
910         }
911         options->ndkHost = ndkHost.toString();
912     }
913 
914     {
915         const QJsonValue extraLibs = jsonObject.value(QLatin1String("android-extra-libs"));
916         if (!extraLibs.isUndefined())
917             options->extraLibs = extraLibs.toString().split(QLatin1Char(','), Qt::SkipEmptyParts);
918     }
919 
920     {
921         const QJsonValue extraPlugins = jsonObject.value(QLatin1String("android-extra-plugins"));
922         if (!extraPlugins.isUndefined())
923             options->extraPlugins = extraPlugins.toString().split(QLatin1Char(','));
924     }
925 
926     {
927         const QJsonValue stdcppPath = jsonObject.value(QLatin1String("stdcpp-path"));
928         if (stdcppPath.isUndefined()) {
929             fprintf(stderr, "No stdcpp-path defined in json file.\n");
930             return false;
931         }
932         options->stdCppPath = stdcppPath.toString();
933     }
934 
935     {
936         const QJsonValue qmlRootPath = jsonObject.value(QLatin1String("qml-root-path"));
937         if (!qmlRootPath.isUndefined())
938             options->rootPath = qmlRootPath.toString();
939     }
940 
941     {
942         const QJsonValue qmlImportPaths = jsonObject.value(QLatin1String("qml-import-paths"));
943         if (!qmlImportPaths.isUndefined())
944             options->qmlImportPaths = qmlImportPaths.toString().split(QLatin1Char(','));
945     }
946 
947     {
948         const QJsonValue applicationBinary = jsonObject.value(QLatin1String("application-binary"));
949         if (applicationBinary.isUndefined()) {
950             fprintf(stderr, "No application binary defined in json file.\n");
951             return false;
952         }
953         options->applicationBinary = applicationBinary.toString();
954         if (options->build) {
955             for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
956                 auto appBinaryPath = QLatin1String("%1/libs/%2/lib%3_%2.so").arg(options->outputDirectory, it.key(), options->applicationBinary);
957                 if (!QFile::exists(appBinaryPath)) {
958                     fprintf(stderr, "Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath));
959                     return false;
960                 }
961             }
962         }
963     }
964 
965     {
966         const QJsonValue deploymentDependencies = jsonObject.value(QLatin1String("deployment-dependencies"));
967         if (!deploymentDependencies.isUndefined()) {
968             QString deploymentDependenciesString = deploymentDependencies.toString();
969             const auto dependencies = deploymentDependenciesString.splitRef(QLatin1Char(','));
970             for (const QStringRef &dependency : dependencies) {
971                 QString path = options->qtInstallDirectory + QLatin1Char('/') + dependency;
972                 if (QFileInfo(path).isDir()) {
973                     QDirIterator iterator(path, QDirIterator::Subdirectories);
974                     while (iterator.hasNext()) {
975                         iterator.next();
976                         if (iterator.fileInfo().isFile()) {
977                             QString subPath = iterator.filePath();
978                             auto arch = fileArchitecture(*options, subPath);
979                             if (!arch.isEmpty()) {
980                                 options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.length() + 1),
981                                                                                   subPath));
982                             } else if (options->verbose) {
983                                 fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(subPath));
984                                 fflush(stderr);
985                             }
986                         }
987                     }
988                 } else {
989                     auto arch = fileArchitecture(*options, path);
990                     if (!arch.isEmpty()) {
991                         options->qtDependencies[arch].append(QtDependency(dependency.toString(), path));
992                     } else if (options->verbose) {
993                         fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path));
994                         fflush(stderr);
995                     }
996                 }
997             }
998         }
999     }
1000     {
1001         const QJsonValue qrcFiles = jsonObject.value(QLatin1String("qrcFiles"));
1002         options->qrcFiles = qrcFiles.toString().split(QLatin1Char(','), Qt::SkipEmptyParts);
1003     }
1004     options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + QLatin1String("/AndroidManifest.xml"));
1005     if (options->packageName.isEmpty())
1006         options->packageName = cleanPackageName(QLatin1String("org.qtproject.example.%1").arg(options->applicationBinary));
1007 
1008     return true;
1009 }
1010 
copyFiles(const QDir & sourceDirectory,const QDir & destinationDirectory,const Options & options,bool forceOverwrite=false)1011 bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite = false)
1012 {
1013     const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
1014     for (const QFileInfo &entry : entries) {
1015         if (entry.isDir()) {
1016             QDir dir(entry.absoluteFilePath());
1017             if (!destinationDirectory.mkpath(dir.dirName())) {
1018                 fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
1019                 return false;
1020             }
1021 
1022             if (!copyFiles(dir, QDir(destinationDirectory.path() + QLatin1Char('/') + dir.dirName()), options, forceOverwrite))
1023                 return false;
1024         } else {
1025             QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
1026             if (!copyFileIfNewer(entry.absoluteFilePath(), destination, options, forceOverwrite))
1027                 return false;
1028         }
1029     }
1030 
1031     return true;
1032 }
1033 
cleanTopFolders(const Options & options,const QDir & srcDir,const QString & dstDir)1034 void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
1035 {
1036     const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
1037     for (const QFileInfo &dir : dirs) {
1038         if (dir.fileName() != QLatin1String("libs"))
1039             deleteMissingFiles(options, dir.absoluteFilePath(), dstDir + dir.fileName());
1040     }
1041 }
1042 
cleanAndroidFiles(const Options & options)1043 void cleanAndroidFiles(const Options &options)
1044 {
1045     if (!options.androidSourceDirectory.isEmpty())
1046         cleanTopFolders(options, options.androidSourceDirectory, options.outputDirectory);
1047 
1048     cleanTopFolders(options, options.qtInstallDirectory + QLatin1String("/src/android/templates"), options.outputDirectory);
1049 }
1050 
copyAndroidTemplate(const Options & options,const QString & androidTemplate,const QString & outDirPrefix=QString ())1051 bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
1052 {
1053     QDir sourceDirectory(options.qtInstallDirectory + androidTemplate);
1054     if (!sourceDirectory.exists()) {
1055         fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1056         return false;
1057     }
1058 
1059     QString outDir = options.outputDirectory + outDirPrefix;
1060 
1061     if (!QDir::current().mkpath(outDir)) {
1062         fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1063         return false;
1064     }
1065 
1066     return copyFiles(sourceDirectory, QDir(outDir), options);
1067 }
1068 
copyGradleTemplate(const Options & options)1069 bool copyGradleTemplate(const Options &options)
1070 {
1071     QDir sourceDirectory(options.qtInstallDirectory + QLatin1String("/src/3rdparty/gradle"));
1072     if (!sourceDirectory.exists()) {
1073         fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1074         return false;
1075     }
1076 
1077     QString outDir(options.outputDirectory);
1078     if (!QDir::current().mkpath(outDir)) {
1079         fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1080         return false;
1081     }
1082 
1083     return copyFiles(sourceDirectory, QDir(outDir), options);
1084 }
1085 
copyAndroidTemplate(const Options & options)1086 bool copyAndroidTemplate(const Options &options)
1087 {
1088     if (options.verbose)
1089         fprintf(stdout, "Copying Android package template.\n");
1090 
1091     if (!copyGradleTemplate(options))
1092         return false;
1093 
1094     if (!copyAndroidTemplate(options, QLatin1String("/src/android/templates")))
1095         return false;
1096 
1097     return true;
1098 }
1099 
copyAndroidSources(const Options & options)1100 bool copyAndroidSources(const Options &options)
1101 {
1102     if (options.androidSourceDirectory.isEmpty())
1103         return true;
1104 
1105     if (options.verbose)
1106         fprintf(stdout, "Copying Android sources from project.\n");
1107 
1108     QDir sourceDirectory(options.androidSourceDirectory);
1109     if (!sourceDirectory.exists()) {
1110         fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
1111         return false;
1112     }
1113 
1114     return copyFiles(sourceDirectory, QDir(options.outputDirectory), options, true);
1115 }
1116 
copyAndroidExtraLibs(Options * options)1117 bool copyAndroidExtraLibs(Options *options)
1118 {
1119     if (options->extraLibs.isEmpty())
1120         return true;
1121 
1122     if (options->verbose)
1123         fprintf(stdout, "Copying %d external libraries to package.\n", options->extraLibs.size());
1124 
1125     for (const QString &extraLib : options->extraLibs) {
1126         QFileInfo extraLibInfo(extraLib);
1127         if (!extraLibInfo.exists()) {
1128             fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
1129             return false;
1130         }
1131         if (!checkArchitecture(*options, extraLibInfo.filePath())) {
1132             if (options->verbose)
1133                 fprintf(stdout, "Skipping \"%s\", architecture mismatch.\n", qPrintable(extraLib));
1134             continue;
1135         }
1136         if (!extraLibInfo.fileName().startsWith(QLatin1String("lib")) || extraLibInfo.suffix() != QLatin1String("so")) {
1137             fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
1138                     qPrintable(extraLib));
1139             return false;
1140         }
1141         QString destinationFile(options->outputDirectory
1142                                 + QLatin1String("/libs/")
1143                                 + options->currentArchitecture
1144                                 + QLatin1Char('/')
1145                                 + extraLibInfo.fileName());
1146 
1147         if (!copyFileIfNewer(extraLib, destinationFile, *options))
1148             return false;
1149         options->archExtraLibs[options->currentArchitecture] += extraLib;
1150     }
1151 
1152     return true;
1153 }
1154 
allFilesInside(const QDir & current,const QDir & rootDir)1155 QStringList allFilesInside(const QDir& current, const QDir& rootDir)
1156 {
1157     QStringList result;
1158     const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1159     const auto files = current.entryList(QDir::Files);
1160     result.reserve(dirs.size() + files.size());
1161     for (const QString &dir : dirs) {
1162         result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1163     }
1164     for (const QString &file : files) {
1165         result += rootDir.relativeFilePath(current.filePath(file));
1166     }
1167     return result;
1168 }
1169 
copyAndroidExtraResources(Options * options)1170 bool copyAndroidExtraResources(Options *options)
1171 {
1172     if (options->extraPlugins.isEmpty())
1173         return true;
1174 
1175     if (options->verbose)
1176         fprintf(stdout, "Copying %d external resources to package.\n", options->extraPlugins.size());
1177 
1178     for (const QString &extraResource : options->extraPlugins) {
1179         QFileInfo extraResourceInfo(extraResource);
1180         if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1181             fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1182             return false;
1183         }
1184 
1185         QDir resourceDir(extraResource);
1186         QString assetsDir = options->outputDirectory + QLatin1String("/assets/") + resourceDir.dirName() + QLatin1Char('/');
1187         QString libsDir = options->outputDirectory + QLatin1String("/libs/") + options->currentArchitecture + QLatin1Char('/');
1188 
1189         const QStringList files = allFilesInside(resourceDir, resourceDir);
1190         for (const QString &resourceFile : files) {
1191             QString originFile(resourceDir.filePath(resourceFile));
1192             QString destinationFile;
1193             if (!resourceFile.endsWith(QLatin1String(".so"))) {
1194                 destinationFile = assetsDir + resourceFile;
1195             } else {
1196                 if (!checkArchitecture(*options, originFile))
1197                     continue;
1198                 destinationFile = libsDir + resourceFile;
1199                 options->archExtraPlugins[options->currentArchitecture] += resourceFile;
1200             }
1201             if (!copyFileIfNewer(originFile, destinationFile, *options))
1202                 return false;
1203         }
1204     }
1205 
1206     return true;
1207 }
1208 
updateFile(const QString & fileName,const QHash<QString,QString> & replacements)1209 bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
1210 {
1211     QFile inputFile(fileName);
1212     if (!inputFile.open(QIODevice::ReadOnly)) {
1213         fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
1214         return false;
1215     }
1216 
1217     // All the files we are doing substitutes in are quite small. If this
1218     // ever changes, this code should be updated to be more conservative.
1219     QByteArray contents = inputFile.readAll();
1220 
1221     bool hasReplacements = false;
1222     QHash<QString, QString>::const_iterator it;
1223     for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1224         if (it.key() == it.value())
1225             continue; // Nothing to actually replace
1226 
1227         forever {
1228             int index = contents.indexOf(it.key().toUtf8());
1229             if (index >= 0) {
1230                 contents.replace(index, it.key().length(), it.value().toUtf8());
1231                 hasReplacements = true;
1232             } else {
1233                 break;
1234             }
1235         }
1236     }
1237 
1238     if (hasReplacements) {
1239         inputFile.close();
1240 
1241         if (!inputFile.open(QIODevice::WriteOnly)) {
1242             fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
1243             return false;
1244         }
1245 
1246         inputFile.write(contents);
1247     }
1248 
1249     return true;
1250 
1251 }
1252 
updateLibsXml(Options * options)1253 bool updateLibsXml(Options *options)
1254 {
1255     if (options->verbose)
1256         fprintf(stdout, "  -- res/values/libs.xml\n");
1257 
1258     QString fileName = options->outputDirectory + QLatin1String("/res/values/libs.xml");
1259     if (!QFile::exists(fileName)) {
1260         fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1261         return false;
1262     }
1263 
1264     QString qtLibs;
1265     QString allLocalLibs;
1266     QString extraLibs;
1267 
1268     for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
1269         QString libsPath = QLatin1String("libs/") + it.key() + QLatin1Char('/');
1270 
1271         qtLibs += QLatin1String("        <item>%1;%2</item>\n").arg(it.key(), options->stdCppName);
1272         for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) {
1273             if (bundledFile.second.startsWith(QLatin1String("lib/"))) {
1274                 QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
1275                 s.chop(sizeof(".so") - 1);
1276                 qtLibs += QLatin1String("        <item>%1;%2</item>\n").arg(it.key(), s);
1277             }
1278         }
1279 
1280         if (!options->archExtraLibs[it.key()].isEmpty()) {
1281             for (const QString &extraLib : options->archExtraLibs[it.key()]) {
1282                 QFileInfo extraLibInfo(extraLib);
1283                 QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
1284                 name.chop(sizeof(".so") - 1);
1285                 extraLibs += QLatin1String("        <item>%1;%2</item>\n").arg(it.key(), name);
1286             }
1287         }
1288 
1289         QStringList localLibs;
1290         localLibs = options->localLibs[it.key()];
1291         // If .pro file overrides dependency detection, we need to see which platform plugin they picked
1292         if (localLibs.isEmpty()) {
1293             QString plugin;
1294             for (const QtDependency &qtDependency : options->qtDependencies[it.key()]) {
1295                 if (qtDependency.relativePath.endsWith(QLatin1String("libqtforandroid.so"))
1296                         || qtDependency.relativePath.endsWith(QLatin1String("libqtforandroidGL.so"))) {
1297                     if (!plugin.isEmpty() && plugin != qtDependency.relativePath) {
1298                         fprintf(stderr, "Both platform plugins libqtforandroid.so and libqtforandroidGL.so included in package. Please include only one.\n");
1299                         return false;
1300                     }
1301 
1302                     plugin = qtDependency.relativePath;
1303                 }
1304                 if (qtDependency.relativePath.contains(QLatin1String("libQt5OpenGL"))
1305                         || qtDependency.relativePath.contains(QLatin1String("libQt5Quick"))) {
1306                     options->usesOpenGL |= true;
1307                     break;
1308                 }
1309             }
1310 
1311             if (plugin.isEmpty()) {
1312                 fflush(stdout);
1313                 fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n");
1314                 fflush(stderr);
1315                 return false;
1316             }
1317 
1318             localLibs.append(plugin);
1319             if (options->verbose)
1320                 fprintf(stdout, "  -- Using platform plugin %s\n", qPrintable(plugin));
1321         }
1322 
1323         // remove all paths
1324         for (auto &lib : localLibs) {
1325             if (lib.endsWith(QLatin1String(".so")))
1326                 lib = lib.mid(lib.lastIndexOf(QLatin1Char('/')) + 1);
1327         }
1328         allLocalLibs += QLatin1String("        <item>%1;%2</item>\n").arg(it.key(), localLibs.join(QLatin1Char(':')));
1329     }
1330 
1331     QHash<QString, QString> replacements;
1332     replacements[QStringLiteral("<!-- %%INSERT_QT_LIBS%% -->")] += qtLibs.trimmed();
1333     replacements[QStringLiteral("<!-- %%INSERT_LOCAL_LIBS%% -->")] = allLocalLibs.trimmed();
1334     replacements[QStringLiteral("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs.trimmed();
1335 
1336     if (!updateFile(fileName, replacements))
1337         return false;
1338 
1339     return true;
1340 }
1341 
updateStringsXml(const Options & options)1342 bool updateStringsXml(const Options &options)
1343 {
1344     if (options.verbose)
1345         fprintf(stdout, "  -- res/values/strings.xml\n");
1346 
1347     QHash<QString, QString> replacements;
1348     replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = options.applicationBinary;
1349 
1350     QString fileName = options.outputDirectory + QLatin1String("/res/values/strings.xml");
1351     if (!QFile::exists(fileName)) {
1352         if (options.verbose)
1353             fprintf(stdout, "  -- Create strings.xml since it's missing.\n");
1354         QFile file(fileName);
1355         if (!file.open(QIODevice::WriteOnly)) {
1356             fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
1357             return false;
1358         }
1359         file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\" translatable=\"false\">")
1360                    .append(options.applicationBinary.toLatin1())
1361                    .append("</string></resources>\n"));
1362         return true;
1363     }
1364 
1365     if (!updateFile(fileName, replacements))
1366         return false;
1367 
1368     return true;
1369 }
1370 
updateAndroidManifest(Options & options)1371 bool updateAndroidManifest(Options &options)
1372 {
1373     if (options.verbose)
1374         fprintf(stdout, "  -- AndroidManifest.xml \n");
1375 
1376     options.localJars.removeDuplicates();
1377     options.initClasses.removeDuplicates();
1378 
1379     QHash<QString, QString> replacements;
1380     replacements[QStringLiteral("-- %%INSERT_APP_NAME%% --")] = options.applicationBinary;
1381     replacements[QStringLiteral("-- %%INSERT_APP_LIB_NAME%% --")] = options.applicationBinary;
1382     replacements[QStringLiteral("-- %%INSERT_LOCAL_JARS%% --")] = options.localJars.join(QLatin1Char(':'));
1383     replacements[QStringLiteral("-- %%INSERT_INIT_CLASSES%% --")] = options.initClasses.join(QLatin1Char(':'));
1384     replacements[QStringLiteral("-- %%INSERT_VERSION_NAME%% --")] = options.versionName;
1385     replacements[QStringLiteral("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode;
1386     replacements[QStringLiteral("package=\"org.qtproject.example\"")] = QLatin1String("package=\"%1\"").arg(options.packageName);
1387     replacements[QStringLiteral("-- %%BUNDLE_LOCAL_QT_LIBS%% --")]
1388             = (options.deploymentMechanism == Options::Bundled) ? QLatin1String("1") : QLatin1String("0");
1389     replacements[QStringLiteral("-- %%USE_LOCAL_QT_LIBS%% --")]
1390             = (options.deploymentMechanism != Options::Ministro) ? QLatin1String("1") : QLatin1String("0");
1391 
1392     QString permissions;
1393     for (const QString &permission : qAsConst(options.permissions))
1394         permissions += QLatin1String("    <uses-permission android:name=\"%1\" />\n").arg(permission);
1395     replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
1396 
1397     QString features;
1398     for (const QString &feature : qAsConst(options.features))
1399         features += QLatin1String("    <uses-feature android:name=\"%1\" android:required=\"false\" />\n").arg(feature);
1400     if (options.usesOpenGL)
1401         features += QLatin1String("    <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />");
1402 
1403     replacements[QStringLiteral("<!-- %%INSERT_FEATURES -->")] = features.trimmed();
1404 
1405     QString androidManifestPath = options.outputDirectory + QLatin1String("/AndroidManifest.xml");
1406     if (!updateFile(androidManifestPath, replacements))
1407         return false;
1408 
1409     // read the package, min & target sdk API levels from manifest file.
1410     bool checkOldAndroidLabelString = false;
1411     QFile androidManifestXml(androidManifestPath);
1412     if (androidManifestXml.exists()) {
1413         if (!androidManifestXml.open(QIODevice::ReadOnly)) {
1414             fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
1415             return false;
1416         }
1417 
1418         QXmlStreamReader reader(&androidManifestXml);
1419         while (!reader.atEnd()) {
1420             reader.readNext();
1421 
1422             if (reader.isStartElement()) {
1423                 if (reader.name() == QLatin1String("manifest")) {
1424                     if (!reader.attributes().hasAttribute(QLatin1String("package"))) {
1425                         fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath));
1426                         return false;
1427                     }
1428                     options.packageName = reader.attributes().value(QLatin1String("package")).toString();
1429                 } else if (reader.name() == QLatin1String("uses-sdk")) {
1430                     if (reader.attributes().hasAttribute(QLatin1String("android:minSdkVersion")))
1431                         if (reader.attributes().value(QLatin1String("android:minSdkVersion")).toInt() < 21) {
1432                             fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 21\n");
1433                             return false;
1434                         }
1435                 } else if ((reader.name() == QLatin1String("application") ||
1436                             reader.name() == QLatin1String("activity")) &&
1437                            reader.attributes().hasAttribute(QLatin1String("android:label")) &&
1438                            reader.attributes().value(QLatin1String("android:label")) == QLatin1String("@string/app_name")) {
1439                     checkOldAndroidLabelString = true;
1440                 }
1441             }
1442         }
1443 
1444         if (reader.hasError()) {
1445             fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
1446             return false;
1447         }
1448     } else {
1449         fprintf(stderr, "No android manifest file");
1450         return false;
1451     }
1452 
1453     if (checkOldAndroidLabelString)
1454         updateStringsXml(options);
1455 
1456     return true;
1457 }
1458 
updateAndroidFiles(Options & options)1459 bool updateAndroidFiles(Options &options)
1460 {
1461     if (options.verbose)
1462         fprintf(stdout, "Updating Android package files with project settings.\n");
1463 
1464     if (!updateLibsXml(&options))
1465         return false;
1466 
1467     if (!updateAndroidManifest(options))
1468         return false;
1469 
1470     return true;
1471 }
1472 
absoluteFilePath(const Options * options,const QString & relativeFileName)1473 static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
1474 {
1475     for (const auto &prefix : options->extraPrefixDirs) {
1476         const QString path = prefix + QLatin1Char('/') + relativeFileName;
1477         if (QFile::exists(path))
1478             return path;
1479     }
1480     return options->qtInstallDirectory + QLatin1Char('/') + relativeFileName;
1481 }
1482 
findFilesRecursively(const Options & options,const QFileInfo & info,const QString & rootPath)1483 QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath)
1484 {
1485     if (!info.exists())
1486         return QList<QtDependency>();
1487 
1488     if (info.isDir()) {
1489         QList<QtDependency> ret;
1490 
1491         QDir dir(info.filePath());
1492         const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
1493 
1494         for (const QString &entry : entries) {
1495             QString s = info.absoluteFilePath() + QLatin1Char('/') + entry;
1496             ret += findFilesRecursively(options, s, rootPath);
1497         }
1498 
1499         return ret;
1500     } else {
1501         return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.length()), info.absoluteFilePath());
1502     }
1503 }
1504 
findFilesRecursively(const Options & options,const QString & fileName)1505 QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName)
1506 {
1507     for (const auto &prefix : options.extraPrefixDirs) {
1508         QFileInfo info(prefix + QLatin1Char('/') + fileName);
1509         if (info.exists())
1510             return findFilesRecursively(options, info, prefix + QLatin1Char('/'));
1511     }
1512     QFileInfo info(options.qtInstallDirectory + QLatin1Char('/') + fileName);
1513     return findFilesRecursively(options, info, options.qtInstallDirectory + QLatin1Char('/'));
1514 }
1515 
readAndroidDependencyXml(Options * options,const QString & moduleName,QSet<QString> * usedDependencies,QSet<QString> * remainingDependencies)1516 bool readAndroidDependencyXml(Options *options,
1517                               const QString &moduleName,
1518                               QSet<QString> *usedDependencies,
1519                               QSet<QString> *remainingDependencies)
1520 {
1521     QString androidDependencyName = absoluteFilePath(options, QLatin1String("/lib/%1-android-dependencies.xml").arg(moduleName));
1522 
1523     QFile androidDependencyFile(androidDependencyName);
1524     if (androidDependencyFile.exists()) {
1525         if (options->verbose)
1526             fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
1527 
1528         if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
1529             fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
1530             return false;
1531         }
1532 
1533         QXmlStreamReader reader(&androidDependencyFile);
1534         while (!reader.atEnd()) {
1535             reader.readNext();
1536 
1537             if (reader.isStartElement()) {
1538                 if (reader.name() == QLatin1String("bundled")) {
1539                     if (!reader.attributes().hasAttribute(QLatin1String("file"))) {
1540                         fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
1541                         return false;
1542                     }
1543 
1544                     QString file = reader.attributes().value(QLatin1String("file")).toString();
1545 
1546                     // Special case, since this is handled by qmlimportscanner instead
1547                     if (!options->rootPath.isEmpty() && (file == QLatin1String("qml") || file == QLatin1String("qml/")))
1548                         continue;
1549 
1550                     const QList<QtDependency> fileNames = findFilesRecursively(*options, file);
1551                     for (const QtDependency &fileName : fileNames) {
1552                         if (usedDependencies->contains(fileName.absolutePath))
1553                             continue;
1554 
1555                         usedDependencies->insert(fileName.absolutePath);
1556 
1557                         if (options->verbose)
1558                             fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath));
1559 
1560                         options->qtDependencies[options->currentArchitecture].append(fileName);
1561                     }
1562                 } else if (reader.name() == QLatin1String("jar")) {
1563                     int bundling = reader.attributes().value(QLatin1String("bundling")).toInt();
1564                     QString fileName = reader.attributes().value(QLatin1String("file")).toString();
1565                     if (bundling == (options->deploymentMechanism == Options::Bundled)) {
1566                         QtDependency dependency(fileName, absoluteFilePath(options, fileName));
1567                         if (!usedDependencies->contains(dependency.absolutePath)) {
1568                             options->qtDependencies[options->currentArchitecture].append(dependency);
1569                             usedDependencies->insert(dependency.absolutePath);
1570                         }
1571                     }
1572 
1573                     if (!fileName.isEmpty())
1574                         options->localJars.append(fileName);
1575 
1576                     if (reader.attributes().hasAttribute(QLatin1String("initClass"))) {
1577                         options->initClasses.append(reader.attributes().value(QLatin1String("initClass")).toString());
1578                     }
1579                 } else if (reader.name() == QLatin1String("lib")) {
1580                     QString fileName = reader.attributes().value(QLatin1String("file")).toString();
1581                     if (reader.attributes().hasAttribute(QLatin1String("replaces"))) {
1582                         QString replaces = reader.attributes().value(QLatin1String("replaces")).toString();
1583                         for (int i=0; i<options->localLibs.size(); ++i) {
1584                             if (options->localLibs[options->currentArchitecture].at(i) == replaces) {
1585                                 options->localLibs[options->currentArchitecture][i] = fileName;
1586                                 break;
1587                             }
1588                         }
1589                     } else if (!fileName.isEmpty()) {
1590                         options->localLibs[options->currentArchitecture].append(fileName);
1591                     }
1592                     if (fileName.endsWith(QLatin1String(".so")) && checkArchitecture(*options, fileName)) {
1593                         remainingDependencies->insert(fileName);
1594                     }
1595                 } else if (reader.name() == QLatin1String("permission")) {
1596                     QString name = reader.attributes().value(QLatin1String("name")).toString();
1597                     options->permissions.append(name);
1598                 } else if (reader.name() == QLatin1String("feature")) {
1599                     QString name = reader.attributes().value(QLatin1String("name")).toString();
1600                     options->features.append(name);
1601                 }
1602             }
1603         }
1604 
1605         if (reader.hasError()) {
1606             fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
1607             return false;
1608         }
1609     } else if (options->verbose) {
1610         fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
1611     }
1612     options->permissions.removeDuplicates();
1613     options->features.removeDuplicates();
1614 
1615     return true;
1616 }
1617 
getQtLibsFromElf(const Options & options,const QString & fileName)1618 QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
1619 {
1620     QString readElf = QLatin1String("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj").arg(options.ndkPath,
1621                                                                                           options.toolchainPrefix,
1622                                                                                           options.ndkHost);
1623 #if defined(Q_OS_WIN32)
1624     readElf += QLatin1String(".exe");
1625 #endif
1626 
1627     if (!QFile::exists(readElf)) {
1628         fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
1629         return QStringList();
1630     }
1631 
1632     readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(fileName));
1633 
1634     FILE *readElfCommand = openProcess(readElf);
1635     if (!readElfCommand) {
1636         fprintf(stderr, "Cannot execute command %s\n", qPrintable(readElf));
1637         return QStringList();
1638     }
1639 
1640     QStringList ret;
1641 
1642     bool readLibs = false;
1643     char buffer[512];
1644     while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
1645         QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
1646         QString library;
1647         line = line.trimmed();
1648         if (!readLibs) {
1649             if (line.startsWith("Arch: ")) {
1650                 auto it = elfArchitecures.find(line.mid(6));
1651                 if (it == elfArchitecures.constEnd() || *it != options.currentArchitecture.toLatin1()) {
1652                     if (options.verbose)
1653                         fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName));
1654                     return {};
1655                 }
1656             }
1657             readLibs = line.startsWith("NeededLibraries");
1658             continue;
1659         }
1660         if (!line.startsWith("lib"))
1661             continue;
1662         library = QString::fromLatin1(line);
1663         QString libraryName = QLatin1String("lib/") + library;
1664         if (QFile::exists(absoluteFilePath(&options, libraryName)))
1665             ret += libraryName;
1666     }
1667 
1668     pclose(readElfCommand);
1669 
1670     return ret;
1671 }
1672 
readDependenciesFromElf(Options * options,const QString & fileName,QSet<QString> * usedDependencies,QSet<QString> * remainingDependencies)1673 bool readDependenciesFromElf(Options *options,
1674                              const QString &fileName,
1675                              QSet<QString> *usedDependencies,
1676                              QSet<QString> *remainingDependencies)
1677 {
1678     // Get dependencies on libraries in $QTDIR/lib
1679     const QStringList dependencies = getQtLibsFromElf(*options, fileName);
1680 
1681     if (options->verbose) {
1682         fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
1683         for (const QString &dep : dependencies)
1684             fprintf(stdout, "      %s\n", qPrintable(dep));
1685     }
1686     // Recursively add dependencies from ELF and supplementary XML information
1687     QList<QString> dependenciesToCheck;
1688     for (const QString &dependency : dependencies) {
1689         if (usedDependencies->contains(dependency))
1690             continue;
1691 
1692         QString absoluteDependencyPath = absoluteFilePath(options, dependency);
1693         usedDependencies->insert(dependency);
1694         if (!readDependenciesFromElf(options,
1695                               absoluteDependencyPath,
1696                               usedDependencies,
1697                               remainingDependencies)) {
1698             return false;
1699         }
1700 
1701         options->qtDependencies[options->currentArchitecture].append(QtDependency(dependency, absoluteDependencyPath));
1702         if (options->verbose)
1703             fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
1704         dependenciesToCheck.append(dependency);
1705     }
1706 
1707     for (const QString &dependency : qAsConst(dependenciesToCheck)) {
1708         QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
1709         qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
1710         if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
1711             return false;
1712         }
1713     }
1714 
1715     return true;
1716 }
1717 
1718 bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
1719 
scanImports(Options * options,QSet<QString> * usedDependencies)1720 bool scanImports(Options *options, QSet<QString> *usedDependencies)
1721 {
1722     if (options->verbose)
1723         fprintf(stdout, "Scanning for QML imports.\n");
1724 
1725     QString qmlImportScanner = options->qtInstallDirectory + QLatin1String("/bin/qmlimportscanner");
1726 #if defined(Q_OS_WIN32)
1727     qmlImportScanner += QLatin1String(".exe");
1728 #endif
1729 
1730     if (!QFile::exists(qmlImportScanner)) {
1731         fprintf(stderr, "qmlimportscanner not found: %s\n", qPrintable(qmlImportScanner));
1732         return true;
1733     }
1734 
1735     QString rootPath = options->rootPath;
1736     if (!options->qrcFiles.isEmpty()) {
1737         qmlImportScanner += QLatin1String(" -qrcFiles");
1738         for (const QString &qrcFile : options->qrcFiles)
1739             qmlImportScanner += QLatin1Char(' ') + shellQuote(qrcFile);
1740     }
1741 
1742     if (rootPath.isEmpty())
1743         rootPath = QFileInfo(options->inputFileName).absolutePath();
1744     else
1745         rootPath = QFileInfo(rootPath).absoluteFilePath();
1746 
1747     if (!rootPath.endsWith(QLatin1Char('/')))
1748         rootPath += QLatin1Char('/');
1749 
1750     qmlImportScanner += QLatin1String(" -rootPath %1").arg(shellQuote(rootPath));
1751 
1752     QStringList importPaths;
1753     importPaths += shellQuote(options->qtInstallDirectory + QLatin1String("/qml"));
1754     if (!rootPath.isEmpty())
1755         importPaths += shellQuote(rootPath);
1756     for (const QString &qmlImportPath : qAsConst(options->qmlImportPaths))
1757         importPaths += shellQuote(qmlImportPath);
1758     qmlImportScanner += QLatin1String(" -importPath %1").arg(importPaths.join(QLatin1Char(' ')));
1759 
1760     if (options->verbose) {
1761         fprintf(stdout, "Running qmlimportscanner with the following command: %s\n",
1762             qmlImportScanner.toLocal8Bit().constData());
1763     }
1764 
1765     FILE *qmlImportScannerCommand = popen(qmlImportScanner.toLocal8Bit().constData(), QT_POPEN_READ);
1766     if (qmlImportScannerCommand == 0) {
1767         fprintf(stderr, "Couldn't run qmlimportscanner.\n");
1768         return false;
1769     }
1770 
1771     QByteArray output;
1772     char buffer[512];
1773     while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand) != 0)
1774         output += QByteArray(buffer, qstrlen(buffer));
1775 
1776     QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
1777     if (jsonDocument.isNull()) {
1778         fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
1779         return false;
1780     }
1781 
1782     QJsonArray jsonArray = jsonDocument.array();
1783     for (int i=0; i<jsonArray.count(); ++i) {
1784         QJsonValue value = jsonArray.at(i);
1785         if (!value.isObject()) {
1786             fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
1787             return false;
1788         }
1789 
1790         QJsonObject object = value.toObject();
1791         QString path = object.value(QLatin1String("path")).toString();
1792         if (path.isEmpty()) {
1793             fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n",
1794                     qPrintable(object.value(QLatin1String("name")).toString()));
1795         } else {
1796             if (options->verbose)
1797                 fprintf(stdout, "  -- Adding '%s' as QML dependency\n", path.toLocal8Bit().constData());
1798 
1799             QFileInfo info(path);
1800 
1801             // The qmlimportscanner sometimes outputs paths that do not exist.
1802             if (!info.exists()) {
1803                 if (options->verbose)
1804                     fprintf(stdout, "    -- Skipping because path does not exist.\n");
1805                 continue;
1806             }
1807 
1808             QString absolutePath = info.absolutePath();
1809             if (!absolutePath.endsWith(QLatin1Char('/')))
1810                 absolutePath += QLatin1Char('/');
1811 
1812             if (absolutePath.startsWith(rootPath)) {
1813                 if (options->verbose)
1814                     fprintf(stdout, "    -- Skipping because path is in QML root path.\n");
1815                 continue;
1816             }
1817 
1818             QString importPathOfThisImport;
1819             for (const QString &importPath : qAsConst(importPaths)) {
1820 #if defined(Q_OS_WIN32)
1821                 Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
1822 #else
1823                 Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
1824 #endif
1825                 QString cleanImportPath = QDir::cleanPath(importPath);
1826                 if (info.absoluteFilePath().startsWith(cleanImportPath, caseSensitivity)) {
1827                     importPathOfThisImport = importPath;
1828                     break;
1829                 }
1830             }
1831 
1832             if (importPathOfThisImport.isEmpty()) {
1833                 fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
1834                 return false;
1835             }
1836 
1837             QDir dir(importPathOfThisImport);
1838             importPathOfThisImport = dir.absolutePath() + QLatin1Char('/');
1839 
1840             const QList<QtDependency> fileNames = findFilesRecursively(*options, info, importPathOfThisImport);
1841             for (QtDependency fileName : fileNames) {
1842                 if (usedDependencies->contains(fileName.absolutePath))
1843                     continue;
1844 
1845                 usedDependencies->insert(fileName.absolutePath);
1846 
1847                 if (options->verbose)
1848                     fprintf(stdout, "    -- Appending dependency found by qmlimportscanner: %s\n", qPrintable(fileName.absolutePath));
1849 
1850                 // Put all imports in default import path in assets
1851                 fileName.relativePath.prepend(QLatin1String("qml/"));
1852                 options->qtDependencies[options->currentArchitecture].append(fileName);
1853 
1854                 if (fileName.absolutePath.endsWith(QLatin1String(".so")) && checkArchitecture(*options, fileName.absolutePath)) {
1855                     QSet<QString> remainingDependencies;
1856                     if (!readDependenciesFromElf(options, fileName.absolutePath, usedDependencies, &remainingDependencies))
1857                         return false;
1858 
1859                 }
1860             }
1861         }
1862     }
1863 
1864     return true;
1865 }
1866 
runCommand(const Options & options,const QString & command)1867 bool runCommand(const Options &options, const QString &command)
1868 {
1869     if (options.verbose)
1870         fprintf(stdout, "Running command '%s'\n", qPrintable(command));
1871 
1872     FILE *runCommand = openProcess(command);
1873     if (runCommand == nullptr) {
1874         fprintf(stderr, "Cannot run command '%s'\n", qPrintable(command));
1875         return false;
1876     }
1877     char buffer[4096];
1878     while (fgets(buffer, sizeof(buffer), runCommand) != nullptr) {
1879         if (options.verbose)
1880             fprintf(stdout, "%s", buffer);
1881     }
1882     pclose(runCommand);
1883     fflush(stdout);
1884     fflush(stderr);
1885     return true;
1886 }
1887 
createRcc(const Options & options)1888 bool createRcc(const Options &options)
1889 {
1890     auto assetsDir = QLatin1String("%1/assets").arg(options.outputDirectory);
1891     if (!QDir{QLatin1String("%1/android_rcc_bundle").arg(assetsDir)}.exists()) {
1892         fprintf(stdout, "Skipping createRCC\n");
1893         return true;
1894     }
1895 
1896     if (options.verbose)
1897         fprintf(stdout, "Create rcc bundle.\n");
1898 
1899     QString rcc = options.qtInstallDirectory + QLatin1String("/bin/rcc");
1900 #if defined(Q_OS_WIN32)
1901     rcc += QLatin1String(".exe");
1902 #endif
1903 
1904     if (!QFile::exists(rcc)) {
1905         fprintf(stderr, "rcc not found: %s\n", qPrintable(rcc));
1906         return false;
1907     }
1908     auto currentDir = QDir::currentPath();
1909     if (!QDir::setCurrent(QLatin1String("%1/android_rcc_bundle").arg(assetsDir))) {
1910         fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(QLatin1String("%1/android_rcc_bundle").arg(assetsDir)));
1911         return false;
1912     }
1913 
1914     bool res = runCommand(options, QLatin1String("%1 --project -o %2").arg(rcc, shellQuote(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir))));
1915     if (!res)
1916         return false;
1917 
1918     QFile::rename(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir), QLatin1String("%1/android_rcc_bundle/android_rcc_bundle.qrc").arg(assetsDir));
1919 
1920     res = runCommand(options, QLatin1String("%1 %2 --binary -o %3 android_rcc_bundle.qrc").arg(rcc, shellQuote(QLatin1String("--root=/android_rcc_bundle/")),
1921                                                                                                shellQuote(QLatin1String("%1/android_rcc_bundle.rcc").arg(assetsDir))));
1922     if (!QDir::setCurrent(currentDir)) {
1923         fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(currentDir));
1924         return false;
1925     }
1926     QFile::remove(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir));
1927     QDir{QLatin1String("%1/android_rcc_bundle").arg(assetsDir)}.removeRecursively();
1928     return res;
1929 }
1930 
readDependencies(Options * options)1931 bool readDependencies(Options *options)
1932 {
1933     if (options->verbose)
1934         fprintf(stdout, "Detecting dependencies of application.\n");
1935 
1936     // Override set in .pro file
1937     if (!options->qtDependencies[options->currentArchitecture].isEmpty()) {
1938         if (options->verbose)
1939             fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
1940         return true;
1941     }
1942 
1943     QSet<QString> usedDependencies;
1944     QSet<QString> remainingDependencies;
1945 
1946     // Add dependencies of application binary first
1947     if (!readDependenciesFromElf(options, QLatin1String("%1/libs/%2/lib%3_%2.so").arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies))
1948         return false;
1949 
1950     while (!remainingDependencies.isEmpty()) {
1951         QSet<QString>::iterator start = remainingDependencies.begin();
1952         QString fileName = absoluteFilePath(options, *start);
1953         remainingDependencies.erase(start);
1954 
1955         QStringList unmetDependencies;
1956         if (goodToCopy(options, fileName, &unmetDependencies)) {
1957             bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
1958             if (!ok)
1959                 return false;
1960         } else {
1961             fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
1962                     qPrintable(fileName),
1963                     qPrintable(unmetDependencies.join(QLatin1Char(','))));
1964         }
1965     }
1966 
1967     QStringList::iterator it = options->localLibs[options->currentArchitecture].begin();
1968     while (it != options->localLibs[options->currentArchitecture].end()) {
1969         QStringList unmetDependencies;
1970         if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) {
1971             fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
1972                     qPrintable(*it),
1973                     qPrintable(unmetDependencies.join(QLatin1Char(','))));
1974             it = options->localLibs[options->currentArchitecture].erase(it);
1975         } else {
1976             ++it;
1977         }
1978     }
1979 
1980     if ((!options->rootPath.isEmpty() || options->qrcFiles.isEmpty()) &&
1981         !scanImports(options, &usedDependencies))
1982         return false;
1983 
1984     return true;
1985 }
1986 
containsApplicationBinary(Options * options)1987 bool containsApplicationBinary(Options *options)
1988 {
1989     if (!options->build)
1990         return true;
1991 
1992     if (options->verbose)
1993         fprintf(stdout, "Checking if application binary is in package.\n");
1994 
1995     QFileInfo applicationBinary(options->applicationBinary);
1996     QString applicationFileName = QLatin1String("lib%1_%2.so").arg(options->applicationBinary,
1997                                                                     options->currentArchitecture);
1998 
1999     QString applicationPath = QLatin1String("%1/libs/%2/%3").arg(options->outputDirectory,
2000                                                                                options->currentArchitecture,
2001                                                                                applicationFileName);
2002     if (!QFile::exists(applicationPath)) {
2003 #if defined(Q_OS_WIN32)
2004         QLatin1String makeTool("mingw32-make"); // Only Mingw host builds supported on Windows currently
2005 #else
2006         QLatin1String makeTool("make");
2007 #endif
2008         fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
2009                 qPrintable(applicationFileName),
2010                 qPrintable(makeTool),
2011                 qPrintable(options->outputDirectory));
2012         return false;
2013     }
2014     return true;
2015 }
2016 
runAdb(const Options & options,const QString & arguments)2017 FILE *runAdb(const Options &options, const QString &arguments)
2018 {
2019     QString adb = options.sdkPath + QLatin1String("/platform-tools/adb");
2020 #if defined(Q_OS_WIN32)
2021     adb += QLatin1String(".exe");
2022 #endif
2023 
2024     if (!QFile::exists(adb)) {
2025         fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
2026         return 0;
2027     }
2028     QString installOption;
2029     if (!options.installLocation.isEmpty())
2030         installOption = QLatin1String(" -s ") + shellQuote(options.installLocation);
2031 
2032     adb = QLatin1String("%1%2 %3").arg(shellQuote(adb), installOption, arguments);
2033 
2034     if (options.verbose)
2035         fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
2036 
2037     FILE *adbCommand = openProcess(adb);
2038     if (adbCommand == 0) {
2039         fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
2040         return 0;
2041     }
2042 
2043     return adbCommand;
2044 }
2045 
goodToCopy(const Options * options,const QString & file,QStringList * unmetDependencies)2046 bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
2047 {
2048     if (!file.endsWith(QLatin1String(".so")))
2049         return true;
2050 
2051     if (!checkArchitecture(*options, file))
2052         return false;
2053 
2054     bool ret = true;
2055     const auto libs = getQtLibsFromElf(*options, file);
2056     for (const QString &lib : libs) {
2057         if (!options->qtDependencies[options->currentArchitecture].contains(QtDependency(lib, absoluteFilePath(options, lib)))) {
2058             ret = false;
2059             unmetDependencies->append(lib);
2060         }
2061     }
2062 
2063     return ret;
2064 }
2065 
copyQtFiles(Options * options)2066 bool copyQtFiles(Options *options)
2067 {
2068     if (options->verbose) {
2069         switch (options->deploymentMechanism) {
2070         case Options::Bundled:
2071             fprintf(stdout, "Copying %d dependencies from Qt into package.\n", options->qtDependencies.size());
2072             break;
2073         case Options::Ministro:
2074             fprintf(stdout, "Setting %d dependencies from Qt in package.\n", options->qtDependencies.size());
2075             break;
2076         };
2077     }
2078 
2079     if (!options->build)
2080         return true;
2081 
2082 
2083     QString libsDirectory = QLatin1String("libs/");
2084 
2085     // Copy other Qt dependencies
2086     auto assetsDestinationDirectory = QLatin1String("assets/android_rcc_bundle/");
2087     for (const QtDependency &qtDependency : qAsConst(options->qtDependencies[options->currentArchitecture])) {
2088         QString sourceFileName = qtDependency.absolutePath;
2089         QString destinationFileName;
2090 
2091         if (qtDependency.relativePath.endsWith(QLatin1String(".so"))) {
2092             QString garbledFileName;
2093             if (qtDependency.relativePath.startsWith(QLatin1String("lib/"))) {
2094                 garbledFileName = qtDependency.relativePath.mid(sizeof("lib/") - 1);
2095             } else {
2096                 garbledFileName = qtDependency.relativePath.mid(qtDependency.relativePath.lastIndexOf(QLatin1Char('/')) + 1);
2097             }
2098             destinationFileName = libsDirectory + options->currentArchitecture + QLatin1Char('/') + garbledFileName;
2099         } else if (qtDependency.relativePath.startsWith(QLatin1String("jar/"))) {
2100             destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
2101         } else {
2102             destinationFileName = assetsDestinationDirectory + qtDependency.relativePath;
2103         }
2104 
2105         if (!QFile::exists(sourceFileName)) {
2106             fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
2107             return false;
2108         }
2109 
2110         QStringList unmetDependencies;
2111         if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
2112             if (unmetDependencies.isEmpty()) {
2113                 if (options->verbose) {
2114                     fprintf(stdout, "  -- Skipping %s, architecture mismatch.\n",
2115                             qPrintable(sourceFileName));
2116                 }
2117             } else {
2118                 if (unmetDependencies.isEmpty()) {
2119                     if (options->verbose) {
2120                         fprintf(stdout, "  -- Skipping %s, architecture mismatch.\n",
2121                                 qPrintable(sourceFileName));
2122                     }
2123                 } else {
2124                     fprintf(stdout, "  -- Skipping %s. It has unmet dependencies: %s.\n",
2125                             qPrintable(sourceFileName),
2126                             qPrintable(unmetDependencies.join(QLatin1Char(','))));
2127                 }
2128             }
2129             continue;
2130         }
2131 
2132         if (options->deploymentMechanism == Options::Bundled
2133                 && !copyFileIfNewer(sourceFileName,
2134                                     options->outputDirectory + QLatin1Char('/') + destinationFileName,
2135                                     *options)) {
2136             return false;
2137         }
2138 
2139         options->bundledFiles[options->currentArchitecture] += qMakePair(destinationFileName, qtDependency.relativePath);
2140     }
2141 
2142     return true;
2143 }
2144 
getLibraryProjectsInOutputFolder(const Options & options)2145 QStringList getLibraryProjectsInOutputFolder(const Options &options)
2146 {
2147     QStringList ret;
2148 
2149     QFile file(options.outputDirectory + QLatin1String("/project.properties"));
2150     if (file.open(QIODevice::ReadOnly)) {
2151         while (!file.atEnd()) {
2152             QByteArray line = file.readLine().trimmed();
2153             if (line.startsWith("android.library.reference")) {
2154                 int equalSignIndex = line.indexOf('=');
2155                 if (equalSignIndex >= 0) {
2156                     QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
2157 
2158                     QFileInfo info(options.outputDirectory + QLatin1Char('/') + path);
2159                     if (QDir::isRelativePath(path)
2160                             && info.exists()
2161                             && info.isDir()
2162                             && info.canonicalFilePath().startsWith(options.outputDirectory)) {
2163                         ret += info.canonicalFilePath();
2164                     }
2165                 }
2166             }
2167         }
2168     }
2169 
2170     return ret;
2171 }
2172 
createAndroidProject(const Options & options)2173 bool createAndroidProject(const Options &options)
2174 {
2175     if (options.verbose)
2176         fprintf(stdout, "Running Android tool to create package definition.\n");
2177 
2178     QString androidToolExecutable = options.sdkPath + QLatin1String("/tools/android");
2179 #if defined(Q_OS_WIN32)
2180     androidToolExecutable += QLatin1String(".bat");
2181 #endif
2182 
2183     if (!QFile::exists(androidToolExecutable)) {
2184         fprintf(stderr, "Cannot find Android tool: %s\n", qPrintable(androidToolExecutable));
2185         return false;
2186     }
2187 
2188     QString androidTool = QLatin1String("%1 update project --path %2 --target %3 --name QtApp")
2189                             .arg(shellQuote(androidToolExecutable))
2190                             .arg(shellQuote(options.outputDirectory))
2191                             .arg(shellQuote(options.androidPlatform));
2192 
2193     if (options.verbose)
2194         fprintf(stdout, "  -- Command: %s\n", qPrintable(androidTool));
2195 
2196     FILE *androidToolCommand = openProcess(androidTool);
2197     if (androidToolCommand == 0) {
2198         fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
2199         return false;
2200     }
2201 
2202     pclose(androidToolCommand);
2203 
2204     // If the project has subprojects inside the current folder, we need to also run android update on these.
2205     const QStringList libraryProjects = getLibraryProjectsInOutputFolder(options);
2206     for (const QString &libraryProject : libraryProjects) {
2207         if (options.verbose)
2208             fprintf(stdout, "Updating subproject %s\n", qPrintable(libraryProject));
2209 
2210         androidTool = QLatin1String("%1 update lib-project --path %2 --target %3")
2211                 .arg(shellQuote(androidToolExecutable))
2212                 .arg(shellQuote(libraryProject))
2213                 .arg(shellQuote(options.androidPlatform));
2214 
2215         if (options.verbose)
2216             fprintf(stdout, "  -- Command: %s\n", qPrintable(androidTool));
2217 
2218         FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), QT_POPEN_READ);
2219         if (androidToolCommand == 0) {
2220             fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
2221             return false;
2222         }
2223 
2224         pclose(androidToolCommand);
2225     }
2226 
2227     return true;
2228 }
2229 
findInPath(const QString & fileName)2230 QString findInPath(const QString &fileName)
2231 {
2232     const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
2233 #if defined(Q_OS_WIN32)
2234     QLatin1Char separator(';');
2235 #else
2236     QLatin1Char separator(':');
2237 #endif
2238 
2239     const QStringList paths = path.split(separator);
2240     for (const QString &path : paths) {
2241         QFileInfo fileInfo(path + QLatin1Char('/') + fileName);
2242         if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2243             return path + QLatin1Char('/') + fileName;
2244     }
2245 
2246     return QString();
2247 }
2248 
2249 typedef QMap<QByteArray, QByteArray> GradleProperties;
2250 
readGradleProperties(const QString & path)2251 static GradleProperties readGradleProperties(const QString &path)
2252 {
2253     GradleProperties properties;
2254     QFile file(path);
2255     if (!file.open(QIODevice::ReadOnly))
2256         return properties;
2257 
2258     const auto lines = file.readAll().split('\n');
2259     for (const QByteArray &line : lines) {
2260         if (line.trimmed().startsWith('#'))
2261             continue;
2262 
2263         QList<QByteArray> prop(line.split('='));
2264         if (prop.size() > 1)
2265             properties[prop.at(0).trimmed()] = prop.at(1).trimmed();
2266     }
2267     file.close();
2268     return properties;
2269 }
2270 
mergeGradleProperties(const QString & path,GradleProperties properties)2271 static bool mergeGradleProperties(const QString &path, GradleProperties properties)
2272 {
2273     QFile::remove(path + QLatin1Char('~'));
2274     QFile::rename(path, path + QLatin1Char('~'));
2275     QFile file(path);
2276     if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
2277         fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName()));
2278         return false;
2279     }
2280 
2281     QFile oldFile(path + QLatin1Char('~'));
2282     if (oldFile.open(QIODevice::ReadOnly)) {
2283         while (!oldFile.atEnd()) {
2284             QByteArray line(oldFile.readLine());
2285             QList<QByteArray> prop(line.split('='));
2286             if (prop.size() > 1) {
2287                 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
2288                 if (it != properties.end()) {
2289                     file.write(it.key() + '=' + it.value() + '\n');
2290                     properties.erase(it);
2291                     continue;
2292                 }
2293             }
2294             file.write(line);
2295         }
2296         oldFile.close();
2297     }
2298 
2299     for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
2300         file.write(it.key() + '=' + it.value() + '\n');
2301 
2302     file.close();
2303     return true;
2304 }
2305 
2306 #if defined(Q_OS_WIN32)
checkAndWarnGradleLongPaths(const QString & outputDirectory)2307 void checkAndWarnGradleLongPaths(const QString &outputDirectory)
2308 {
2309     QStringList longFileNames;
2310     QDirIterator it(outputDirectory, QStringList(QStringLiteral("*.java")), QDir::Files,
2311                     QDirIterator::Subdirectories);
2312     while (it.hasNext()) {
2313         if (it.next().size() >= MAX_PATH)
2314             longFileNames.append(it.next());
2315     }
2316 
2317     if (!longFileNames.isEmpty()) {
2318         fprintf(stderr,
2319                 "The maximum path length that can be processed by Gradle on Windows is %d characters.\n"
2320                 "Consider moving your project to reduce its path length.\n"
2321                 "The following files have too long paths:\n%s.\n",
2322                 MAX_PATH, qPrintable(longFileNames.join(QLatin1Char('\n'))));
2323     }
2324 }
2325 #endif
2326 
buildAndroidProject(const Options & options)2327 bool buildAndroidProject(const Options &options)
2328 {
2329     GradleProperties localProperties;
2330     localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
2331     localProperties["ndk.dir"] = QDir::fromNativeSeparators(options.ndkPath).toUtf8();
2332 
2333     if (!mergeGradleProperties(options.outputDirectory + QLatin1String("local.properties"), localProperties))
2334         return false;
2335 
2336     QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties");
2337     GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
2338     gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
2339     gradleProperties["buildDir"] = "build";
2340     gradleProperties["qt5AndroidDir"] = (options.qtInstallDirectory + QLatin1String("/src/android/java")).toUtf8();
2341     gradleProperties["androidCompileSdkVersion"] = options.androidPlatform.split(QLatin1Char('-')).last().toLocal8Bit();
2342     gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
2343     gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
2344     if (gradleProperties["androidBuildToolsVersion"].isEmpty())
2345         gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit();
2346 
2347     if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
2348         return false;
2349 
2350 #if defined(Q_OS_WIN32)
2351     QString gradlePath(options.outputDirectory + QLatin1String("gradlew.bat"));
2352 #else
2353     QString gradlePath(options.outputDirectory + QLatin1String("gradlew"));
2354     {
2355         QFile f(gradlePath);
2356         if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser))
2357             fprintf(stderr, "Cannot set permissions  %s\n", qPrintable(gradlePath));
2358     }
2359 #endif
2360 
2361     QString oldPath = QDir::currentPath();
2362     if (!QDir::setCurrent(options.outputDirectory)) {
2363         fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory));
2364         return false;
2365     }
2366 
2367     QString commandLine = QLatin1String("%1 %2").arg(shellQuote(gradlePath), options.releasePackage ? QLatin1String(" assembleRelease") : QLatin1String(" assembleDebug"));
2368     if (options.buildAAB)
2369         commandLine += QLatin1String(" bundle");
2370 
2371     if (options.verbose)
2372         commandLine += QLatin1String(" --info");
2373 
2374     FILE *gradleCommand = openProcess(commandLine);
2375     if (gradleCommand == 0) {
2376         fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine));
2377         return false;
2378     }
2379 
2380     char buffer[512];
2381     while (fgets(buffer, sizeof(buffer), gradleCommand) != 0) {
2382         fprintf(stdout, "%s", buffer);
2383         fflush(stdout);
2384     }
2385 
2386     int errorCode = pclose(gradleCommand);
2387     if (errorCode != 0) {
2388         fprintf(stderr, "Building the android package failed!\n");
2389         if (!options.verbose)
2390             fprintf(stderr, "  -- For more information, run this command with --verbose.\n");
2391 
2392 #if defined(Q_OS_WIN32)
2393         checkAndWarnGradleLongPaths(options.outputDirectory);
2394 #endif
2395         return false;
2396     }
2397 
2398     if (!QDir::setCurrent(oldPath)) {
2399         fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath));
2400         return false;
2401     }
2402 
2403     return true;
2404 }
2405 
uninstallApk(const Options & options)2406 bool uninstallApk(const Options &options)
2407 {
2408     if (options.verbose)
2409         fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
2410 
2411 
2412     FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + shellQuote(options.packageName));
2413     if (adbCommand == 0)
2414         return false;
2415 
2416     if (options.verbose || mustReadOutputAnyway) {
2417         char buffer[512];
2418         while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
2419             if (options.verbose)
2420                 fprintf(stdout, "%s", buffer);
2421     }
2422 
2423     int returnCode = pclose(adbCommand);
2424     if (returnCode != 0) {
2425         fprintf(stderr, "Warning: Uninstall failed!\n");
2426         if (!options.verbose)
2427             fprintf(stderr, "  -- Run with --verbose for more information.\n");
2428         return false;
2429     }
2430 
2431     return true;
2432 }
2433 
2434 enum PackageType {
2435     AAB,
2436     UnsignedAPK,
2437     SignedAPK
2438 };
2439 
packagePath(const Options & options,PackageType pt)2440 QString packagePath(const Options &options, PackageType pt)
2441 {
2442     QString path(options.outputDirectory);
2443     path += QLatin1String("/build/outputs/%1/").arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle"));
2444     QString buildType(options.releasePackage ? QLatin1String("release/") : QLatin1String("debug/"));
2445     if (QDir(path + buildType).exists())
2446         path += buildType;
2447     path += QDir(options.outputDirectory).dirName() + QLatin1Char('-');
2448     if (options.releasePackage) {
2449         path += QLatin1String("release-");
2450         if (pt >= UnsignedAPK) {
2451             if (pt == UnsignedAPK)
2452                 path += QLatin1String("un");
2453             path += QLatin1String("signed.apk");
2454         } else {
2455             path.chop(1);
2456             path += QLatin1String(".aab");
2457         }
2458     } else {
2459         path += QLatin1String("debug");
2460         if (pt >= UnsignedAPK) {
2461             if (pt == SignedAPK)
2462                 path += QLatin1String("-signed");
2463             path += QLatin1String(".apk");
2464         } else {
2465             path += QLatin1String(".aab");
2466         }
2467     }
2468     return shellQuote(path);
2469 }
2470 
installApk(const Options & options)2471 bool installApk(const Options &options)
2472 {
2473     fflush(stdout);
2474     // Uninstall if necessary
2475     if (options.uninstallApk)
2476         uninstallApk(options);
2477 
2478     if (options.verbose)
2479         fprintf(stdout, "Installing Android package to device.\n");
2480 
2481     FILE *adbCommand = runAdb(options,
2482                               QLatin1String(" install -r ")
2483                               + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
2484                                                                             : SignedAPK));
2485     if (adbCommand == 0)
2486         return false;
2487 
2488     if (options.verbose || mustReadOutputAnyway) {
2489         char buffer[512];
2490         while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
2491             if (options.verbose)
2492                 fprintf(stdout, "%s", buffer);
2493     }
2494 
2495     int returnCode = pclose(adbCommand);
2496     if (returnCode != 0) {
2497         fprintf(stderr, "Installing to device failed!\n");
2498         if (!options.verbose)
2499             fprintf(stderr, "  -- Run with --verbose for more information.\n");
2500         return false;
2501     }
2502 
2503     return true;
2504 }
2505 
copyPackage(const Options & options)2506 bool copyPackage(const Options &options)
2507 {
2508     fflush(stdout);
2509     auto from = packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK : SignedAPK);
2510     QFile::remove(options.apkPath);
2511     return QFile::copy(from, options.apkPath);
2512 }
2513 
copyStdCpp(Options * options)2514 bool copyStdCpp(Options *options)
2515 {
2516     if (options->verbose)
2517         fprintf(stdout, "Copying STL library\n");
2518 
2519     QString stdCppPath = QLatin1String("%1/%2/lib%3.so").arg(options->stdCppPath, options->architectures[options->currentArchitecture], options->stdCppName);
2520     if (!QFile::exists(stdCppPath)) {
2521         fprintf(stderr, "STL library does not exist at %s\n", qPrintable(stdCppPath));
2522         fflush(stdout);
2523         fflush(stderr);
2524         return false;
2525     }
2526 
2527     const QString destinationFile = QLatin1String("%1/libs/%2/lib%3.so").arg(options->outputDirectory,
2528                                                                               options->currentArchitecture,
2529                                                                               options->stdCppName);
2530     return copyFileIfNewer(stdCppPath, destinationFile, *options);
2531 }
2532 
jarSignerSignPackage(const Options & options)2533 bool jarSignerSignPackage(const Options &options)
2534 {
2535     if (options.verbose)
2536         fprintf(stdout, "Signing Android package.\n");
2537 
2538     QString jdkPath = options.jdkPath;
2539 
2540     if (jdkPath.isEmpty())
2541         jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME"));
2542 
2543 #if defined(Q_OS_WIN32)
2544     QString jarSignerTool = QLatin1String("jarsigner.exe");
2545 #else
2546     QString jarSignerTool = QLatin1String("jarsigner");
2547 #endif
2548 
2549     if (jdkPath.isEmpty() || !QFile::exists(jdkPath + QLatin1String("/bin/") + jarSignerTool))
2550         jarSignerTool = findInPath(jarSignerTool);
2551     else
2552         jarSignerTool = jdkPath + QLatin1String("/bin/") + jarSignerTool;
2553 
2554     if (!QFile::exists(jarSignerTool)) {
2555         fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
2556         return false;
2557     }
2558 
2559     jarSignerTool = QLatin1String("%1 -sigalg %2 -digestalg %3 -keystore %4")
2560             .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore));
2561 
2562     if (!options.keyStorePassword.isEmpty())
2563         jarSignerTool += QLatin1String(" -storepass %1").arg(shellQuote(options.keyStorePassword));
2564 
2565     if (!options.storeType.isEmpty())
2566         jarSignerTool += QLatin1String(" -storetype %1").arg(shellQuote(options.storeType));
2567 
2568     if (!options.keyPass.isEmpty())
2569         jarSignerTool += QLatin1String(" -keypass %1").arg(shellQuote(options.keyPass));
2570 
2571     if (!options.sigFile.isEmpty())
2572         jarSignerTool += QLatin1String(" -sigfile %1").arg(shellQuote(options.sigFile));
2573 
2574     if (!options.signedJar.isEmpty())
2575         jarSignerTool += QLatin1String(" -signedjar %1").arg(shellQuote(options.signedJar));
2576 
2577     if (!options.tsaUrl.isEmpty())
2578         jarSignerTool += QLatin1String(" -tsa %1").arg(shellQuote(options.tsaUrl));
2579 
2580     if (!options.tsaCert.isEmpty())
2581         jarSignerTool += QLatin1String(" -tsacert %1").arg(shellQuote(options.tsaCert));
2582 
2583     if (options.internalSf)
2584         jarSignerTool += QLatin1String(" -internalsf");
2585 
2586     if (options.sectionsOnly)
2587         jarSignerTool += QLatin1String(" -sectionsonly");
2588 
2589     if (options.protectedAuthenticationPath)
2590         jarSignerTool += QLatin1String(" -protected");
2591 
2592     auto signPackage = [&](const QString &file) {
2593         fprintf(stdout, "Signing file %s\n", qPrintable(file));
2594         fflush(stdout);
2595         auto command = jarSignerTool + QLatin1String(" %1 %2")
2596                 .arg(file)
2597                 .arg(shellQuote(options.keyStoreAlias));
2598 
2599         FILE *jarSignerCommand = openProcess(command);
2600         if (jarSignerCommand == 0) {
2601             fprintf(stderr, "Couldn't run jarsigner.\n");
2602             return false;
2603         }
2604 
2605         if (options.verbose) {
2606             char buffer[512];
2607             while (fgets(buffer, sizeof(buffer), jarSignerCommand) != 0)
2608                 fprintf(stdout, "%s", buffer);
2609         }
2610 
2611         int errorCode = pclose(jarSignerCommand);
2612         if (errorCode != 0) {
2613             fprintf(stderr, "jarsigner command failed.\n");
2614             if (!options.verbose)
2615                 fprintf(stderr, "  -- Run with --verbose for more information.\n");
2616             return false;
2617         }
2618         return true;
2619     };
2620 
2621     if (!signPackage(packagePath(options, UnsignedAPK)))
2622         return false;
2623     if (options.buildAAB && !signPackage(packagePath(options, AAB)))
2624         return false;
2625 
2626     QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign");
2627 #if defined(Q_OS_WIN32)
2628     zipAlignTool += QLatin1String(".exe");
2629 #endif
2630 
2631     if (!QFile::exists(zipAlignTool)) {
2632         zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign");
2633 #if defined(Q_OS_WIN32)
2634         zipAlignTool += QLatin1String(".exe");
2635 #endif
2636         if (!QFile::exists(zipAlignTool)) {
2637             fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
2638             return false;
2639         }
2640     }
2641 
2642     zipAlignTool = QLatin1String("%1%2 -f 4 %3 %4")
2643             .arg(shellQuote(zipAlignTool),
2644                  options.verbose ? QLatin1String(" -v") : QLatin1String(),
2645                  packagePath(options, UnsignedAPK),
2646                  packagePath(options, SignedAPK));
2647 
2648     FILE *zipAlignCommand = openProcess(zipAlignTool);
2649     if (zipAlignCommand == 0) {
2650         fprintf(stderr, "Couldn't run zipalign.\n");
2651         return false;
2652     }
2653 
2654     char buffer[512];
2655     while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0)
2656         fprintf(stdout, "%s", buffer);
2657 
2658     int errorCode = pclose(zipAlignCommand);
2659     if (errorCode != 0) {
2660         fprintf(stderr, "zipalign command failed.\n");
2661         if (!options.verbose)
2662             fprintf(stderr, "  -- Run with --verbose for more information.\n");
2663         return false;
2664     }
2665 
2666     return QFile::remove(packagePath(options, UnsignedAPK));
2667 }
2668 
signPackage(const Options & options)2669 bool signPackage(const Options &options)
2670 {
2671     QString apksignerTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/apksigner");
2672 #if defined(Q_OS_WIN32)
2673     apksignerTool += QLatin1String(".bat");
2674 #endif
2675 
2676     if (options.jarSigner || !QFile::exists(apksignerTool))
2677         return jarSignerSignPackage(options);
2678 
2679     // APKs signed with apksigner must not be changed after they're signed, therefore we need to zipalign it before we sign it.
2680 
2681     QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign");
2682 #if defined(Q_OS_WIN32)
2683     zipAlignTool += QLatin1String(".exe");
2684 #endif
2685 
2686     if (!QFile::exists(zipAlignTool)) {
2687         zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign");
2688 #if defined(Q_OS_WIN32)
2689         zipAlignTool += QLatin1String(".exe");
2690 #endif
2691         if (!QFile::exists(zipAlignTool)) {
2692             fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
2693             return false;
2694         }
2695     }
2696 
2697     zipAlignTool = QLatin1String("%1%2 -f 4 %3 %4")
2698             .arg(shellQuote(zipAlignTool),
2699                  options.verbose ? QLatin1String(" -v") : QLatin1String(),
2700                  packagePath(options, UnsignedAPK),
2701                  packagePath(options, SignedAPK));
2702 
2703     FILE *zipAlignCommand = openProcess(zipAlignTool);
2704     if (zipAlignCommand == 0) {
2705         fprintf(stderr, "Couldn't run zipalign.\n");
2706         return false;
2707     }
2708 
2709     char buffer[512];
2710     while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0)
2711         fprintf(stdout, "%s", buffer);
2712 
2713     int errorCode = pclose(zipAlignCommand);
2714     if (errorCode != 0) {
2715         fprintf(stderr, "zipalign command failed.\n");
2716         if (!options.verbose)
2717             fprintf(stderr, "  -- Run with --verbose for more information.\n");
2718         return false;
2719     }
2720 
2721     QString apkSignerCommandLine = QLatin1String("%1 sign --ks %2")
2722             .arg(shellQuote(apksignerTool), shellQuote(options.keyStore));
2723 
2724     if (!options.keyStorePassword.isEmpty())
2725         apkSignerCommandLine += QLatin1String(" --ks-pass pass:%1").arg(shellQuote(options.keyStorePassword));
2726 
2727     if (!options.keyStoreAlias.isEmpty())
2728         apkSignerCommandLine += QLatin1String(" --ks-key-alias %1").arg(shellQuote(options.keyStoreAlias));
2729 
2730     if (!options.keyPass.isEmpty())
2731         apkSignerCommandLine += QLatin1String(" --key-pass pass:%1").arg(shellQuote(options.keyPass));
2732 
2733     if (options.verbose)
2734         apkSignerCommandLine += QLatin1String(" --verbose");
2735 
2736     apkSignerCommandLine += QLatin1String(" %1")
2737             .arg(packagePath(options, SignedAPK));
2738 
2739     auto apkSignerRunner = [&] {
2740         FILE *apkSignerCommand = openProcess(apkSignerCommandLine);
2741         if (apkSignerCommand == 0) {
2742             fprintf(stderr, "Couldn't run apksigner.\n");
2743             return false;
2744         }
2745 
2746         char buffer[512];
2747         while (fgets(buffer, sizeof(buffer), apkSignerCommand) != 0)
2748             fprintf(stdout, "%s", buffer);
2749 
2750         errorCode = pclose(apkSignerCommand);
2751         if (errorCode != 0) {
2752             fprintf(stderr, "apksigner command failed.\n");
2753             if (!options.verbose)
2754                 fprintf(stderr, "  -- Run with --verbose for more information.\n");
2755             return false;
2756         }
2757         return true;
2758     };
2759 
2760     // Sign the package
2761     if (!apkSignerRunner())
2762         return false;
2763 
2764     apkSignerCommandLine = QLatin1String("%1 verify --verbose %2")
2765         .arg(shellQuote(apksignerTool), packagePath(options, SignedAPK));
2766 
2767     // Verify the package and remove the unsigned apk
2768     return apkSignerRunner() && QFile::remove(packagePath(options, UnsignedAPK));
2769 }
2770 
2771 enum ErrorCode
2772 {
2773     Success,
2774     SyntaxErrorOrHelpRequested = 1,
2775     CannotReadInputFile = 2,
2776     CannotCopyAndroidTemplate = 3,
2777     CannotReadDependencies = 4,
2778     CannotCopyGnuStl = 5,
2779     CannotCopyQtFiles = 6,
2780     CannotFindApplicationBinary = 7,
2781     CannotCopyAndroidExtraLibs = 10,
2782     CannotCopyAndroidSources = 11,
2783     CannotUpdateAndroidFiles = 12,
2784     CannotCreateAndroidProject = 13,
2785     CannotBuildAndroidProject = 14,
2786     CannotSignPackage = 15,
2787     CannotInstallApk = 16,
2788     CannotCopyAndroidExtraResources = 19,
2789     CannotCopyApk = 20,
2790     CannotCreateRcc = 21
2791 };
2792 
main(int argc,char * argv[])2793 int main(int argc, char *argv[])
2794 {
2795     QCoreApplication a(argc, argv);
2796 
2797     Options options = parseOptions();
2798     if (options.helpRequested || options.outputDirectory.isEmpty()) {
2799         printHelp();
2800         return SyntaxErrorOrHelpRequested;
2801     }
2802 
2803     options.timer.start();
2804 
2805     if (!readInputFile(&options))
2806         return CannotReadInputFile;
2807 
2808     if (Q_UNLIKELY(options.timing))
2809         fprintf(stdout, "[TIMING] %d ms: Read input file\n", options.timer.elapsed());
2810 
2811     fprintf(stdout,
2812 //          "012345678901234567890123456789012345678901234567890123456789012345678901"
2813             "Generating Android Package\n"
2814             "  Input file: %s\n"
2815             "  Output directory: %s\n"
2816             "  Application binary: %s\n"
2817             "  Android build platform: %s\n"
2818             "  Install to device: %s\n",
2819             qPrintable(options.inputFileName),
2820             qPrintable(options.outputDirectory),
2821             qPrintable(options.applicationBinary),
2822             qPrintable(options.androidPlatform),
2823             options.installApk
2824                 ? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation))
2825                 : "No"
2826             );
2827 
2828     if (options.build && !options.auxMode) {
2829         cleanAndroidFiles(options);
2830         if (Q_UNLIKELY(options.timing))
2831             fprintf(stdout, "[TIMING] %d ms: Cleaned Android file\n", options.timer.elapsed());
2832 
2833         if (!copyAndroidTemplate(options))
2834             return CannotCopyAndroidTemplate;
2835 
2836         if (Q_UNLIKELY(options.timing))
2837             fprintf(stdout, "[TIMING] %d ms: Copied Android template\n", options.timer.elapsed());
2838     }
2839 
2840     for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
2841         options.clear(it.key());
2842 
2843         if (!readDependencies(&options))
2844             return CannotReadDependencies;
2845 
2846         if (Q_UNLIKELY(options.timing))
2847             fprintf(stdout, "[TIMING] %d ms: Read dependencies\n", options.timer.elapsed());
2848 
2849         if (!copyQtFiles(&options))
2850             return CannotCopyQtFiles;
2851 
2852         if (Q_UNLIKELY(options.timing))
2853             fprintf(stdout, "[TIMING] %d ms: Copied Qt files\n", options.timer.elapsed());
2854 
2855         if (!copyAndroidExtraLibs(&options))
2856             return CannotCopyAndroidExtraLibs;
2857 
2858         if (Q_UNLIKELY(options.timing))
2859             fprintf(stdout, "[TIMING] %d ms: Copied extra libs\n", options.timer.elapsed());
2860 
2861         if (!copyAndroidExtraResources(&options))
2862             return CannotCopyAndroidExtraResources;
2863 
2864         if (Q_UNLIKELY(options.timing))
2865             fprintf(stdout, "[TIMING] %d ms: Copied extra resources\n", options.timer.elapsed());
2866 
2867         if (!options.auxMode) {
2868             if (options.deploymentMechanism != Options::Ministro && !copyStdCpp(&options))
2869                 return CannotCopyGnuStl;
2870 
2871             if (Q_UNLIKELY(options.timing))
2872                 fprintf(stdout, "[TIMING] %d ms: Copied GNU STL\n", options.timer.elapsed());
2873         }
2874 
2875         if (!containsApplicationBinary(&options))
2876             return CannotFindApplicationBinary;
2877 
2878         if (Q_UNLIKELY(options.timing))
2879             fprintf(stdout, "[TIMING] %d ms: Checked for application binary\n", options.timer.elapsed());
2880 
2881         if (options.deploymentMechanism != Options::Ministro) {
2882             if (Q_UNLIKELY(options.timing))
2883                 fprintf(stdout, "[TIMING] %d ms: Bundled Qt libs\n", options.timer.elapsed());
2884         }
2885     }
2886 
2887     if (!createRcc(options))
2888         return CannotCreateRcc;
2889 
2890     if (options.auxMode) {
2891         if (!updateAndroidFiles(options))
2892             return CannotUpdateAndroidFiles;
2893         return 0;
2894     }
2895 
2896 
2897     if (options.build) {
2898         if (!copyAndroidSources(options))
2899             return CannotCopyAndroidSources;
2900 
2901         if (Q_UNLIKELY(options.timing))
2902             fprintf(stdout, "[TIMING] %d ms: Copied android sources\n", options.timer.elapsed());
2903 
2904         if (!updateAndroidFiles(options))
2905             return CannotUpdateAndroidFiles;
2906 
2907         if (Q_UNLIKELY(options.timing))
2908             fprintf(stdout, "[TIMING] %d ms: Updated files\n", options.timer.elapsed());
2909 
2910         if (Q_UNLIKELY(options.timing))
2911             fprintf(stdout, "[TIMING] %d ms: Created project\n", options.timer.elapsed());
2912 
2913         if (!buildAndroidProject(options))
2914             return CannotBuildAndroidProject;
2915 
2916         if (Q_UNLIKELY(options.timing))
2917             fprintf(stdout, "[TIMING] %d ms: Built project\n", options.timer.elapsed());
2918 
2919         if (!options.keyStore.isEmpty() && !signPackage(options))
2920             return CannotSignPackage;
2921 
2922         if (!options.apkPath.isEmpty() && !copyPackage(options))
2923             return CannotCopyApk;
2924 
2925         if (Q_UNLIKELY(options.timing))
2926             fprintf(stdout, "[TIMING] %d ms: Signed package\n", options.timer.elapsed());
2927     }
2928 
2929     if (options.installApk && !installApk(options))
2930         return CannotInstallApk;
2931 
2932     if (Q_UNLIKELY(options.timing))
2933         fprintf(stdout, "[TIMING] %d ms: Installed APK\n", options.timer.elapsed());
2934 
2935     fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.);
2936 
2937     if (options.installApk)
2938         fprintf(stdout, "  -- It can now be run from the selected device/emulator.\n");
2939 
2940     fprintf(stdout, "  -- File: %s\n", qPrintable(packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK
2941                                                                                               : SignedAPK)));
2942     fflush(stdout);
2943     return 0;
2944 }
2945