Warning: That file was not part of the compilation database. It may have many parsing errors.

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#ifdef Q_CC_MSVC
48#define popen _popen
49#define QT_POPEN_READ "rb"
50#define pclose _pclose
51#else
52#define QT_POPEN_READ "r"
53#endif
54
55class ActionTimer
56{
57 qint64 started;
58public:
59 ActionTimer() = default;
60 void start()
61 {
62 started = QDateTime::currentMSecsSinceEpoch();
63 }
64 int elapsed()
65 {
66 return int(QDateTime::currentMSecsSinceEpoch() - started);
67 }
68};
69
70static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
71
72void deleteRecursively(const QString &dirName)
73{
74 QDir dir(dirName);
75 if (!dir.exists())
76 return;
77
78 const QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
79 for (const QFileInfo &entry : entries) {
80 if (entry.isDir())
81 deleteRecursively(entry.absoluteFilePath());
82 else
83 QFile::remove(entry.absoluteFilePath());
84 }
85
86 QDir().rmdir(dirName);
87}
88
89FILE *openProcess(const QString &command)
90{
91#if defined(Q_OS_WIN32)
92 QString processedCommand = QLatin1Char('\"') + command + QLatin1Char('\"');
93#else
94 QString processedCommand = command;
95#endif
96
97 return popen(processedCommand.toLocal8Bit().constData(), QT_POPEN_READ);
98}
99
100struct QtDependency
101{
102 QtDependency(QString rpath, QString apath) : relativePath(rpath), absolutePath(apath) {}
103
104 bool operator==(const QtDependency &other) const
105 {
106 return relativePath == other.relativePath && absolutePath == other.absolutePath;
107 }
108
109 QString relativePath;
110 QString absolutePath;
111};
112
113struct Options
114{
115 Options()
116 : helpRequested(false)
117 , verbose(false)
118 , timing(false)
119 , generateAssetsFileList(true)
120 , build(true)
121 , auxMode(false)
122 , deploymentMechanism(Bundled)
123 , releasePackage(false)
124 , digestAlg(QLatin1String("SHA1"))
125 , sigAlg(QLatin1String("SHA1withRSA"))
126 , internalSf(false)
127 , sectionsOnly(false)
128 , protectedAuthenticationPath(false)
129 , jarSigner(false)
130 , gdbServer(Auto)
131 , installApk(false)
132 , uninstallApk(false)
133 {}
134
135 enum DeploymentMechanism
136 {
137 Bundled,
138 Ministro
139 };
140
141 enum TriState {
142 Auto,
143 False,
144 True
145 };
146
147 bool helpRequested;
148 bool verbose;
149 bool timing;
150 bool generateAssetsFileList;
151 bool build;
152 bool auxMode;
153 bool stripLibraries = true;
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
172 // Versioning
173 QString versionName;
174 QString versionCode;
175
176 // lib c++ path
177 QString stdCppPath;
178 QString stdCppName = QStringLiteral("gnustl_shared");
179
180 // Build information
181 QString androidPlatform;
182 QString architecture;
183 QString toolchainVersion;
184 QString toolchainPrefix;
185 QString toolPrefix;
186 bool useLLVM = false;
187 QString ndkHost;
188
189 // Package information
190 DeploymentMechanism deploymentMechanism;
191 QString packageName;
192 QStringList extraLibs;
193 QStringList extraPlugins;
194
195 // Signing information
196 bool releasePackage;
197 QString keyStore;
198 QString keyStorePassword;
199 QString keyStoreAlias;
200 QString storeType;
201 QString keyPass;
202 QString sigFile;
203 QString signedJar;
204 QString digestAlg;
205 QString sigAlg;
206 QString tsaUrl;
207 QString tsaCert;
208 bool internalSf;
209 bool sectionsOnly;
210 bool protectedAuthenticationPath;
211 bool jarSigner;
212 QString apkPath;
213
214 // Gdbserver
215 TriState gdbServer;
216
217 // Installation information
218 bool installApk;
219 bool uninstallApk;
220 QString installLocation;
221
222 // Collected information
223 typedef QPair<QString, QString> BundledFile;
224 QList<BundledFile> bundledFiles;
225 QList<QtDependency> qtDependencies;
226 QStringList localLibs;
227 QStringList localJars;
228 QStringList initClasses;
229 QStringList permissions;
230 QStringList features;
231};
232
233// Copy-pasted from qmake/library/ioutil.cpp
234inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
235{
236 for (int x = arg.length() - 1; x >= 0; --x) {
237 ushort c = arg.unicode()[x].unicode();
238 if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
239 return true;
240 }
241 return false;
242}
243
244static QString shellQuoteUnix(const QString &arg)
245{
246 // Chars that should be quoted (TM). This includes:
247 static const uchar iqm[] = {
248 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
249 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
250 }; // 0-32 \'"$`<>|;&(){}*?#!~[]
251
252 if (!arg.length())
253 return QString::fromLatin1("\"\"");
254
255 QString ret(arg);
256 if (hasSpecialChars(ret, iqm)) {
257 ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
258 ret.prepend(QLatin1Char('\''));
259 ret.append(QLatin1Char('\''));
260 }
261 return ret;
262}
263
264static QString shellQuoteWin(const QString &arg)
265{
266 // Chars that should be quoted (TM). This includes:
267 // - control chars & space
268 // - the shell meta chars "&()<>^|
269 // - the potential separators ,;=
270 static const uchar iqm[] = {
271 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
272 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
273 };
274
275 if (!arg.length())
276 return QString::fromLatin1("\"\"");
277
278 QString ret(arg);
279 if (hasSpecialChars(ret, iqm)) {
280 // Quotes are escaped and their preceding backslashes are doubled.
281 // It's impossible to escape anything inside a quoted string on cmd
282 // level, so the outer quoting must be "suspended".
283 ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
284 // The argument must not end with a \ since this would be interpreted
285 // as escaping the quote -- rather put the \ behind the quote: e.g.
286 // rather use "foo"\ than "foo\"
287 int i = ret.length();
288 while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
289 --i;
290 ret.insert(i, QLatin1Char('"'));
291 ret.prepend(QLatin1Char('"'));
292 }
293 return ret;
294}
295
296static QString shellQuote(const QString &arg)
297{
298 if (QDir::separator() == QLatin1Char('\\'))
299 return shellQuoteWin(arg);
300 else
301 return shellQuoteUnix(arg);
302}
303
304
305void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir)
306{
307 if (options.verbose)
308 fprintf(stdout, "Delete missing files %s %s\n", qPrintable(srcDir.absolutePath()), qPrintable(dstDir.absolutePath()));
309
310 const QFileInfoList srcEntries = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
311 const QFileInfoList dstEntries = dstDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
312 for (const QFileInfo &dst : dstEntries) {
313 bool found = false;
314 for (const QFileInfo &src : srcEntries)
315 if (dst.fileName() == src.fileName()) {
316 if (dst.isDir())
317 deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath());
318 found = true;
319 break;
320 }
321
322 if (!found) {
323 if (options.verbose)
324 fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath()));
325
326 if (dst.isDir())
327 deleteRecursively(dst.absolutePath());
328 else
329 QFile::remove(dst.absoluteFilePath());
330 }
331 }
332 fflush(stdout);
333}
334
335
336Options parseOptions()
337{
338 Options options;
339
340 QStringList arguments = QCoreApplication::arguments();
341 for (int i=0; i<arguments.size(); ++i) {
342 const QString &argument = arguments.at(i);
343 if (argument.compare(QLatin1String("--output"), Qt::CaseInsensitive) == 0) {
344 if (i + 1 == arguments.size())
345 options.helpRequested = true;
346 else
347 options.outputDirectory = arguments.at(++i).trimmed();
348 } else if (argument.compare(QLatin1String("--input"), Qt::CaseInsensitive) == 0) {
349 if (i + 1 == arguments.size())
350 options.helpRequested = true;
351 else
352 options.inputFileName = arguments.at(++i);
353 } else if (argument.compare(QLatin1String("--no-build"), Qt::CaseInsensitive) == 0) {
354 options.build = false;
355 } else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) {
356 options.installApk = true;
357 options.uninstallApk = true;
358 } else if (argument.compare(QLatin1String("--reinstall"), Qt::CaseInsensitive) == 0) {
359 options.installApk = true;
360 options.uninstallApk = false;
361 } else if (argument.compare(QLatin1String("--android-platform"), Qt::CaseInsensitive) == 0) {
362 if (i + 1 == arguments.size())
363 options.helpRequested = true;
364 else
365 options.androidPlatform = arguments.at(++i);
366 } else if (argument.compare(QLatin1String("--help"), Qt::CaseInsensitive) == 0) {
367 options.helpRequested = true;
368 } else if (argument.compare(QLatin1String("--verbose"), Qt::CaseInsensitive) == 0) {
369 options.verbose = true;
370 } else if (argument.compare(QLatin1String("--deployment"), Qt::CaseInsensitive) == 0) {
371 if (i + 1 == arguments.size()) {
372 options.helpRequested = true;
373 } else {
374 QString deploymentMechanism = arguments.at(++i);
375 if (deploymentMechanism.compare(QLatin1String("ministro"), Qt::CaseInsensitive) == 0) {
376 options.deploymentMechanism = Options::Ministro;
377 } else if (deploymentMechanism.compare(QLatin1String("bundled"), Qt::CaseInsensitive) == 0) {
378 options.deploymentMechanism = Options::Bundled;
379 } else {
380 fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
381 options.helpRequested = true;
382 }
383 }
384 } else if (argument.compare(QLatin1String("--device"), Qt::CaseInsensitive) == 0) {
385 if (i + 1 == arguments.size())
386 options.helpRequested = true;
387 else
388 options.installLocation = arguments.at(++i);
389 } else if (argument.compare(QLatin1String("--release"), Qt::CaseInsensitive) == 0) {
390 options.releasePackage = true;
391 } else if (argument.compare(QLatin1String("--gdbserver"), Qt::CaseInsensitive) == 0) {
392 options.gdbServer = Options::True;
393 } else if (argument.compare(QLatin1String("--no-gdbserver"), Qt::CaseInsensitive) == 0) {
394 options.gdbServer = Options::False;
395 } else if (argument.compare(QLatin1String("--jdk"), Qt::CaseInsensitive) == 0) {
396 if (i + 1 == arguments.size())
397 options.helpRequested = true;
398 else
399 options.jdkPath = arguments.at(++i);
400 } else if (argument.compare(QLatin1String("--apk"), Qt::CaseInsensitive) == 0) {
401 if (i + 1 == arguments.size())
402 options.helpRequested = true;
403 else
404 options.apkPath = arguments.at(++i);
405 } else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) {
406 if (i + 2 >= arguments.size()) {
407 options.helpRequested = true;
408 } else {
409 options.releasePackage = true;
410 options.keyStore = arguments.at(++i);
411 options.keyStoreAlias = arguments.at(++i);
412 }
413 } else if (argument.compare(QLatin1String("--storepass"), Qt::CaseInsensitive) == 0) {
414 if (i + 1 == arguments.size())
415 options.helpRequested = true;
416 else
417 options.keyStorePassword = arguments.at(++i);
418 } else if (argument.compare(QLatin1String("--storetype"), Qt::CaseInsensitive) == 0) {
419 if (i + 1 == arguments.size())
420 options.helpRequested = true;
421 else
422 options.storeType = arguments.at(++i);
423 } else if (argument.compare(QLatin1String("--keypass"), Qt::CaseInsensitive) == 0) {
424 if (i + 1 == arguments.size())
425 options.helpRequested = true;
426 else
427 options.keyPass = arguments.at(++i);
428 } else if (argument.compare(QLatin1String("--sigfile"), Qt::CaseInsensitive) == 0) {
429 if (i + 1 == arguments.size())
430 options.helpRequested = true;
431 else
432 options.sigFile = arguments.at(++i);
433 } else if (argument.compare(QLatin1String("--digestalg"), Qt::CaseInsensitive) == 0) {
434 if (i + 1 == arguments.size())
435 options.helpRequested = true;
436 else
437 options.digestAlg = arguments.at(++i);
438 } else if (argument.compare(QLatin1String("--sigalg"), Qt::CaseInsensitive) == 0) {
439 if (i + 1 == arguments.size())
440 options.helpRequested = true;
441 else
442 options.sigAlg = arguments.at(++i);
443 } else if (argument.compare(QLatin1String("--tsa"), Qt::CaseInsensitive) == 0) {
444 if (i + 1 == arguments.size())
445 options.helpRequested = true;
446 else
447 options.tsaUrl = arguments.at(++i);
448 } else if (argument.compare(QLatin1String("--tsacert"), Qt::CaseInsensitive) == 0) {
449 if (i + 1 == arguments.size())
450 options.helpRequested = true;
451 else
452 options.tsaCert = arguments.at(++i);
453 } else if (argument.compare(QLatin1String("--internalsf"), Qt::CaseInsensitive) == 0) {
454 options.internalSf = true;
455 } else if (argument.compare(QLatin1String("--sectionsonly"), Qt::CaseInsensitive) == 0) {
456 options.sectionsOnly = true;
457 } else if (argument.compare(QLatin1String("--protected"), Qt::CaseInsensitive) == 0) {
458 options.protectedAuthenticationPath = true;
459 } else if (argument.compare(QLatin1String("--jarsigner"), Qt::CaseInsensitive) == 0) {
460 options.jarSigner = true;
461 } else if (argument.compare(QLatin1String("--no-generated-assets-cache"), Qt::CaseInsensitive) == 0) {
462 options.generateAssetsFileList = false;
463 } else if (argument.compare(QLatin1String("--aux-mode"), Qt::CaseInsensitive) == 0) {
464 options.auxMode = true;
465 } else if (argument.compare(QLatin1String("--no-strip"), Qt::CaseInsensitive) == 0) {
466 options.stripLibraries = false;
467 }
468 }
469
470 if (options.inputFileName.isEmpty())
471 options.inputFileName = QString::fromLatin1("android-lib%1.so-deployment-settings.json").arg(QDir::current().dirName());
472
473 options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT");
474
475 if (!QDir::current().mkpath(options.outputDirectory)) {
476 fprintf(stderr, "Invalid output directory: %s\n", qPrintable(options.outputDirectory));
477 options.outputDirectory.clear();
478 } else {
479 options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath();
480 if (!options.outputDirectory.endsWith(QLatin1Char('/')))
481 options.outputDirectory += QLatin1Char('/');
482 }
483
484 return options;
485}
486
487void printHelp()
488{// "012345678901234567890123456789012345678901234567890123456789012345678901"
489 fprintf(stderr, "Syntax: %s --output <destination> [options]\n"
490 "\n"
491 " Creates an Android package in the build directory <destination> and\n"
492 " builds it into an .apk file.\n\n"
493 " Optional arguments:\n"
494 " --input <inputfile>: Reads <inputfile> for options generated by\n"
495 " qmake. A default file name based on the current working\n"
496 " directory will be used if nothing else is specified.\n"
497 " --deployment <mechanism>: Supported deployment mechanisms:\n"
498 " bundled (default): Include Qt files in stand-alone package.\n"
499 " ministro: Use the Ministro service to manage Qt files.\n"
500 " --no-build: Do not build the package, it is useful to just install\n"
501 " a package previously built.\n"
502 " --install: Installs apk to device/emulator. By default this step is\n"
503 " not taken. If the application has previously been installed on\n"
504 " the device, it will be uninstalled first.\n"
505 " --reinstall: Installs apk to device/emulator. By default this step\n"
506 " is not taken. If the application has previously been installed on\n"
507 " the device, it will be overwritten, but its data will be left\n"
508 " intact.\n"
509 " --device [device ID]: Use specified device for deployment. Default\n"
510 " is the device selected by default by adb.\n"
511 " --android-platform <platform>: Builds against the given android\n"
512 " platform. By default, the highest available version will be\n"
513 " used.\n"
514 " --release: Builds a package ready for release. By default, the\n"
515 " package will be signed with a debug key.\n"
516 " --sign <url/to/keystore> <alias>: Signs the package with the\n"
517 " specified keystore, alias and store password. Also implies the\n"
518 " --release option.\n"
519 " Optional arguments for use with signing:\n"
520 " --storepass <password>: Keystore password.\n"
521 " --storetype <type>: Keystore type.\n"
522 " --keypass <password>: Password for private key (if different\n"
523 " from keystore password.)\n"
524 " --sigfile <file>: Name of .SF/.DSA file.\n"
525 " --digestalg <name>: Name of digest algorithm. Default is\n"
526 " \"SHA1\".\n"
527 " --sigalg <name>: Name of signature algorithm. Default is\n"
528 " \"SHA1withRSA\".\n"
529 " --tsa <url>: Location of the Time Stamping Authority.\n"
530 " --tsacert <alias>: Public key certificate for TSA.\n"
531 " --internalsf: Include the .SF file inside the signature block.\n"
532 " --sectionsonly: Don't compute hash of entire manifest.\n"
533 " --protected: Keystore has protected authentication path.\n"
534 " --jarsigner: Force jarsigner usage, otherwise apksigner will be\n"
535 " used if available.\n"
536 " --gdbserver: Adds the gdbserver to the package. By default the gdbserver\n"
537 " is bundled for debug pacakges.\n"
538 " --no-gdbserver: Prevents the gdbserver from being added to the package\n"
539 " By default the gdbserver is bundled for debug pacakges.\n"
540 " --jdk <path/to/jdk>: Used to find the jarsigner tool when used\n"
541 " in combination with the --release argument. By default,\n"
542 " an attempt is made to detect the tool using the JAVA_HOME and\n"
543 " PATH environment variables, in that order.\n"
544 " --qml-import-paths: Specify additional search paths for QML\n"
545 " imports.\n"
546 " --verbose: Prints out information during processing.\n"
547 " --no-generated-assets-cache: Do not pregenerate the entry list for\n"
548 " the assets file engine.\n"
549 " --aux-mode: Operate in auxiliary mode. This will only copy the\n"
550 " dependencies into the build directory and update the XML templates.\n"
551 " The project will not be built or installed.\n"
552 " --no-strip: Do not strip debug symbols from libraries.\n"
553 " --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.\n"
554 " --help: Displays this information.\n\n",
555 qPrintable(QCoreApplication::arguments().at(0))
556 );
557}
558
559// Since strings compared will all start with the same letters,
560// sorting by length and then alphabetically within each length
561// gives the natural order.
562bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2)
563{
564 QString s1 = fi1.baseName();
565 QString s2 = fi2.baseName();
566
567 if (s1.length() == s2.length())
568 return s1 > s2;
569 else
570 return s1.length() > s2.length();
571}
572
573// Files which contain templates that need to be overwritten by build data should be overwritten every
574// time.
575bool alwaysOverwritableFile(const QString &fileName)
576{
577 return (fileName.endsWith(QLatin1String("/res/values/libs.xml"))
578 || fileName.endsWith(QLatin1String("/AndroidManifest.xml"))
579 || fileName.endsWith(QLatin1String("/res/values/strings.xml"))
580 || fileName.endsWith(QLatin1String("/src/org/qtproject/qt5/android/bindings/QtActivity.java")));
581}
582
583bool copyFileIfNewer(const QString &sourceFileName,
584 const QString &destinationFileName,
585 bool verbose,
586 bool forceOverwrite = false)
587{
588 if (QFile::exists(destinationFileName)) {
589 QFileInfo destinationFileInfo(destinationFileName);
590 QFileInfo sourceFileInfo(sourceFileName);
591
592 if (!forceOverwrite
593 && sourceFileInfo.lastModified() <= destinationFileInfo.lastModified()
594 && !alwaysOverwritableFile(destinationFileName)) {
595 if (verbose)
596 fprintf(stdout, " -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName));
597 return true;
598 } else {
599 if (!QFile(destinationFileName).remove()) {
600 fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName));
601 return false;
602 }
603 }
604 }
605
606 if (!QDir().mkpath(QFileInfo(destinationFileName).path())) {
607 fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName));
608 return false;
609 }
610
611 if (!QFile::exists(destinationFileName) && !QFile::copy(sourceFileName, destinationFileName)) {
612 fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName));
613 return false;
614 } else if (verbose) {
615 fprintf(stdout, " -- Copied %s\n", qPrintable(destinationFileName));
616 fflush(stdout);
617 }
618
619 return true;
620}
621
622QString cleanPackageName(QString packageName)
623{
624 QRegExp legalChars(QLatin1String("[a-zA-Z0-9_\\.]"));
625
626 for (int i = 0; i < packageName.length(); ++i) {
627 if (!legalChars.exactMatch(packageName.mid(i, 1)))
628 packageName[i] = QLatin1Char('_');
629 }
630
631 static QStringList keywords;
632 if (keywords.isEmpty()) {
633 keywords << QLatin1String("abstract") << QLatin1String("continue") << QLatin1String("for")
634 << QLatin1String("new") << QLatin1String("switch") << QLatin1String("assert")
635 << QLatin1String("default") << QLatin1String("if") << QLatin1String("package")
636 << QLatin1String("synchronized") << QLatin1String("boolean") << QLatin1String("do")
637 << QLatin1String("goto") << QLatin1String("private") << QLatin1String("this")
638 << QLatin1String("break") << QLatin1String("double") << QLatin1String("implements")
639 << QLatin1String("protected") << QLatin1String("throw") << QLatin1String("byte")
640 << QLatin1String("else") << QLatin1String("import") << QLatin1String("public")
641 << QLatin1String("throws") << QLatin1String("case") << QLatin1String("enum")
642 << QLatin1String("instanceof") << QLatin1String("return") << QLatin1String("transient")
643 << QLatin1String("catch") << QLatin1String("extends") << QLatin1String("int")
644 << QLatin1String("short") << QLatin1String("try") << QLatin1String("char")
645 << QLatin1String("final") << QLatin1String("interface") << QLatin1String("static")
646 << QLatin1String("void") << QLatin1String("class") << QLatin1String("finally")
647 << QLatin1String("long") << QLatin1String("strictfp") << QLatin1String("volatile")
648 << QLatin1String("const") << QLatin1String("float") << QLatin1String("native")
649 << QLatin1String("super") << QLatin1String("while");
650 }
651
652 // No keywords
653 int index = -1;
654 while (index < packageName.length()) {
655 int next = packageName.indexOf(QLatin1Char('.'), index + 1);
656 if (next == -1)
657 next = packageName.length();
658 QString word = packageName.mid(index + 1, next - index - 1);
659 if (!word.isEmpty()) {
660 QChar c = word[0];
661 if ((c >= QChar(QLatin1Char('0')) && c<= QChar(QLatin1Char('9')))
662 || c == QLatin1Char('_')) {
663 packageName.insert(index + 1, QLatin1Char('a'));
664 index = next + 1;
665 continue;
666 }
667 }
668 if (keywords.contains(word)) {
669 packageName.insert(next, QLatin1String("_"));
670 index = next + 1;
671 } else {
672 index = next;
673 }
674 }
675
676 return packageName;
677}
678
679QString detectLatestAndroidPlatform(const QString &sdkPath)
680{
681 QDir dir(sdkPath + QLatin1String("/platforms"));
682 if (!dir.exists()) {
683 fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath()));
684 return QString();
685 }
686
687 QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
688 if (fileInfos.isEmpty()) {
689 fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath()));
690 return QString();
691 }
692
693 std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
694
695 QFileInfo latestPlatform = fileInfos.first();
696 return latestPlatform.baseName();
697}
698
699QString packageNameFromAndroidManifest(const QString &androidManifestPath)
700{
701 QFile androidManifestXml(androidManifestPath);
702 if (androidManifestXml.open(QIODevice::ReadOnly)) {
703 QXmlStreamReader reader(&androidManifestXml);
704 while (!reader.atEnd()) {
705 reader.readNext();
706 if (reader.isStartElement() && reader.name() == QLatin1String("manifest"))
707 return cleanPackageName(
708 reader.attributes().value(QLatin1String("package")).toString());
709 }
710 }
711 return QString();
712}
713
714bool readInputFile(Options *options)
715{
716 QFile file(options->inputFileName);
717 if (!file.open(QIODevice::ReadOnly)) {
718 fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
719 return false;
720 }
721
722 QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll());
723 if (jsonDocument.isNull()) {
724 fprintf(stderr, "Invalid json file: %s\n", qPrintable(options->inputFileName));
725 return false;
726 }
727
728 QJsonObject jsonObject = jsonDocument.object();
729
730 {
731 QJsonValue sdkPath = jsonObject.value(QLatin1String("sdk"));
732 if (sdkPath.isUndefined()) {
733 fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName));
734 return false;
735 }
736
737 options->sdkPath = QDir::fromNativeSeparators(sdkPath.toString());
738
739 if (options->androidPlatform.isEmpty()) {
740 options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
741 if (options->androidPlatform.isEmpty())
742 return false;
743 } else {
744 if (!QDir(options->sdkPath + QLatin1String("/platforms/") + options->androidPlatform).exists()) {
745 fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n",
746 qPrintable(options->androidPlatform));
747 }
748 }
749 }
750
751 {
752
753 const QJsonValue value = jsonObject.value(QStringLiteral("sdkBuildToolsRevision"));
754 if (!value.isUndefined())
755 options->sdkBuildToolsVersion = value.toString();
756 }
757
758 {
759 const QJsonValue qtInstallDirectory = jsonObject.value(QStringLiteral("qt"));
760 if (qtInstallDirectory.isUndefined()) {
761 fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName));
762 return false;
763 }
764 options->qtInstallDirectory = qtInstallDirectory.toString();
765 }
766
767 {
768 const auto extraPrefixDirs = jsonObject.value(QLatin1String("extraPrefixDirs")).toArray();
769 options->extraPrefixDirs.reserve(extraPrefixDirs.size());
770 for (const auto &prefix : extraPrefixDirs) {
771 options->extraPrefixDirs.push_back(prefix.toString());
772 }
773 }
774
775 {
776 const QJsonValue androidSourcesDirectory = jsonObject.value(QStringLiteral("android-package-source-directory"));
777 if (!androidSourcesDirectory.isUndefined())
778 options->androidSourceDirectory = androidSourcesDirectory.toString();
779 }
780
781 {
782 const QJsonValue androidVersionName = jsonObject.value(QStringLiteral("android-version-name"));
783 if (!androidVersionName.isUndefined())
784 options->versionName = androidVersionName.toString();
785 else
786 options->versionName = QStringLiteral("1.0");
787 }
788
789 {
790 const QJsonValue androidVersionCode = jsonObject.value(QStringLiteral("android-version-code"));
791 if (!androidVersionCode.isUndefined())
792 options->versionCode = androidVersionCode.toString();
793 else
794 options->versionCode = QStringLiteral("1");
795 }
796
797 {
798 const QJsonValue applicationBinary = jsonObject.value(QStringLiteral("application-binary"));
799 if (applicationBinary.isUndefined()) {
800 fprintf(stderr, "No application binary defined in json file.\n");
801 return false;
802 }
803 options->applicationBinary = applicationBinary.toString();
804
805 if (!QFile::exists(options->applicationBinary)) {
806 fprintf(stderr, "Cannot find application binary %s.\n", qPrintable(options->applicationBinary));
807 return false;
808 }
809 }
810
811 {
812 const QJsonValue deploymentDependencies = jsonObject.value(QStringLiteral("deployment-dependencies"));
813 if (!deploymentDependencies.isUndefined()) {
814 QString deploymentDependenciesString = deploymentDependencies.toString();
815 const auto dependencies = deploymentDependenciesString.splitRef(QLatin1Char(','));
816 for (const QStringRef &dependency : dependencies) {
817 QString path = options->qtInstallDirectory + QLatin1Char('/') + dependency;
818 if (QFileInfo(path).isDir()) {
819 QDirIterator iterator(path, QDirIterator::Subdirectories);
820 while (iterator.hasNext()) {
821 iterator.next();
822 if (iterator.fileInfo().isFile()) {
823 QString subPath = iterator.filePath();
824 options->qtDependencies.append(QtDependency(subPath.mid(options->qtInstallDirectory.length() + 1),
825 subPath));
826 }
827 }
828 } else {
829 options->qtDependencies.append(QtDependency(dependency.toString(), path));
830 }
831 }
832 }
833 }
834
835
836 {
837 const QJsonValue targetArchitecture = jsonObject.value(QStringLiteral("target-architecture"));
838 if (targetArchitecture.isUndefined()) {
839 fprintf(stderr, "No target architecture defined in json file.\n");
840 return false;
841 }
842 options->architecture = targetArchitecture.toString();
843 }
844
845 {
846 const QJsonValue ndk = jsonObject.value(QStringLiteral("ndk"));
847 if (ndk.isUndefined()) {
848 fprintf(stderr, "No NDK path defined in json file.\n");
849 return false;
850 }
851 options->ndkPath = ndk.toString();
852 }
853
854 {
855 const QJsonValue value = jsonObject.value(QStringLiteral("useLLVM"));
856 options->useLLVM = value.toBool(false);
857 }
858
859 {
860 const QJsonValue toolchainPrefix = jsonObject.value(QStringLiteral("toolchain-prefix"));
861 if (toolchainPrefix.isUndefined()) {
862 fprintf(stderr, "No toolchain prefix defined in json file.\n");
863 return false;
864 }
865 options->toolchainPrefix = toolchainPrefix.toString();
866 }
867
868 {
869 const QJsonValue toolPrefix = jsonObject.value(QStringLiteral("tool-prefix"));
870 if (toolPrefix.isUndefined()) {
871 fprintf(stderr, "Warning: No tool prefix defined in json file.\n");
872 options->toolPrefix = options->toolchainPrefix;
873 } else {
874 options->toolPrefix = toolPrefix.toString();
875 }
876 }
877
878 if (!options->useLLVM) {
879 const QJsonValue toolchainVersion = jsonObject.value(QStringLiteral("toolchain-version"));
880 if (toolchainVersion.isUndefined()) {
881 fprintf(stderr, "No toolchain version defined in json file.\n");
882 return false;
883 }
884 options->toolchainVersion = toolchainVersion.toString();
885 }
886
887 {
888 const QJsonValue ndkHost = jsonObject.value(QStringLiteral("ndk-host"));
889 if (ndkHost.isUndefined()) {
890 fprintf(stderr, "No NDK host defined in json file.\n");
891 return false;
892 }
893 options->ndkHost = ndkHost.toString();
894 }
895
896 options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + QLatin1String("/AndroidManifest.xml"));
897 if (options->packageName.isEmpty())
898 options->packageName = cleanPackageName(QString::fromLatin1("org.qtproject.example.%1").arg(QFileInfo(options->applicationBinary).baseName().mid(sizeof("lib") - 1)));
899
900 {
901 const QJsonValue extraLibs = jsonObject.value(QStringLiteral("android-extra-libs"));
902 if (!extraLibs.isUndefined())
903 options->extraLibs = extraLibs.toString().split(QLatin1Char(','), QString::SkipEmptyParts);
904 }
905
906 {
907 const QJsonValue extraPlugins = jsonObject.value(QStringLiteral("android-extra-plugins"));
908 if (!extraPlugins.isUndefined())
909 options->extraPlugins = extraPlugins.toString().split(QLatin1Char(','));
910 }
911
912 {
913 const QJsonValue stdcppPath = jsonObject.value(QStringLiteral("stdcpp-path"));
914 if (stdcppPath.isUndefined()) {
915 fprintf(stderr, "No stdcpp-path defined in json file.\n");
916 return false;
917 }
918 options->stdCppPath = stdcppPath.toString();
919 auto name = QFileInfo(options->stdCppPath).baseName();
920 if (!name.startsWith(QLatin1String("lib"))) {
921 fprintf(stderr, "Invalid STD C++ library name.\n");
922 return false;
923 }
924 options->stdCppName = name.mid(3);
925 }
926
927 {
928 const QJsonValue qmlRootPath = jsonObject.value(QStringLiteral("qml-root-path"));
929 if (!qmlRootPath.isUndefined())
930 options->rootPath = qmlRootPath.toString();
931 }
932
933 {
934 const QJsonValue qmlImportPaths = jsonObject.value(QStringLiteral("qml-import-paths"));
935 if (!qmlImportPaths.isUndefined())
936 options->qmlImportPaths = qmlImportPaths.toString().split(QLatin1Char(','));
937 }
938 return true;
939}
940
941bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, bool verbose, bool forceOverwrite = false)
942{
943 const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
944 for (const QFileInfo &entry : entries) {
945 if (entry.isDir()) {
946 QDir dir(entry.absoluteFilePath());
947 if (!destinationDirectory.mkpath(dir.dirName())) {
948 fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
949 return false;
950 }
951
952 if (!copyFiles(dir, QDir(destinationDirectory.path() + QLatin1String("/") + dir.dirName()), verbose, forceOverwrite))
953 return false;
954 } else {
955 QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
956 if (!copyFileIfNewer(entry.absoluteFilePath(), destination, verbose, forceOverwrite))
957 return false;
958 }
959 }
960
961 return true;
962}
963
964void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir)
965{
966 const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
967 for (const QFileInfo &dir : dirs) {
968 if (dir.fileName() != QLatin1String("libs"))
969 deleteMissingFiles(options, dir.absoluteFilePath(), dstDir + dir.fileName());
970 }
971}
972
973void cleanAndroidFiles(const Options &options)
974{
975 if (!options.androidSourceDirectory.isEmpty())
976 cleanTopFolders(options, options.androidSourceDirectory, options.outputDirectory);
977
978 cleanTopFolders(options, options.qtInstallDirectory + QLatin1String("/src/android/templates"), options.outputDirectory);
979}
980
981bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
982{
983 QDir sourceDirectory(options.qtInstallDirectory + androidTemplate);
984 if (!sourceDirectory.exists()) {
985 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
986 return false;
987 }
988
989 QString outDir = options.outputDirectory + outDirPrefix;
990
991 if (!QDir::current().mkpath(outDir)) {
992 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
993 return false;
994 }
995
996 return copyFiles(sourceDirectory, QDir(outDir), options.verbose);
997}
998
999bool copyGradleTemplate(const Options &options)
1000{
1001 QDir sourceDirectory(options.qtInstallDirectory + QLatin1String("/src/3rdparty/gradle"));
1002 if (!sourceDirectory.exists()) {
1003 fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
1004 return false;
1005 }
1006
1007 QString outDir(options.outputDirectory);
1008 if (!QDir::current().mkpath(outDir)) {
1009 fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
1010 return false;
1011 }
1012
1013 return copyFiles(sourceDirectory, QDir(outDir), options.verbose);
1014}
1015
1016bool copyAndroidTemplate(const Options &options)
1017{
1018 if (options.verbose)
1019 fprintf(stdout, "Copying Android package template.\n");
1020
1021 if (!copyGradleTemplate(options))
1022 return false;
1023
1024 if (!copyAndroidTemplate(options, QLatin1String("/src/android/templates")))
1025 return false;
1026
1027 return true;
1028}
1029
1030bool copyAndroidSources(const Options &options)
1031{
1032 if (options.androidSourceDirectory.isEmpty())
1033 return true;
1034
1035 if (options.verbose)
1036 fprintf(stdout, "Copying Android sources from project.\n");
1037
1038 QDir sourceDirectory(options.androidSourceDirectory);
1039 if (!sourceDirectory.exists()) {
1040 fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
1041 return false;
1042 }
1043
1044 return copyFiles(sourceDirectory, QDir(options.outputDirectory), options.verbose, true);
1045}
1046
1047bool copyAndroidExtraLibs(const Options &options)
1048{
1049 if (options.extraLibs.isEmpty())
1050 return true;
1051
1052 if (options.verbose)
1053 fprintf(stdout, "Copying %d external libraries to package.\n", options.extraLibs.size());
1054
1055 for (const QString &extraLib : options.extraLibs) {
1056 QFileInfo extraLibInfo(extraLib);
1057 if (!extraLibInfo.exists()) {
1058 fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
1059 return false;
1060 }
1061
1062 if (!extraLibInfo.fileName().startsWith(QLatin1String("lib")) || extraLibInfo.suffix() != QLatin1String("so")) {
1063 fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
1064 qPrintable(extraLib));
1065 return false;
1066 }
1067
1068 QString destinationFile(options.outputDirectory
1069 + QLatin1String("/libs/")
1070 + options.architecture
1071 + QLatin1Char('/')
1072 + extraLibInfo.fileName());
1073
1074 if (!copyFileIfNewer(extraLib, destinationFile, options.verbose))
1075 return false;
1076 }
1077
1078 return true;
1079}
1080
1081QStringList allFilesInside(const QDir& current, const QDir& rootDir)
1082{
1083 QStringList result;
1084 const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot);
1085 const auto files = current.entryList(QDir::Files);
1086 result.reserve(dirs.size() + files.size());
1087 for (const QString &dir : dirs) {
1088 result += allFilesInside(QDir(current.filePath(dir)), rootDir);
1089 }
1090 for (const QString &file : files) {
1091 result += rootDir.relativeFilePath(current.filePath(file));
1092 }
1093 return result;
1094}
1095
1096bool copyAndroidExtraResources(const Options &options)
1097{
1098 if (options.extraPlugins.isEmpty())
1099 return true;
1100
1101 if (options.verbose)
1102 fprintf(stdout, "Copying %d external resources to package.\n", options.extraPlugins.size());
1103
1104 for (const QString &extraResource : options.extraPlugins) {
1105 QFileInfo extraResourceInfo(extraResource);
1106 if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) {
1107 fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource));
1108 return false;
1109 }
1110
1111 QDir resourceDir(extraResource);
1112 QString assetsDir = options.outputDirectory + QStringLiteral("/assets/") + resourceDir.dirName() + QLatin1Char('/');
1113 QString libsDir = options.outputDirectory + QStringLiteral("/libs/") + options.architecture + QLatin1Char('/');
1114
1115 const QStringList files = allFilesInside(resourceDir, resourceDir);
1116 for (const QString &resourceFile : files) {
1117 QString originFile(resourceDir.filePath(resourceFile));
1118 QString destinationFile;
1119 if (!resourceFile.endsWith(QLatin1String(".so"))) {
1120 destinationFile = assetsDir + resourceFile;
1121 } else {
1122 destinationFile = libsDir + QStringLiteral("/lib") + QString(resourceDir.dirName() + QLatin1Char('/') + resourceFile).replace(QLatin1Char('/'), QLatin1Char('_'));
1123 }
1124
1125 if (!copyFileIfNewer(originFile, destinationFile, options.verbose))
1126 return false;
1127 }
1128 }
1129
1130 return true;
1131}
1132
1133bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
1134{
1135 QFile inputFile(fileName);
1136 if (!inputFile.open(QIODevice::ReadOnly)) {
1137 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
1138 return false;
1139 }
1140
1141 // All the files we are doing substitutes in are quite small. If this
1142 // ever changes, this code should be updated to be more conservative.
1143 QByteArray contents = inputFile.readAll();
1144
1145 bool hasReplacements = false;
1146 QHash<QString, QString>::const_iterator it;
1147 for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
1148 if (it.key() == it.value())
1149 continue; // Nothing to actually replace
1150
1151 forever {
1152 int index = contents.indexOf(it.key().toUtf8());
1153 if (index >= 0) {
1154 contents.replace(index, it.key().length(), it.value().toUtf8());
1155 hasReplacements = true;
1156 } else {
1157 break;
1158 }
1159 }
1160 }
1161
1162 if (hasReplacements) {
1163 inputFile.close();
1164
1165 if (!inputFile.open(QIODevice::WriteOnly)) {
1166 fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
1167 return false;
1168 }
1169
1170 inputFile.write(contents);
1171 }
1172
1173 return true;
1174
1175}
1176
1177bool updateLibsXml(const Options &options)
1178{
1179 if (options.verbose)
1180 fprintf(stdout, " -- res/values/libs.xml\n");
1181
1182 QString fileName = options.outputDirectory + QLatin1String("/res/values/libs.xml");
1183 if (!QFile::exists(fileName)) {
1184 fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
1185 return false;
1186 }
1187
1188 QString libsPath = QLatin1String("libs/") + options.architecture + QLatin1Char('/');
1189
1190 QString qtLibs = QLatin1String("<item>") + options.stdCppName + QLatin1String("</item>\n");
1191 QString bundledInLibs;
1192 QString bundledInAssets;
1193 for (const Options::BundledFile &bundledFile : options.bundledFiles) {
1194 if (bundledFile.second.startsWith(QLatin1String("lib/"))) {
1195 QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
1196 s.chop(sizeof(".so") - 1);
1197 qtLibs += QString::fromLatin1("<item>%1</item>\n").arg(s);
1198 } else if (bundledFile.first.startsWith(libsPath)) {
1199 QString s = bundledFile.first.mid(libsPath.length());
1200 bundledInLibs += QString::fromLatin1("<item>%1:%2</item>\n")
1201 .arg(s).arg(bundledFile.second);
1202 } else if (bundledFile.first.startsWith(QLatin1String("assets/"))) {
1203 QString s = bundledFile.first.mid(sizeof("assets/") - 1);
1204 bundledInAssets += QString::fromLatin1("<item>%1:%2</item>\n")
1205 .arg(s).arg(bundledFile.second);
1206 }
1207 }
1208
1209 if (!options.extraPlugins.isEmpty()) {
1210 for (const QString &extraRes : options.extraPlugins) {
1211 QDir resourceDir(extraRes);
1212 const QStringList files = allFilesInside(resourceDir, resourceDir);
1213 for (const QString &file : files) {
1214 QString destinationPath = resourceDir.dirName() + QLatin1Char('/') + file;
1215 if (!file.endsWith(QLatin1String(".so"))) {
1216 bundledInAssets += QStringLiteral("<item>%1:%1</item>\n")
1217 .arg(destinationPath);
1218 } else {
1219 bundledInLibs += QStringLiteral("<item>lib%1:%2</item>\n")
1220 .arg(QString(destinationPath).replace(QLatin1Char('/'), QLatin1Char('_')))
1221 .arg(destinationPath);
1222 }
1223 }
1224 }
1225 }
1226
1227 QHash<QString, QString> replacements;
1228 replacements[QLatin1String("<!-- %%INSERT_QT_LIBS%% -->")] = qtLibs;
1229
1230 if (options.deploymentMechanism == Options::Bundled) {
1231 replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_LIB%% -->")] = bundledInLibs;
1232 replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->")] = bundledInAssets;
1233 }
1234
1235 QString extraLibs;
1236 if (!options.extraLibs.isEmpty()) {
1237 for (const QString extraLib : options.extraLibs) {
1238 QFileInfo extraLibInfo(extraLib);
1239 QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
1240 name.chop(sizeof(".so") - 1);
1241
1242 extraLibs += QLatin1String("<item>") + name + QLatin1String("</item>\n");
1243 }
1244 }
1245 replacements[QLatin1String("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs;
1246
1247 if (!updateFile(fileName, replacements))
1248 return false;
1249
1250 return true;
1251}
1252
1253bool updateStringsXml(const Options &options)
1254{
1255 if (options.verbose)
1256 fprintf(stdout, " -- res/values/strings.xml\n");
1257
1258 QHash<QString, QString> replacements;
1259 replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1);
1260
1261 QString fileName = options.outputDirectory + QLatin1String("/res/values/strings.xml");
1262 if (!QFile::exists(fileName)) {
1263 if (options.verbose)
1264 fprintf(stdout, " -- Create strings.xml since it's missing.\n");
1265 QFile file(fileName);
1266 if (!file.open(QIODevice::WriteOnly)) {
1267 fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
1268 return false;
1269 }
1270 file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\" translatable=\"false\">")
1271 .append(QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1).toLatin1())
1272 .append("</string></resources>\n"));
1273 return true;
1274 }
1275
1276 if (!updateFile(fileName, replacements))
1277 return false;
1278
1279 return true;
1280}
1281
1282bool updateAndroidManifest(Options &options)
1283{
1284 if (options.verbose)
1285 fprintf(stdout, " -- AndroidManifest.xml \n");
1286
1287 QStringList localLibs = options.localLibs;
1288
1289 // If .pro file overrides dependency detection, we need to see which platform plugin they picked
1290 if (localLibs.isEmpty()) {
1291 QString plugin;
1292 for (const QtDependency &qtDependency : qAsConst(options.qtDependencies)) {
1293 if (qtDependency.relativePath.endsWith(QLatin1String("libqtforandroid.so"))
1294 || qtDependency.relativePath.endsWith(QLatin1String("libqtforandroidGL.so"))) {
1295 if (!plugin.isEmpty() && plugin != qtDependency.relativePath) {
1296 fprintf(stderr, "Both platform plugins libqtforandroid.so and libqtforandroidGL.so included in package. Please include only one.\n");
1297 return false;
1298 }
1299
1300 plugin = qtDependency.relativePath;
1301 }
1302 }
1303
1304 if (plugin.isEmpty()) {
1305 fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n");
1306 return false;
1307 }
1308
1309 localLibs.append(plugin);
1310 if (options.verbose)
1311 fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin));
1312 }
1313
1314 bool usesGL = false;
1315 for (const QtDependency &qtDependency : qAsConst(options.qtDependencies)) {
1316 if (qtDependency.relativePath.endsWith(QLatin1String("libQt5OpenGL.so"))
1317 || qtDependency.relativePath.endsWith(QLatin1String("libQt5Quick.so"))) {
1318 usesGL = true;
1319 break;
1320 }
1321 }
1322
1323 options.localJars.removeDuplicates();
1324 options.initClasses.removeDuplicates();
1325
1326 QHash<QString, QString> replacements;
1327 replacements[QLatin1String("-- %%INSERT_APP_NAME%% --")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1);
1328 replacements[QLatin1String("-- %%INSERT_APP_LIB_NAME%% --")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1);
1329 replacements[QLatin1String("-- %%INSERT_LOCAL_LIBS%% --")] = localLibs.join(QLatin1Char(':'));
1330 replacements[QLatin1String("-- %%INSERT_LOCAL_JARS%% --")] = options.localJars.join(QLatin1Char(':'));
1331 replacements[QLatin1String("-- %%INSERT_INIT_CLASSES%% --")] = options.initClasses.join(QLatin1Char(':'));
1332 replacements[QLatin1String("-- %%INSERT_VERSION_NAME%% --")] = options.versionName;
1333 replacements[QLatin1String("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode;
1334 replacements[QLatin1String("package=\"org.qtproject.example\"")] = QString::fromLatin1("package=\"%1\"").arg(options.packageName);
1335 replacements[QLatin1String("-- %%BUNDLE_LOCAL_QT_LIBS%% --")]
1336 = (options.deploymentMechanism == Options::Bundled) ? QString::fromLatin1("1") : QString::fromLatin1("0");
1337 replacements[QLatin1String("-- %%USE_LOCAL_QT_LIBS%% --")]
1338 = (options.deploymentMechanism != Options::Ministro) ? QString::fromLatin1("1") : QString::fromLatin1("0");
1339
1340 QString permissions;
1341 for (const QString &permission : qAsConst(options.permissions))
1342 permissions += QString::fromLatin1(" <uses-permission android:name=\"%1\" />\n").arg(permission);
1343 replacements[QLatin1String("<!-- %%INSERT_PERMISSIONS -->")] = permissions;
1344
1345 QString features;
1346 for (const QString &feature : qAsConst(options.features))
1347 features += QStringLiteral(" <uses-feature android:name=\"%1\" android:required=\"false\" />\n").arg(feature);
1348 if (usesGL)
1349 features += QStringLiteral(" <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />");
1350
1351 replacements[QLatin1String("<!-- %%INSERT_FEATURES -->")] = features;
1352
1353 QString androidManifestPath = options.outputDirectory + QLatin1String("/AndroidManifest.xml");
1354 if (!updateFile(androidManifestPath, replacements))
1355 return false;
1356
1357 // read the package, min & target sdk API levels from manifest file.
1358 bool checkOldAndroidLabelString = false;
1359 QFile androidManifestXml(androidManifestPath);
1360 if (androidManifestXml.exists()) {
1361 if (!androidManifestXml.open(QIODevice::ReadOnly)) {
1362 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
1363 return false;
1364 }
1365
1366 QXmlStreamReader reader(&androidManifestXml);
1367 while (!reader.atEnd()) {
1368 reader.readNext();
1369
1370 if (reader.isStartElement()) {
1371 if (reader.name() == QLatin1String("manifest")) {
1372 if (!reader.attributes().hasAttribute(QLatin1String("package"))) {
1373 fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath));
1374 return false;
1375 }
1376 options.packageName = reader.attributes().value(QLatin1String("package")).toString();
1377 } else if (reader.name() == QLatin1String("uses-sdk")) {
1378 if (reader.attributes().hasAttribute(QLatin1String("android:minSdkVersion")))
1379 if (reader.attributes().value(QLatin1String("android:minSdkVersion")).toInt() < 21) {
1380 fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 21\n");
1381 return false;
1382 }
1383 } else if ((reader.name() == QLatin1String("application") ||
1384 reader.name() == QLatin1String("activity")) &&
1385 reader.attributes().hasAttribute(QLatin1String("android:label")) &&
1386 reader.attributes().value(QLatin1String("android:label")) == QLatin1String("@string/app_name")) {
1387 checkOldAndroidLabelString = true;
1388 }
1389 }
1390 }
1391
1392 if (reader.hasError()) {
1393 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
1394 return false;
1395 }
1396 } else {
1397 fprintf(stderr, "No android manifest file");
1398 return false;
1399 }
1400
1401 if (checkOldAndroidLabelString)
1402 updateStringsXml(options);
1403
1404 return true;
1405}
1406
1407bool updateAndroidFiles(Options &options)
1408{
1409 if (options.verbose)
1410 fprintf(stdout, "Updating Android package files with project settings.\n");
1411
1412 if (!updateLibsXml(options))
1413 return false;
1414
1415 if (!updateAndroidManifest(options))
1416 return false;
1417
1418 return true;
1419}
1420
1421static QString absoluteFilePath(const Options *options, const QString &relativeFileName)
1422{
1423 for (const auto &prefix : options->extraPrefixDirs) {
1424 const QString path = prefix + QLatin1Char('/') + relativeFileName;
1425 if (QFile::exists(path))
1426 return path;
1427 }
1428 return options->qtInstallDirectory + QLatin1Char('/') + relativeFileName;
1429}
1430
1431QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath)
1432{
1433 if (!info.exists())
1434 return QList<QtDependency>();
1435
1436 if (info.isDir()) {
1437 QList<QtDependency> ret;
1438
1439 QDir dir(info.filePath());
1440 const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
1441
1442 for (const QString &entry : entries) {
1443 QString s = info.absoluteFilePath() + QLatin1Char('/') + entry;
1444 ret += findFilesRecursively(options, s, rootPath);
1445 }
1446
1447 return ret;
1448 } else {
1449 return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.length()), info.absoluteFilePath());
1450 }
1451}
1452
1453QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName)
1454{
1455 for (const auto &prefix : options.extraPrefixDirs) {
1456 QFileInfo info(prefix + QLatin1Char('/') + fileName);
1457 if (info.exists())
1458 return findFilesRecursively(options, info, prefix + QLatin1Char('/'));
1459 }
1460 QFileInfo info(options.qtInstallDirectory + QLatin1Char('/') + fileName);
1461 return findFilesRecursively(options, info, options.qtInstallDirectory + QLatin1Char('/'));
1462}
1463
1464bool readAndroidDependencyXml(Options *options,
1465 const QString &moduleName,
1466 QSet<QString> *usedDependencies,
1467 QSet<QString> *remainingDependencies)
1468{
1469 QString androidDependencyName = absoluteFilePath(options, QString::fromLatin1("/lib/%1-android-dependencies.xml").arg(moduleName));
1470
1471 QFile androidDependencyFile(androidDependencyName);
1472 if (androidDependencyFile.exists()) {
1473 if (options->verbose)
1474 fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
1475
1476 if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
1477 fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
1478 return false;
1479 }
1480
1481 QXmlStreamReader reader(&androidDependencyFile);
1482 while (!reader.atEnd()) {
1483 reader.readNext();
1484
1485 if (reader.isStartElement()) {
1486 if (reader.name() == QLatin1String("bundled")) {
1487 if (!reader.attributes().hasAttribute(QLatin1String("file"))) {
1488 fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
1489 return false;
1490 }
1491
1492 QString file = reader.attributes().value(QLatin1String("file")).toString();
1493
1494 // Special case, since this is handled by qmlimportscanner instead
1495 if (!options->rootPath.isEmpty() && (file == QLatin1String("qml") || file == QLatin1String("qml/")))
1496 continue;
1497
1498 const QList<QtDependency> fileNames = findFilesRecursively(*options, file);
1499 for (const QtDependency &fileName : fileNames) {
1500 if (usedDependencies->contains(fileName.absolutePath))
1501 continue;
1502
1503 usedDependencies->insert(fileName.absolutePath);
1504
1505 if (options->verbose)
1506 fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath));
1507
1508 options->qtDependencies.append(fileName);
1509 }
1510 } else if (reader.name() == QLatin1String("jar")) {
1511 int bundling = reader.attributes().value(QLatin1String("bundling")).toInt();
1512 QString fileName = reader.attributes().value(QLatin1String("file")).toString();
1513 if (bundling == (options->deploymentMechanism == Options::Bundled)) {
1514 QtDependency dependency(fileName, absoluteFilePath(options, fileName));
1515 if (!usedDependencies->contains(dependency.absolutePath)) {
1516 options->qtDependencies.append(dependency);
1517 usedDependencies->insert(dependency.absolutePath);
1518 }
1519 }
1520
1521 if (!fileName.isEmpty())
1522 options->localJars.append(fileName);
1523
1524 if (reader.attributes().hasAttribute(QLatin1String("initClass"))) {
1525 options->initClasses.append(reader.attributes().value(QLatin1String("initClass")).toString());
1526 }
1527 } else if (reader.name() == QLatin1String("lib")) {
1528 QString fileName = reader.attributes().value(QLatin1String("file")).toString();
1529 if (reader.attributes().hasAttribute(QLatin1String("replaces"))) {
1530 QString replaces = reader.attributes().value(QLatin1String("replaces")).toString();
1531 for (int i=0; i<options->localLibs.size(); ++i) {
1532 if (options->localLibs.at(i) == replaces) {
1533 options->localLibs[i] = fileName;
1534 break;
1535 }
1536 }
1537 } else if (!fileName.isEmpty()) {
1538 options->localLibs.append(fileName);
1539 }
1540 if (fileName.endsWith(QLatin1String(".so"))) {
1541 remainingDependencies->insert(fileName);
1542 }
1543 } else if (reader.name() == QLatin1String("permission")) {
1544 QString name = reader.attributes().value(QLatin1String("name")).toString();
1545 options->permissions.append(name);
1546 } else if (reader.name() == QLatin1String("feature")) {
1547 QString name = reader.attributes().value(QLatin1String("name")).toString();
1548 options->features.append(name);
1549 }
1550 }
1551 }
1552
1553 if (reader.hasError()) {
1554 fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
1555 return false;
1556 }
1557 } else if (options->verbose) {
1558 fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
1559 }
1560
1561 return true;
1562}
1563
1564QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
1565{
1566 QString readElf = options.ndkPath
1567 + QLatin1String("/toolchains/")
1568 + options.toolchainPrefix;
1569
1570 if (!options.useLLVM)
1571 readElf += QLatin1Char('-') + options.toolchainVersion;
1572
1573 readElf += QLatin1String("/prebuilt/")
1574 + options.ndkHost
1575 + QLatin1String("/bin/")
1576 + options.toolPrefix +
1577 (options.useLLVM ? QLatin1String("-readobj") : QLatin1String("-readelf"));
1578#if defined(Q_OS_WIN32)
1579 readElf += QLatin1String(".exe");
1580#endif
1581
1582 if (!QFile::exists(readElf)) {
1583 fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
1584 return QStringList();
1585 }
1586
1587 if (options.useLLVM)
1588 readElf = QString::fromLatin1("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(fileName));
1589 else
1590 readElf = QString::fromLatin1("%1 -d -W %2").arg(shellQuote(readElf), shellQuote(fileName));
1591
1592 FILE *readElfCommand = openProcess(readElf);
1593 if (!readElfCommand) {
1594 fprintf(stderr, "Cannot execute command %s", qPrintable(readElf));
1595 return QStringList();
1596 }
1597
1598 QStringList ret;
1599
1600 bool readLibs = false;
1601 char buffer[512];
1602 while (fgets(buffer, sizeof(buffer), readElfCommand) != 0) {
1603 QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
1604 QString library;
1605 if (options.useLLVM) {
1606 line = line.trimmed();
1607 if (!readLibs) {
1608 readLibs = line.startsWith("NeededLibraries");
1609 continue;
1610 }
1611 if (!line.startsWith("lib"))
1612 continue;
1613 library = QString::fromLatin1(line);
1614 } else if (line.contains("(NEEDED)") && line.contains("Shared library:")) {
1615 const int pos = line.lastIndexOf('[') + 1;
1616 library = QString::fromLatin1(line.mid(pos, line.length() - pos - 2));
1617 }
1618 QString libraryName = QLatin1String("lib/") + library;
1619 if (QFile::exists(absoluteFilePath(&options, libraryName)))
1620 ret += libraryName;
1621 }
1622
1623 pclose(readElfCommand);
1624
1625 return ret;
1626}
1627
1628bool readDependenciesFromElf(Options *options,
1629 const QString &fileName,
1630 QSet<QString> *usedDependencies,
1631 QSet<QString> *remainingDependencies)
1632{
1633 // Get dependencies on libraries in $QTDIR/lib
1634 const QStringList dependencies = getQtLibsFromElf(*options, fileName);
1635
1636 if (options->verbose) {
1637 fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
1638 for (const QString &dep : dependencies)
1639 fprintf(stdout, " %s\n", qPrintable(dep));
1640 }
1641 // Recursively add dependencies from ELF and supplementary XML information
1642 QList<QString> dependenciesToCheck;
1643 for (const QString &dependency : dependencies) {
1644 if (usedDependencies->contains(dependency))
1645 continue;
1646
1647 QString absoluteDependencyPath = absoluteFilePath(options, dependency);
1648 usedDependencies->insert(dependency);
1649 if (!readDependenciesFromElf(options,
1650 absoluteDependencyPath,
1651 usedDependencies,
1652 remainingDependencies)) {
1653 return false;
1654 }
1655
1656 options->qtDependencies.append(QtDependency(dependency, absoluteDependencyPath));
1657 if (options->verbose)
1658 fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
1659 dependenciesToCheck.append(dependency);
1660 }
1661
1662 for (const QString &dependency : qAsConst(dependenciesToCheck)) {
1663 QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
1664 qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
1665 if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
1666 return false;
1667 }
1668 }
1669
1670 return true;
1671}
1672
1673bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
1674
1675bool scanImports(Options *options, QSet<QString> *usedDependencies)
1676{
1677 if (options->verbose)
1678 fprintf(stdout, "Scanning for QML imports.\n");
1679
1680 QString qmlImportScanner = options->qtInstallDirectory + QLatin1String("/bin/qmlimportscanner");
1681#if defined(Q_OS_WIN32)
1682 qmlImportScanner += QLatin1String(".exe");
1683#endif
1684
1685 if (!QFile::exists(qmlImportScanner)) {
1686 fprintf(stderr, "qmlimportscanner not found: %s\n", qPrintable(qmlImportScanner));
1687 return true;
1688 }
1689
1690 QString rootPath = options->rootPath;
1691 if (rootPath.isEmpty())
1692 rootPath = QFileInfo(options->inputFileName).absolutePath();
1693 else
1694 rootPath = QFileInfo(rootPath).absoluteFilePath();
1695
1696 if (!rootPath.endsWith(QLatin1Char('/')))
1697 rootPath += QLatin1Char('/');
1698
1699 QStringList importPaths;
1700 importPaths += shellQuote(options->qtInstallDirectory + QLatin1String("/qml"));
1701 importPaths += shellQuote(rootPath);
1702 for (const QString &qmlImportPath : qAsConst(options->qmlImportPaths))
1703 importPaths += shellQuote(qmlImportPath);
1704
1705 qmlImportScanner += QString::fromLatin1(" -rootPath %1 -importPath %2")
1706 .arg(shellQuote(rootPath))
1707 .arg(importPaths.join(QLatin1Char(' ')));
1708
1709 if (options->verbose) {
1710 fprintf(stdout, "Running qmlimportscanner with the following command: %s\n",
1711 qmlImportScanner.toLocal8Bit().constData());
1712 }
1713
1714 FILE *qmlImportScannerCommand = popen(qmlImportScanner.toLocal8Bit().constData(), QT_POPEN_READ);
1715 if (qmlImportScannerCommand == 0) {
1716 fprintf(stderr, "Couldn't run qmlimportscanner.\n");
1717 return false;
1718 }
1719
1720 QByteArray output;
1721 char buffer[512];
1722 while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand) != 0)
1723 output += QByteArray(buffer, qstrlen(buffer));
1724
1725 QJsonDocument jsonDocument = QJsonDocument::fromJson(output);
1726 if (jsonDocument.isNull()) {
1727 fprintf(stderr, "Invalid json output from qmlimportscanner.\n");
1728 return false;
1729 }
1730
1731 QJsonArray jsonArray = jsonDocument.array();
1732 for (int i=0; i<jsonArray.count(); ++i) {
1733 QJsonValue value = jsonArray.at(i);
1734 if (!value.isObject()) {
1735 fprintf(stderr, "Invalid format of qmlimportscanner output.\n");
1736 return false;
1737 }
1738
1739 QJsonObject object = value.toObject();
1740 QString path = object.value(QLatin1String("path")).toString();
1741 if (path.isEmpty()) {
1742 fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n",
1743 qPrintable(object.value(QLatin1String("name")).toString()));
1744 } else {
1745 if (options->verbose)
1746 fprintf(stdout, " -- Adding '%s' as QML dependency\n", path.toLocal8Bit().constData());
1747
1748 QFileInfo info(path);
1749
1750 // The qmlimportscanner sometimes outputs paths that do not exist.
1751 if (!info.exists()) {
1752 if (options->verbose)
1753 fprintf(stdout, " -- Skipping because file does not exist.\n");
1754 continue;
1755 }
1756
1757 QString absolutePath = info.absolutePath();
1758 if (!absolutePath.endsWith(QLatin1Char('/')))
1759 absolutePath += QLatin1Char('/');
1760
1761 if (absolutePath.startsWith(rootPath)) {
1762 if (options->verbose)
1763 fprintf(stdout, " -- Skipping because file is in QML root path.\n");
1764 continue;
1765 }
1766
1767 QString importPathOfThisImport;
1768 for (const QString &importPath : qAsConst(importPaths)) {
1769#if defined(Q_OS_WIN32)
1770 Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
1771#else
1772 Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
1773#endif
1774 QString cleanImportPath = QDir::cleanPath(importPath);
1775 if (info.absoluteFilePath().startsWith(cleanImportPath, caseSensitivity)) {
1776 importPathOfThisImport = importPath;
1777 break;
1778 }
1779 }
1780
1781 if (importPathOfThisImport.isEmpty()) {
1782 fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath()));
1783 return false;
1784 }
1785
1786 QDir dir(importPathOfThisImport);
1787 importPathOfThisImport = dir.absolutePath() + QLatin1Char('/');
1788
1789 const QList<QtDependency> fileNames = findFilesRecursively(*options, info, importPathOfThisImport);
1790 for (QtDependency fileName : fileNames) {
1791 if (usedDependencies->contains(fileName.absolutePath))
1792 continue;
1793
1794 usedDependencies->insert(fileName.absolutePath);
1795
1796 if (options->verbose)
1797 fprintf(stdout, " -- Appending dependency found by qmlimportscanner: %s\n", qPrintable(fileName.absolutePath));
1798
1799 // Put all imports in default import path in assets
1800 fileName.relativePath.prepend(QLatin1String("qml/"));
1801 options->qtDependencies.append(fileName);
1802
1803 if (fileName.absolutePath.endsWith(QLatin1String(".so"))) {
1804 QSet<QString> remainingDependencies;
1805 if (!readDependenciesFromElf(options, fileName.absolutePath, usedDependencies, &remainingDependencies))
1806 return false;
1807
1808 }
1809 }
1810 }
1811 }
1812
1813 return true;
1814}
1815
1816bool readDependencies(Options *options)
1817{
1818 if (options->verbose)
1819 fprintf(stdout, "Detecting dependencies of application.\n");
1820
1821 // Override set in .pro file
1822 if (!options->qtDependencies.isEmpty()) {
1823 if (options->verbose)
1824 fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
1825 return true;
1826 }
1827
1828 QSet<QString> usedDependencies;
1829 QSet<QString> remainingDependencies;
1830
1831 // Add dependencies of application binary first
1832 if (!readDependenciesFromElf(options, options->applicationBinary, &usedDependencies, &remainingDependencies))
1833 return false;
1834
1835 // Jam in the dependencies of the platform plugin, since the application will crash without it
1836 if (!readDependenciesFromElf(options, options->qtInstallDirectory + QLatin1String("/plugins/platforms/android/libqtforandroid.so"), &usedDependencies, &remainingDependencies))
1837 return false;
1838
1839 while (!remainingDependencies.isEmpty()) {
1840 QSet<QString>::iterator start = remainingDependencies.begin();
1841 QString fileName = absoluteFilePath(options, *start);
1842 remainingDependencies.erase(start);
1843
1844 QStringList unmetDependencies;
1845 if (goodToCopy(options, fileName, &unmetDependencies)) {
1846 bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
1847 if (!ok)
1848 return false;
1849 } else {
1850 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
1851 qPrintable(fileName),
1852 qPrintable(unmetDependencies.join(QLatin1Char(','))));
1853 }
1854 }
1855
1856 QStringList::iterator it = options->localLibs.begin();
1857 while (it != options->localLibs.end()) {
1858 QStringList unmetDependencies;
1859 if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) {
1860 fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
1861 qPrintable(*it),
1862 qPrintable(unmetDependencies.join(QLatin1Char(','))));
1863 it = options->localLibs.erase(it);
1864 } else {
1865 ++it;
1866 }
1867 }
1868
1869 if (!options->rootPath.isEmpty() && !scanImports(options, &usedDependencies))
1870 return false;
1871
1872 return true;
1873}
1874
1875bool stripFile(const Options &options, const QString &fileName)
1876{
1877 QString strip = options.ndkPath
1878 + QLatin1String("/toolchains/")
1879 + options.toolchainPrefix;
1880
1881 if (!options.useLLVM)
1882 strip += QLatin1Char('-') + options.toolchainVersion;
1883
1884 strip += QLatin1String("/prebuilt/")
1885 + options.ndkHost
1886 + QLatin1String("/bin/")
1887 + options.toolPrefix
1888 + QLatin1String("-strip");
1889#if defined(Q_OS_WIN32)
1890 strip += QLatin1String(".exe");
1891#endif
1892
1893 if (!QFile::exists(strip)) {
1894 fprintf(stderr, "Command does not exist: %s\n", qPrintable(strip));
1895 return false;
1896 }
1897
1898 if (options.useLLVM)
1899 strip = QString::fromLatin1("%1 -strip-all %2").arg(shellQuote(strip), shellQuote(fileName));
1900 else
1901 strip = QString::fromLatin1("%1 %2").arg(shellQuote(strip), shellQuote(fileName));
1902
1903 FILE *stripCommand = openProcess(strip);
1904 if (stripCommand == 0) {
1905 fprintf(stderr, "Cannot execute command %s", qPrintable(strip));
1906 return false;
1907 }
1908
1909 pclose(stripCommand);
1910
1911 return true;
1912}
1913
1914bool stripLibraries(const Options &options)
1915{
1916 if (!options.stripLibraries)
1917 return true;
1918 if (options.verbose)
1919 fprintf(stdout, "Stripping libraries to minimize size.\n");
1920
1921
1922 QString libraryPath = options.outputDirectory
1923 + QLatin1String("/libs/")
1924 + options.architecture;
1925 const QStringList libraries = QDir(libraryPath).entryList(QDir::Files);
1926 for (const QString &library : libraries) {
1927 if (library.endsWith(QLatin1String(".so"))) {
1928 if (!stripFile(options, libraryPath + QLatin1Char('/') + library))
1929 return false;
1930 }
1931 }
1932
1933
1934 return true;
1935}
1936
1937bool containsApplicationBinary(const Options &options)
1938{
1939 if (options.verbose)
1940 fprintf(stdout, "Checking if application binary is in package.\n");
1941
1942 QFileInfo applicationBinary(options.applicationBinary);
1943 QString destinationFileName = options.outputDirectory
1944 + QLatin1String("/libs/")
1945 + options.architecture
1946 + QLatin1Char('/')
1947 + applicationBinary.fileName();
1948
1949 if (!QFile::exists(destinationFileName)) {
1950#if defined(Q_OS_WIN32)
1951 QLatin1String makeTool("mingw32-make"); // Only Mingw host builds supported on Windows currently
1952#else
1953 QLatin1String makeTool("make");
1954#endif
1955
1956 fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
1957 qPrintable(destinationFileName),
1958 qPrintable(makeTool),
1959 qPrintable(options.outputDirectory));
1960 return false;
1961 }
1962
1963 return true;
1964}
1965
1966FILE *runAdb(const Options &options, const QString &arguments)
1967{
1968 QString adb = options.sdkPath + QLatin1String("/platform-tools/adb");
1969#if defined(Q_OS_WIN32)
1970 adb += QLatin1String(".exe");
1971#endif
1972
1973 if (!QFile::exists(adb)) {
1974 fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
1975 return 0;
1976 }
1977 QString installOption;
1978 if (!options.installLocation.isEmpty())
1979 installOption = QLatin1String(" -s ") + shellQuote(options.installLocation);
1980
1981 adb = QString::fromLatin1("%1%2 %3").arg(shellQuote(adb)).arg(installOption).arg(arguments);
1982
1983 if (options.verbose)
1984 fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
1985
1986 FILE *adbCommand = openProcess(adb);
1987 if (adbCommand == 0) {
1988 fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
1989 return 0;
1990 }
1991
1992 return adbCommand;
1993}
1994
1995bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
1996{
1997 if (!file.endsWith(QLatin1String(".so")))
1998 return true;
1999
2000 bool ret = true;
2001 const auto libs = getQtLibsFromElf(*options, file);
2002 for (const QString &lib : libs) {
2003 if (!options->qtDependencies.contains(QtDependency(lib, absoluteFilePath(options, lib)))) {
2004 ret = false;
2005 unmetDependencies->append(lib);
2006 }
2007 }
2008
2009 return ret;
2010}
2011
2012bool copyQtFiles(Options *options)
2013{
2014 if (options->verbose) {
2015 switch (options->deploymentMechanism) {
2016 case Options::Bundled:
2017 fprintf(stdout, "Copying %d dependencies from Qt into package.\n", options->qtDependencies.size());
2018 break;
2019 case Options::Ministro:
2020 fprintf(stdout, "Setting %d dependencies from Qt in package.\n", options->qtDependencies.size());
2021 break;
2022 };
2023 }
2024
2025 if (!options->build)
2026 return true;
2027
2028 QString libsDirectory = QLatin1String("libs/");
2029
2030 // Copy other Qt dependencies
2031 QString libDestinationDirectory = libsDirectory + options->architecture + QLatin1Char('/');
2032 QString assetsDestinationDirectory = QLatin1String("assets/--Added-by-androiddeployqt--/");
2033 for (const QtDependency &qtDependency : qAsConst(options->qtDependencies)) {
2034 QString sourceFileName = qtDependency.absolutePath;
2035 QString destinationFileName;
2036
2037 if (qtDependency.relativePath.endsWith(QLatin1String(".so"))) {
2038 QString garbledFileName;
2039 if (qtDependency.relativePath.startsWith(QLatin1String("lib/"))) {
2040 garbledFileName = qtDependency.relativePath.mid(sizeof("lib/") - 1);
2041 } else {
2042 garbledFileName = QLatin1String("lib")
2043 + QString(qtDependency.relativePath).replace(QLatin1Char('/'), QLatin1Char('_'));
2044
2045 }
2046 destinationFileName = libDestinationDirectory + garbledFileName;
2047
2048 } else if (qtDependency.relativePath.startsWith(QLatin1String("jar/"))) {
2049 destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
2050 } else {
2051 destinationFileName = assetsDestinationDirectory + qtDependency.relativePath;
2052 }
2053
2054 if (!QFile::exists(sourceFileName)) {
2055 fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
2056 return false;
2057 }
2058
2059 QStringList unmetDependencies;
2060 if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
2061 fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n",
2062 qPrintable(sourceFileName),
2063 qPrintable(unmetDependencies.join(QLatin1Char(','))));
2064 continue;
2065 }
2066
2067 if (options->deploymentMechanism == Options::Bundled
2068 && !copyFileIfNewer(sourceFileName,
2069 options->outputDirectory + QLatin1Char('/') + destinationFileName,
2070 options->verbose)) {
2071 return false;
2072 }
2073
2074 options->bundledFiles += qMakePair(destinationFileName, qtDependency.relativePath);
2075 }
2076
2077 return true;
2078}
2079
2080QStringList getLibraryProjectsInOutputFolder(const Options &options)
2081{
2082 QStringList ret;
2083
2084 QFile file(options.outputDirectory + QLatin1String("/project.properties"));
2085 if (file.open(QIODevice::ReadOnly)) {
2086 while (!file.atEnd()) {
2087 QByteArray line = file.readLine().trimmed();
2088 if (line.startsWith("android.library.reference")) {
2089 int equalSignIndex = line.indexOf('=');
2090 if (equalSignIndex >= 0) {
2091 QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
2092
2093 QFileInfo info(options.outputDirectory + QLatin1Char('/') + path);
2094 if (QDir::isRelativePath(path)
2095 && info.exists()
2096 && info.isDir()
2097 && info.canonicalFilePath().startsWith(options.outputDirectory)) {
2098 ret += info.canonicalFilePath();
2099 }
2100 }
2101 }
2102 }
2103 }
2104
2105 return ret;
2106}
2107
2108bool createAndroidProject(const Options &options)
2109{
2110 if (options.verbose)
2111 fprintf(stdout, "Running Android tool to create package definition.\n");
2112
2113 QString androidToolExecutable = options.sdkPath + QLatin1String("/tools/android");
2114#if defined(Q_OS_WIN32)
2115 androidToolExecutable += QLatin1String(".bat");
2116#endif
2117
2118 if (!QFile::exists(androidToolExecutable)) {
2119 fprintf(stderr, "Cannot find Android tool: %s\n", qPrintable(androidToolExecutable));
2120 return false;
2121 }
2122
2123 QString androidTool = QString::fromLatin1("%1 update project --path %2 --target %3 --name QtApp")
2124 .arg(shellQuote(androidToolExecutable))
2125 .arg(shellQuote(options.outputDirectory))
2126 .arg(shellQuote(options.androidPlatform));
2127
2128 if (options.verbose)
2129 fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool));
2130
2131 FILE *androidToolCommand = openProcess(androidTool);
2132 if (androidToolCommand == 0) {
2133 fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
2134 return false;
2135 }
2136
2137 pclose(androidToolCommand);
2138
2139 // If the project has subprojects inside the current folder, we need to also run android update on these.
2140 const QStringList libraryProjects = getLibraryProjectsInOutputFolder(options);
2141 for (const QString &libraryProject : libraryProjects) {
2142 if (options.verbose)
2143 fprintf(stdout, "Updating subproject %s\n", qPrintable(libraryProject));
2144
2145 androidTool = QString::fromLatin1("%1 update lib-project --path %2 --target %3")
2146 .arg(shellQuote(androidToolExecutable))
2147 .arg(shellQuote(libraryProject))
2148 .arg(shellQuote(options.androidPlatform));
2149
2150 if (options.verbose)
2151 fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool));
2152
2153 FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), QT_POPEN_READ);
2154 if (androidToolCommand == 0) {
2155 fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
2156 return false;
2157 }
2158
2159 pclose(androidToolCommand);
2160 }
2161
2162 return true;
2163}
2164
2165QString findInPath(const QString &fileName)
2166{
2167 const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
2168#if defined(Q_OS_WIN32)
2169 QLatin1Char separator(';');
2170#else
2171 QLatin1Char separator(':');
2172#endif
2173
2174 const QStringList paths = path.split(separator);
2175 for (const QString &path : paths) {
2176 QFileInfo fileInfo(path + QLatin1Char('/') + fileName);
2177 if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
2178 return path + QLatin1Char('/') + fileName;
2179 }
2180
2181 return QString();
2182}
2183
2184typedef QMap<QByteArray, QByteArray> GradleProperties;
2185
2186static GradleProperties readGradleProperties(const QString &path)
2187{
2188 GradleProperties properties;
2189 QFile file(path);
2190 if (!file.open(QIODevice::ReadOnly))
2191 return properties;
2192
2193 const auto lines = file.readAll().split('\n');
2194 for (const QByteArray &line : lines) {
2195 if (line.trimmed().startsWith('#'))
2196 continue;
2197
2198 QList<QByteArray> prop(line.split('='));
2199 if (prop.size() > 1)
2200 properties[prop.at(0).trimmed()] = prop.at(1).trimmed();
2201 }
2202 file.close();
2203 return properties;
2204}
2205
2206static bool mergeGradleProperties(const QString &path, GradleProperties properties)
2207{
2208 QFile::remove(path + QLatin1Char('~'));
2209 QFile::rename(path, path + QLatin1Char('~'));
2210 QFile file(path);
2211 if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
2212 fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName()));
2213 return false;
2214 }
2215
2216 QFile oldFile(path + QLatin1Char('~'));
2217 if (oldFile.open(QIODevice::ReadOnly)) {
2218 while (!oldFile.atEnd()) {
2219 QByteArray line(oldFile.readLine());
2220 QList<QByteArray> prop(line.split('='));
2221 if (prop.size() > 1) {
2222 GradleProperties::iterator it = properties.find(prop.at(0).trimmed());
2223 if (it != properties.end()) {
2224 file.write(it.key() + '=' + it.value() + '\n');
2225 properties.erase(it);
2226 continue;
2227 }
2228 }
2229 file.write(line);
2230 }
2231 oldFile.close();
2232 }
2233
2234 for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
2235 file.write(it.key() + '=' + it.value() + '\n');
2236
2237 file.close();
2238 return true;
2239}
2240
2241bool buildAndroidProject(const Options &options)
2242{
2243 GradleProperties localProperties;
2244 localProperties["sdk.dir"] = options.sdkPath.toLocal8Bit();
2245
2246 if (!mergeGradleProperties(options.outputDirectory + QLatin1String("local.properties"), localProperties))
2247 return false;
2248
2249 QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties");
2250 GradleProperties gradleProperties =