summaryrefslogtreecommitdiffstats
path: root/src/tools/androiddeployqt/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/androiddeployqt/main.cpp')
-rw-r--r--src/tools/androiddeployqt/main.cpp740
1 files changed, 468 insertions, 272 deletions
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index 3db00ad01f..20030bd3bd 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -13,7 +13,7 @@
#include <QXmlStreamReader>
#include <QStandardPaths>
#include <QUuid>
-#include <QDirIterator>
+#include <QDirListing>
#include <QElapsedTimer>
#include <QRegularExpression>
#include <QSettings>
@@ -70,10 +70,18 @@ struct QtDependency
struct QtInstallDirectoryWithTriple
{
- QtInstallDirectoryWithTriple(const QString &dir = QString(), const QString &t = QString()) :
- qtInstallDirectory(dir), triple(t), enabled(false) {}
+ QtInstallDirectoryWithTriple(const QString &dir = QString(),
+ const QString &t = QString(),
+ const QHash<QString, QString> &dirs = QHash<QString, QString>()
+ ) :
+ qtInstallDirectory(dir),
+ qtDirectories(dirs),
+ triple(t),
+ enabled(false)
+ {}
QString qtInstallDirectory;
+ QHash<QString, QString> qtDirectories;
QString triple;
bool enabled;
};
@@ -93,7 +101,6 @@ struct Options
, internalSf(false)
, sectionsOnly(false)
, protectedAuthenticationPath(false)
- , jarSigner(false)
, installApk(false)
, uninstallApk(false)
, qmlImportScannerBinaryPath()
@@ -129,8 +136,15 @@ struct Options
// Build paths
QString qtInstallDirectory;
+ QHash<QString, QString> qtDirectories;
+ QString qtDataDirectory;
+ QString qtLibsDirectory;
+ QString qtLibExecsDirectory;
+ QString qtPluginsDirectory;
+ QString qtQmlDirectory;
QString qtHostDirectory;
std::vector<QString> extraPrefixDirs;
+ QStringList androidDeployPlugins;
// Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder
// when looking for dependencies.
std::vector<QString> extraLibraryDirs;
@@ -150,7 +164,7 @@ struct Options
QString versionName;
QString versionCode;
QByteArray minSdkVersion{"23"};
- QByteArray targetSdkVersion{"30"};
+ QByteArray targetSdkVersion{"34"};
// lib c++ path
QString stdCppPath;
@@ -191,7 +205,6 @@ struct Options
bool internalSf;
bool sectionsOnly;
bool protectedAuthenticationPath;
- bool jarSigner;
QString apkPath;
// Installation information
@@ -200,12 +213,19 @@ struct Options
QString installLocation;
// Per architecture collected information
- void setCurrentQtArchitecture(const QString &arch, const QString &directory)
+ void setCurrentQtArchitecture(const QString &arch,
+ const QString &directory,
+ const QHash<QString, QString> &directories)
{
currentArchitecture = arch;
qtInstallDirectory = directory;
+ qtDataDirectory = directories["qtDataDirectory"_L1];
+ qtLibsDirectory = directories["qtLibsDirectory"_L1];
+ qtLibExecsDirectory = directories["qtLibExecsDirectory"_L1];
+ qtPluginsDirectory = directories["qtPluginsDirectory"_L1];
+ qtQmlDirectory = directories["qtQmlDirectory"_L1];
}
- typedef QPair<QString, QString> BundledFile;
+ using BundledFile = std::pair<QString, QString>;
QHash<QString, QList<BundledFile>> bundledFiles;
QHash<QString, QList<QtDependency>> qtDependencies;
QHash<QString, QStringList> localLibs;
@@ -218,6 +238,7 @@ struct Options
// Override qml import scanner path
QString qmlImportScannerBinaryPath;
+ bool qmlSkipImportScanning = false;
};
static const QHash<QByteArray, QByteArray> elfArchitectures = {
@@ -227,6 +248,12 @@ static const QHash<QByteArray, QByteArray> elfArchitectures = {
{"x86_64", "x86_64"}
};
+bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
+bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
+ const QString &moduleUrl);
+bool readDependenciesFromElf(Options *options, const QString &fileName,
+ QSet<QString> *usedDependencies, QSet<QString> *remainingDependencies);
+
QString architectureFromName(const QString &name)
{
QRegularExpression architecture(QStringLiteral("_(armeabi-v7a|arm64-v8a|x86|x86_64).so$"));
@@ -292,7 +319,6 @@ QString fileArchitecture(const Options &options, const QString &path)
char buffer[512];
while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) {
QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
- QString library;
line = line.trimmed();
if (line.startsWith("Arch: ")) {
auto it = elfArchitectures.find(line.mid(6));
@@ -321,7 +347,7 @@ void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir &
for (const QFileInfo &src : srcEntries)
if (dst.fileName() == src.fileName()) {
if (dst.isDir())
- deleteMissingFiles(options, src.absoluteDir(), dst.absoluteDir());
+ deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath());
found = true;
break;
}
@@ -359,7 +385,6 @@ Options parseOptions()
} else if (argument.compare("--aab"_L1, Qt::CaseInsensitive) == 0) {
options.buildAAB = true;
options.build = true;
- options.jarSigner = true;
} else if (!options.buildAAB && argument.compare("--no-build"_L1, Qt::CaseInsensitive) == 0) {
options.build = false;
} else if (argument.compare("--install"_L1, Qt::CaseInsensitive) == 0) {
@@ -425,6 +450,7 @@ Options parseOptions()
const QString storeAlias = qEnvironmentVariable("QT_ANDROID_KEYSTORE_ALIAS");
if (keyStore.isEmpty() || storeAlias.isEmpty()) {
options.helpRequested = true;
+ fprintf(stderr, "Package signing path and alias values are not specified.\n");
} else {
fprintf(stdout,
"Using package signing path and alias values found from the "
@@ -432,9 +458,14 @@ Options parseOptions()
options.keyStore = keyStore;
options.keyStoreAlias = storeAlias;
}
- } else {
+ } else if (!arguments.at(i + 1).startsWith("--"_L1) &&
+ !arguments.at(i + 2).startsWith("--"_L1)) {
options.keyStore = arguments.at(++i);
options.keyStoreAlias = arguments.at(++i);
+ } else {
+ options.helpRequested = true;
+ fprintf(stderr, "Package signing path and alias values are not "
+ "specified.\n");
}
// Do not override if the passwords are provided through arguments
@@ -494,8 +525,6 @@ Options parseOptions()
options.sectionsOnly = true;
} else if (argument.compare("--protected"_L1, Qt::CaseInsensitive) == 0) {
options.protectedAuthenticationPath = true;
- } else if (argument.compare("--jarsigner"_L1, Qt::CaseInsensitive) == 0) {
- options.jarSigner = true;
} else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) {
options.auxMode = true;
} else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) {
@@ -531,113 +560,112 @@ Options parseOptions()
void printHelp()
{
- 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"
- "\n"
- " --deployment <mechanism>: Supported deployment mechanisms:\n"
- " bundled (default): Includes Qt files in stand-alone package.\n"
- " unbundled: Assumes native libraries are present on the device\n"
- " and does not include them in the APK.\n"
- "\n"
- " --aab: Build an Android App Bundle.\n"
- "\n"
- " --no-build: Do not build the package, it is useful to just install\n"
- " a package previously built.\n"
- "\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"
- "\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"
- "\n"
- " --device [device ID]: Use specified device for deployment. Default\n"
- " is the device selected by default by adb.\n"
- "\n"
- " --android-platform <platform>: Builds against the given android\n"
- " platform. By default, the highest available version will be\n"
- " used.\n"
- "\n"
- " --release: Builds a package ready for release. By default, the\n"
- " package will be signed with a debug key.\n"
- "\n"
- " --sign <url/to/keystore> <alias>: Signs the package with the\n"
- " specified keystore, alias and store password.\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"
- "\n"
- " NOTE: To conceal the keystore information, the environment variables\n"
- " QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to\n"
- " set the values keysotore and alias respectively.\n"
- " Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,\n"
- " and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key\n"
- " passwords respectively. This option needs only the --sign parameter.\n"
- "\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"
- "\n"
- " --qml-import-paths: Specify additional search paths for QML\n"
- " imports.\n"
- "\n"
- " --verbose: Prints out information during processing.\n"
- "\n"
- " --no-generated-assets-cache: Do not pregenerate the entry list for\n"
- " the assets file engine.\n"
- "\n"
- " --aux-mode: Operate in auxiliary mode. This will only copy the\n"
- " dependencies into the build directory and update the XML templates.\n"
- " The project will not be built or installed.\n"
- "\n"
- " --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.\n"
- "\n"
- " --qml-importscanner-binary <path/to/qmlimportscanner>: Override the\n"
- " default qmlimportscanner binary path. By default the\n"
- " qmlimportscanner binary is located using the Qt directory\n"
- " specified in the input file.\n"
- "\n"
- " --depfile <path/to/depfile>: Output a dependency file.\n"
- "\n"
- " --builddir <path/to/build/directory>: build directory. Necessary when\n"
- " generating a depfile because ninja requires relative paths.\n"
- "\n"
- " --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after\n"
- " running androiddeployqt. This option simplifies debugging of\n"
- " the resource bundle content, but it should not be used when deploying\n"
- " a project, since it litters the 'assets' directory.\n"
- "\n"
- " --copy-dependencies-only: resolve application dependencies and stop\n"
- " deploying process after all libraries and resources that the\n"
- " application depends on have been copied.\n"
- "\n"
- " --help: Displays this information.\n",
- qPrintable(QCoreApplication::arguments().at(0))
- );
+ fprintf(stderr, R"(
+Syntax: androiddeployqt --output <destination> [options]
+
+Creates an Android package in the build directory <destination> and
+builds it into an .apk file.
+
+Optional arguments:
+ --input <inputfile>: Reads <inputfile> for options generated by
+ qmake. A default file name based on the current working
+ directory will be used if nothing else is specified.
+
+ --deployment <mechanism>: Supported deployment mechanisms:
+ bundled (default): Includes Qt files in stand-alone package.
+ unbundled: Assumes native libraries are present on the device
+ and does not include them in the APK.
+
+ --aab: Build an Android App Bundle.
+
+ --no-build: Do not build the package, it is useful to just install
+ a package previously built.
+
+ --install: Installs apk to device/emulator. By default this step is
+ not taken. If the application has previously been installed on
+ the device, it will be uninstalled first.
+
+ --reinstall: Installs apk to device/emulator. By default this step
+ is not taken. If the application has previously been installed on
+ the device, it will be overwritten, but its data will be left
+ intact.
+
+ --device [device ID]: Use specified device for deployment. Default
+ is the device selected by default by adb.
+
+ --android-platform <platform>: Builds against the given android
+ platform. By default, the highest available version will be
+ used.
+
+ --release: Builds a package ready for release. By default, the
+ package will be signed with a debug key.
+
+ --sign <url/to/keystore> <alias>: Signs the package with the
+ specified keystore, alias and store password.
+ Optional arguments for use with signing:
+ --storepass <password>: Keystore password.
+ --storetype <type>: Keystore type.
+ --keypass <password>: Password for private key (if different
+ from keystore password.)
+ --sigfile <file>: Name of .SF/.DSA file.
+ --digestalg <name>: Name of digest algorithm. Default is
+ "SHA-256".
+ --sigalg <name>: Name of signature algorithm. Default is
+ "SHA256withRSA".
+ --tsa <url>: Location of the Time Stamping Authority.
+ --tsacert <alias>: Public key certificate for TSA.
+ --internalsf: Include the .SF file inside the signature block.
+ --sectionsonly: Don't compute hash of entire manifest.
+ --protected: Keystore has protected authentication path.
+ --jarsigner: Deprecated, ignored.
+
+ NOTE: To conceal the keystore information, the environment variables
+ QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to
+ set the values keysotore and alias respectively.
+ Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,
+ and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key
+ passwords respectively. This option needs only the --sign parameter.
+
+ --jdk <path/to/jdk>: Used to find the jarsigner tool when used
+ in combination with the --release argument. By default,
+ an attempt is made to detect the tool using the JAVA_HOME and
+ PATH environment variables, in that order.
+
+ --qml-import-paths: Specify additional search paths for QML
+ imports.
+
+ --verbose: Prints out information during processing.
+
+ --no-generated-assets-cache: Do not pregenerate the entry list for
+ the assets file engine.
+
+ --aux-mode: Operate in auxiliary mode. This will only copy the
+ dependencies into the build directory and update the XML templates.
+ The project will not be built or installed.
+
+ --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.
+
+ --qml-importscanner-binary <path/to/qmlimportscanner>: Override the
+ default qmlimportscanner binary path. By default the
+ qmlimportscanner binary is located using the Qt directory
+ specified in the input file.
+
+ --depfile <path/to/depfile>: Output a dependency file.
+
+ --builddir <path/to/build/directory>: build directory. Necessary when
+ generating a depfile because ninja requires relative paths.
+
+ --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after
+ running androiddeployqt. This option simplifies debugging of
+ the resource bundle content, but it should not be used when deploying
+ a project, since it litters the 'assets' directory.
+
+ --copy-dependencies-only: resolve application dependencies and stop
+ deploying process after all libraries and resources that the
+ application depends on have been copied.
+
+ --help: Displays this information.
+)");
}
// Since strings compared will all start with the same letters,
@@ -648,10 +676,10 @@ bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &
QString s1 = fi1.baseName();
QString s2 = fi2.baseName();
- if (s1.length() == s2.length())
+ if (s1.size() == s2.size())
return s1 > s2;
else
- return s1.length() > s2.length();
+ return s1.size() > s2.size();
}
// Files which contain templates that need to be overwritten by build data should be overwritten every
@@ -741,10 +769,10 @@ QString cleanPackageName(QString packageName)
// No keywords
qsizetype index = -1;
- while (index < packageName.length()) {
+ while (index < packageName.size()) {
qsizetype next = packageName.indexOf(u'.', index + 1);
if (next == -1)
- next = packageName.length();
+ next = packageName.size();
QString word = packageName.mid(index + 1, next - index - 1);
if (!word.isEmpty()) {
QChar c = word[0];
@@ -781,7 +809,7 @@ QString detectLatestAndroidPlatform(const QString &sdkPath)
std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
- QFileInfo latestPlatform = fileInfos.first();
+ const QFileInfo& latestPlatform = fileInfos.constFirst();
return latestPlatform.baseName();
}
@@ -809,6 +837,66 @@ bool parseCmakeBoolean(const QJsonValue &value)
|| stringValue.toInt() > 0);
}
+bool readInputFileDirectory(Options *options, QJsonObject &jsonObject, const QString keyName)
+{
+ const QJsonValue qtDirectory = jsonObject.value(keyName);
+ if (qtDirectory.isUndefined()) {
+ for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
+ if (keyName == "qtDataDirectory"_L1) {
+ options->architectures[it.key()].qtDirectories[keyName] = "."_L1;
+ break;
+ } else if (keyName == "qtLibsDirectory"_L1) {
+ options->architectures[it.key()].qtDirectories[keyName] = "lib"_L1;
+ break;
+ } else if (keyName == "qtLibExecsDirectory"_L1) {
+ options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir();
+ break;
+ } else if (keyName == "qtPluginsDirectory"_L1) {
+ options->architectures[it.key()].qtDirectories[keyName] = "plugins"_L1;
+ break;
+ } else if (keyName == "qtQmlDirectory"_L1) {
+ options->architectures[it.key()].qtDirectories[keyName] = "qml"_L1;
+ break;
+ }
+ }
+ return true;
+ }
+
+ if (qtDirectory.isObject()) {
+ const QJsonObject object = qtDirectory.toObject();
+ for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
+ if (it.value().isUndefined()) {
+ fprintf(stderr,
+ "Invalid '%s' record in deployment settings: %s\n",
+ qPrintable(keyName),
+ qPrintable(it.value().toString()));
+ return false;
+ }
+ if (it.value().isNull())
+ continue;
+ if (!options->architectures.contains(it.key())) {
+ fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()),
+ qPrintable(options->architectures.keys().join(u',')));
+ return false;
+ }
+ options->architectures[it.key()].qtDirectories[keyName] = it.value().toString();
+ }
+ } else if (qtDirectory.isString()) {
+ // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch.
+ // We assume Qt > 5.14 where all architectures are in the same directory.
+ const QString directory = qtDirectory.toString();
+ options->architectures["arm64-v8a"_L1].qtDirectories[keyName] = directory;
+ options->architectures["armeabi-v7a"_L1].qtDirectories[keyName] = directory;
+ options->architectures["x86"_L1].qtDirectories[keyName] = directory;
+ options->architectures["x86_64"_L1].qtDirectories[keyName] = directory;
+ } else {
+ fprintf(stderr, "Invalid format for %s in json file %s.\n",
+ qPrintable(keyName), qPrintable(options->inputFileName));
+ return false;
+ }
+ return true;
+}
+
bool readInputFile(Options *options)
{
QFile file(options->inputFileName);
@@ -865,7 +953,8 @@ bool readInputFile(Options *options)
const QJsonObject object = qtInstallDirectory.toObject();
for (auto it = object.constBegin(); it != object.constEnd(); ++it) {
if (it.value().isUndefined()) {
- fprintf(stderr, "Invalid architecture: %s\n",
+ fprintf(stderr,
+ "Invalid 'qt' record in deployment settings: %s\n",
qPrintable(it.value().toString()));
return false;
}
@@ -893,6 +982,14 @@ bool readInputFile(Options *options)
return false;
}
}
+
+ if (!readInputFileDirectory(options, jsonObject, "qtDataDirectory"_L1) ||
+ !readInputFileDirectory(options, jsonObject, "qtLibsDirectory"_L1) ||
+ !readInputFileDirectory(options, jsonObject, "qtLibExecsDirectory"_L1) ||
+ !readInputFileDirectory(options, jsonObject, "qtPluginsDirectory"_L1) ||
+ !readInputFileDirectory(options, jsonObject, "qtQmlDirectory"_L1))
+ return false;
+
{
const QJsonValue qtHostDirectory = jsonObject.value("qtHostDir"_L1);
if (!qtHostDirectory.isUndefined()) {
@@ -915,6 +1012,11 @@ bool readInputFile(Options *options)
}
{
+ const auto androidDeployPlugins = jsonObject.value("android-deploy-plugins"_L1).toString();
+ options->androidDeployPlugins = androidDeployPlugins.split(";"_L1, Qt::SkipEmptyParts);
+ }
+
+ {
const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray();
options->extraLibraryDirs.reserve(extraLibraryDirs.size());
for (const QJsonValue path : extraLibraryDirs) {
@@ -1030,6 +1132,12 @@ bool readInputFile(Options *options)
}
{
+ const QJsonValue qmlSkipImportScanning = jsonObject.value("qml-skip-import-scanning"_L1);
+ if (!qmlSkipImportScanning.isUndefined())
+ options->qmlSkipImportScanning = qmlSkipImportScanning.toBool();
+ }
+
+ {
const QJsonValue extraPlugins = jsonObject.value("android-extra-plugins"_L1);
if (!extraPlugins.isUndefined())
options->extraPlugins = extraPlugins.toString().split(u',');
@@ -1071,7 +1179,7 @@ bool readInputFile(Options *options)
options->rootPaths.push_back(path.toString());
}
} else {
- options->rootPaths.push_back(options->inputFileName);
+ options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath());
}
}
@@ -1114,6 +1222,7 @@ bool readInputFile(Options *options)
}
{
+ using ItFlag = QDirListing::IteratorFlag;
const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1);
if (!deploymentDependencies.isUndefined()) {
QString deploymentDependenciesString = deploymentDependencies.toString();
@@ -1122,14 +1231,12 @@ bool readInputFile(Options *options)
QString path = options->qtInstallDirectory + QChar::fromLatin1('/');
path += dependency;
if (QFileInfo(path).isDir()) {
- QDirIterator iterator(path, QDirIterator::Subdirectories);
- while (iterator.hasNext()) {
- iterator.next();
- if (iterator.fileInfo().isFile()) {
- QString subPath = iterator.filePath();
+ for (const auto &dirEntry : QDirListing(path, ItFlag::Recursive)) {
+ if (dirEntry.isFile()) {
+ const QString subPath = dirEntry.filePath();
auto arch = fileArchitecture(*options, subPath);
if (!arch.isEmpty()) {
- options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.length() + 1),
+ options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.size() + 1),
subPath));
} else if (options->verbose) {
fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(subPath));
@@ -1138,12 +1245,24 @@ bool readInputFile(Options *options)
}
}
} else {
- auto arch = fileArchitecture(*options, path);
- if (!arch.isEmpty()) {
- options->qtDependencies[arch].append(QtDependency(dependency.toString(), path));
- } else if (options->verbose) {
- fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path));
- fflush(stderr);
+ auto qtDependency = [options](const QStringView &dependency,
+ const QString &arch) {
+ const auto installDir = options->architectures[arch].qtInstallDirectory;
+ const auto absolutePath = "%1/%2"_L1.arg(installDir, dependency.toString());
+ return QtDependency(dependency.toString(), absolutePath);
+ };
+
+ if (dependency.endsWith(QLatin1String(".so"))) {
+ auto arch = fileArchitecture(*options, path);
+ if (!arch.isEmpty()) {
+ options->qtDependencies[arch].append(qtDependency(dependency, arch));
+ } else if (options->verbose) {
+ fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path));
+ fflush(stderr);
+ }
+ } else {
+ for (auto arch : options->architectures.keys())
+ options->qtDependencies[arch].append(qtDependency(dependency, arch));
}
}
}
@@ -1199,7 +1318,7 @@ void cleanTopFolders(const Options &options, const QDir &srcDir, const QString &
const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs);
for (const QFileInfo &dir : dirs) {
if (dir.fileName() != "libs"_L1)
- deleteMissingFiles(options, dir.absoluteDir(), QDir(dstDir + dir.fileName()));
+ deleteMissingFiles(options, dir.absoluteFilePath(), QDir(dstDir + dir.fileName()));
}
}
@@ -1208,13 +1327,15 @@ void cleanAndroidFiles(const Options &options)
if (!options.androidSourceDirectory.isEmpty())
cleanTopFolders(options, QDir(options.androidSourceDirectory), options.outputDirectory);
- cleanTopFolders(options, QDir(options.qtInstallDirectory + "/src/android/templates"_L1),
+ cleanTopFolders(options,
+ QDir(options.qtInstallDirectory + u'/' +
+ options.qtDataDirectory + "/src/android/templates"_L1),
options.outputDirectory);
}
bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString())
{
- QDir sourceDirectory(options.qtInstallDirectory + androidTemplate);
+ QDir sourceDirectory(options.qtInstallDirectory + u'/' + options.qtDataDirectory + androidTemplate);
if (!sourceDirectory.exists()) {
fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
return false;
@@ -1232,7 +1353,8 @@ bool copyAndroidTemplate(const Options &options, const QString &androidTemplate,
bool copyGradleTemplate(const Options &options)
{
- QDir sourceDirectory(options.qtInstallDirectory + "/src/3rdparty/gradle"_L1);
+ QDir sourceDirectory(options.qtInstallDirectory + u'/' +
+ options.qtDataDirectory + "/src/3rdparty/gradle"_L1);
if (!sourceDirectory.exists()) {
fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
return false;
@@ -1357,7 +1479,8 @@ bool copyAndroidExtraResources(Options *options)
}
QDir resourceDir(extraResource);
- QString assetsDir = options->outputDirectory + "/assets/"_L1 + resourceDir.dirName() + u'/';
+ QString assetsDir = options->outputDirectory + "/assets/"_L1 +
+ resourceDir.dirName() + u'/';
QString libsDir = options->outputDirectory + "/libs/"_L1 + options->currentArchitecture + u'/';
const QStringList files = allFilesInside(resourceDir, resourceDir);
@@ -1403,7 +1526,7 @@ bool updateFile(const QString &fileName, const QHash<QString, QString> &replacem
forever {
int index = contents.indexOf(it.key().toUtf8());
if (index >= 0) {
- contents.replace(index, it.key().length(), it.value().toUtf8());
+ contents.replace(index, it.key().size(), it.value().toUtf8());
hasReplacements = true;
} else {
break;
@@ -1444,7 +1567,6 @@ bool updateLibsXml(Options *options)
for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) {
if (!it->enabled)
continue;
- QString libsPath = "libs/"_L1 + it.key() + u'/';
qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), options->stdCppName);
for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) {
@@ -1486,27 +1608,21 @@ bool updateLibsXml(Options *options)
if (localLibs.isEmpty()) {
QString plugin;
for (const QtDependency &qtDependency : options->qtDependencies[it.key()]) {
- if (qtDependency.relativePath.endsWith("libqtforandroid.so"_L1)
- || qtDependency.relativePath.endsWith("libqtforandroidGL.so"_L1)) {
- 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;
- }
-
+ if (qtDependency.relativePath.contains("libplugins_platforms_qtforandroid_"_L1))
plugin = qtDependency.relativePath;
- }
+
if (qtDependency.relativePath.contains(
QString::asprintf("libQt%dOpenGL", QT_VERSION_MAJOR))
|| qtDependency.relativePath.contains(
QString::asprintf("libQt%dQuick", QT_VERSION_MAJOR))) {
options->usesOpenGL |= true;
- break;
}
}
if (plugin.isEmpty()) {
fflush(stdout);
- fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n");
+ fprintf(stderr, "No platform plugin (libplugins_platforms_qtforandroid.so) included"
+ " in the deployment. Make sure the app links to Qt Gui library.\n");
fflush(stderr);
return false;
}
@@ -1589,12 +1705,12 @@ bool updateAndroidManifest(Options &options)
replacements[QStringLiteral("package=\"org.qtproject.example\"")] = "package=\"%1\""_L1.arg(options.packageName);
QString permissions;
- for (const QString &permission : qAsConst(options.permissions))
+ for (const QString &permission : std::as_const(options.permissions))
permissions += " <uses-permission android:name=\"%1\" />\n"_L1.arg(permission);
replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
QString features;
- for (const QString &feature : qAsConst(options.features))
+ for (const QString &feature : std::as_const(options.features))
features += " <uses-feature android:name=\"%1\" android:required=\"false\" />\n"_L1.arg(feature);
if (options.usesOpenGL)
features += " <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"_L1;
@@ -1695,6 +1811,26 @@ static QString absoluteFilePath(const Options *options, const QString &relativeF
if (QFile::exists(path))
return path;
}
+
+ if (relativeFileName.endsWith("-android-dependencies.xml"_L1)) {
+ for (const auto &dir : options->extraLibraryDirs) {
+ const QString path = dir + u'/' + relativeFileName;
+ if (QFile::exists(path))
+ return path;
+ }
+ return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
+ u'/' + relativeFileName;
+ }
+
+ if (relativeFileName.startsWith("jar/"_L1)) {
+ return options->qtInstallDirectory + u'/' + options->qtDataDirectory +
+ u'/' + relativeFileName;
+ }
+
+ if (relativeFileName.startsWith("lib/"_L1)) {
+ return options->qtInstallDirectory + u'/' + options->qtLibsDirectory +
+ u'/' + relativeFileName.mid(sizeof("lib/") - 1);
+ }
return options->qtInstallDirectory + u'/' + relativeFileName;
}
@@ -1717,19 +1853,63 @@ QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo
return ret;
} else {
- return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.length()), info.absoluteFilePath());
+ return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath());
}
}
QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName)
{
+ // We try to find the fileName in extraPrefixDirs first. The function behaves differently
+ // depending on what the fileName points to. If fileName is a file then we try to find the
+ // first occurrence in extraPrefixDirs and return this file. If fileName is directory function
+ // iterates over it and looks for deployment artifacts in each 'extraPrefixDirs' entry.
+ // Also we assume that if the fileName is recognized as a directory once it will be directory
+ // for every 'extraPrefixDirs' entry.
+ QList<QtDependency> deps;
for (const auto &prefix : options.extraPrefixDirs) {
QFileInfo info(prefix + u'/' + fileName);
- if (info.exists())
- return findFilesRecursively(options, info, prefix + u'/');
+ if (info.exists()) {
+ if (info.isDir())
+ deps.append(findFilesRecursively(options, info, prefix + u'/'));
+ else
+ return findFilesRecursively(options, info, prefix + u'/');
+ }
+ }
+
+ // Usually android deployment settings contain Qt install directory in extraPrefixDirs.
+ if (std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(),
+ options.qtInstallDirectory) == options.extraPrefixDirs.end()) {
+ QFileInfo info(options.qtInstallDirectory + "/"_L1 + fileName);
+ QFileInfo rootPath(options.qtInstallDirectory + "/"_L1);
+ deps.append(findFilesRecursively(options, info, rootPath.absolutePath()));
+ }
+ return deps;
+}
+
+void readDependenciesFromFiles(Options *options, const QList<QtDependency> &files,
+ QSet<QString> &usedDependencies,
+ QSet<QString> &remainingDependencies)
+{
+ for (const QtDependency &fileName : files) {
+ if (usedDependencies.contains(fileName.absolutePath))
+ continue;
+
+ if (fileName.absolutePath.endsWith(".so"_L1)) {
+ if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies,
+ &remainingDependencies)) {
+ fprintf(stdout, "Skipping file dependency: %s\n",
+ qPrintable(fileName.relativePath));
+ continue;
+ }
+ }
+ usedDependencies.insert(fileName.absolutePath);
+
+ if (options->verbose) {
+ fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath));
+ }
+
+ options->qtDependencies[options->currentArchitecture].append(fileName);
}
- QFileInfo info(options.qtInstallDirectory + u'/' + fileName);
- return findFilesRecursively(options, info, options.qtInstallDirectory + u'/');
}
bool readAndroidDependencyXml(Options *options,
@@ -1737,7 +1917,7 @@ bool readAndroidDependencyXml(Options *options,
QSet<QString> *usedDependencies,
QSet<QString> *remainingDependencies)
{
- QString androidDependencyName = absoluteFilePath(options, "/lib/%1-android-dependencies.xml"_L1.arg(moduleName));
+ QString androidDependencyName = absoluteFilePath(options, "%1-android-dependencies.xml"_L1.arg(moduleName));
QFile androidDependencyFile(androidDependencyName);
if (androidDependencyFile.exists()) {
@@ -1762,23 +1942,15 @@ bool readAndroidDependencyXml(Options *options,
QString file = reader.attributes().value("file"_L1).toString();
- // Special case, since this is handled by qmlimportscanner instead
- if (!options->rootPaths.empty()
- && (file == "qml"_L1 || file == "qml/"_L1))
+ if (reader.attributes().hasAttribute("type"_L1)
+ && reader.attributes().value("type"_L1) == "plugin_dir"_L1
+ && !options->androidDeployPlugins.isEmpty()) {
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[options->currentArchitecture].append(fileName);
- }
+ readDependenciesFromFiles(options, fileNames, *usedDependencies,
+ *remainingDependencies);
} else if (reader.name() == "jar"_L1) {
int bundling = reader.attributes().value("bundling"_L1).toInt();
QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString());
@@ -1915,7 +2087,7 @@ bool readDependenciesFromElf(Options *options,
dependenciesToCheck.append(dependency);
}
- for (const QString &dependency : qAsConst(dependenciesToCheck)) {
+ for (const QString &dependency : std::as_const(dependenciesToCheck)) {
QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
@@ -1926,10 +2098,6 @@ bool readDependenciesFromElf(Options *options,
return true;
}
-bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
-bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
- const QUrl &moduleUrl);
-
bool scanImports(Options *options, QSet<QString> *usedDependencies)
{
if (options->verbose)
@@ -1939,8 +2107,8 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
if (!options->qmlImportScannerBinaryPath.isEmpty()) {
qmlImportScanner = options->qmlImportScannerBinaryPath;
} else {
- qmlImportScanner = execSuffixAppended(options->qtInstallDirectory + u'/'
- + defaultLibexecDir() + "/qmlimportscanner"_L1);
+ qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory +
+ "/qmlimportscanner"_L1);
}
QStringList importPaths;
@@ -1949,7 +2117,7 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
// lacks a qml directory. We don't want to pass it as an import path if it doesn't exist
// because it will cause qmlimportscanner to fail.
// This also covers the case when only qtbase is installed in a regular Qt build.
- const QString mainImportPath = options->qtInstallDirectory + "/qml"_L1;
+ const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory;
if (QFile::exists(mainImportPath))
importPaths += shellQuote(mainImportPath);
@@ -1960,7 +2128,7 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
importPaths += shellQuote(prefix + "/qml"_L1);
// These are provided by both CMake and qmake.
- for (const QString &qmlImportPath : qAsConst(options->qmlImportPaths)) {
+ for (const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) {
if (QFile::exists(qmlImportPath)) {
importPaths += shellQuote(qmlImportPath);
} else {
@@ -2068,21 +2236,17 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
const QUrl url(object.value("name"_L1).toString());
- if (checkCanImportFromRootPaths(options, info.absolutePath(), url)) {
+ const QString moduleUrlPath = u"/"_s + url.toString().replace(u'.', u'/');
+ if (checkCanImportFromRootPaths(options, info.absolutePath(), moduleUrlPath)) {
if (options->verbose)
fprintf(stdout, " -- Skipping because path 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
+ for (const QString &importPath : std::as_const(importPaths)) {
QString cleanImportPath = QDir::cleanPath(importPath);
- if (info.absoluteFilePath().startsWith(cleanImportPath, caseSensitivity)) {
+ if (QFile::exists(cleanImportPath + moduleUrlPath)) {
importPathOfThisImport = importPath;
break;
}
@@ -2161,11 +2325,10 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies)
}
bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath,
- const QUrl &moduleUrl)
+ const QString &moduleUrlPath)
{
- const QString pathFromUrl = u"/"_s + moduleUrl.toString().replace(u'.', u'/');
for (auto rootPath : options->rootPaths) {
- if ((rootPath + pathFromUrl) == absolutePath)
+ if ((rootPath + moduleUrlPath) == absolutePath)
return true;
}
return false;
@@ -2208,8 +2371,7 @@ bool createRcc(const Options &options)
if (!options.rccBinaryPath.isEmpty()) {
rcc = options.rccBinaryPath;
} else {
- rcc = execSuffixAppended(options.qtInstallDirectory + u'/' + defaultLibexecDir() +
- "/rcc"_L1);
+ rcc = execSuffixAppended(options.qtLibExecsDirectory + "/rcc"_L1);
}
if (!QFile::exists(rcc)) {
@@ -2265,6 +2427,14 @@ bool readDependencies(Options *options)
if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies))
return false;
+ QList<QtDependency> pluginDeps;
+ for (const auto &pluginPath : options->androidDeployPlugins) {
+ pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath),
+ options->qtInstallDirectory + "/"_L1));
+ }
+
+ readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies);
+
while (!remainingDependencies.isEmpty()) {
QSet<QString>::iterator start = remainingDependencies.begin();
QString fileName = absoluteFilePath(options, *start);
@@ -2295,11 +2465,10 @@ bool readDependencies(Options *options)
}
}
- if ((!options->rootPaths.empty() || options->qrcFiles.isEmpty()) &&
- !scanImports(options, &usedDependencies))
- return false;
-
- return true;
+ if (options->qmlSkipImportScanning
+ || (options->rootPaths.empty() && options->qrcFiles.isEmpty()))
+ return true;
+ return scanImports(options, &usedDependencies);
}
bool containsApplicationBinary(Options *options)
@@ -2310,7 +2479,6 @@ bool containsApplicationBinary(Options *options)
if (options->verbose)
fprintf(stdout, "Checking if application binary is in package.\n");
- QFileInfo applicationBinary(options->applicationBinary);
QString applicationFileName = "lib%1_%2.so"_L1.arg(options->applicationBinary,
options->currentArchitecture);
@@ -2399,17 +2567,13 @@ bool copyQtFiles(Options *options)
// Copy other Qt dependencies
auto assetsDestinationDirectory = "assets/android_rcc_bundle/"_L1;
- for (const QtDependency &qtDependency : qAsConst(options->qtDependencies[options->currentArchitecture])) {
+ for (const QtDependency &qtDependency : std::as_const(options->qtDependencies[options->currentArchitecture])) {
QString sourceFileName = qtDependency.absolutePath;
QString destinationFileName;
bool isSharedLibrary = qtDependency.relativePath.endsWith(".so"_L1);
if (isSharedLibrary) {
- QString garbledFileName;
- if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("lib/"_L1)) {
- garbledFileName = qtDependency.relativePath.mid(sizeof("lib/") - 1);
- } else {
- garbledFileName = qtDependency.relativePath.mid(qtDependency.relativePath.lastIndexOf(u'/') + 1);
- }
+ QString garbledFileName = qtDependency.relativePath.mid(
+ qtDependency.relativePath.lastIndexOf(u'/') + 1);
destinationFileName = libsDirectory + options->currentArchitecture + u'/' + garbledFileName;
} else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("jar/"_L1)) {
destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1);
@@ -2443,7 +2607,7 @@ bool copyQtFiles(Options *options)
*options)) {
return false;
}
- options->bundledFiles[options->currentArchitecture] += qMakePair(destinationFileName, qtDependency.relativePath);
+ options->bundledFiles[options->currentArchitecture] += std::make_pair(destinationFileName, qtDependency.relativePath);
}
return true;
@@ -2542,7 +2706,7 @@ static bool mergeGradleProperties(const QString &path, GradleProperties properti
continue;
}
}
- file.write(line);
+ file.write(line.trimmed() + '\n');
}
oldFile.close();
QFile::remove(oldPathStr);
@@ -2559,11 +2723,11 @@ static bool mergeGradleProperties(const QString &path, GradleProperties properti
void checkAndWarnGradleLongPaths(const QString &outputDirectory)
{
QStringList longFileNames;
- QDirIterator it(outputDirectory, QStringList(QStringLiteral("*.java")), QDir::Files,
- QDirIterator::Subdirectories);
- while (it.hasNext()) {
- if (it.next().size() >= MAX_PATH)
- longFileNames.append(it.next());
+ using F = QDirListing::IteratorFlag;
+ for (const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s),
+ QDir::Files, F::Recursive)) {
+ if (dirEntry.size() >= MAX_PATH)
+ longFileNames.append(dirEntry.filePath());
}
if (!longFileNames.isEmpty()) {
@@ -2576,6 +2740,38 @@ void checkAndWarnGradleLongPaths(const QString &outputDirectory)
}
#endif
+struct GradleFlags {
+ bool setsLegacyPackaging = false;
+ bool usesIntegerCompileSdkVersion = false;
+};
+
+GradleFlags gradleBuildFlags(const QString &path)
+{
+ GradleFlags flags;
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ return flags;
+
+ auto isComment = [](const QByteArray &line) {
+ const auto trimmed = line.trimmed();
+ return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
+ };
+
+ const auto lines = file.readAll().split('\n');
+ for (const auto &line : lines) {
+ if (isComment(line))
+ continue;
+ if (line.contains("useLegacyPackaging")) {
+ flags.setsLegacyPackaging = true;
+ } else if (line.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) {
+ flags.usesIntegerCompileSdkVersion = true;
+ }
+ }
+
+ return flags;
+}
+
bool buildAndroidProject(const Options &options)
{
GradleProperties localProperties;
@@ -2584,16 +2780,46 @@ bool buildAndroidProject(const Options &options)
if (!mergeGradleProperties(localPropertiesPath, localProperties))
return false;
- QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1;
+ const QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1;
GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
- gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
+
+ const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1;
+ GradleFlags gradleFlags = gradleBuildFlags(gradleBuildFilePath);
+ if (!gradleFlags.setsLegacyPackaging)
+ gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
+
gradleProperties["buildDir"] = "build";
- gradleProperties["qtAndroidDir"] = (options.qtInstallDirectory + "/src/android/java"_L1).toUtf8();
+ gradleProperties["qtAndroidDir"] =
+ (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
+ "/src/android/java"_L1)
+ .toUtf8();
// The following property "qt5AndroidDir" is only for compatibility.
// Projects using a custom build.gradle file may use this variable.
// ### Qt7: Remove the following line
- gradleProperties["qt5AndroidDir"] = (options.qtInstallDirectory + "/src/android/java"_L1).toUtf8();
- gradleProperties["androidCompileSdkVersion"] = options.androidPlatform.split(u'-').last().toLocal8Bit();
+ gradleProperties["qt5AndroidDir"] =
+ (options.qtInstallDirectory + u'/' + options.qtDataDirectory +
+ "/src/android/java"_L1)
+ .toUtf8();
+
+ QByteArray sdkPlatformVersion;
+ // Provide the integer version only if build.gradle explicitly converts to Integer,
+ // to avoid regression to existing projects that build for sdk platform of form android-xx.
+ if (gradleFlags.usesIntegerCompileSdkVersion) {
+ const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit();
+ bool ok;
+ tmp.toInt(&ok);
+ if (ok) {
+ sdkPlatformVersion = tmp;
+ } else {
+ fprintf(stderr, "Warning: Gradle expects SDK platform version to be an integer, "
+ "but the set version is not convertible to an integer.");
+ }
+ }
+
+ if (sdkPlatformVersion.isEmpty())
+ sdkPlatformVersion = options.androidPlatform.toLocal8Bit();
+
+ gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion;
gradleProperties["qtMinSdkVersion"] = options.minSdkVersion;
gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion;
gradleProperties["androidNdkVersion"] = options.ndkVersion.toUtf8();
@@ -2811,7 +3037,7 @@ static QString zipalignPath(const Options &options, bool *ok)
return zipAlignTool;
}
-bool jarSignerSignPackage(const Options &options)
+bool signAAB(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Signing Android package.\n");
@@ -2865,7 +3091,7 @@ bool jarSignerSignPackage(const Options &options)
if (options.protectedAuthenticationPath)
jarSignerTool += " -protected"_L1;
- auto signPackage = [&](const QString &file) {
+ auto jarSignPackage = [&](const QString &file) {
fprintf(stdout, "Signing file %s\n", qPrintable(file));
fflush(stdout);
QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file))
@@ -2893,49 +3119,15 @@ bool jarSignerSignPackage(const Options &options)
return true;
};
- if (!signPackage(packagePath(options, UnsignedAPK)))
- return false;
- if (options.buildAAB && !signPackage(packagePath(options, AAB)))
- return false;
-
- bool ok;
- QString zipAlignTool = zipalignPath(options, &ok);
- if (!ok)
+ if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
return false;
-
- zipAlignTool = "%1%2 -f 4 %3 %4"_L1.arg(shellQuote(zipAlignTool),
- options.verbose ? " -v"_L1 : QLatin1StringView(),
- shellQuote(packagePath(options, UnsignedAPK)),
- shellQuote(packagePath(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;
- }
-
- return QFile::remove(packagePath(options, UnsignedAPK));
+ return true;
}
bool signPackage(const Options &options)
{
const QString apksignerTool = batSuffixAppended(options.sdkPath + "/build-tools/"_L1 +
options.sdkBuildToolsVersion + "/apksigner"_L1);
- 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.
@@ -3040,6 +3232,9 @@ bool signPackage(const Options &options)
"%1 verify --verbose %2"_L1
.arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
+ if (options.buildAAB && !signAAB(options))
+ return false;
+
// Verify the package and remove the unsigned apk
return apkSignerRunner(apkVerifyCommand, true) && QFile::remove(packagePath(options, UnsignedAPK));
}
@@ -3135,10 +3330,12 @@ int main(int argc, char *argv[])
for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
if (!it->enabled)
continue;
- options.setCurrentQtArchitecture(it.key(), it.value().qtInstallDirectory);
+ options.setCurrentQtArchitecture(it.key(),
+ it.value().qtInstallDirectory,
+ it.value().qtDirectories);
// All architectures have a copy of the gradle files but only one set needs to be copied.
- if (!androidTemplatetCopied && options.build && !options.auxMode && !options.copyDependenciesOnly) {
+ if (!androidTemplatetCopied && options.build && !options.copyDependenciesOnly) {
cleanAndroidFiles(options);
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed());
@@ -3175,13 +3372,12 @@ int main(int argc, char *argv[])
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %lld ns: Copied extra resources\n", options.timer.nsecsElapsed());
- if (!options.auxMode) {
- if (!copyStdCpp(&options))
- return CannotCopyGnuStl;
+ if (!copyStdCpp(&options))
+ return CannotCopyGnuStl;
+
+ if (Q_UNLIKELY(options.timing))
+ fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
- if (Q_UNLIKELY(options.timing))
- fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed());
- }
// If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK
if (options.deploymentMechanism == Options::Unbundled) {
QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1.