aboutsummaryrefslogtreecommitdiffstats
path: root/share
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@qt.io>2017-08-31 20:04:54 -0700
committerJake Petroules <jake.petroules@qt.io>2017-09-08 18:57:24 +0000
commitc27d43463a5932f2a70a3b72af0ff9fc634017b6 (patch)
tree82298a98492fd7edd7ad8bc27e54ce05896789e0 /share
parent6e4311fbd616b6fd3218d4e3ceff1c91081af202 (diff)
Remove usage of deprecated apkbuilder tool
Instead, we use aapt and apksigner/jarsigner. For the most part, ApkBuilder is simply a "dumb copy" machine. It takes the initial .ap_ (which is merely a ZIP archive) and adds the dex output and native shared libraries to it (-z and -nf options). We now just put all of these an a properly structured directory and tell aapt to package it. The sole purpose of "debug mode" (-d) is to determine whether to copy gdbserver in, but Qbs already handles this bit by not copying it to the input directory in the first place for release builds. Finally, apkbuilder signs the APK with the Android debug keystore by default. We instead defer that task to the purpose built and more flexible apksigner, or jarsigner from the JDK as a fallback for older versions of the Android SDK Build Tools. No functionality is lost, and this also helps pave the way for adding aar support as this will rely heavily on aapt as well. Note that there are now *two* invocations of aapt. One to generate R.java (which then gets compiled to a .class file and later added to the dex output), and one to actually package the APK from the assets, resources, dex output, native libraries, and manifest. Change-Id: I0fd6a2ea598856d38316a3f0c8ae15a67693ddfb Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'share')
-rw-r--r--share/qbs/modules/Android/sdk/sdk.qbs107
-rw-r--r--share/qbs/modules/Android/sdk/utils.js86
-rw-r--r--share/qbs/modules/java/JavaModule.qbs4
3 files changed, 136 insertions, 61 deletions
diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs
index 89f0881d1..9afc423a0 100644
--- a/share/qbs/modules/Android/sdk/sdk.qbs
+++ b/share/qbs/modules/Android/sdk/sdk.qbs
@@ -29,6 +29,7 @@
****************************************************************************/
import qbs
+import qbs.Environment
import qbs.File
import qbs.FileInfo
import qbs.ModUtils
@@ -77,6 +78,7 @@ Module {
property path buildToolsDir: FileInfo.joinPaths(sdkDir, "build-tools", buildToolsVersion)
property path aaptFilePath: FileInfo.joinPaths(buildToolsDir, "aapt")
+ property path apksignerFilePath: FileInfo.joinPaths(buildToolsDir, "apksigner")
property path aidlFilePath: FileInfo.joinPaths(buildToolsDir, "aidl")
property path dxFilePath: FileInfo.joinPaths(buildToolsDir, "dx")
property path zipalignFilePath: FileInfo.joinPaths(buildToolsDir, "zipalign")
@@ -85,6 +87,12 @@ Module {
property path generatedJavaFilesBaseDir: FileInfo.joinPaths(product.buildDirectory, "gen")
property path generatedJavaFilesDir: FileInfo.joinPaths(generatedJavaFilesBaseDir,
(product.packageName || "").split('.').join('/'))
+ property string apkContentsDir: FileInfo.joinPaths(product.buildDirectory, "bin")
+ property string debugKeyStorePath: FileInfo.joinPaths(
+ Environment.getEnv(qbs.hostOS.contains("windows")
+ ? "USERPROFILE" : "HOME"),
+ ".android", "debug.keystore")
+ property bool useApksigner: Utilities.versionCompare(buildToolsVersion, "24.0.3") >= 0
Depends { name: "java" }
java.languageVersion: platformJavaVersion
@@ -101,6 +109,36 @@ Module {
fileTags: ["android.aidl"]
}
+ FileTagger {
+ patterns: ["*.keystore"]
+ fileTags: ["android.keystore"]
+ }
+
+ // Typically there is a debug keystore in ~/.android/debug.keystore which gets created
+ // by the native build tools the first time a build is done. However, we don't want to create it
+ // ourselves, because writing to a location outside the qbs build directory is both polluting
+ // and has the potential for race conditions. So we'll instruct the user what to do.
+ Group {
+ name: "Android debug keystore"
+ files: {
+ if (!File.exists(Android.sdk.debugKeyStorePath)) {
+ throw ModUtils.ModuleError("Could not find an Android debug keystore at " +
+ Android.sdk.debugKeyStorePath + ". " +
+ "If you are developing for Android on this machine for the first time and " +
+ "have never built an application using the native Gradle / Android Studio " +
+ "tooling, this is normal. You must create the debug keystore now using the " +
+ "following command, in order to continue:\n\n" +
+ SdkUtils.createDebugKeyStoreCommandString(java.keytoolFilePath,
+ Android.sdk.debugKeyStorePath) +
+ "\n\n" +
+ "See the following URL for more information: " +
+ "https://developer.android.com/studio/publish/app-signing.html#debug-mode");
+ }
+ return [Android.sdk.debugKeyStorePath];
+ }
+ fileTags: ["android.keystore"]
+ }
+
Parameter {
property bool embedJar: true
}
@@ -125,13 +163,9 @@ Module {
multiplex: true
inputs: ["android.resources", "android.assets", "android.manifest"]
- outputFileTags: ["android.ap_", "java.java"]
+ outputFileTags: ["java.java"]
outputArtifacts: {
- var artifacts = [{
- filePath: product.name + ".ap_",
- fileTags: ["android.ap_"]
- }];
-
+ var artifacts = [];
var resources = inputs["android.resources"];
if (resources && resources.length) {
artifacts.push({
@@ -145,24 +179,7 @@ Module {
return artifacts;
}
- prepare: {
- var manifestFilePath = inputs["android.manifest"][0].filePath;
- var args = ["package", "-f", "-m", "--no-crunch",
- "-M", manifestFilePath,
- "-I", ModUtils.moduleProperty(product, "androidJarFilePath"),
- "-F", outputs["android.ap_"][0].filePath, "--generate-dependencies"];
- var resources = inputs["android.resources"];
- if (resources && resources.length)
- args.push("-S", product.resourcesDir,
- "-J", ModUtils.moduleProperty(product, "generatedJavaFilesBaseDir"));
- if (product.moduleProperty("qbs", "buildVariant") === "debug")
- args.push("--debug-mode");
- if (File.exists(product.assetsDir))
- args.push("-A", product.assetsDir);
- var cmd = new Command(ModUtils.moduleProperty(product, "aaptFilePath"), args);
- cmd.description = "Processing resources";
- return [cmd];
- }
+ prepare: SdkUtils.prepareAaptGenerate.apply(SdkUtils, arguments)
}
Rule {
@@ -197,7 +214,7 @@ Module {
inputs: ["java.class"]
inputsFromDependencies: ["java.jar"]
Artifact {
- filePath: "classes.dex"
+ filePath: FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "classes.dex")
fileTags: ["android.dex"]
}
prepare: SdkUtils.prepareDex.apply(SdkUtils, arguments)
@@ -214,7 +231,7 @@ Module {
if (inputs["android.nativelibrary"]) {
for (var i = 0; i < inputs["android.nativelibrary"].length; ++i) {
var inp = inputs["android.nativelibrary"][i];
- var destDir = FileInfo.joinPaths("lib",
+ var destDir = FileInfo.joinPaths(product.Android.sdk.apkContentsDir, "lib",
inp.moduleProperty("Android.ndk", "abi"));
libArtifacts.push({
filePath: FileInfo.joinPaths(destDir, inp.fileName),
@@ -259,47 +276,17 @@ Module {
}
}
- // TODO: ApkBuilderMain is deprecated. Do we have to provide our own tool directly
- // accessing com.android.sdklib.build.ApkBuilder or is there a simpler way?
Rule {
multiplex: true
inputs: [
- "android.dex", "android.ap_", "android.gdbserver", "android.stl",
- "android.nativelibrary-deployed"
+ "android.resources", "android.assets", "android.manifest",
+ "android.dex", "android.gdbserver", "android.stl",
+ "android.nativelibrary-deployed", "android.keystore"
]
Artifact {
- filePath: product.targetName + ".apk.unaligned"
- fileTags: ["android.apk.unaligned"]
- }
- prepare: {
- var args = ["-classpath", FileInfo.joinPaths(ModUtils.moduleProperty(product, "sdkDir"),
- "tools/lib/*"),
- "com.android.sdklib.build.ApkBuilderMain", output.filePath,
- "-z", inputs["android.ap_"][0].filePath,
- "-f", inputs["android.dex"][0].filePath];
- if (product.moduleProperty("qbs", "buildVariant") === "debug")
- args.push("-d");
- if (inputs["android.nativelibrary-deployed"])
- args.push("-nf", FileInfo.joinPaths(product.buildDirectory, "lib"));
- var cmd = new Command(product.moduleProperty("java", "interpreterFilePath"), args);
- cmd.description = "Generating " + output.fileName;
- return [cmd];
- }
- }
-
- Rule {
- multiplex: true
- inputs: ["android.apk.unaligned"]
- Artifact {
filePath: product.targetName + ".apk"
fileTags: ["android.apk"]
}
- prepare: {
- var zipalign = ModUtils.moduleProperty(product, "zipalignFilePath");
- var args = ["-f", "4", inputs["android.apk.unaligned"][0].filePath, output.filePath];
- var cmd = new Command(zipalign, args);
- cmd.description = "Creating " + output.fileName;
- return [cmd];
- }
+ prepare: SdkUtils.prepareAaptPackage.apply(SdkUtils, arguments)
}
}
diff --git a/share/qbs/modules/Android/sdk/utils.js b/share/qbs/modules/Android/sdk/utils.js
index 1d825d39b..6d9fff65c 100644
--- a/share/qbs/modules/Android/sdk/utils.js
+++ b/share/qbs/modules/Android/sdk/utils.js
@@ -30,6 +30,7 @@
var File = require("qbs.File");
var FileInfo = require("qbs.FileInfo");
+var Process = require("qbs.Process");
var TextFile = require("qbs.TextFile");
var Utilities = require("qbs.Utilities");
@@ -41,7 +42,8 @@ function sourceAndTargetFilePathsFromInfoFiles(inputs, product, inputTag)
for (var i = 0; i < inputsLength; ++i) {
var infoFile = new TextFile(inputs[inputTag][i].filePath, TextFile.ReadOnly);
var sourceFilePath = infoFile.readLine();
- var targetFilePath = FileInfo.joinPaths(product.buildDirectory, infoFile.readLine());
+ var targetFilePath = FileInfo.joinPaths(product.Android.sdk.apkContentsDir,
+ infoFile.readLine());
if (!targetFilePaths.contains(targetFilePath)) {
sourceFilePaths.push(sourceFilePath);
targetFilePaths.push(targetFilePath);
@@ -126,3 +128,85 @@ function prepareDex(project, product, inputs, outputs, input, output, explicitly
cmd.description = "Creating " + output.fileName;
return [cmd];
}
+
+function commonAaptPackageArgs(project, product, inputs, outputs, input, output,
+ explicitlyDependsOn) {
+ var manifestFilePath = inputs["android.manifest"][0].filePath;
+ var args = ["package", "-f",
+ "-M", manifestFilePath,
+ "-I", product.Android.sdk.androidJarFilePath];
+ var resources = inputs["android.resources"];
+ if (resources && resources.length)
+ args.push("-S", product.resourcesDir);
+ if (product.qbs.buildVariant === "debug")
+ args.push("--debug-mode");
+ if (File.exists(product.assetsDir))
+ args.push("-A", product.assetsDir);
+ return args;
+}
+
+function prepareAaptGenerate(project, product, inputs, outputs, input, output,
+ explicitlyDependsOn) {
+ var args = commonAaptPackageArgs.apply(this, arguments);
+ args.push("--no-crunch", "-m");
+ var resources = inputs["android.resources"];
+ if (resources && resources.length)
+ args.push("-J", ModUtils.moduleProperty(product, "generatedJavaFilesBaseDir"));
+ var cmd = new Command(product.Android.sdk.aaptFilePath, args);
+ cmd.description = "Processing resources";
+ return [cmd];
+}
+
+function prepareAaptPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) {
+ var cmds = [];
+ var apkOutput = outputs["android.apk"][0];
+ var args = commonAaptPackageArgs.apply(this, arguments);
+ args.push("-F", apkOutput.filePath + ".unaligned");
+ args.push(product.Android.sdk.apkContentsDir);
+ var cmd = new Command(product.Android.sdk.aaptFilePath, args);
+ cmd.description = "Generating " + apkOutput.filePath;
+ cmds.push(cmd);
+
+ if (!product.Android.sdk.useApksigner) {
+ args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1",
+ "-keystore", inputs["android.keystore"][0].filePath,
+ "-storepass", "android",
+ apkOutput.filePath + ".unaligned",
+ "androiddebugkey"];
+ cmd = new Command(product.java.jarsignerFilePath, args);
+ cmd.description = "Signing " + apkOutput.fileName;
+ cmds.push(cmd);
+ }
+
+ cmd = new Command(product.Android.sdk.zipalignFilePath,
+ ["-f", "4", apkOutput.filePath + ".unaligned", apkOutput.filePath]);
+ cmd.silent = true;
+ cmds.push(cmd);
+
+ cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.unalignedApk = apkOutput.filePath + ".unaligned";
+ cmd.sourceCode = function() { File.remove(unalignedApk); };
+ cmds.push(cmd);
+
+ if (product.Android.sdk.useApksigner) {
+ // TODO: Implement full signing support, not just using the debug keystore
+ args = ["sign",
+ "--ks", inputs["android.keystore"][0].filePath,
+ "--ks-pass", "pass:android",
+ apkOutput.filePath];
+ cmd = new Command(product.Android.sdk.apksignerFilePath, args);
+ cmd.description = "Signing " + apkOutput.fileName;
+ cmds.push(cmd);
+ }
+
+ return cmds;
+}
+
+function createDebugKeyStoreCommandString(keytoolFilePath, keystoreFilePath) {
+ var args = ["-genkey", "-keystore", keystoreFilePath, "-alias", "androiddebugkey",
+ "-storepass", "android", "-keypass", "android", "-keyalg", "RSA",
+ "-keysize", "2048", "-validity", "10000", "-dname",
+ "CN=Android Debug,O=Android,C=US"];
+ return Process.shellQuote(keytoolFilePath, args);
+}
diff --git a/share/qbs/modules/java/JavaModule.qbs b/share/qbs/modules/java/JavaModule.qbs
index 252371684..dcf6c8ba2 100644
--- a/share/qbs/modules/java/JavaModule.qbs
+++ b/share/qbs/modules/java/JavaModule.qbs
@@ -59,6 +59,10 @@ Module {
property string interpreterName: "java"
property string jarFilePath: FileInfo.joinPaths(jdkPath, "bin", jarName)
property string jarName: "jar"
+ property string jarsignerFilePath: FileInfo.joinPaths(jdkPath, "bin", jarsignerName)
+ property string jarsignerName: "jarsigner"
+ property string keytoolFilePath: FileInfo.joinPaths(jdkPath, "bin", keytoolName)
+ property string keytoolName: "keytool"
property string jdkPath: jdk.path