// 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.ImmutableMap; 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.collect.nestedset.NestedSet; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod; import com.google.devtools.build.lib.rules.java.JavaHelper; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Map; /** * 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 Artifact resourceApk;
private Artifact javaResourceZip;
private Artifact javaResourceFile;
private NestedSet 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.
*
* 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.
*
* 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;
}
public ApkActionsBuilder setNativeLibsZips(NestedSet 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, 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 (nativeLibsZips != null) {
for (Artifact nativeLibsZip : nativeLibsZips) {
actionBuilder
.addArgument("-z")
.addInputArgument(nativeLibsZip);
}
}
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);
}
ruleContext.registerAction(actionBuilder.build(ruleContext));
}
/**
* Registers generating actions for {@code outApk} that build an unsigned APK using SingleJar.
*/
private void buildApk(RuleContext ruleContext, Artifact outApk, String message) {
Map
*
* 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 " + 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 signingKey,
Artifact unsignedApk, Artifact signedAndZipalignedApk) {
ruleContext.registerAction(new SpawnAction.Builder()
.setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkSigner())
.setProgressMessage("Signing " + 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(
ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable(),
singleJar,
JavaToolchainProvider.fromRuleContext(ruleContext).getJvmOptions())
.addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext));
} else {
builder.setExecutable(singleJar);
}
}
}