aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Adam Michael <ajmichael@google.com>2016-08-31 23:44:59 +0000
committerGravatar Klaus Aehlig <aehlig@google.com>2016-09-01 08:39:36 +0000
commit602f3ae70f49bd8bae6205a93f3df5918d3bacac (patch)
treea255b0eaa3c1888ed2422b1737e06d6ba014c04a /src
parent60a7e633a2ecd239794ebc4067de48d68de549ce (diff)
Adds support for ApkSignerTool and APK signature schema v2 behind --apk_signing_method flag. Default is legacy_v1 which is the already existing functionality.
Promotes AndroidBinary.ApkActionBuilder to a toplevel abstract class ApkActionsBuilder with two implementations, one for default signing/zipaligning and one for ApkSignerTool based signing/zipaligning. In addition to build the action for constructing the APK, it now responsible for orchestrating the various tools to build, sign and zipalign the APK. -- MOS_MIGRATED_REVID=131889338
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java305
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java65
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java317
6 files changed, 464 insertions, 239 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index 21ed2dd069..ee00868dc5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
-import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.FailAction;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
@@ -55,7 +54,10 @@ import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidBinaryType;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
+import com.google.devtools.build.lib.rules.android.ApkActionsBuilder.LegacySignerApkActionsBuilder;
+import com.google.devtools.build.lib.rules.android.ApkActionsBuilder.SignerToolApkActionsBuilder;
import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
import com.google.devtools.build.lib.rules.cpp.CppHelper;
import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder;
@@ -76,6 +78,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
@@ -430,30 +433,23 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
resourceApk.getMainDexProguardConfig(),
resourceClasses);
+ ApkSigningMethod signingMethod =
+ ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod();
+
Artifact unsignedApk =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
- Artifact signedApk =
- ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_SIGNED_APK);
-
- ApkActionBuilder apkBuilder = new ApkActionBuilder(ruleContext, androidSemantics)
- .classesDex(dexingOutput.classesDexZip)
- .resourceApk(resourceApk.getArtifact())
- .javaResourceZip(dexingOutput.javaResourceJar)
- .nativeLibs(nativeLibs);
-
- ruleContext.registerAction(apkBuilder
- .message("Generating unsigned apk")
- .build(unsignedApk));
-
- ruleContext.registerAction(apkBuilder
- .message("Generating signed apk")
- .sign(true)
- .build(signedApk));
-
- Artifact zipAlignedApk = zipalignApk(
- ruleContext,
- signedApk,
- ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK));
+ Artifact zipAlignedApk =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK);
+
+ createApkActionsBuilder(signingMethod)
+ .setClassesDex(dexingOutput.classesDexZip)
+ .setResourceApk(resourceApk.getArtifact())
+ .setJavaResourceZip(dexingOutput.javaResourceJar)
+ .setNativeLibs(nativeLibs)
+ .setUnsignedApk(unsignedApk)
+ .setSignedAndZipalignedApk(zipAlignedApk)
+ .setApkName("apk")
+ .registerActions(ruleContext, androidSemantics);
// Don't add blacklistedApk, so it's only built if explicitly requested.
filesBuilder.add(binaryJar);
@@ -509,19 +505,19 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
Artifact stubDex = getStubDex(ruleContext, javaSemantics, false);
ruleContext.assertNoErrors();
- ApkActionBuilder incrementalActionBuilder = new ApkActionBuilder(ruleContext, androidSemantics)
- .classesDex(stubDex)
- .resourceApk(incrementalResourceApk.getArtifact())
- .javaResourceZip(dexingOutput.javaResourceJar)
- .sign(true)
- .javaResourceFile(stubData)
- .message("Generating incremental apk");
+ ApkActionsBuilder incrementalActionsBuilder = createApkActionsBuilder(signingMethod)
+ .setClassesDex(stubDex)
+ .setResourceApk(incrementalResourceApk.getArtifact())
+ .setJavaResourceZip(dexingOutput.javaResourceJar)
+ .setJavaResourceFile(stubData)
+ .setApkName("incremental apk")
+ .setSignedApk(incrementalApk);
if (!ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) {
- incrementalActionBuilder.nativeLibs(nativeLibs);
+ incrementalActionsBuilder.setNativeLibs(nativeLibs);
}
- ruleContext.registerAction(incrementalActionBuilder.build(incrementalApk));
+ incrementalActionsBuilder.registerActions(ruleContext, androidSemantics);
Artifact argsArtifact =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.MOBILE_INSTALL_ARGS);
@@ -569,11 +565,11 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
// installation of each split (said references would eventually get installed, but it cannot
// know that in advance)
Artifact resourceSplitApk = getDxArtifact(ruleContext, "android_resources.apk");
- ruleContext.registerAction(new ApkActionBuilder(ruleContext, androidSemantics)
- .resourceApk(splitResourceApk.getArtifact())
- .sign(true)
- .message("Generating split Android resource apk")
- .build(resourceSplitApk));
+ createApkActionsBuilder(signingMethod)
+ .setResourceApk(splitResourceApk.getArtifact())
+ .setApkName("split Android resource apk")
+ .setSignedApk(resourceSplitApk)
+ .registerActions(ruleContext, androidSemantics);
splitApkSetBuilder.add(resourceSplitApk);
for (int i = 0; i < dexingOutput.shardDexZips.size(); i++) {
@@ -581,35 +577,35 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
Artifact splitApkResources = createSplitApkResources(
ruleContext, applicationManifest, splitName, true);
Artifact splitApk = getDxArtifact(ruleContext, splitName + ".apk");
- ruleContext.registerAction(new ApkActionBuilder(ruleContext, androidSemantics)
- .classesDex(dexingOutput.shardDexZips.get(i))
- .resourceApk(splitApkResources)
- .sign(true)
- .message("Generating split dex apk " + (i + 1))
- .build(splitApk));
+ createApkActionsBuilder(signingMethod)
+ .setClassesDex(dexingOutput.shardDexZips.get(i))
+ .setResourceApk(splitApkResources)
+ .setApkName("split dex apk " + (i + 1))
+ .setSignedApk(splitApk)
+ .registerActions(ruleContext, androidSemantics);
splitApkSetBuilder.add(splitApk);
}
Artifact nativeSplitApkResources = createSplitApkResources(
ruleContext, applicationManifest, "native", false);
Artifact nativeSplitApk = getDxArtifact(ruleContext, "native.apk");
- ruleContext.registerAction(new ApkActionBuilder(ruleContext, androidSemantics)
- .resourceApk(nativeSplitApkResources)
- .sign(true)
- .message("Generating split native apk")
- .nativeLibs(nativeLibs)
- .build(nativeSplitApk));
+ createApkActionsBuilder(signingMethod)
+ .setResourceApk(nativeSplitApkResources)
+ .setNativeLibs(nativeLibs)
+ .setApkName("split native apk")
+ .setSignedApk(nativeSplitApk)
+ .registerActions(ruleContext, androidSemantics);
splitApkSetBuilder.add(nativeSplitApk);
Artifact javaSplitApkResources = createSplitApkResources(
ruleContext, applicationManifest, "java_resources", false);
Artifact javaSplitApk = getDxArtifact(ruleContext, "java_resources.apk");
- ruleContext.registerAction(new ApkActionBuilder(ruleContext, androidSemantics)
- .resourceApk(javaSplitApkResources)
- .javaResourceZip(dexingOutput.javaResourceJar)
- .sign(true)
- .message("Generating split Java resource apk")
- .build(javaSplitApk));
+ createApkActionsBuilder(signingMethod)
+ .setResourceApk(javaSplitApkResources)
+ .setJavaResourceZip(dexingOutput.javaResourceJar)
+ .setApkName("split Java resource apk")
+ .setSignedApk(javaSplitApk)
+ .registerActions(ruleContext, androidSemantics);
splitApkSetBuilder.add(javaSplitApk);
Artifact splitMainApkResources = getDxArtifact(ruleContext, "split_main.ap_");
@@ -627,12 +623,12 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
Artifact splitMainApk = getDxArtifact(ruleContext, "split_main.apk");
Artifact splitStubDex = getStubDex(ruleContext, javaSemantics, true);
ruleContext.assertNoErrors();
- ruleContext.registerAction(new ApkActionBuilder(ruleContext, androidSemantics)
- .resourceApk(splitMainApkResources)
- .classesDex(splitStubDex)
- .sign(true)
- .message("Generating split main apk")
- .build(splitMainApk));
+ createApkActionsBuilder(signingMethod)
+ .setClassesDex(splitStubDex)
+ .setResourceApk(splitMainApkResources)
+ .setApkName("split main apk")
+ .setSignedApk(splitMainApk)
+ .registerActions(ruleContext, androidSemantics);
splitApkSetBuilder.add(splitMainApk);
NestedSet<Artifact> allSplitApks = splitApkSetBuilder.build();
@@ -1091,9 +1087,10 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
// Always OFF if finalJarIsDerived
ImmutableSet<AndroidBinaryType> incrementalDexing =
- getEffectiveIncrementalDexing(ruleContext, dexopts, binaryJar != proguardedJar);
+ getEffectiveIncrementalDexing(
+ ruleContext, dexopts, !Objects.equals(binaryJar, proguardedJar));
Artifact inclusionFilterJar =
- isBinaryJarFiltered && binaryJar == proguardedJar ? binaryJar : null;
+ isBinaryJarFiltered && Objects.equals(binaryJar, proguardedJar) ? binaryJar : null;
if (multidexMode == MultidexMode.OFF) {
// Single dex mode: generate classes.dex directly from the input jar.
if (incrementalDexing.contains(AndroidBinaryType.MONODEX)) {
@@ -1131,7 +1128,7 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
createShuffleJarAction(
ruleContext,
incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED),
- /*proguardedJar*/ binaryJar != proguardedJar ? proguardedJar : null,
+ /*proguardedJar*/ !Objects.equals(binaryJar, proguardedJar) ? proguardedJar : null,
shards,
common,
inclusionFilterJar,
@@ -1490,184 +1487,14 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
: null;
}
- /**
- * Builder class for {@link com.google.devtools.build.lib.analysis.actions.SpawnAction}s that
- * generate APKs.
- *
- * <p>Instances of this class can be reused after calling {@code build()}.
- */
- private static final class ApkActionBuilder {
- private final RuleContext ruleContext;
- private final AndroidSemantics semantics;
-
- private boolean sign;
- private String message;
- private Artifact classesDex;
- private Artifact resourceApk;
- private Artifact javaResourceZip;
- // javaResourceFile adds Java resources just like javaResourceZip. We should make the stub
- // manifest writer output a zip file, then we could do away with this input to APK building.
- private Artifact javaResourceFile;
- private NativeLibs nativeLibs = NativeLibs.EMPTY;
-
- private ApkActionBuilder(
- RuleContext ruleContext, AndroidSemantics semantics) {
- this.ruleContext = ruleContext;
- this.semantics = semantics;
- }
-
- /**
- * Sets the user-visible message that is displayed when the action is running.
- */
- public ApkActionBuilder message(String message) {
- this.message = message;
- return this;
- }
-
- /**
- * Sets the native libraries to be included in the APK.
- */
- public ApkActionBuilder nativeLibs(NativeLibs nativeLibs) {
- this.nativeLibs = nativeLibs;
- return this;
- }
-
- /**
- * Sets the dex file to be included in the APK.
- *
- * <p>Can be either a plain .dex or a .zip file containing dexes.
- */
- public ApkActionBuilder classesDex(Artifact classesDex) {
- this.classesDex = classesDex;
- return this;
- }
-
- /**
- * Sets the resource APK that contains the Android resources to be bundled into the output.
- */
- public ApkActionBuilder resourceApk(Artifact resourceApk) {
- this.resourceApk = resourceApk;
- return this;
- }
-
- /**
- * Sets the file where Java resources are taken.
- *
- * <p>Everything in this will will be put directly into the APK except files with the extension
- * {@code .class}.
- */
- public ApkActionBuilder javaResourceZip(Artifact javaResourcezip) {
- this.javaResourceZip = javaResourcezip;
- return this;
- }
-
- /**
- * Adds an individual resource file to the root directory of the APK.
- *
- * <p>This provides the same functionality as {@code javaResourceZip}, except much more hacky.
- * Will most probably won't work if there is an input artifact in the same directory as this
- * file.
- */
- public ApkActionBuilder javaResourceFile(Artifact javaResourceFile) {
- this.javaResourceFile = javaResourceFile;
- return this;
- }
-
- /**
- * Sets if the APK will be signed. By default, it won't be.
- */
- public ApkActionBuilder sign(boolean sign) {
- this.sign = sign;
- return this;
- }
-
- /**
- * Creates a generating action for {@code outApk} that builds the APK specified.
- */
- public Action[] build(Artifact outApk) {
- Builder actionBuilder = new SpawnAction.Builder()
- .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkBuilder())
- .setProgressMessage(message)
- .setMnemonic("AndroidApkBuilder")
- .addOutputArgument(outApk);
-
- if (javaResourceZip != null) {
- actionBuilder
- .addArgument("-rj")
- .addInputArgument(javaResourceZip);
- }
-
- Artifact nativeSymlinks = nativeLibs.createApkBuilderSymlinks(ruleContext);
- if (nativeSymlinks != null) {
- PathFragment nativeSymlinksDir = nativeSymlinks.getExecPath().getParentDirectory();
- actionBuilder
- .addInputManifest(nativeSymlinks, nativeSymlinksDir)
- .addInput(nativeSymlinks)
- .addInputs(nativeLibs.getAllNativeLibs())
- .addArgument("-nf")
- // If the native libs are "foo/bar/x86/foo.so", we need to pass "foo/bar" here
- .addArgument(nativeSymlinksDir.getPathString());
- }
-
- if (nativeLibs.getName() != null) {
- actionBuilder
- .addArgument("-rf")
- .addArgument(nativeLibs.getName().getExecPath().getParentDirectory().getPathString())
- .addInput(nativeLibs.getName());
- }
-
- if (javaResourceFile != null) {
- actionBuilder
- .addArgument("-rf")
- .addArgument((javaResourceFile.getExecPath().getParentDirectory().getPathString()))
- .addInput(javaResourceFile);
- }
-
- if (sign) {
- Artifact signingKey = semantics.getApkDebugSigningKey(ruleContext);
- actionBuilder.addArgument("-ks").addArgument(signingKey.getExecPathString());
- actionBuilder.addInput(signingKey);
- } else {
- actionBuilder.addArgument("-u");
- }
-
- actionBuilder
- .addArgument("-z")
- .addInputArgument(resourceApk);
-
- if (classesDex != null) {
- actionBuilder
- .addArgument(classesDex.getFilename().endsWith(".dex") ? "-f" : "-z")
- .addInputArgument(classesDex);
- }
-
- return actionBuilder.build(ruleContext);
+ private static ApkActionsBuilder createApkActionsBuilder(ApkSigningMethod signingMethod) {
+ if (signingMethod.signLegacy()) {
+ return new LegacySignerApkActionsBuilder();
+ } else {
+ return new SignerToolApkActionsBuilder(signingMethod);
}
}
- /** Last step in buildings an apk: align the zip boundaries by 4 bytes. */
- static Artifact zipalignApk(RuleContext ruleContext,
- Artifact signedApk, Artifact zipAlignedApk) {
- List<String> args = new ArrayList<>();
- // "4" is the only valid value for zipalign, according to:
- // http://developer.android.com/guide/developing/tools/zipalign.html
- args.add("4");
- args.add(signedApk.getExecPathString());
- args.add(zipAlignedApk.getExecPathString());
-
- ruleContext.registerAction(new SpawnAction.Builder()
- .addInput(signedApk)
- .addOutput(zipAlignedApk)
- .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getZipalign())
- .addArguments(args)
- .setProgressMessage("Zipaligning apk")
- .setMnemonic("AndroidZipAlign")
- .build(ruleContext));
- args.add(signedApk.getExecPathString());
- args.add(zipAlignedApk.getExecPathString());
- return zipAlignedApk;
- }
-
/**
* Tests if the resources need to be regenerated.
*
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
index 45d9492863..c7b3650796 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
@@ -67,6 +67,15 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
}
/**
+ * Converter for {@link ApkSigningMethod}.
+ */
+ public static final class ApkSigningMethodConverter extends EnumConverter<ApkSigningMethod> {
+ public ApkSigningMethodConverter() {
+ super(ApkSigningMethod.class, "apk signing method");
+ }
+ }
+
+ /**
* Converter for a set of {@link AndroidBinaryType}s.
*/
public static final class AndroidBinaryTypesConverter
@@ -133,6 +142,49 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
MONODEX, MULTIDEX_UNSHARDED, MULTIDEX_SHARDED
}
+ /**
+ * Which APK signing method to use with the debug key for rules that build APKs.
+ *
+ * <ul>
+ * <li>LEGACY_V1 uses the signer inside the deprecated apkbuilder tool.
+ * <li>V1 uses the apksigner attribute from the android_sdk and signs the APK as a JAR.
+ * <li>V2 uses the apksigner attribute from the android_sdk and signs the APK according to the APK
+ * Signing Schema V2 that is only supported on Android N and later.
+ * </ul>
+ */
+ public enum ApkSigningMethod {
+ LEGACY_V1(true, false, false),
+ V1(false, true, false),
+ V2(false, false, true),
+ V1_V2(false, true, true);
+
+ private final boolean signLegacy;
+ private final boolean signV1;
+ private final boolean signV2;
+
+ ApkSigningMethod(boolean signLegacy, boolean signV1, boolean signV2) {
+ // If signLegacy is true, the other two values will be ignored.
+ this.signLegacy = signLegacy;
+ this.signV1 = signV1;
+ this.signV2 = signV2;
+ }
+
+ /** Whether to sign with the signer inside the deprecated apkbuilder tool. */
+ public boolean signLegacy() {
+ return signLegacy;
+ }
+
+ /** Whether to JAR sign the APK with the apksigner tool. */
+ public boolean signV1() {
+ return signV1;
+ }
+
+ /** Wheter to sign the APK with the apksigner tool with APK Signature Schema V2. */
+ public boolean signV2() {
+ return signV2;
+ }
+ }
+
/** When to use incremental dexing (using {@link DexArchiveProvider}). */
private enum IncrementalDexing {
OFF(),
@@ -343,6 +395,13 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
+ "R classes from a merge action, separately from aapt.")
public boolean useParallelResourceProcessing;
+ @Option(name = "apk_signing_method",
+ converter = ApkSigningMethodConverter.class,
+ defaultValue = "legacy_v1",
+ category = "undocumented",
+ help = "Implementation to use to sign APKs")
+ public ApkSigningMethod apkSigningMethod;
+
@Override
public void addAllLabels(Multimap<String, Label> labelMap) {
if (androidCrosstoolTop != null) {
@@ -413,6 +472,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
private final boolean useRClassGenerator;
private final boolean useParallelResourceProcessing;
private final AndroidManifestMerger manifestMerger;
+ private final ApkSigningMethod apkSigningMethod;
AndroidConfiguration(Options options, Label androidSdk) {
this.sdk = androidSdk;
@@ -436,6 +496,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
this.useRClassGenerator = options.useRClassGenerator;
this.useParallelResourceProcessing = options.useParallelResourceProcessing;
this.manifestMerger = options.manifestMerger;
+ this.apkSigningMethod = options.apkSigningMethod;
}
public String getCpu() {
@@ -512,6 +573,10 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment {
return manifestMerger;
}
+ public ApkSigningMethod getApkSigningMethod() {
+ return apkSigningMethod;
+ }
+
@Override
public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) {
globalMakeEnvBuilder.put("ANDROID_CPU", cpu);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index 7582f81564..ac641ce419 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -359,6 +359,7 @@ public final class AndroidRuleClasses {
.add(attr("annotations_jar", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE))
.add(attr("main_dex_classes", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE))
.add(attr("apkbuilder", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("apksigner", LABEL).cfg(HOST).allowedFileTypes(ANY_FILE).exec())
.add(attr("zipalign", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
.add(
attr("jack", LABEL)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java
index da7b3b40a6..fc957f38f5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java
@@ -29,6 +29,7 @@ import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod;
import com.google.devtools.build.lib.rules.java.BaseJavaCompilationHelper;
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
@@ -67,6 +68,13 @@ public class AndroidSdk implements RuleConfiguredTargetFactory {
FilesToRunProvider aapt = ruleContext.getExecutablePrerequisite("aapt", Mode.HOST);
FilesToRunProvider apkBuilder = ruleContext.getExecutablePrerequisite(
"apkbuilder", Mode.HOST);
+ FilesToRunProvider apkSigner = ruleContext.getExecutablePrerequisite("apksigner", Mode.HOST);
+ ApkSigningMethod apkSigningMethod =
+ ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod();
+ if (apkSigner == null && !apkSigningMethod.signLegacy()) {
+ ruleContext.throwWithRuleError(
+ "android_sdk attribute apksigner must be set to use signing method " + apkSigningMethod);
+ }
FilesToRunProvider adb = ruleContext.getExecutablePrerequisite("adb", Mode.HOST);
FilesToRunProvider dx = ruleContext.getExecutablePrerequisite("dx", Mode.HOST);
FilesToRunProvider mainDexListCreator = ruleContext.getExecutablePrerequisite(
@@ -120,6 +128,7 @@ public class AndroidSdk implements RuleConfiguredTargetFactory {
aidl,
aapt,
apkBuilder,
+ apkSigner,
proguard,
zipalign,
jack,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java
index c790e30afb..da82a92d45 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java
@@ -22,6 +22,7 @@ import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import javax.annotation.Nullable;
/** Description of the tools Blaze needs from an Android SDK. */
@AutoValue
@@ -44,6 +45,7 @@ public abstract class AndroidSdkProvider implements TransitiveInfoProvider {
FilesToRunProvider aidl,
FilesToRunProvider aapt,
FilesToRunProvider apkBuilder,
+ @Nullable FilesToRunProvider apkSigner,
FilesToRunProvider proguard,
FilesToRunProvider zipalign,
FilesToRunProvider jack,
@@ -66,6 +68,7 @@ public abstract class AndroidSdkProvider implements TransitiveInfoProvider {
aidl,
aapt,
apkBuilder,
+ apkSigner,
proguard,
zipalign,
jack,
@@ -138,6 +141,9 @@ public abstract class AndroidSdkProvider implements TransitiveInfoProvider {
public abstract FilesToRunProvider getApkBuilder();
+ @Nullable
+ public abstract FilesToRunProvider getApkSigner();
+
public abstract FilesToRunProvider getProguard();
public abstract FilesToRunProvider getZipalign();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java
new file mode 100644
index 0000000000..5829294b2f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApkActionsBuilder.java
@@ -0,0 +1,317 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.rules.android;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for coordinating APK building, signing and zipaligning.
+ *
+ * <p>It is not always necessary to zip align APKs, for instance if the APK does not contain
+ * resources. Furthermore, we do not always care about the unsigned apk because it cannot be
+ * installed on a device until it is signed.
+ *
+ * <p>This interface allows the caller to specify their desired APKs and the implementations
+ * determines which tools to use to build actions to fulfill them.
+ */
+public abstract class ApkActionsBuilder {
+ private Artifact classesDex;
+ private Artifact resourceApk;
+ private Artifact javaResourceZip;
+ private Artifact javaResourceFile;
+ private NativeLibs nativeLibs = NativeLibs.EMPTY;
+
+ Artifact unsignedApk;
+ Artifact signedApk;
+ Artifact signedAndZipalignedApk;
+ String apkName;
+
+ /** Sets the user-visible apkName that is included in the action progress messages. */
+ public ApkActionsBuilder setApkName(String apkName) {
+ this.apkName = apkName;
+ return this;
+ }
+
+ /** Registers the actions needed to build the requested APKs in the rule context. */
+ public abstract void registerActions(RuleContext ruleContext, AndroidSemantics semantics);
+
+ /** Sets the native libraries to be included in the APK. */
+ public ApkActionsBuilder setNativeLibs(NativeLibs nativeLibs) {
+ this.nativeLibs = nativeLibs;
+ return this;
+ }
+
+ /**
+ * Sets the dex file to be included in the APK.
+ *
+ * <p>Can be either a plain .dex or a .zip file containing dexes.
+ */
+ public ApkActionsBuilder setClassesDex(Artifact classesDex) {
+ this.classesDex = classesDex;
+ return this;
+ }
+
+ /** Sets the resource APK that contains the Android resources to be bundled into the output. */
+ public ApkActionsBuilder setResourceApk(Artifact resourceApk) {
+ this.resourceApk = resourceApk;
+ return this;
+ }
+
+ /**
+ * Sets the file where Java resources are taken.
+ *
+ * <p>The contents of this zip will will be put directly into the APK except for files that are
+ * filtered out by the {@link com.android.sdklib.build.ApkBuilder} which seem to not be resources,
+ * e.g. files with the extension {@code .class}.
+ */
+ public ApkActionsBuilder setJavaResourceZip(Artifact javaResourceZip) {
+ this.javaResourceZip = javaResourceZip;
+ return this;
+ }
+
+ /**
+ * Adds an individual resource file to the root directory of the APK.
+ *
+ * <p>This provides the same functionality as {@code javaResourceZip}, except much more hacky.
+ * Will most probably won't work if there is an input artifact in the same directory as this
+ * file.
+ */
+ public ApkActionsBuilder setJavaResourceFile(Artifact javaResourceFile) {
+ this.javaResourceFile = javaResourceFile;
+ return this;
+ }
+
+ /** Requests an unsigned APK be built at the specified artifact. */
+ public ApkActionsBuilder setUnsignedApk(Artifact unsignedApk) {
+ this.unsignedApk = unsignedApk;
+ return this;
+ }
+
+ /** Requests a signed but not necessarily zip aligned APK be built at the specified artifact. */
+ public ApkActionsBuilder setSignedApk(Artifact signedApk) {
+ this.signedApk = signedApk;
+ return this;
+ }
+
+ /** Requests a signed and zipaligned APK be built at the specified artifact. */
+ public ApkActionsBuilder setSignedAndZipalignedApk(Artifact signedAndZipalignedApk) {
+ this.signedAndZipalignedApk = signedAndZipalignedApk;
+ return this;
+ }
+
+ /**
+ * Creates a generating action for {@code outApk} that builds the APK specified.
+ *
+ * <p>If {@code signingKey} is not null, the apk will be signed with it using the V1 signature
+ * scheme.
+ */
+ Action[] buildApk(RuleContext ruleContext, Artifact outApk, Artifact signingKey, String message) {
+ SpawnAction.Builder actionBuilder = new SpawnAction.Builder()
+ .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkBuilder())
+ .setProgressMessage(message)
+ .setMnemonic("AndroidApkBuilder")
+ .addOutputArgument(outApk);
+
+ if (javaResourceZip != null) {
+ actionBuilder
+ .addArgument("-rj")
+ .addInputArgument(javaResourceZip);
+ }
+
+ Artifact nativeSymlinks = nativeLibs.createApkBuilderSymlinks(ruleContext);
+ if (nativeSymlinks != null) {
+ PathFragment nativeSymlinksDir = nativeSymlinks.getExecPath().getParentDirectory();
+ actionBuilder
+ .addInputManifest(nativeSymlinks, nativeSymlinksDir)
+ .addInput(nativeSymlinks)
+ .addInputs(nativeLibs.getAllNativeLibs())
+ .addArgument("-nf")
+ // If the native libs are "foo/bar/x86/foo.so", we need to pass "foo/bar" here
+ .addArgument(nativeSymlinksDir.getPathString());
+ }
+
+ if (nativeLibs.getName() != null) {
+ actionBuilder
+ .addArgument("-rf")
+ .addArgument(nativeLibs.getName().getExecPath().getParentDirectory().getPathString())
+ .addInput(nativeLibs.getName());
+ }
+
+ if (javaResourceFile != null) {
+ actionBuilder
+ .addArgument("-rf")
+ .addArgument((javaResourceFile.getExecPath().getParentDirectory().getPathString()))
+ .addInput(javaResourceFile);
+ }
+
+ if (signingKey == null) {
+ actionBuilder.addArgument("-u");
+ } else {
+ actionBuilder.addArgument("-ks").addArgument(signingKey.getExecPathString());
+ actionBuilder.addInput(signingKey);
+ }
+
+ actionBuilder
+ .addArgument("-z")
+ .addInputArgument(resourceApk);
+
+ if (classesDex != null) {
+ actionBuilder
+ .addArgument(classesDex.getFilename().endsWith(".dex") ? "-f" : "-z")
+ .addInputArgument(classesDex);
+ }
+
+ return actionBuilder.build(ruleContext);
+ }
+
+ /**
+ * An implementation that uses ApkBuilderMain to both build and sign APKs and the Android SDK
+ * zipalign tool to zipalign. This implementation only supports V1 signature scheme (JAR signing).
+ */
+ static class LegacySignerApkActionsBuilder extends ApkActionsBuilder {
+
+ @Override
+ public void registerActions(RuleContext ruleContext, AndroidSemantics semantics) {
+ Preconditions.checkNotNull(
+ apkName, "APK name must be set to create progress messages for APK actions.");
+
+ if (unsignedApk != null) {
+ ruleContext.registerAction(
+ buildApk(ruleContext, unsignedApk, null, "Generating unsigned " + apkName));
+ }
+
+ if (signedAndZipalignedApk != null) {
+ Artifact intermediateSignedApk = this.signedApk;
+ if (intermediateSignedApk == null) {
+ // If the caller requested a zipaligned APK but not a signed APK, we still need to build
+ // a signed APK as an intermediate so we construct an artifact.
+ intermediateSignedApk = AndroidBinary.getDxArtifact(
+ ruleContext, "signed_" + signedAndZipalignedApk.getFilename());
+ }
+ ruleContext.registerAction(buildApk(
+ ruleContext,
+ intermediateSignedApk,
+ semantics.getApkDebugSigningKey(ruleContext),
+ "Generating signed " + apkName));
+ ruleContext.registerAction(
+ zipalignApk(ruleContext, intermediateSignedApk, signedAndZipalignedApk));
+ } else if (signedApk != null) {
+ ruleContext.registerAction(buildApk(
+ ruleContext,
+ signedApk,
+ semantics.getApkDebugSigningKey(ruleContext),
+ "Generating signed " + apkName));
+ }
+ }
+
+ /** Last step in buildings an apk: align the zip boundaries by 4 bytes. */
+ private Action[] zipalignApk(RuleContext ruleContext, Artifact signedApk,
+ Artifact zipAlignedApk) {
+ List<String> args = new ArrayList<>();
+ // "4" is the only valid value for zipalign, according to:
+ // http://developer.android.com/guide/developing/tools/zipalign.html
+ args.add("4");
+ args.add(signedApk.getExecPathString());
+ args.add(zipAlignedApk.getExecPathString());
+
+ return new SpawnAction.Builder()
+ .addInput(signedApk)
+ .addOutput(zipAlignedApk)
+ .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getZipalign())
+ .addArguments(args)
+ .setProgressMessage("Zipaligning " + apkName)
+ .setMnemonic("AndroidZipAlign")
+ .build(ruleContext);
+ }
+ }
+
+ /**
+ * This implementation uses the executables from the apkbuilder and apksigner attributes of
+ * android_sdk to build and sign the SDKs. Zipaligning is done by the apksigner
+ * {@link com.android.apksigner.ApkSignerTool} between the V1 and V2 signature steps. The signer
+ * supports both V1 and V2 signing signatures configured by the {@link ApkSigningMethod}
+ * parameter.
+ */
+ static class SignerToolApkActionsBuilder extends ApkActionsBuilder {
+ private final ApkSigningMethod signingMethod;
+
+ SignerToolApkActionsBuilder(ApkSigningMethod signingMethod) {
+ this.signingMethod = signingMethod;
+ }
+
+ @Override
+ public void registerActions(RuleContext ruleContext, AndroidSemantics semantics) {
+ // Only one should ever be specified. The only reason that both options exist are as a slight
+ // optimization for the legacy code path in which we only zip align full APKs, not split APKs.
+ Preconditions.checkState(
+ signedApk == null || signedAndZipalignedApk == null,
+ "ApkSignerTool cannot generate separate signedApk and signedAndZipalignedApk because "
+ + "zipaligning is done between the v1 signing and v2 signing in the same action.");
+ Preconditions.checkNotNull(
+ apkName, "APK name must be set to create progress messages for APK actions.");
+
+ Artifact finalApk = signedApk == null ? signedAndZipalignedApk : signedApk;
+ if (finalApk != null) {
+ Artifact intermediateUnsignedApk = this.unsignedApk;
+ if (intermediateUnsignedApk == null) {
+ // If the caller did not request an unsigned APK, we still need to construct one so that
+ // we can sign it. So we make up an intermediate artifact.
+ intermediateUnsignedApk = AndroidBinary.getDxArtifact(
+ ruleContext, "unsigned_" + finalApk.getFilename());
+ }
+ ruleContext.registerAction(
+ buildApk(ruleContext, intermediateUnsignedApk, null, "Generating unsigned " + apkName));
+ ruleContext.registerAction(sign(
+ ruleContext,
+ semantics.getApkDebugSigningKey(ruleContext),
+ intermediateUnsignedApk,
+ finalApk));
+ } else if (unsignedApk != null) {
+ ruleContext.registerAction(
+ buildApk(ruleContext, unsignedApk, null, "Generating unsigned " + apkName));
+ }
+ }
+
+ /**
+ * Signs and zip aligns an APK using the ApkSignerTool. Supports both the jar signing schema
+ * (v1) and the apk signing schema v2.
+ */
+ private Action[] sign(RuleContext ruleContext, Artifact signingKey, Artifact unsignedApk,
+ Artifact signedAndZipalignedApk) {
+ return new SpawnAction.Builder()
+ .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkSigner())
+ .setProgressMessage("Signing and zipaligning " + apkName)
+ .setMnemonic("ApkSignerTool")
+ .addArgument("sign")
+ .addArgument("--ks")
+ .addInputArgument(signingKey)
+ .addArguments("--ks-pass", "pass:android")
+ .addArguments("--v1-signing-enabled", Boolean.toString(signingMethod.signV1()))
+ .addArguments("--v1-signer-name", "CERT")
+ .addArguments("--v2-signing-enabled", Boolean.toString(signingMethod.signV2()))
+ .addArgument("--out")
+ .addOutputArgument(signedAndZipalignedApk)
+ .addInputArgument(unsignedApk)
+ .build(ruleContext);
+ }
+ }
+}