diff options
Diffstat (limited to 'src/androiddeployqt/main.cpp')
-rw-r--r-- | src/androiddeployqt/main.cpp | 2909 |
1 files changed, 0 insertions, 2909 deletions
diff --git a/src/androiddeployqt/main.cpp b/src/androiddeployqt/main.cpp deleted file mode 100644 index f00188396..000000000 --- a/src/androiddeployqt/main.cpp +++ /dev/null @@ -1,2909 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QCoreApplication> -#include <QStringList> -#include <QDir> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QDebug> -#include <QDataStream> -#include <QXmlStreamReader> -#include <QDateTime> -#include <QStandardPaths> -#include <QUuid> -#include <QDirIterator> -#include <QRegExp> - -#include <algorithm> -static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output - -void deleteRecursively(const QString &dirName) -{ - QDir dir(dirName); - if (!dir.exists()) - return; - - const QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - for (const QFileInfo &entry : entries) { - if (entry.isDir()) - deleteRecursively(entry.absoluteFilePath()); - else - QFile::remove(entry.absoluteFilePath()); - } - - QDir().rmdir(dirName); -} - -FILE *openProcess(const QString &command) -{ -#if defined(Q_OS_WIN32) - QString processedCommand = QLatin1Char('\"') + command + QLatin1Char('\"'); -#else - QString processedCommand = command; -#endif - - return popen(processedCommand.toLocal8Bit().constData(), "r"); -} - -struct QtDependency -{ - QtDependency(QString rpath, QString apath) : relativePath(rpath), absolutePath(apath) {} - - bool operator==(const QtDependency &other) const - { - return relativePath == other.relativePath && absolutePath == other.absolutePath; - } - - QString relativePath; - QString absolutePath; -}; - -struct Options -{ - Options() - : helpRequested(false) - , verbose(false) - , timing(false) - , generateAssetsFileList(true) - , build(true) - , gradle(false) - , deploymentMechanism(Bundled) - , releasePackage(false) - , digestAlg(QLatin1String("SHA1")) - , sigAlg(QLatin1String("SHA1withRSA")) - , internalSf(false) - , sectionsOnly(false) - , protectedAuthenticationPath(false) - , jarSigner(false) - , gdbServer(Auto) - , installApk(false) - , uninstallApk(false) - {} - - enum DeploymentMechanism - { - Bundled, - Ministro - }; - - enum TriState { - Auto, - False, - True - }; - - bool helpRequested; - bool verbose; - bool timing; - bool generateAssetsFileList; - bool build; - bool gradle; - QTime timer; - - // External tools - QString sdkPath; - QString sdkBuildToolsVersion; - QString ndkPath; - QString antTool; - QString jdkPath; - - // Build paths - QString qtInstallDirectory; - QString androidSourceDirectory; - QString outputDirectory; - QString inputFileName; - QString applicationBinary; - QString rootPath; - QStringList qmlImportPaths; - - // lib c++ path - QString stdCppPath; - QString stdCppName = QStringLiteral("gnustl_shared"); - - // Build information - QString androidPlatform; - QString architecture; - QString toolchainVersion; - QString toolchainPrefix; - QString toolPrefix; - QString ndkHost; - - // Package information - DeploymentMechanism deploymentMechanism; - QString packageName; - QStringList extraLibs; - QStringList extraPlugins; - - // Signing information - bool releasePackage; - QString keyStore; - QString keyStorePassword; - QString keyStoreAlias; - QString storeType; - QString keyPass; - QString sigFile; - QString signedJar; - QString digestAlg; - QString sigAlg; - QString tsaUrl; - QString tsaCert; - bool internalSf; - bool sectionsOnly; - bool protectedAuthenticationPath; - bool jarSigner; - - // Gdbserver - TriState gdbServer; - - // Installation information - bool installApk; - bool uninstallApk; - QString installLocation; - - // Collected information - typedef QPair<QString, QString> BundledFile; - QList<BundledFile> bundledFiles; - QList<QtDependency> qtDependencies; - QStringList localLibs; - QStringList localJars; - QStringList initClasses; - QStringList permissions; - QStringList features; -}; - -// Copy-pasted from qmake/library/ioutil.cpp -inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) -{ - for (int x = arg.length() - 1; x >= 0; --x) { - ushort c = arg.unicode()[x].unicode(); - if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) - return true; - } - return false; -} - -static QString shellQuoteUnix(const QString &arg) -{ - // Chars that should be quoted (TM). This includes: - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, - 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 - }; // 0-32 \'"$`<>|;&(){}*?#!~[] - - if (!arg.length()) - return QString::fromLatin1("\"\""); - - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - ret.replace(QLatin1Char('\''), QLatin1String("'\\''")); - ret.prepend(QLatin1Char('\'')); - ret.append(QLatin1Char('\'')); - } - return ret; -} - -static QString shellQuoteWin(const QString &arg) -{ - // Chars that should be quoted (TM). This includes: - // - control chars & space - // - the shell meta chars "&()<>^| - // - the potential separators ,;= - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 - }; - - if (!arg.length()) - return QString::fromLatin1("\"\""); - - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - // Quotes are escaped and their preceding backslashes are doubled. - // It's impossible to escape anything inside a quoted string on cmd - // level, so the outer quoting must be "suspended". - ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\"")); - // The argument must not end with a \ since this would be interpreted - // as escaping the quote -- rather put the \ behind the quote: e.g. - // rather use "foo"\ than "foo\" - int i = ret.length(); - while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) - --i; - ret.insert(i, QLatin1Char('"')); - ret.prepend(QLatin1Char('"')); - } - return ret; -} - -static QString shellQuote(const QString &arg) -{ - if (QDir::separator() == QLatin1Char('\\')) - return shellQuoteWin(arg); - else - return shellQuoteUnix(arg); -} - - -void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &dstDir) -{ - if (options.verbose) - fprintf(stdout, "Delete missing files %s %s\n", qPrintable(srcDir.absolutePath()), qPrintable(dstDir.absolutePath())); - - const QFileInfoList srcEntries = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - const QFileInfoList dstEntries = dstDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - for (const QFileInfo &dst : dstEntries) { - bool found = false; - for (const QFileInfo &src : srcEntries) - if (dst.fileName() == src.fileName()) { - if (dst.isDir()) - deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath()); - found = true; - break; - } - - if (!found) { - if (options.verbose) - fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath())); - - if (dst.isDir()) - deleteRecursively(dst.absolutePath()); - else - QFile::remove(dst.absoluteFilePath()); - } - } - fflush(stdout); -} - - -Options parseOptions() -{ - Options options; - - QStringList arguments = QCoreApplication::arguments(); - for (int i=0; i<arguments.size(); ++i) { - const QString &argument = arguments.at(i); - if (argument.compare(QLatin1String("--output"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.outputDirectory = arguments.at(++i).trimmed(); - } else if (argument.compare(QLatin1String("--input"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.inputFileName = arguments.at(++i); - } else if (argument.compare(QLatin1String("--no-build"), Qt::CaseInsensitive) == 0) { - options.build = false; - } else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) { - options.installApk = true; - options.uninstallApk = true; - } else if (argument.compare(QLatin1String("--reinstall"), Qt::CaseInsensitive) == 0) { - options.installApk = true; - options.uninstallApk = false; - } else if (argument.compare(QLatin1String("--android-platform"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.androidPlatform = arguments.at(++i); - } else if (argument.compare(QLatin1String("--help"), Qt::CaseInsensitive) == 0) { - options.helpRequested = true; - } else if (argument.compare(QLatin1String("--verbose"), Qt::CaseInsensitive) == 0) { - options.verbose = true; - } else if (argument.compare(QLatin1String("--gradle"), Qt::CaseInsensitive) == 0) { - options.gradle = true; - } else if (argument.compare(QLatin1String("--ant"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.antTool = arguments.at(++i); - } else if (argument.compare(QLatin1String("--deployment"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) { - options.helpRequested = true; - } else { - QString deploymentMechanism = arguments.at(++i); - if (deploymentMechanism.compare(QLatin1String("ministro"), Qt::CaseInsensitive) == 0) { - options.deploymentMechanism = Options::Ministro; - } else if (deploymentMechanism.compare(QLatin1String("bundled"), Qt::CaseInsensitive) == 0) { - options.deploymentMechanism = Options::Bundled; - } else { - fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism)); - options.helpRequested = true; - } - } - } else if (argument.compare(QLatin1String("--device"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.installLocation = arguments.at(++i); - } else if (argument.compare(QLatin1String("--release"), Qt::CaseInsensitive) == 0) { - options.releasePackage = true; - } else if (argument.compare(QLatin1String("--gdbserver"), Qt::CaseInsensitive) == 0) { - options.gdbServer = Options::True; - } else if (argument.compare(QLatin1String("--no-gdbserver"), Qt::CaseInsensitive) == 0) { - options.gdbServer = Options::False; - } else if (argument.compare(QLatin1String("--jdk"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.jdkPath = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) { - if (i + 2 >= arguments.size()) { - options.helpRequested = true; - } else { - options.releasePackage = true; - options.keyStore = arguments.at(++i); - options.keyStoreAlias = arguments.at(++i); - } - } else if (argument.compare(QLatin1String("--storepass"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.keyStorePassword = arguments.at(++i); - } else if (argument.compare(QLatin1String("--storetype"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.storeType = arguments.at(++i); - } else if (argument.compare(QLatin1String("--keypass"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.keyPass = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sigfile"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.sigFile = arguments.at(++i); - } else if (argument.compare(QLatin1String("--digestalg"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.digestAlg = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sigalg"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.sigAlg = arguments.at(++i); - } else if (argument.compare(QLatin1String("--tsa"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.tsaUrl = arguments.at(++i); - } else if (argument.compare(QLatin1String("--tsacert"), Qt::CaseInsensitive) == 0) { - if (i + 1 == arguments.size()) - options.helpRequested = true; - else - options.tsaCert = arguments.at(++i); - } else if (argument.compare(QLatin1String("--internalsf"), Qt::CaseInsensitive) == 0) { - options.internalSf = true; - } else if (argument.compare(QLatin1String("--sectionsonly"), Qt::CaseInsensitive) == 0) { - options.sectionsOnly = true; - } else if (argument.compare(QLatin1String("--protected"), Qt::CaseInsensitive) == 0) { - options.protectedAuthenticationPath = true; - } else if (argument.compare(QLatin1String("--jarsigner"), Qt::CaseInsensitive) == 0) { - options.jarSigner = true; - } else if (argument.compare(QLatin1String("--no-generated-assets-cache"), Qt::CaseInsensitive) == 0) { - options.generateAssetsFileList = false; - } - } - - if (options.inputFileName.isEmpty()) - options.inputFileName = QString::fromLatin1("android-lib%1.so-deployment-settings.json").arg(QDir::current().dirName()); - - options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT"); - - if (!QDir::current().mkpath(options.outputDirectory)) { - fprintf(stderr, "Invalid output directory: %s\n", qPrintable(options.outputDirectory)); - options.outputDirectory.clear(); - } else { - options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath(); - if (!options.outputDirectory.endsWith(QLatin1Char('/'))) - options.outputDirectory += QLatin1Char('/'); - } - - return options; -} - -void printHelp() -{// "012345678901234567890123456789012345678901234567890123456789012345678901" - fprintf(stderr, "Syntax: %s --output <destination> [options]\n" - "\n" - " Creates an Android package in the build directory <destination> and\n" - " builds it into an .apk file.\n\n" - " Optional arguments:\n" - " --input <inputfile>: Reads <inputfile> for options generated by\n" - " qmake. A default file name based on the current working\n" - " directory will be used if nothing else is specified.\n" - " --deployment <mechanism>: Supported deployment mechanisms:\n" - " bundled (default): Include Qt files in stand-alone package.\n" - " ministro: Use the Ministro service to manage Qt files.\n" - " --no-build: Do not build the package, it is useful to just install\n" - " a package previously built.\n" - " --install: Installs apk to device/emulator. By default this step is\n" - " not taken. If the application has previously been installed on\n" - " the device, it will be uninstalled first.\n" - " --reinstall: Installs apk to device/emulator. By default this step\n" - " is not taken. If the application has previously been installed on\n" - " the device, it will be overwritten, but its data will be left\n" - " intact.\n" - " --device [device ID]: Use specified device for deployment. Default\n" - " is the device selected by default by adb.\n" - " --android-platform <platform>: Builds against the given android\n" - " platform. By default, the highest available version will be\n" - " used.\n" - " --gradle. Use gradle instead of ant to create and install the apk.\n" - " --ant <path/to/ant>: If unspecified, ant from the PATH will be\n" - " used.\n" - " --release: Builds a package ready for release. By default, the\n" - " package will be signed with a debug key.\n" - " --sign <url/to/keystore> <alias>: Signs the package with the\n" - " specified keystore, alias and store password. Also implies the\n" - " --release option.\n" - " Optional arguments for use with signing:\n" - " --storepass <password>: Keystore password.\n" - " --storetype <type>: Keystore type.\n" - " --keypass <password>: Password for private key (if different\n" - " from keystore password.)\n" - " --sigfile <file>: Name of .SF/.DSA file.\n" - " --digestalg <name>: Name of digest algorithm. Default is\n" - " \"SHA1\".\n" - " --sigalg <name>: Name of signature algorithm. Default is\n" - " \"SHA1withRSA\".\n" - " --tsa <url>: Location of the Time Stamping Authority.\n" - " --tsacert <alias>: Public key certificate for TSA.\n" - " --internalsf: Include the .SF file inside the signature block.\n" - " --sectionsonly: Don't compute hash of entire manifest.\n" - " --protected: Keystore has protected authentication path.\n" - " --jarsigner: Force jarsigner usage, otherwise apksigner will be\n" - " used if available.\n" - " --gdbserver: Adds the gdbserver to the package. By default the gdbserver\n" - " is bundled for debug pacakges.\n" - " --no-gdbserver: Prevents the gdbserver from being added to the package\n" - " By default the gdbserver is bundled for debug pacakges.\n" - " --jdk <path/to/jdk>: Used to find the jarsigner tool when used\n" - " in combination with the --release argument. By default,\n" - " an attempt is made to detect the tool using the JAVA_HOME and\n" - " PATH environment variables, in that order.\n" - " --qml-import-paths: Specify additional search paths for QML\n" - " imports.\n" - " --verbose: Prints out information during processing.\n" - " --no-generated-assets-cache: Do not pregenerate the entry list for\n" - " the assets file engine.\n" - " --help: Displays this information.\n\n", - qPrintable(QCoreApplication::arguments().at(0)) - ); -} - -// Since strings compared will all start with the same letters, -// sorting by length and then alphabetically within each length -// gives the natural order. -bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2) -{ - QString s1 = fi1.baseName(); - QString s2 = fi2.baseName(); - - if (s1.length() == s2.length()) - return s1 > s2; - else - return s1.length() > s2.length(); -} - -// Files which contain templates that need to be overwritten by build data should be overwritten every -// time. -bool alwaysOverwritableFile(const QString &fileName) -{ - return (fileName.endsWith(QLatin1String("/res/values/libs.xml")) - || fileName.endsWith(QLatin1String("/AndroidManifest.xml")) - || fileName.endsWith(QLatin1String("/res/values/strings.xml")) - || fileName.endsWith(QLatin1String("/src/org/qtproject/qt5/android/bindings/QtActivity.java"))); -} - -bool copyFileIfNewer(const QString &sourceFileName, - const QString &destinationFileName, - bool verbose, - bool forceOverwrite = false) -{ - if (QFile::exists(destinationFileName)) { - QFileInfo destinationFileInfo(destinationFileName); - QFileInfo sourceFileInfo(sourceFileName); - - if (!forceOverwrite - && sourceFileInfo.lastModified() <= destinationFileInfo.lastModified() - && !alwaysOverwritableFile(destinationFileName)) { - if (verbose) - fprintf(stdout, " -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName)); - return true; - } else { - if (!QFile(destinationFileName).remove()) { - fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName)); - return false; - } - } - } - - if (!QDir().mkpath(QFileInfo(destinationFileName).path())) { - fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName)); - return false; - } - - if (!QFile::exists(destinationFileName) && !QFile::copy(sourceFileName, destinationFileName)) { - fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName)); - return false; - } else if (verbose) { - fprintf(stdout, " -- Copied %s\n", qPrintable(destinationFileName)); - fflush(stdout); - } - - return true; -} - -QString cleanPackageName(QString packageName) -{ - QRegExp legalChars(QLatin1String("[a-zA-Z0-9_\\.]")); - - for (int i = 0; i < packageName.length(); ++i) { - if (!legalChars.exactMatch(packageName.mid(i, 1))) - packageName[i] = QLatin1Char('_'); - } - - static QStringList keywords; - if (keywords.isEmpty()) { - keywords << QLatin1String("abstract") << QLatin1String("continue") << QLatin1String("for") - << QLatin1String("new") << QLatin1String("switch") << QLatin1String("assert") - << QLatin1String("default") << QLatin1String("if") << QLatin1String("package") - << QLatin1String("synchronized") << QLatin1String("boolean") << QLatin1String("do") - << QLatin1String("goto") << QLatin1String("private") << QLatin1String("this") - << QLatin1String("break") << QLatin1String("double") << QLatin1String("implements") - << QLatin1String("protected") << QLatin1String("throw") << QLatin1String("byte") - << QLatin1String("else") << QLatin1String("import") << QLatin1String("public") - << QLatin1String("throws") << QLatin1String("case") << QLatin1String("enum") - << QLatin1String("instanceof") << QLatin1String("return") << QLatin1String("transient") - << QLatin1String("catch") << QLatin1String("extends") << QLatin1String("int") - << QLatin1String("short") << QLatin1String("try") << QLatin1String("char") - << QLatin1String("final") << QLatin1String("interface") << QLatin1String("static") - << QLatin1String("void") << QLatin1String("class") << QLatin1String("finally") - << QLatin1String("long") << QLatin1String("strictfp") << QLatin1String("volatile") - << QLatin1String("const") << QLatin1String("float") << QLatin1String("native") - << QLatin1String("super") << QLatin1String("while"); - } - - // No keywords - int index = -1; - while (index < packageName.length()) { - int next = packageName.indexOf(QLatin1Char('.'), index + 1); - if (next == -1) - next = packageName.length(); - QString word = packageName.mid(index + 1, next - index - 1); - if (!word.isEmpty()) { - QChar c = word[0]; - if ((c >= QChar(QLatin1Char('0')) && c<= QChar(QLatin1Char('9'))) - || c == QLatin1Char('_')) { - packageName.insert(index + 1, QLatin1Char('a')); - index = next + 1; - continue; - } - } - if (keywords.contains(word)) { - packageName.insert(next, QLatin1String("_")); - index = next + 1; - } else { - index = next; - } - } - - return packageName; -} - -QString detectLatestAndroidPlatform(const QString &sdkPath) -{ - QDir dir(sdkPath + QLatin1String("/platforms")); - if (!dir.exists()) { - fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath())); - return QString(); - } - - QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - if (fileInfos.isEmpty()) { - fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath())); - return QString(); - } - - std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan); - - QFileInfo latestPlatform = fileInfos.first(); - return latestPlatform.baseName(); -} - -QString packageNameFromAndroidManifest(const QString &androidManifestPath) -{ - QFile androidManifestXml(androidManifestPath); - if (androidManifestXml.open(QIODevice::ReadOnly)) { - QXmlStreamReader reader(&androidManifestXml); - while (!reader.atEnd()) { - reader.readNext(); - if (reader.isStartElement() && reader.name() == QLatin1String("manifest")) - return cleanPackageName( - reader.attributes().value(QLatin1String("package")).toString()); - } - } - return QString(); -} - -bool readInputFile(Options *options) -{ - QFile file(options->inputFileName); - if (!file.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName)); - return false; - } - - QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll()); - if (jsonDocument.isNull()) { - fprintf(stderr, "Invalid json file: %s\n", qPrintable(options->inputFileName)); - return false; - } - - QJsonObject jsonObject = jsonDocument.object(); - - { - QJsonValue sdkPath = jsonObject.value(QLatin1String("sdk")); - if (sdkPath.isUndefined()) { - fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName)); - return false; - } - - options->sdkPath = sdkPath.toString(); - - if (options->androidPlatform.isEmpty()) { - options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath); - if (options->androidPlatform.isEmpty()) - return false; - } else { - if (!QDir(options->sdkPath + QLatin1String("/platforms/") + options->androidPlatform).exists()) { - fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n", - qPrintable(options->androidPlatform)); - } - } - } - - { - - const QJsonValue value = jsonObject.value(QStringLiteral("sdkBuildToolsRevision")); - if (!value.isUndefined()) - options->sdkBuildToolsVersion = value.toString(); - } - - { - const QJsonValue qtInstallDirectory = jsonObject.value(QStringLiteral("qt")); - if (qtInstallDirectory.isUndefined()) { - fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName)); - return false; - } - options->qtInstallDirectory = qtInstallDirectory.toString(); - } - - { - const QJsonValue androidSourcesDirectory = jsonObject.value(QStringLiteral("android-package-source-directory")); - if (!androidSourcesDirectory.isUndefined()) - options->androidSourceDirectory = androidSourcesDirectory.toString(); - } - - { - const QJsonValue applicationBinary = jsonObject.value(QStringLiteral("application-binary")); - if (applicationBinary.isUndefined()) { - fprintf(stderr, "No application binary defined in json file.\n"); - return false; - } - options->applicationBinary = applicationBinary.toString(); - - if (!QFile::exists(options->applicationBinary)) { - fprintf(stderr, "Cannot find application binary %s.\n", qPrintable(options->applicationBinary)); - return false; - } - } - - { - const QJsonValue deploymentDependencies = jsonObject.value(QStringLiteral("deployment-dependencies")); - if (!deploymentDependencies.isUndefined()) { - QString deploymentDependenciesString = deploymentDependencies.toString(); - const auto dependencies = deploymentDependenciesString.splitRef(QLatin1Char(',')); - for (const QStringRef &dependency : dependencies) { - QString path = options->qtInstallDirectory + QLatin1Char('/') + dependency; - if (QFileInfo(path).isDir()) { - QDirIterator iterator(path, QDirIterator::Subdirectories); - while (iterator.hasNext()) { - iterator.next(); - if (iterator.fileInfo().isFile()) { - QString subPath = iterator.filePath(); - options->qtDependencies.append(QtDependency(subPath.mid(options->qtInstallDirectory.length() + 1), - subPath)); - } - } - } else { - options->qtDependencies.append(QtDependency(dependency.toString(), path)); - } - } - } - } - - - { - const QJsonValue targetArchitecture = jsonObject.value(QStringLiteral("target-architecture")); - if (targetArchitecture.isUndefined()) { - fprintf(stderr, "No target architecture defined in json file.\n"); - return false; - } - options->architecture = targetArchitecture.toString(); - } - - { - const QJsonValue ndk = jsonObject.value(QStringLiteral("ndk")); - if (ndk.isUndefined()) { - fprintf(stderr, "No NDK path defined in json file.\n"); - return false; - } - options->ndkPath = ndk.toString(); - } - - { - const QJsonValue toolchainPrefix = jsonObject.value(QStringLiteral("toolchain-prefix")); - if (toolchainPrefix.isUndefined()) { - fprintf(stderr, "No toolchain prefix defined in json file.\n"); - return false; - } - options->toolchainPrefix = toolchainPrefix.toString(); - } - - { - const QJsonValue toolPrefix = jsonObject.value(QStringLiteral("tool-prefix")); - if (toolPrefix.isUndefined()) { - fprintf(stderr, "Warning: No tool prefix defined in json file.\n"); - options->toolPrefix = options->toolchainPrefix; - } else { - options->toolPrefix = toolPrefix.toString(); - } - } - - { - const QJsonValue toolchainVersion = jsonObject.value(QStringLiteral("toolchain-version")); - if (toolchainVersion.isUndefined()) { - fprintf(stderr, "No toolchain version defined in json file.\n"); - return false; - } - options->toolchainVersion = toolchainVersion.toString(); - } - - { - const QJsonValue ndkHost = jsonObject.value(QStringLiteral("ndk-host")); - if (ndkHost.isUndefined()) { - fprintf(stderr, "No NDK host defined in json file.\n"); - return false; - } - options->ndkHost = ndkHost.toString(); - } - - options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + QLatin1String("/AndroidManifest.xml")); - if (options->packageName.isEmpty()) - options->packageName = cleanPackageName(QString::fromLatin1("org.qtproject.example.%1").arg(QFileInfo(options->applicationBinary).baseName().mid(sizeof("lib") - 1))); - - { - const QJsonValue extraLibs = jsonObject.value(QStringLiteral("android-extra-libs")); - if (!extraLibs.isUndefined()) - options->extraLibs = extraLibs.toString().split(QLatin1Char(','), QString::SkipEmptyParts); - } - - { - const QJsonValue extraPlugins = jsonObject.value(QStringLiteral("android-extra-plugins")); - if (!extraPlugins.isUndefined()) - options->extraPlugins = extraPlugins.toString().split(QLatin1Char(',')); - } - - { - const QJsonValue stdcppPath = jsonObject.value(QStringLiteral("stdcpp-path")); - if (!stdcppPath.isUndefined()) { - options->stdCppPath = stdcppPath.toString(); - auto name = QFileInfo(options->stdCppPath).baseName(); - if (!name.startsWith(QLatin1String("lib"))) { - fprintf(stderr, "Invalid STD C++ library name.\n"); - return false; - } - options->stdCppName = name.mid(3); - } - } - - { - const QJsonValue qmlRootPath = jsonObject.value(QStringLiteral("qml-root-path")); - if (!qmlRootPath.isUndefined()) - options->rootPath = qmlRootPath.toString(); - } - - { - const QJsonValue qmlImportPaths = jsonObject.value(QStringLiteral("qml-import-paths")); - if (!qmlImportPaths.isUndefined()) - options->qmlImportPaths = qmlImportPaths.toString().split(QLatin1Char(',')); - } - return true; -} - -bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, bool verbose, bool forceOverwrite = false) -{ - const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - for (const QFileInfo &entry : entries) { - if (entry.isDir()) { - QDir dir(entry.absoluteFilePath()); - if (!destinationDirectory.mkpath(dir.dirName())) { - fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path())); - return false; - } - - if (!copyFiles(dir, QDir(destinationDirectory.path() + QLatin1String("/") + dir.dirName()), verbose, forceOverwrite)) - return false; - } else { - QString destination = destinationDirectory.absoluteFilePath(entry.fileName()); - if (!copyFileIfNewer(entry.absoluteFilePath(), destination, verbose, forceOverwrite)) - return false; - } - } - - return true; -} - -void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &dstDir) -{ - const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs); - for (const QFileInfo &dir : dirs) { - if (dir.fileName() != QLatin1String("libs")) - deleteMissingFiles(options, dir.absoluteFilePath(), dstDir + dir.fileName()); - } -} - -void cleanAndroidFiles(const Options &options) -{ - if (!options.androidSourceDirectory.isEmpty()) - cleanTopFolders(options, options.androidSourceDirectory, options.outputDirectory); - - cleanTopFolders(options, options.qtInstallDirectory + QLatin1String("/src/android/templates"), options.outputDirectory); -} - -bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString()) -{ - QDir sourceDirectory(options.qtInstallDirectory + androidTemplate); - if (!sourceDirectory.exists()) { - fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath())); - return false; - } - - QString outDir = options.outputDirectory + outDirPrefix; - - if (!QDir::current().mkpath(outDir)) { - fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory)); - return false; - } - - return copyFiles(sourceDirectory, QDir(outDir), options.verbose); -} - -bool copyGradleTemplate(const Options &options) -{ - QDir sourceDirectory(options.qtInstallDirectory + QLatin1String("/src/3rdparty/gradle")); - if (!sourceDirectory.exists()) { - fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath())); - return false; - } - - QString outDir(options.outputDirectory); - if (!QDir::current().mkpath(outDir)) { - fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory)); - return false; - } - - return copyFiles(sourceDirectory, QDir(outDir), options.verbose); -} - -bool copyAndroidTemplate(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Copying Android package template.\n"); - - if (options.gradle && !copyGradleTemplate(options)) - return false; - - if (!copyAndroidTemplate(options, QLatin1String("/src/android/templates"))) - return false; - - if (options.gradle) - return true; - - return copyAndroidTemplate(options, QLatin1String("/src/android/java")); -} - -bool copyAndroidSources(const Options &options) -{ - if (options.androidSourceDirectory.isEmpty()) - return true; - - if (options.verbose) - fprintf(stdout, "Copying Android sources from project.\n"); - - QDir sourceDirectory(options.androidSourceDirectory); - if (!sourceDirectory.exists()) { - fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory)); - return false; - } - - return copyFiles(sourceDirectory, QDir(options.outputDirectory), options.verbose, true); -} - -bool copyAndroidExtraLibs(const Options &options) -{ - if (options.extraLibs.isEmpty()) - return true; - - if (options.verbose) - fprintf(stdout, "Copying %d external libraries to package.\n", options.extraLibs.size()); - - for (const QString &extraLib : options.extraLibs) { - QFileInfo extraLibInfo(extraLib); - if (!extraLibInfo.exists()) { - fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib)); - return false; - } - - if (!extraLibInfo.fileName().startsWith(QLatin1String("lib")) || extraLibInfo.suffix() != QLatin1String("so")) { - fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n", - qPrintable(extraLib)); - return false; - } - - QString destinationFile(options.outputDirectory - + QLatin1String("/libs/") - + options.architecture - + QLatin1Char('/') - + extraLibInfo.fileName()); - - if (!copyFileIfNewer(extraLib, destinationFile, options.verbose)) - return false; - } - - return true; -} - -QStringList allFilesInside(const QDir& current, const QDir& rootDir) -{ - QStringList result; - const auto dirs = current.entryList(QDir::Dirs|QDir::NoDotAndDotDot); - const auto files = current.entryList(QDir::Files); - result.reserve(dirs.size() + files.size()); - for (const QString &dir : dirs) { - result += allFilesInside(QDir(current.filePath(dir)), rootDir); - } - for (const QString &file : files) { - result += rootDir.relativeFilePath(current.filePath(file)); - } - return result; -} - -bool copyAndroidExtraResources(const Options &options) -{ - if (options.extraPlugins.isEmpty()) - return true; - - if (options.verbose) - fprintf(stdout, "Copying %d external resources to package.\n", options.extraPlugins.size()); - - for (const QString &extraResource : options.extraPlugins) { - QFileInfo extraResourceInfo(extraResource); - if (!extraResourceInfo.exists() || !extraResourceInfo.isDir()) { - fprintf(stderr, "External resource %s does not exist or not a correct directory!\n", qPrintable(extraResource)); - return false; - } - - QDir resourceDir(extraResource); - QString assetsDir = options.outputDirectory + QStringLiteral("/assets/") + resourceDir.dirName() + QLatin1Char('/'); - QString libsDir = options.outputDirectory + QStringLiteral("/libs/") + options.architecture + QLatin1Char('/'); - - const QStringList files = allFilesInside(resourceDir, resourceDir); - for (const QString &resourceFile : files) { - QString originFile(resourceDir.filePath(resourceFile)); - QString destinationFile; - if (!resourceFile.endsWith(QLatin1String(".so"))) { - destinationFile = assetsDir + resourceFile; - } else { - destinationFile = libsDir + QStringLiteral("/lib") + QString(resourceDir.dirName() + QLatin1Char('/') + resourceFile).replace(QLatin1Char('/'), QLatin1Char('_')); - } - - if (!copyFileIfNewer(originFile, destinationFile, options.verbose)) - return false; - } - } - - return true; -} - -bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements) -{ - QFile inputFile(fileName); - if (!inputFile.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName)); - return false; - } - - // All the files we are doing substitutes in are quite small. If this - // ever changes, this code should be updated to be more conservative. - QByteArray contents = inputFile.readAll(); - - bool hasReplacements = false; - QHash<QString, QString>::const_iterator it; - for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) { - if (it.key() == it.value()) - continue; // Nothing to actually replace - - forever { - int index = contents.indexOf(it.key().toUtf8()); - if (index >= 0) { - contents.replace(index, it.key().length(), it.value().toUtf8()); - hasReplacements = true; - } else { - break; - } - } - } - - if (hasReplacements) { - inputFile.close(); - - if (!inputFile.open(QIODevice::WriteOnly)) { - fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName)); - return false; - } - - inputFile.write(contents); - } - - return true; - -} - -bool updateLibsXml(const Options &options) -{ - if (options.verbose) - fprintf(stdout, " -- res/values/libs.xml\n"); - - QString fileName = options.outputDirectory + QLatin1String("/res/values/libs.xml"); - if (!QFile::exists(fileName)) { - fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName)); - return false; - } - - QString libsPath = QLatin1String("libs/") + options.architecture + QLatin1Char('/'); - - QString qtLibs = QLatin1String("<item>") + options.stdCppName + QLatin1String("</item>\n"); - QString bundledInLibs; - QString bundledInAssets; - for (const Options::BundledFile &bundledFile : options.bundledFiles) { - if (bundledFile.second.startsWith(QLatin1String("lib/"))) { - QString s = bundledFile.second.mid(sizeof("lib/lib") - 1); - s.chop(sizeof(".so") - 1); - qtLibs += QString::fromLatin1("<item>%1</item>\n").arg(s); - } else if (bundledFile.first.startsWith(libsPath)) { - QString s = bundledFile.first.mid(libsPath.length()); - bundledInLibs += QString::fromLatin1("<item>%1:%2</item>\n") - .arg(s).arg(bundledFile.second); - } else if (bundledFile.first.startsWith(QLatin1String("assets/"))) { - QString s = bundledFile.first.mid(sizeof("assets/") - 1); - bundledInAssets += QString::fromLatin1("<item>%1:%2</item>\n") - .arg(s).arg(bundledFile.second); - } - } - - if (!options.extraPlugins.isEmpty()) { - for (const QString &extraRes : options.extraPlugins) { - QDir resourceDir(extraRes); - const QStringList files = allFilesInside(resourceDir, resourceDir); - for (const QString &file : files) { - QString destinationPath = resourceDir.dirName() + QLatin1Char('/') + file; - if (!file.endsWith(QLatin1String(".so"))) { - bundledInAssets += QStringLiteral("<item>%1:%1</item>\n") - .arg(destinationPath); - } else { - bundledInLibs += QStringLiteral("<item>lib%1:%2</item>\n") - .arg(QString(destinationPath).replace(QLatin1Char('/'), QLatin1Char('_'))) - .arg(destinationPath); - } - } - } - } - - QHash<QString, QString> replacements; - replacements[QLatin1String("<!-- %%INSERT_QT_LIBS%% -->")] = qtLibs; - - if (options.deploymentMechanism == Options::Bundled) { - replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_LIB%% -->")] = bundledInLibs; - replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->")] = bundledInAssets; - } - - QString extraLibs; - if (!options.extraLibs.isEmpty()) { - for (const QString extraLib : options.extraLibs) { - QFileInfo extraLibInfo(extraLib); - QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1); - name.chop(sizeof(".so") - 1); - - extraLibs += QLatin1String("<item>") + name + QLatin1String("</item>\n"); - } - } - replacements[QLatin1String("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs; - - if (!updateFile(fileName, replacements)) - return false; - - return true; -} - -bool updateStringsXml(const Options &options) -{ - if (options.verbose) - fprintf(stdout, " -- res/values/strings.xml\n"); - - QHash<QString, QString> replacements; - replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1); - - QString fileName = options.outputDirectory + QLatin1String("/res/values/strings.xml"); - if (!QFile::exists(fileName)) { - if (options.verbose) - fprintf(stdout, " -- Create strings.xml since it's missing.\n"); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName)); - return false; - } - file.write(QByteArray("<?xml version='1.0' encoding='utf-8'?><resources><string name=\"app_name\">") - .append(QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1).toLatin1()) - .append("</string></resources>\n")); - return true; - } - - if (!updateFile(fileName, replacements)) - return false; - - if (options.gradle) - return true; - - // ant can't (easily) build multiple res folders, - // so we need to replace the "<!-- %%INSERT_STRINGS -->" placeholder - // from the main res folder - QFile stringsXml(fileName); - if (!stringsXml.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName)); - return false; - } - - QXmlStreamReader reader(&stringsXml); - while (!reader.atEnd()) { - reader.readNext(); - if (reader.isStartElement() && - reader.name() == QLatin1String("string") && - reader.attributes().hasAttribute(QLatin1String("name")) && - reader.attributes().value(QLatin1String("name")) == QLatin1String("app_name")) { - return true; - } - } - - replacements.clear(); - replacements[QStringLiteral("<!-- %%INSERT_STRINGS -->")] = QString::fromLatin1("<string name=\"app_name\">%1</string>\n") - .arg(QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1)); - - if (!updateFile(fileName, replacements)) - return false; - - return true; -} - -bool updateAndroidManifest(Options &options) -{ - if (options.verbose) - fprintf(stdout, " -- AndroidManifest.xml \n"); - - QStringList localLibs = options.localLibs; - - // If .pro file overrides dependency detection, we need to see which platform plugin they picked - if (localLibs.isEmpty()) { - QString plugin; - for (const QtDependency &qtDependency : qAsConst(options.qtDependencies)) { - if (qtDependency.relativePath.endsWith(QLatin1String("libqtforandroid.so")) - || qtDependency.relativePath.endsWith(QLatin1String("libqtforandroidGL.so"))) { - if (!plugin.isEmpty() && plugin != qtDependency.relativePath) { - fprintf(stderr, "Both platform plugins libqtforandroid.so and libqtforandroidGL.so included in package. Please include only one.\n"); - return false; - } - - plugin = qtDependency.relativePath; - } - } - - if (plugin.isEmpty()) { - fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n"); - return false; - } - - localLibs.append(plugin); - if (options.verbose) - fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin)); - } - - bool usesGL = false; - for (const QtDependency &qtDependency : qAsConst(options.qtDependencies)) { - if (qtDependency.relativePath.endsWith(QLatin1String("libQt5OpenGL.so")) - || qtDependency.relativePath.endsWith(QLatin1String("libQt5Quick.so"))) { - usesGL = true; - break; - } - } - - QHash<QString, QString> replacements; - replacements[QLatin1String("-- %%INSERT_APP_NAME%% --")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1); - replacements[QLatin1String("-- %%INSERT_APP_LIB_NAME%% --")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1); - replacements[QLatin1String("-- %%INSERT_LOCAL_LIBS%% --")] = localLibs.join(QLatin1Char(':')); - replacements[QLatin1String("-- %%INSERT_LOCAL_JARS%% --")] = options.localJars.join(QLatin1Char(':')); - replacements[QLatin1String("-- %%INSERT_INIT_CLASSES%% --")] = options.initClasses.join(QLatin1Char(':')); - replacements[QLatin1String("package=\"org.qtproject.example\"")] = QString::fromLatin1("package=\"%1\"").arg(options.packageName); - replacements[QLatin1String("-- %%BUNDLE_LOCAL_QT_LIBS%% --")] - = (options.deploymentMechanism == Options::Bundled) ? QString::fromLatin1("1") : QString::fromLatin1("0"); - replacements[QLatin1String("-- %%USE_LOCAL_QT_LIBS%% --")] - = (options.deploymentMechanism != Options::Ministro) ? QString::fromLatin1("1") : QString::fromLatin1("0"); - - QString permissions; - for (const QString &permission : qAsConst(options.permissions)) - permissions += QString::fromLatin1(" <uses-permission android:name=\"%1\" />\n").arg(permission); - replacements[QLatin1String("<!-- %%INSERT_PERMISSIONS -->")] = permissions; - - QString features; - for (const QString &feature : qAsConst(options.features)) - features += QStringLiteral(" <uses-feature android:name=\"%1\" android:required=\"false\" />\n").arg(feature); - if (usesGL) - features += QStringLiteral(" <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"); - - replacements[QLatin1String("<!-- %%INSERT_FEATURES -->")] = features; - - QString androidManifestPath = options.outputDirectory + QLatin1String("/AndroidManifest.xml"); - if (!updateFile(androidManifestPath, replacements)) - return false; - - // read the package, min & target sdk API levels from manifest file. - bool checkOldAndroidLabelString = false; - QFile androidManifestXml(androidManifestPath); - if (androidManifestXml.exists()) { - if (!androidManifestXml.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath)); - return false; - } - - QXmlStreamReader reader(&androidManifestXml); - while (!reader.atEnd()) { - reader.readNext(); - - if (reader.isStartElement()) { - if (reader.name() == QLatin1String("manifest")) { - if (!reader.attributes().hasAttribute(QLatin1String("package"))) { - fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath)); - return false; - } - options.packageName = reader.attributes().value(QLatin1String("package")).toString(); - } else if (reader.name() == QLatin1String("uses-sdk")) { - if (reader.attributes().hasAttribute(QLatin1String("android:minSdkVersion"))) - if (reader.attributes().value(QLatin1String("android:minSdkVersion")).toInt() < 16) { - fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 16\n"); - return false; - } - } else if ((reader.name() == QLatin1String("application") || - reader.name() == QLatin1String("activity")) && - reader.attributes().hasAttribute(QLatin1String("android:label")) && - reader.attributes().value(QLatin1String("android:label")) == QLatin1String("@string/app_name")) { - checkOldAndroidLabelString = true; - } - } - } - - if (reader.hasError()) { - fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString())); - return false; - } - } else { - fprintf(stderr, "No android manifest file"); - return false; - } - - if (checkOldAndroidLabelString) - updateStringsXml(options); - - return true; -} - -bool updateAndroidFiles(Options &options) -{ - if (options.verbose) - fprintf(stdout, "Updating Android package files with project settings.\n"); - - if (!updateLibsXml(options)) - return false; - - if (!updateAndroidManifest(options)) - return false; - - return true; -} - -QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath) -{ - if (!info.exists()) - return QList<QtDependency>(); - - if (info.isDir()) { - QList<QtDependency> ret; - - QDir dir(info.filePath()); - const QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - for (const QString &entry : entries) { - QString s = info.absoluteFilePath() + QLatin1Char('/') + entry; - ret += findFilesRecursively(options, s, rootPath); - } - - return ret; - } else { - return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.length()), info.absoluteFilePath()); - } -} - -QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName) -{ - QFileInfo info(options.qtInstallDirectory + QLatin1Char('/') + fileName); - return findFilesRecursively(options, info, options.qtInstallDirectory + QLatin1Char('/')); -} - -bool readAndroidDependencyXml(Options *options, - const QString &moduleName, - QSet<QString> *usedDependencies, - QSet<QString> *remainingDependencies) -{ - QString androidDependencyName = options->qtInstallDirectory + QString::fromLatin1("/lib/%1-android-dependencies.xml").arg(moduleName); - - QFile androidDependencyFile(androidDependencyName); - if (androidDependencyFile.exists()) { - if (options->verbose) - fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName)); - - if (!androidDependencyFile.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName)); - return false; - } - - QXmlStreamReader reader(&androidDependencyFile); - while (!reader.atEnd()) { - reader.readNext(); - - if (reader.isStartElement()) { - if (reader.name() == QLatin1String("bundled")) { - if (!reader.attributes().hasAttribute(QLatin1String("file"))) { - fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName)); - return false; - } - - QString file = reader.attributes().value(QLatin1String("file")).toString(); - - // Special case, since this is handled by qmlimportscanner instead - if (!options->rootPath.isEmpty() && (file == QLatin1String("qml") || file == QLatin1String("qml/"))) - continue; - - const QList<QtDependency> fileNames = findFilesRecursively(*options, file); - for (const QtDependency &fileName : fileNames) { - if (usedDependencies->contains(fileName.absolutePath)) - continue; - - usedDependencies->insert(fileName.absolutePath); - - if (options->verbose) - fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath)); - - options->qtDependencies.append(fileName); - } - } else if (reader.name() == QLatin1String("jar")) { - int bundling = reader.attributes().value(QLatin1String("bundling")).toInt(); - QString fileName = reader.attributes().value(QLatin1String("file")).toString(); - if (bundling == (options->deploymentMechanism == Options::Bundled)) { - QtDependency dependency(fileName, options->qtInstallDirectory + QLatin1Char('/') + fileName); - if (!usedDependencies->contains(dependency.absolutePath)) { - options->qtDependencies.append(dependency); - usedDependencies->insert(dependency.absolutePath); - } - } - - if (!fileName.isEmpty()) - options->localJars.append(fileName); - - if (reader.attributes().hasAttribute(QLatin1String("initClass"))) { - options->initClasses.append(reader.attributes().value(QLatin1String("initClass")).toString()); - } - } else if (reader.name() == QLatin1String("lib")) { - QString fileName = reader.attributes().value(QLatin1String("file")).toString(); - if (reader.attributes().hasAttribute(QLatin1String("replaces"))) { - QString replaces = reader.attributes().value(QLatin1String("replaces")).toString(); - for (int i=0; i<options->localLibs.size(); ++i) { - if (options->localLibs.at(i) == replaces) { - options->localLibs[i] = fileName; - break; - } - } - } else if (!fileName.isEmpty()) { - options->localLibs.append(fileName); - } - if (fileName.endsWith(QLatin1String(".so"))) { - remainingDependencies->insert(fileName); - } - } else if (reader.name() == QLatin1String("permission")) { - QString name = reader.attributes().value(QLatin1String("name")).toString(); - options->permissions.append(name); - } else if (reader.name() == QLatin1String("feature")) { - QString name = reader.attributes().value(QLatin1String("name")).toString(); - options->features.append(name); - } - } - } - - if (reader.hasError()) { - fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString())); - return false; - } - } else if (options->verbose) { - fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName)); - } - - return true; -} - -QStringList getQtLibsFromElf(const Options &options, const QString &fileName) -{ - QString readElf = options.ndkPath - + QLatin1String("/toolchains/") - + options.toolchainPrefix - + QLatin1Char('-') - + options.toolchainVersion - + QLatin1String("/prebuilt/") - + options.ndkHost - + QLatin1String("/bin/") - + options.toolPrefix - + QLatin1String("-readelf"); -#if defined(Q_OS_WIN32) - readElf += QLatin1String(".exe"); -#endif - - if (!QFile::exists(readElf)) { - fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf)); - return QStringList(); - } - - readElf = QString::fromLatin1("%1 -d -W %2").arg(shellQuote(readElf)).arg(shellQuote(fileName)); - - FILE *readElfCommand = openProcess(readElf); - if (readElfCommand == 0) { - fprintf(stderr, "Cannot execute command %s", qPrintable(readElf)); - return QStringList(); - } - - QStringList ret; - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), readElfCommand) != 0) { - QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer)); - if (line.contains("(NEEDED)") && line.contains("Shared library:") ) { - const int pos = line.lastIndexOf('[') + 1; - QString libraryName = QLatin1String("lib/") + QString::fromLatin1(line.mid(pos, line.length() - pos - 2)); - if (QFile::exists(options.qtInstallDirectory + QLatin1Char('/') + libraryName)) { - ret += libraryName; - } - - } - } - - pclose(readElfCommand); - - return ret; -} - -bool readDependenciesFromElf(Options *options, - const QString &fileName, - QSet<QString> *usedDependencies, - QSet<QString> *remainingDependencies) -{ - // Get dependencies on libraries in $QTDIR/lib - const QStringList dependencies = getQtLibsFromElf(*options, fileName); - - if (options->verbose) { - fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName)); - for (const QString &dep : dependencies) - fprintf(stdout, " %s\n", qPrintable(dep)); - } - // Recursively add dependencies from ELF and supplementary XML information - QList<QString> dependenciesToCheck; - for (const QString &dependency : dependencies) { - if (usedDependencies->contains(dependency)) - continue; - - QString absoluteDependencyPath(options->qtInstallDirectory + QLatin1Char('/') + dependency); - usedDependencies->insert(dependency); - if (!readDependenciesFromElf(options, - absoluteDependencyPath, - usedDependencies, - remainingDependencies)) { - return false; - } - - options->qtDependencies.append(QtDependency(dependency, absoluteDependencyPath)); - if (options->verbose) - fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency)); - dependenciesToCheck.append(dependency); - } - - for (const QString &dependency : qAsConst(dependenciesToCheck)) { - QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1); - qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1)); - if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) { - return false; - } - } - - return true; -} - -bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies); - -bool scanImports(Options *options, QSet<QString> *usedDependencies) -{ - if (options->verbose) - fprintf(stdout, "Scanning for QML imports.\n"); - - QString qmlImportScanner = options->qtInstallDirectory + QLatin1String("/bin/qmlimportscanner"); -#if defined(Q_OS_WIN32) - qmlImportScanner += QLatin1String(".exe"); -#endif - - if (!QFile::exists(qmlImportScanner)) { - fprintf(stderr, "qmlimportscanner not found: %s\n", qPrintable(qmlImportScanner)); - return true; - } - - QString rootPath = options->rootPath; - if (rootPath.isEmpty()) - rootPath = QFileInfo(options->inputFileName).absolutePath(); - else - rootPath = QFileInfo(rootPath).absoluteFilePath(); - - if (!rootPath.endsWith(QLatin1Char('/'))) - rootPath += QLatin1Char('/'); - - QStringList importPaths; - importPaths += shellQuote(options->qtInstallDirectory + QLatin1String("/qml")); - importPaths += rootPath; - for (const QString &qmlImportPath : qAsConst(options->qmlImportPaths)) - importPaths += shellQuote(qmlImportPath); - - qmlImportScanner += QString::fromLatin1(" -rootPath %1 -importPath %2") - .arg(shellQuote(rootPath)) - .arg(importPaths.join(QLatin1Char(' '))); - - FILE *qmlImportScannerCommand = popen(qmlImportScanner.toLocal8Bit().constData(), "r"); - if (qmlImportScannerCommand == 0) { - fprintf(stderr, "Couldn't run qmlimportscanner.\n"); - return false; - } - - QByteArray output; - char buffer[512]; - while (fgets(buffer, sizeof(buffer), qmlImportScannerCommand) != 0) - output += QByteArray(buffer, qstrlen(buffer)); - - QJsonDocument jsonDocument = QJsonDocument::fromJson(output); - if (jsonDocument.isNull()) { - fprintf(stderr, "Invalid json output from qmlimportscanner.\n"); - return false; - } - - QJsonArray jsonArray = jsonDocument.array(); - for (int i=0; i<jsonArray.count(); ++i) { - QJsonValue value = jsonArray.at(i); - if (!value.isObject()) { - fprintf(stderr, "Invalid format of qmlimportscanner output.\n"); - return false; - } - - QJsonObject object = value.toObject(); - QString path = object.value(QLatin1String("path")).toString(); - if (path.isEmpty()) { - fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n", - qPrintable(object.value(QLatin1String("name")).toString())); - } else { - if (options->verbose) - fprintf(stdout, " -- Adding '%s' as QML dependency\n", path.toLocal8Bit().constData()); - - QFileInfo info(path); - - // The qmlimportscanner sometimes outputs paths that do not exist. - if (!info.exists()) { - if (options->verbose) - fprintf(stdout, " -- Skipping because file does not exist.\n"); - continue; - } - - QString absolutePath = info.absolutePath(); - if (!absolutePath.endsWith(QLatin1Char('/'))) - absolutePath += QLatin1Char('/'); - - if (absolutePath.startsWith(rootPath)) { - if (options->verbose) - fprintf(stdout, " -- Skipping because file is in QML root path.\n"); - continue; - } - - QString importPathOfThisImport; - for (const QString &importPath : qAsConst(importPaths)) { -#if defined(Q_OS_WIN32) - Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - QString cleanImportPath = QDir::cleanPath(importPath); - if (info.absoluteFilePath().startsWith(cleanImportPath, caseSensitivity)) { - importPathOfThisImport = importPath; - break; - } - } - - if (importPathOfThisImport.isEmpty()) { - fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath())); - return false; - } - - QDir dir(importPathOfThisImport); - importPathOfThisImport = dir.absolutePath() + QLatin1Char('/'); - - const QList<QtDependency> fileNames = findFilesRecursively(*options, info, importPathOfThisImport); - for (QtDependency fileName : fileNames) { - if (usedDependencies->contains(fileName.absolutePath)) - continue; - - usedDependencies->insert(fileName.absolutePath); - - if (options->verbose) - fprintf(stdout, " -- Appending dependency found by qmlimportscanner: %s\n", qPrintable(fileName.absolutePath)); - - // Put all imports in default import path in assets - fileName.relativePath.prepend(QLatin1String("qml/")); - options->qtDependencies.append(fileName); - - if (fileName.absolutePath.endsWith(QLatin1String(".so"))) { - QSet<QString> remainingDependencies; - if (!readDependenciesFromElf(options, fileName.absolutePath, usedDependencies, &remainingDependencies)) - return false; - - } - } - } - } - - return true; -} - -bool readDependencies(Options *options) -{ - if (options->verbose) - fprintf(stdout, "Detecting dependencies of application.\n"); - - // Override set in .pro file - if (!options->qtDependencies.isEmpty()) { - if (options->verbose) - fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n"); - return true; - } - - QSet<QString> usedDependencies; - QSet<QString> remainingDependencies; - - // Add dependencies of application binary first - if (!readDependenciesFromElf(options, options->applicationBinary, &usedDependencies, &remainingDependencies)) - return false; - - // Jam in the dependencies of the platform plugin, since the application will crash without it - if (!readDependenciesFromElf(options, options->qtInstallDirectory + QLatin1String("/plugins/platforms/android/libqtforandroid.so"), &usedDependencies, &remainingDependencies)) - return false; - - QString qtDir = options->qtInstallDirectory + QLatin1Char('/'); - - while (!remainingDependencies.isEmpty()) { - QSet<QString>::iterator start = remainingDependencies.begin(); - QString fileName = qtDir + *start; - remainingDependencies.erase(start); - - QStringList unmetDependencies; - if (goodToCopy(options, fileName, &unmetDependencies)) { - bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies); - if (!ok) - return false; - } else { - fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n", - qPrintable(fileName), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); - } - } - - QStringList::iterator it = options->localLibs.begin(); - while (it != options->localLibs.end()) { - QStringList unmetDependencies; - if (!goodToCopy(options, qtDir + *it, &unmetDependencies)) { - fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n", - qPrintable(*it), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); - it = options->localLibs.erase(it); - } else { - ++it; - } - } - - if (!options->rootPath.isEmpty() && !scanImports(options, &usedDependencies)) - return false; - - return true; -} - -bool stripFile(const Options &options, const QString &fileName) -{ - QString strip = options.ndkPath - + QLatin1String("/toolchains/") - + options.toolchainPrefix - + QLatin1Char('-') - + options.toolchainVersion - + QLatin1String("/prebuilt/") - + options.ndkHost - + QLatin1String("/bin/") - + options.toolPrefix - + QLatin1String("-strip"); -#if defined(Q_OS_WIN32) - strip += QLatin1String(".exe"); -#endif - - if (!QFile::exists(strip)) { - fprintf(stderr, "Command does not exist: %s\n", qPrintable(strip)); - return false; - } - - strip = QString::fromLatin1("%1 %2").arg(shellQuote(strip)).arg(shellQuote(fileName)); - - FILE *stripCommand = openProcess(strip); - if (stripCommand == 0) { - fprintf(stderr, "Cannot execute command %s", qPrintable(strip)); - return false; - } - - pclose(stripCommand); - - return true; -} - -bool stripLibraries(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Stripping libraries to minimize size.\n"); - - - QString libraryPath = options.outputDirectory - + QLatin1String("/libs/") - + options.architecture; - const QStringList libraries = QDir(libraryPath).entryList(QDir::Files); - for (const QString &library : libraries) { - if (library.endsWith(QLatin1String(".so"))) { - if (!stripFile(options, libraryPath + QLatin1Char('/') + library)) - return false; - } - } - - - return true; -} - -bool containsApplicationBinary(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Checking if application binary is in package.\n"); - - QFileInfo applicationBinary(options.applicationBinary); - QString destinationFileName = options.outputDirectory - + QLatin1String("/libs/") - + options.architecture - + QLatin1Char('/') - + applicationBinary.fileName(); - - if (!QFile::exists(destinationFileName)) { -#if defined(Q_OS_WIN32) - QLatin1String makeTool("mingw32-make"); // Only Mingw host builds supported on Windows currently -#else - QLatin1String makeTool("make"); -#endif - - fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n", - qPrintable(destinationFileName), - qPrintable(makeTool), - qPrintable(options.outputDirectory)); - return false; - } - - return true; -} - -FILE *runAdb(const Options &options, const QString &arguments) -{ - QString adb = options.sdkPath + QLatin1String("/platform-tools/adb"); -#if defined(Q_OS_WIN32) - adb += QLatin1String(".exe"); -#endif - - if (!QFile::exists(adb)) { - fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb)); - return 0; - } - QString installOption; - if (!options.installLocation.isEmpty()) - installOption = QLatin1String(" -s ") + shellQuote(options.installLocation); - - adb = QString::fromLatin1("%1%2 %3").arg(shellQuote(adb)).arg(installOption).arg(arguments); - - if (options.verbose) - fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData()); - - FILE *adbCommand = openProcess(adb); - if (adbCommand == 0) { - fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb)); - return 0; - } - - return adbCommand; -} - -bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies) -{ - if (!file.endsWith(QLatin1String(".so"))) - return true; - - bool ret = true; - const auto libs = getQtLibsFromElf(*options, file); - for (const QString &lib : libs) { - if (!options->qtDependencies.contains(QtDependency(lib, options->qtInstallDirectory + QLatin1Char('/') + lib))) { - ret = false; - unmetDependencies->append(lib); - } - } - - return ret; -} - -bool copyQtFiles(Options *options) -{ - if (options->verbose) { - switch (options->deploymentMechanism) { - case Options::Bundled: - fprintf(stdout, "Copying %d dependencies from Qt into package.\n", options->qtDependencies.size()); - break; - case Options::Ministro: - fprintf(stdout, "Setting %d dependencies from Qt in package.\n", options->qtDependencies.size()); - break; - }; - } - - if (!options->build) - return true; - - QString libsDirectory = QLatin1String("libs/"); - - // Copy other Qt dependencies - QString libDestinationDirectory = libsDirectory + options->architecture + QLatin1Char('/'); - QString assetsDestinationDirectory = QLatin1String("assets/--Added-by-androiddeployqt--/"); - for (const QtDependency &qtDependency : qAsConst(options->qtDependencies)) { - QString sourceFileName = qtDependency.absolutePath; - QString destinationFileName; - - if (qtDependency.relativePath.endsWith(QLatin1String(".so"))) { - QString garbledFileName; - if (qtDependency.relativePath.startsWith(QLatin1String("lib/"))) { - garbledFileName = qtDependency.relativePath.mid(sizeof("lib/") - 1); - } else { - garbledFileName = QLatin1String("lib") - + QString(qtDependency.relativePath).replace(QLatin1Char('/'), QLatin1Char('_')); - - } - destinationFileName = libDestinationDirectory + garbledFileName; - - } else if (qtDependency.relativePath.startsWith(QLatin1String("jar/"))) { - destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1); - } else { - destinationFileName = assetsDestinationDirectory + qtDependency.relativePath; - } - - if (!QFile::exists(sourceFileName)) { - fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName)); - return false; - } - - QStringList unmetDependencies; - if (!goodToCopy(options, sourceFileName, &unmetDependencies)) { - fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n", - qPrintable(sourceFileName), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); - continue; - } - - if (options->deploymentMechanism == Options::Bundled - && !copyFileIfNewer(sourceFileName, - options->outputDirectory + QLatin1Char('/') + destinationFileName, - options->verbose)) { - return false; - } - - options->bundledFiles += qMakePair(destinationFileName, qtDependency.relativePath); - } - - return true; -} - -QStringList getLibraryProjectsInOutputFolder(const Options &options) -{ - QStringList ret; - - QFile file(options.outputDirectory + QLatin1String("/project.properties")); - if (file.open(QIODevice::ReadOnly)) { - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - if (line.startsWith("android.library.reference")) { - int equalSignIndex = line.indexOf('='); - if (equalSignIndex >= 0) { - QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1)); - - QFileInfo info(options.outputDirectory + QLatin1Char('/') + path); - if (QDir::isRelativePath(path) - && info.exists() - && info.isDir() - && info.canonicalFilePath().startsWith(options.outputDirectory)) { - ret += info.canonicalFilePath(); - } - } - } - } - } - - return ret; -} - -bool createAndroidProject(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Running Android tool to create package definition.\n"); - - QString androidToolExecutable = options.sdkPath + QLatin1String("/tools/android"); -#if defined(Q_OS_WIN32) - androidToolExecutable += QLatin1String(".bat"); -#endif - - if (!QFile::exists(androidToolExecutable)) { - fprintf(stderr, "Cannot find Android tool: %s\n", qPrintable(androidToolExecutable)); - return false; - } - - QString androidTool = QString::fromLatin1("%1 update project --path %2 --target %3 --name QtApp") - .arg(shellQuote(androidToolExecutable)) - .arg(shellQuote(options.outputDirectory)) - .arg(shellQuote(options.androidPlatform)); - - if (options.verbose) - fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool)); - - FILE *androidToolCommand = openProcess(androidTool); - if (androidToolCommand == 0) { - fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool)); - return false; - } - - pclose(androidToolCommand); - - // If the project has subprojects inside the current folder, we need to also run android update on these. - const QStringList libraryProjects = getLibraryProjectsInOutputFolder(options); - for (const QString &libraryProject : libraryProjects) { - if (options.verbose) - fprintf(stdout, "Updating subproject %s\n", qPrintable(libraryProject)); - - androidTool = QString::fromLatin1("%1 update lib-project --path %2 --target %3") - .arg(shellQuote(androidToolExecutable)) - .arg(shellQuote(libraryProject)) - .arg(shellQuote(options.androidPlatform)); - - if (options.verbose) - fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool)); - - FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), "r"); - if (androidToolCommand == 0) { - fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool)); - return false; - } - - pclose(androidToolCommand); - } - - return true; -} - -QString findInPath(const QString &fileName) -{ - const QString path = QString::fromLocal8Bit(qgetenv("PATH")); -#if defined(Q_OS_WIN32) - QLatin1Char separator(';'); -#else - QLatin1Char separator(':'); -#endif - - const QStringList paths = path.split(separator); - for (const QString &path : paths) { - QFileInfo fileInfo(path + QLatin1Char('/') + fileName); - if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) - return path + QLatin1Char('/') + fileName; - } - - return QString(); -} - -bool buildAntProject(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Building Android package using ant.\n"); - - QString antTool = options.antTool; - if (antTool.isEmpty()) { -#if defined(Q_OS_WIN32) - antTool = findInPath(QLatin1String("ant.bat")); -#else - antTool = findInPath(QLatin1String("ant")); -#endif - } - - if (antTool.isEmpty()) { - fprintf(stderr, "Cannot find ant in PATH. Please use --ant option to pass in the correct path.\n"); - return false; - } - - if (options.verbose) - fprintf(stdout, "Using ant: %s\n", qPrintable(antTool)); - - QString oldPath = QDir::currentPath(); - if (!QDir::setCurrent(options.outputDirectory)) { - fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory)); - return false; - } - - QString ant = QString::fromLatin1("%1 %2").arg(shellQuote(antTool)).arg(options.releasePackage ? QLatin1String(" release") : QLatin1String(" debug")); - - FILE *antCommand = openProcess(ant); - if (antCommand == 0) { - fprintf(stderr, "Cannot run ant command: %s\n.", qPrintable(ant)); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), antCommand) != 0) { - fprintf(stdout, "%s", buffer); - fflush(stdout); - } - - int errorCode = pclose(antCommand); - if (errorCode != 0) { - fprintf(stderr, "Building the android package failed!\n"); - if (!options.verbose) - fprintf(stderr, " -- For more information, run this command with --verbose.\n"); - return false; - } - - if (!QDir::setCurrent(oldPath)) { - fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath)); - return false; - } - - return true; -} - -typedef QMap<QByteArray, QByteArray> GradleProperties; - -static GradleProperties readGradleProperties(const QString &path) -{ - GradleProperties properties; - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) - return properties; - - const auto lines = file.readAll().split('\n'); - for (const QByteArray &line : lines) { - if (line.trimmed().startsWith('#')) - continue; - - QList<QByteArray> prop(line.split('=')); - if (prop.size() > 1) - properties[prop.at(0).trimmed()] = prop.at(1).trimmed(); - } - file.close(); - return properties; -} - -static bool mergeGradleProperties(const QString &path, GradleProperties properties) -{ - QFile::remove(path + QLatin1Char('~')); - QFile::rename(path, path + QLatin1Char('~')); - QFile file(path); - if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) { - fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName())); - return false; - } - - QFile oldFile(path + QLatin1Char('~')); - if (oldFile.open(QIODevice::ReadOnly)) { - while (!oldFile.atEnd()) { - QByteArray line(oldFile.readLine()); - QList<QByteArray> prop(line.split('=')); - if (prop.size() > 1) { - GradleProperties::iterator it = properties.find(prop.at(0).trimmed()); - if (it != properties.end()) { - file.write(it.key() + '=' + it.value() + '\n'); - properties.erase(it); - continue; - } - } - file.write(line); - } - oldFile.close(); - } - - for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it) - file.write(it.key() + '=' + it.value() + '\n'); - - file.close(); - return true; -} - -bool buildGradleProject(const Options &options) -{ - GradleProperties localProperties; - localProperties["sdk.dir"] = options.sdkPath.toLocal8Bit(); - - if (!mergeGradleProperties(options.outputDirectory + QLatin1String("local.properties"), localProperties)) - return false; - - QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties"); - GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath); - gradleProperties["buildDir"] = "build"; - gradleProperties["qt5AndroidDir"] = (options.qtInstallDirectory + QLatin1String("/src/android/java")).toUtf8(); - gradleProperties["androidCompileSdkVersion"] = options.androidPlatform.split(QLatin1Char('-')).last().toLocal8Bit(); - if (gradleProperties["androidBuildToolsVersion"].isEmpty()) - gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit(); - - if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties)) - return false; - -#if defined(Q_OS_WIN32) - QString gradlePath(options.outputDirectory + QLatin1String("gradlew.bat")); -#else - QString gradlePath(options.outputDirectory + QLatin1String("gradlew")); - { - QFile f(gradlePath); - if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser)) - fprintf(stderr, "Cannot set permissions %s\n", qPrintable(gradlePath)); - } -#endif - - QString oldPath = QDir::currentPath(); - if (!QDir::setCurrent(options.outputDirectory)) { - fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory)); - return false; - } - - QString commandLine = QString::fromLatin1("%1 --no-daemon %2").arg(shellQuote(gradlePath)).arg(options.releasePackage ? QLatin1String(" assembleRelease") : QLatin1String(" assembleDebug")); - if (options.verbose) - commandLine += QLatin1String(" --info"); - - FILE *gradleCommand = openProcess(commandLine); - if (gradleCommand == 0) { - fprintf(stderr, "Cannot run gradle command: %s\n.", qPrintable(commandLine)); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), gradleCommand) != 0) { - fprintf(stdout, "%s", buffer); - fflush(stdout); - } - - int errorCode = pclose(gradleCommand); - if (errorCode != 0) { - fprintf(stderr, "Building the android package failed!\n"); - if (!options.verbose) - fprintf(stderr, " -- For more information, run this command with --verbose.\n"); - return false; - } - - if (!QDir::setCurrent(oldPath)) { - fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath)); - return false; - } - - return true; -} - -bool buildAndroidProject(const Options &options) -{ - return options.gradle ? buildGradleProject(options) - : buildAntProject(options); -} - -bool uninstallApk(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName)); - - - FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + shellQuote(options.packageName)); - if (adbCommand == 0) - return false; - - if (options.verbose || mustReadOutputAnyway) { - char buffer[512]; - while (fgets(buffer, sizeof(buffer), adbCommand) != 0) - if (options.verbose) - fprintf(stdout, "%s", buffer); - } - - int returnCode = pclose(adbCommand); - if (returnCode != 0) { - fprintf(stderr, "Warning: Uninstall failed!\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - - return true; -} - -enum PackageType { - UnsignedAPK, - SignedAPK -}; - -QString apkPath(const Options &options, PackageType pt) -{ - QString path(options.outputDirectory); - if (options.gradle) - path += QLatin1String("/build/outputs/apk/") + QDir(options.outputDirectory).dirName() + QLatin1Char('-'); - else - path += QLatin1String("/bin/QtApp-"); - if (options.releasePackage) { - path += QLatin1String("release-"); - if (pt == UnsignedAPK) - path += QLatin1String("un"); - path += QLatin1String("signed.apk"); - } else { - path += QLatin1String("debug"); - if (pt == SignedAPK) - path += QLatin1String("-signed"); - path += QLatin1String(".apk"); - } - return shellQuote(path); -} - -bool installApk(const Options &options) -{ - fflush(stdout); - // Uninstall if necessary - if (options.uninstallApk) - uninstallApk(options); - - if (options.verbose) - fprintf(stdout, "Installing Android package to device.\n"); - - FILE *adbCommand = runAdb(options, - QLatin1String(" install -r ") - + apkPath(options, options.keyStore.isEmpty() ? UnsignedAPK - : SignedAPK)); - if (adbCommand == 0) - return false; - - if (options.verbose || mustReadOutputAnyway) { - char buffer[512]; - while (fgets(buffer, sizeof(buffer), adbCommand) != 0) - if (options.verbose) - fprintf(stdout, "%s", buffer); - } - - int returnCode = pclose(adbCommand); - if (returnCode != 0) { - fprintf(stderr, "Installing to device failed!\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - - return true; -} - -bool copyStdCpp(Options *options) -{ - if (options->verbose) - fprintf(stdout, "Copying STL library\n"); - - QString filePath = !options->stdCppPath.isEmpty() ? options->stdCppPath - : options->ndkPath - + QLatin1String("/sources/cxx-stl/gnu-libstdc++/") - + options->toolchainVersion - + QLatin1String("/libs/") - + options->architecture - + QLatin1String("/libgnustl_shared.so"); - if (!QFile::exists(filePath)) { - fprintf(stderr, "STL library does not exist at %s\n", qPrintable(filePath)); - return false; - } - - const QString destinationDirectory = options->outputDirectory - + QLatin1String("/libs/") + options->architecture; - - if (!copyFileIfNewer(filePath, destinationDirectory + QLatin1String("/lib") - + options->stdCppName + QLatin1String(".so"), - options->verbose)) { - return false; - } - - return true; -} - -bool jarSignerSignPackage(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Signing Android package.\n"); - - QString jdkPath = options.jdkPath; - - if (jdkPath.isEmpty()) - jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME")); - -#if defined(Q_OS_WIN32) - QString jarSignerTool = QString::fromLatin1("jarsigner.exe"); -#else - QString jarSignerTool = QString::fromLatin1("jarsigner"); -#endif - - if (jdkPath.isEmpty() || !QFile::exists(jdkPath + QLatin1String("/bin/") + jarSignerTool)) - jarSignerTool = findInPath(jarSignerTool); - else - jarSignerTool = jdkPath + QLatin1String("/bin/") + jarSignerTool; - - if (!QFile::exists(jarSignerTool)) { - fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n"); - return false; - } - - jarSignerTool = QString::fromLatin1("%1 -sigalg %2 -digestalg %3 -keystore %4") - .arg(shellQuote(jarSignerTool)).arg(shellQuote(options.sigAlg)).arg(shellQuote(options.digestAlg)).arg(shellQuote(options.keyStore)); - - if (!options.keyStorePassword.isEmpty()) - jarSignerTool += QString::fromLatin1(" -storepass %1").arg(shellQuote(options.keyStorePassword)); - - if (!options.storeType.isEmpty()) - jarSignerTool += QString::fromLatin1(" -storetype %1").arg(shellQuote(options.storeType)); - - if (!options.keyPass.isEmpty()) - jarSignerTool += QString::fromLatin1(" -keypass %1").arg(shellQuote(options.keyPass)); - - if (!options.sigFile.isEmpty()) - jarSignerTool += QString::fromLatin1(" -sigfile %1").arg(shellQuote(options.sigFile)); - - if (!options.signedJar.isEmpty()) - jarSignerTool += QString::fromLatin1(" -signedjar %1").arg(shellQuote(options.signedJar)); - - if (!options.tsaUrl.isEmpty()) - jarSignerTool += QString::fromLatin1(" -tsa %1").arg(shellQuote(options.tsaUrl)); - - if (!options.tsaCert.isEmpty()) - jarSignerTool += QString::fromLatin1(" -tsacert %1").arg(shellQuote(options.tsaCert)); - - if (options.internalSf) - jarSignerTool += QLatin1String(" -internalsf"); - - if (options.sectionsOnly) - jarSignerTool += QLatin1String(" -sectionsonly"); - - if (options.protectedAuthenticationPath) - jarSignerTool += QLatin1String(" -protected"); - - jarSignerTool += QString::fromLatin1(" %1 %2") - .arg(apkPath(options, UnsignedAPK)) - .arg(shellQuote(options.keyStoreAlias)); - - FILE *jarSignerCommand = openProcess(jarSignerTool); - if (jarSignerCommand == 0) { - fprintf(stderr, "Couldn't run jarsigner.\n"); - return false; - } - - if (options.verbose) { - char buffer[512]; - while (fgets(buffer, sizeof(buffer), jarSignerCommand) != 0) - fprintf(stdout, "%s", buffer); - } - - int errorCode = pclose(jarSignerCommand); - if (errorCode != 0) { - fprintf(stderr, "jarsigner command failed.\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - - QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - - if (!QFile::exists(zipAlignTool)) { - zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - if (!QFile::exists(zipAlignTool)) { - fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool)); - return false; - } - } - - zipAlignTool = QString::fromLatin1("%1%2 -f 4 %3 %4") - .arg(shellQuote(zipAlignTool)) - .arg(options.verbose ? QString::fromLatin1(" -v") : QString()) - .arg(apkPath(options, UnsignedAPK)) - .arg(apkPath(options, SignedAPK)); - - FILE *zipAlignCommand = openProcess(zipAlignTool); - if (zipAlignCommand == 0) { - fprintf(stderr, "Couldn't run zipalign.\n"); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0) - fprintf(stdout, "%s", buffer); - - errorCode = pclose(zipAlignCommand); - if (errorCode != 0) { - fprintf(stderr, "zipalign command failed.\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - - return QFile::remove(apkPath(options, UnsignedAPK)); -} - -bool signPackage(const Options &options) -{ - QString apksignerTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/apksigner"); -#if defined(Q_OS_WIN32) - apksignerTool += QLatin1String(".bat"); -#endif - - if (options.jarSigner || !QFile::exists(apksignerTool)) - return jarSignerSignPackage(options); - - // APKs signed with apksigner must not be changed after they're signed, therefore we need to zipalign it before we sign it. - - QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - - if (!QFile::exists(zipAlignTool)) { - zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - if (!QFile::exists(zipAlignTool)) { - fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool)); - return false; - } - } - - zipAlignTool = QString::fromLatin1("%1%2 -f 4 %3 %4") - .arg(shellQuote(zipAlignTool)) - .arg(options.verbose ? QString::fromLatin1(" -v") : QString()) - .arg(apkPath(options, UnsignedAPK)) - .arg(apkPath(options, SignedAPK)); - - FILE *zipAlignCommand = openProcess(zipAlignTool); - if (zipAlignCommand == 0) { - fprintf(stderr, "Couldn't run zipalign.\n"); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0) - fprintf(stdout, "%s", buffer); - - int errorCode = pclose(zipAlignCommand); - if (errorCode != 0) { - fprintf(stderr, "zipalign command failed.\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - - QString apkSignerCommandLine = QString::fromLatin1("%1 sign --ks %2") - .arg(shellQuote(apksignerTool)).arg(shellQuote(options.keyStore)); - - if (!options.keyStorePassword.isEmpty()) - apkSignerCommandLine += QString::fromLatin1(" --ks-pass pass:%1").arg(shellQuote(options.keyStorePassword)); - - if (!options.keyStoreAlias.isEmpty()) - apkSignerCommandLine += QString::fromLatin1(" --ks-key-alias %1").arg(shellQuote(options.keyStoreAlias)); - - if (!options.keyPass.isEmpty()) - apkSignerCommandLine += QString::fromLatin1(" --key-pass pass:%1").arg(shellQuote(options.keyPass)); - - if (options.verbose) - apkSignerCommandLine += QLatin1String(" --verbose"); - - apkSignerCommandLine += QString::fromLatin1(" %1") - .arg(apkPath(options, SignedAPK)); - - auto apkSignerRunner = [&] { - FILE *apkSignerCommand = openProcess(apkSignerCommandLine); - if (apkSignerCommand == 0) { - fprintf(stderr, "Couldn't run apksigner.\n"); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), apkSignerCommand) != 0) - fprintf(stdout, "%s", buffer); - - errorCode = pclose(apkSignerCommand); - if (errorCode != 0) { - fprintf(stderr, "apksigner command failed.\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); - return false; - } - return true; - }; - - // Sign the package - if (!apkSignerRunner()) - return false; - - apkSignerCommandLine = QString::fromLatin1("%1 verify --verbose %2") - .arg(shellQuote(apksignerTool)).arg(apkPath(options, SignedAPK)); - - // Verify the package and remove the unsigned apk - return apkSignerRunner() && QFile::remove(apkPath(options, UnsignedAPK)); -} - -bool copyGdbServer(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Copying gdbserver into package.\n"); - - QString architectureSubDirectory; - if (options.architecture == QLatin1String("arm64-v8a")) - architectureSubDirectory = QLatin1String("android-arm64"); - else if (options.architecture.startsWith(QLatin1String("arm"))) - architectureSubDirectory = QLatin1String("android-arm"); - else - architectureSubDirectory = QLatin1String("android-") + options.architecture; - - QString gdbServerBinary = options.ndkPath - + QLatin1String("/prebuilt/") - + architectureSubDirectory - + QLatin1String("/gdbserver/gdbserver"); - if (!QFile::exists(gdbServerBinary)) { - fprintf(stderr, "Cannot find gdbserver at %s.\n", qPrintable(gdbServerBinary)); - return false; - } - - QString gdbServerTarget = options.outputDirectory + QLatin1String("/libs/") + options.architecture; - - if (!copyFileIfNewer(gdbServerBinary, - gdbServerTarget + QLatin1String("/gdbserver"), - options.verbose) - || !copyFileIfNewer(gdbServerBinary, - gdbServerTarget + QLatin1String("/libgdbserver.so"), - options.verbose)) { - return false; - } - - QString addedByAndroidDeployQtPath = options.outputDirectory + QLatin1String("/assets/--Added-by-androiddeployqt--/"); - if (!QDir().mkpath(addedByAndroidDeployQtPath)) { - fprintf(stderr, "Failed to create directory '%s'", qPrintable(addedByAndroidDeployQtPath)); - return false; - } - QFile f(addedByAndroidDeployQtPath + QLatin1String("debugger.command")); - if (!f.open(QIODevice::WriteOnly)) { - fprintf(stderr, "Failed to create directory '%s'", qPrintable(addedByAndroidDeployQtPath)); - return false; - } - f.write("lib/libgdbserver.so --multi +"); - f.close(); - - return true; -} - -bool generateAssetsFileList(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Pregenerating entry list for assets file engine.\n"); - - QString assetsPath = options.outputDirectory + QLatin1String("/assets/"); - QString addedByAndroidDeployQtPath = assetsPath + QLatin1String("--Added-by-androiddeployqt--/"); - if (!QDir().mkpath(addedByAndroidDeployQtPath)) { - fprintf(stderr, "Failed to create directory '%s'", qPrintable(addedByAndroidDeployQtPath)); - return false; - } - - QFile file(addedByAndroidDeployQtPath + QLatin1String("/qt_cache_pregenerated_file_list")); - if (file.open(QIODevice::WriteOnly)) { - QDirIterator dirIterator(assetsPath, - QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, - QDirIterator::Subdirectories); - - QHash<QString, QStringList> directoryContents; - while (dirIterator.hasNext()) { - const QString name = dirIterator.next().mid(assetsPath.length()); - - int slashIndex = name.lastIndexOf(QLatin1Char('/')); - QString pathName = slashIndex >= 0 ? name.left(slashIndex) : QString::fromLatin1("/"); - QString fileName = slashIndex >= 0 ? name.mid(pathName.length() + 1) : name; - - if (!fileName.isEmpty() && dirIterator.fileInfo().isDir() && !fileName.endsWith(QLatin1Char('/'))) - fileName += QLatin1Char('/'); - - if (fileName.isEmpty() && !directoryContents.contains(pathName)) - directoryContents[pathName] = QStringList(); - else if (!fileName.isEmpty()) - directoryContents[pathName].append(fileName); - } - - QDataStream stream(&file); - stream.setVersion(QDataStream::Qt_5_3); - for (auto it = directoryContents.cbegin(), end = directoryContents.cend(); it != end; ++it) { - const QStringList &entryList = it.value(); - stream << it.key() << entryList.size(); - for (const QString &entry : entryList) - stream << entry; - } - } else { - fprintf(stderr, "Pregenerating entry list for assets file engine failed!\n"); - return false; - } - - return true; -} - -enum ErrorCode -{ - Success, - SyntaxErrorOrHelpRequested = 1, - CannotReadInputFile = 2, - CannotCopyAndroidTemplate = 3, - CannotReadDependencies = 4, - CannotCopyGnuStl = 5, - CannotCopyQtFiles = 6, - CannotFindApplicationBinary = 7, - CannotCopyGdbServer = 8, - CannotStripLibraries = 9, - CannotCopyAndroidExtraLibs = 10, - CannotCopyAndroidSources = 11, - CannotUpdateAndroidFiles = 12, - CannotCreateAndroidProject = 13, - CannotBuildAndroidProject = 14, - CannotSignPackage = 15, - CannotInstallApk = 16, - CannotGenerateAssetsFileList = 18, - CannotCopyAndroidExtraResources = 19 -}; - -int main(int argc, char *argv[]) -{ - QCoreApplication a(argc, argv); - - Options options = parseOptions(); - if (options.helpRequested || options.outputDirectory.isEmpty()) { - printHelp(); - return SyntaxErrorOrHelpRequested; - } - - options.timer.start(); - - if (!readInputFile(&options)) - return CannotReadInputFile; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Read input file\n", options.timer.elapsed()); - - fprintf(stdout, -// "012345678901234567890123456789012345678901234567890123456789012345678901" - "Generating Android Package\n" - " Input file: %s\n" - " Output directory: %s\n" - " Application binary: %s\n" - " Android build platform: %s\n" - " Install to device: %s\n", - qPrintable(options.inputFileName), - qPrintable(options.outputDirectory), - qPrintable(options.applicationBinary), - qPrintable(options.androidPlatform), - options.installApk - ? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation)) - : "No" - ); - - if (options.build) { - if (options.gradle) - cleanAndroidFiles(options); - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Cleaned Android file\n", options.timer.elapsed()); - - if (!copyAndroidTemplate(options)) - return CannotCopyAndroidTemplate; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied Android template\n", options.timer.elapsed()); - } - - if (!readDependencies(&options)) - return CannotReadDependencies; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Read dependencies\n", options.timer.elapsed()); - - if (options.deploymentMechanism != Options::Ministro && !copyStdCpp(&options)) - return CannotCopyGnuStl; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied GNU STL\n", options.timer.elapsed()); - - if (!copyQtFiles(&options)) - return CannotCopyQtFiles; - - if (options.build) { - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied Qt files\n", options.timer.elapsed()); - - if (!containsApplicationBinary(options)) - return CannotFindApplicationBinary; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Checked for application binary\n", options.timer.elapsed()); - - bool needToCopyGdbServer = options.gdbServer == Options::True - || (options.gdbServer == Options::Auto && !options.releasePackage); - if (needToCopyGdbServer && !copyGdbServer(options)) - return CannotCopyGdbServer; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied GDB server\n", options.timer.elapsed()); - - if (!copyAndroidExtraLibs(options)) - return CannotCopyAndroidExtraLibs; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied extra libs\n", options.timer.elapsed()); - - if (!copyAndroidExtraResources(options)) - return CannotCopyAndroidExtraResources; - - if (!copyAndroidSources(options)) - return CannotCopyAndroidSources; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied android sources\n", options.timer.elapsed()); - - if (!stripLibraries(options)) - return CannotStripLibraries; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Stripped libraries\n", options.timer.elapsed()); - - if (!updateAndroidFiles(options)) - return CannotUpdateAndroidFiles; - - if (options.generateAssetsFileList && !generateAssetsFileList(options)) - return CannotGenerateAssetsFileList; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Updated files\n", options.timer.elapsed()); - - if (!options.gradle && !createAndroidProject(options)) - return CannotCreateAndroidProject; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Created project\n", options.timer.elapsed()); - - if (!buildAndroidProject(options)) - return CannotBuildAndroidProject; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Built project\n", options.timer.elapsed()); - - if (!options.keyStore.isEmpty() && !signPackage(options)) - return CannotSignPackage; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Signed package\n", options.timer.elapsed()); - } - - if (options.installApk && !installApk(options)) - return CannotInstallApk; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Installed APK\n", options.timer.elapsed()); - - fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.); - - if (options.installApk) - fprintf(stdout, " -- It can now be run from the selected device/emulator.\n"); - - fprintf(stdout, " -- File: %s\n", qPrintable(apkPath(options, options.keyStore.isEmpty() ? UnsignedAPK - : SignedAPK))); - fflush(stdout); - return 0; -} |