// 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.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl; 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.rules.java.JavaCommon; import com.google.devtools.build.lib.rules.java.JavaHelper; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; /** * A class for coordinating APK building, signing and zipaligning. * *
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.
*/
public class ApkActionsBuilder {
private Artifact classesDex;
private ImmutableList.Builder Can be either a plain classes.dex or a .zip file containing dexes.
*/
public ApkActionsBuilder setClassesDex(Artifact classesDex) {
Preconditions.checkArgument(
classesDex.getFilename().endsWith(".zip")
|| classesDex.getFilename().equals("classes.dex"));
this.classesDex = classesDex;
return this;
}
/** Add a zip file that should be copied as is into the APK. */
public ApkActionsBuilder addInputZip(Artifact inputZip) {
this.inputZips.add(inputZip);
return this;
}
public ApkActionsBuilder addInputZips(Iterable 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 APK be built at the specified artifact. */
public ApkActionsBuilder setSignedApk(Artifact signedApk) {
this.signedApk = signedApk;
return this;
}
/** Requests that signed APKs are zipaligned. */
public ApkActionsBuilder setZipalignApk(boolean zipalign) {
this.zipalignApk = zipalign;
return this;
}
/** Sets the signing key that will be used to sign the APK. */
public ApkActionsBuilder setSigningKey(Artifact signingKey) {
this.signingKey = signingKey;
return this;
}
/** Registers the actions needed to build the requested APKs in the rule context. */
public void registerActions(RuleContext ruleContext) {
boolean useSingleJarApkBuilder =
ruleContext.getFragment(AndroidConfiguration.class).useSingleJarApkBuilder();
// 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.
Artifact intermediateUnsignedApk = unsignedApk != null
? unsignedApk
: AndroidBinary.getDxArtifact(ruleContext, "unsigned_" + signedApk.getFilename());
if (useSingleJarApkBuilder) {
buildApk(ruleContext, intermediateUnsignedApk, "Generating unsigned " + apkName);
} else {
legacyBuildApk(ruleContext, intermediateUnsignedApk, "Generating unsigned " + apkName);
}
if (signedApk != null) {
Artifact apkToSign = intermediateUnsignedApk;
// Zipalignment is performed before signing. So if a zipaligned APK is requested, we need an
// intermediate zipaligned-but-not-signed apk artifact.
if (zipalignApk) {
apkToSign =
AndroidBinary.getDxArtifact(ruleContext, "zipaligned_" + signedApk.getFilename());
zipalignApk(ruleContext, intermediateUnsignedApk, apkToSign);
}
signApk(ruleContext, apkToSign, signedApk);
}
}
/**
* Registers generating actions for {@code outApk} that builds the APK specified.
*
* If {@code signingKey} is not null, the apk will be signed with it using the V1 signature
* scheme.
*/
private void legacyBuildApk(RuleContext ruleContext, Artifact outApk, 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);
}
Pair
*
* This method creates the syntax for the second form.
*/
private static String singleJarResourcesArgument(String from, String to) {
return from + ":" + to;
}
/** Uses the zipalign tool to align the zip boundaries for uncompressed resources by 4 bytes. */
private void zipalignApk(RuleContext ruleContext, Artifact inputApk, Artifact zipAlignedApk) {
ruleContext.registerAction(
new SpawnAction.Builder()
.addInput(inputApk)
.addOutput(zipAlignedApk)
.setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getZipalign())
.addArgument("4")
.addInputArgument(inputApk)
.addOutputArgument(zipAlignedApk)
.setProgressMessage("Zipaligning %s", apkName)
.setMnemonic("AndroidZipAlign")
.build(ruleContext));
}
/**
* Signs an APK using the ApkSignerTool. Supports both the jar signing scheme(v1) and the apk
* signing scheme v2. Note that zip alignment is preserved by this step. Furthermore, zip
* alignment cannot be performed after v2 signing without invalidating the signature.
*/
private void signApk(
RuleContext ruleContext, Artifact unsignedApk, Artifact signedAndZipalignedApk) {
ApkSigningMethod signingMethod =
ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod();
ruleContext.registerAction(
new SpawnAction.Builder()
.setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkSigner())
.setProgressMessage("Signing %s", 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));
}
// Adds the appropriate SpawnAction options depending on if SingleJar is a jar or not.
private static void setSingleJarAsExecutable(
RuleContext ruleContext, SpawnAction.Builder builder) {
Artifact singleJar = JavaToolchainProvider.fromRuleContext(ruleContext).getSingleJar();
if (singleJar.getFilename().endsWith(".jar")) {
builder
.setJarExecutable(
JavaCommon.getHostJavaExecutable(ruleContext),
singleJar,
JavaToolchainProvider.fromRuleContext(ruleContext).getJvmOptions())
.addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext));
} else {
builder.setExecutable(singleJar);
}
}
}