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.cpp236
1 files changed, 137 insertions, 99 deletions
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index 05d9e0932d..b7e96a0b59 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -1,12 +1,11 @@
/****************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2019 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:COMM$
-**
+** $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
@@ -15,14 +14,15 @@
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
-** $QT_END_LICENSE$
-**
-**
-**
-**
-**
-**
+** 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$
**
****************************************************************************/
@@ -129,7 +129,6 @@ struct Options
, internalSf(false)
, sectionsOnly(false)
, protectedAuthenticationPath(false)
- , jarSigner(false)
, installApk(false)
, uninstallApk(false)
{}
@@ -157,6 +156,7 @@ struct Options
QString sdkPath;
QString sdkBuildToolsVersion;
QString ndkPath;
+ QString ndkVersion;
QString jdkPath;
// Build paths
@@ -174,7 +174,7 @@ struct Options
QString versionName;
QString versionCode;
QByteArray minSdkVersion{"21"};
- QByteArray targetSdkVersion{"30"};
+ QByteArray targetSdkVersion{"31"};
// lib c++ path
QString stdCppPath;
@@ -213,7 +213,6 @@ struct Options
bool internalSf;
bool sectionsOnly;
bool protectedAuthenticationPath;
- bool jarSigner;
QString apkPath;
// Installation information
@@ -343,7 +342,7 @@ QString fileArchitecture(const Options &options, const QString &path)
return {};
}
- readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(path));
+ readElf = QLatin1String("%1 --needed-libs %2").arg(shellQuote(readElf), shellQuote(path));
FILE *readElfCommand = openProcess(readElf);
if (!readElfCommand) {
@@ -422,7 +421,6 @@ Options parseOptions()
} else if (argument.compare(QLatin1String("--aab"), Qt::CaseInsensitive) == 0) {
options.buildAAB = true;
options.build = true;
- options.jarSigner = true;
} else if (!options.buildAAB && argument.compare(QLatin1String("--no-build"), Qt::CaseInsensitive) == 0) {
options.build = false;
} else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) {
@@ -525,8 +523,6 @@ Options parseOptions()
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("--aux-mode"), Qt::CaseInsensitive) == 0) {
options.auxMode = true;
}
@@ -597,8 +593,7 @@ void printHelp()
" --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"
+ " --jarsigner: Deprecated, ignored.\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"
@@ -894,6 +889,30 @@ bool readInputFile(Options *options)
}
{
+ const QString ndkPropertiesPath = options->ndkPath + QStringLiteral("/source.properties");
+ QFile file(ndkPropertiesPath);
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ const char pkgRevisionKey[] = "Pkg.Revision";
+ while (!file.atEnd()) {
+ const QByteArray line = file.readLine();
+ if (line.startsWith(pkgRevisionKey)) {
+ const QList<QByteArray> parts = line.split('=');
+ if (parts.size() == 2 && parts.at(0).trimmed() == QByteArray(pkgRevisionKey)) {
+ options->ndkVersion = QString::fromLocal8Bit(parts.at(1).trimmed());
+ break;
+ }
+ }
+ }
+ }
+
+ if (options->ndkVersion.isEmpty()) {
+ fprintf(stderr, "Couldn't retrieve the NDK version from \"%s\".\n",
+ qPrintable(ndkPropertiesPath));
+ return false;
+ }
+ }
+
+ {
const QJsonValue toolchainPrefix = jsonObject.value(QLatin1String("toolchain-prefix"));
if (toolchainPrefix.isUndefined()) {
fprintf(stderr, "No toolchain prefix defined in json file.\n");
@@ -986,12 +1005,19 @@ 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);
+ if (dependency.endsWith(QLatin1String(".so"))) {
+ 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);
+ }
+ } else {
+ if (dependency.endsWith(QLatin1String(".jar")))
+ options->localJars.append(dependency.toString());
+ for (auto arch : options->architectures.keys())
+ options->qtDependencies[arch].append(QtDependency(dependency.toString(), path));
}
}
}
@@ -1292,25 +1318,19 @@ bool updateLibsXml(Options *options)
if (localLibs.isEmpty()) {
QString plugin;
for (const QtDependency &qtDependency : options->qtDependencies[it.key()]) {
- 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;
- }
-
+ if (qtDependency.relativePath.contains(QLatin1String("libplugins_platforms_qtforandroid_")))
plugin = qtDependency.relativePath;
- }
+
if (qtDependency.relativePath.contains(QLatin1String("libQt5OpenGL"))
|| qtDependency.relativePath.contains(QLatin1String("libQt5Quick"))) {
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;
}
@@ -1629,7 +1649,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
return QStringList();
}
- readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(fileName));
+ readElf = QLatin1String("%1 --needed-libs %2").arg(shellQuote(readElf), shellQuote(fileName));
FILE *readElfCommand = openProcess(readElf);
if (!readElfCommand) {
@@ -2270,15 +2290,16 @@ static GradleProperties readGradleProperties(const QString &path)
static bool mergeGradleProperties(const QString &path, GradleProperties properties)
{
- QFile::remove(path + QLatin1Char('~'));
- QFile::rename(path, path + QLatin1Char('~'));
+ const QString oldPathStr = path + QLatin1Char('~');
+ QFile::remove(oldPathStr);
+ QFile::rename(path, oldPathStr);
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('~'));
+ QFile oldFile(oldPathStr);
if (oldFile.open(QIODevice::ReadOnly)) {
while (!oldFile.atEnd()) {
QByteArray line(oldFile.readLine());
@@ -2294,6 +2315,7 @@ static bool mergeGradleProperties(const QString &path, GradleProperties properti
file.write(line);
}
oldFile.close();
+ QFile::remove(oldPathStr);
}
for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it)
@@ -2324,26 +2346,88 @@ 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;
localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8();
- localProperties["ndk.dir"] = QDir::fromNativeSeparators(options.ndkPath).toUtf8();
-
- if (!mergeGradleProperties(options.outputDirectory + QLatin1String("local.properties"), localProperties))
+ const QString localPropertiesPath = options.outputDirectory + QLatin1String("local.properties");
+ if (!mergeGradleProperties(localPropertiesPath, localProperties))
return false;
- QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties");
+ const QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties");
GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath);
- gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
+
+ const QString gradleBuildFilePath = options.outputDirectory + QLatin1String("build.gradle");
+ GradleFlags gradleFlags = gradleBuildFlags(gradleBuildFilePath);
+ if (!gradleFlags.setsLegacyPackaging)
+ gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false";
+
gradleProperties["buildDir"] = "build";
gradleProperties["qt5AndroidDir"] = (options.qtInstallDirectory + QLatin1String("/src/android/java")).toUtf8();
- gradleProperties["androidCompileSdkVersion"] = options.androidPlatform.split(QLatin1Char('-')).last().toLocal8Bit();
+
+ 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(QLatin1Char('-')).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();
if (gradleProperties["androidBuildToolsVersion"].isEmpty())
gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit();
-
+ QString abiList;
+ for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) {
+ if (abiList.size())
+ abiList.append(u",");
+ abiList.append(it.key());
+ }
+ gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ...
if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties))
return false;
@@ -2530,7 +2614,7 @@ bool copyStdCpp(Options *options)
return copyFileIfNewer(stdCppPath, destinationFile, *options);
}
-bool jarSignerSignPackage(const Options &options)
+bool signAAB(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Signing Android package.\n");
@@ -2589,7 +2673,7 @@ bool jarSignerSignPackage(const Options &options)
if (options.protectedAuthenticationPath)
jarSignerTool += QLatin1String(" -protected");
- auto signPackage = [&](const QString &file) {
+ auto jarSignPackage = [&](const QString &file) {
fprintf(stdout, "Signing file %s\n", qPrintable(file));
fflush(stdout);
QString command = jarSignerTool
@@ -2619,52 +2703,9 @@ bool jarSignerSignPackage(const Options &options)
return true;
};
- if (!signPackage(packagePath(options, UnsignedAPK)))
+ if (options.buildAAB && !jarSignPackage(packagePath(options, AAB)))
return false;
- if (options.buildAAB && !signPackage(packagePath(options, AAB)))
- 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 = QLatin1String("%1%2 -f 4 %3 %4")
- .arg(shellQuote(zipAlignTool),
- options.verbose ? QLatin1String(" -v") : QLatin1String(),
- 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)
@@ -2673,12 +2714,6 @@ bool signPackage(const Options &options)
#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");
@@ -2791,6 +2826,9 @@ bool signPackage(const Options &options)
QLatin1String("%1 verify --verbose %2")
.arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK)));
+ if (options.buildAAB && !signAAB(options))
+ return false;
+
// Verify the package and remove the unsigned apk
return apkSignerRunner() && QFile::remove(packagePath(options, UnsignedAPK));
}