blob: acf004d3b0feeb6b20ef6046a649be693862549d [file] [log] [blame]
// Copyright 2015 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.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CommandLine;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder;
import com.google.devtools.build.lib.analysis.config.CompilationMode;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import javax.annotation.Nullable;
/**
* Helper class to generate Android aapt actions.
*/
public final class AndroidAaptActionHelper {
private final RuleContext ruleContext;
private final Artifact manifest;
private final Collection<Artifact> inputs = new LinkedHashSet<>();
private final Iterable<ResourceContainer> resourceContainers;
/**
* Constructs an instance of AndroidAaptActionHelper.
*
* @param ruleContext RuleContext for which the aapt actions
* will be generated.
* @param manifest Artifact representing the AndroidManifest.xml that will be
* used to package resources.
* @param resourceContainers The transitive closure of the ResourceContainers.
*/
public AndroidAaptActionHelper(RuleContext ruleContext, Artifact manifest,
Iterable<ResourceContainer> resourceContainers) {
this.ruleContext = ruleContext;
this.manifest = manifest;
this.resourceContainers = resourceContainers;
}
/**
* Returns the artifacts needed as inputs to process the resources/assets.
*/
private Iterable<Artifact> getInputs() {
if (inputs.isEmpty()) {
inputs.add(AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar());
inputs.add(manifest);
Iterables.addAll(inputs, Iterables.concat(Iterables.transform(resourceContainers,
new Function<AndroidResourcesProvider.ResourceContainer, Iterable<Artifact>>() {
@Override
public Iterable<Artifact> apply(ResourceContainer container) {
return container.getArtifacts();
}
})));
}
return inputs;
}
/**
* Creates an Action that will invoke aapt to generate symbols java sources from the
* resources and pack them into a srcjar.
* @param javaSourcesJar Artifact to be generated by executing the action
* created by this method.
* @param rTxt R.txt artifact to be generated by the aapt invocation.
* @param javaPackage The package for which resources will be generated
* @param inlineConstants whether or not constants in Java generated sources
* should be inlined by the compiler.
*/
public void createGenerateResourceSymbolsAction(Artifact javaSourcesJar,
Artifact rTxt, String javaPackage, boolean inlineConstants) {
// java path from the provided package for the resources
PathFragment javaPath = new PathFragment(javaPackage.replace('.', '/'));
PathFragment javaResourcesRoot = javaSourcesJar.getRoot().getExecPath().getRelative(
ruleContext.getUniqueDirectory("_java_resources"));
String javaResources = javaResourcesRoot.getRelative(javaPath).getPathString();
List<String> args = new ArrayList<>();
args.add(javaSourcesJar.getExecPathString());
args.add(javaResourcesRoot.getPathString());
args.add(javaResources);
args.addAll(createAaptCommand("javasrcs", javaSourcesJar, rTxt, inlineConstants,
"-J", javaResources, "--custom-package", javaPackage, "--rename-manifest-package",
javaPackage));
final Builder builder = new SpawnAction.Builder()
.addInputs(getInputs())
.addTool(AndroidSdkProvider.fromRuleContext(ruleContext).getAapt())
.setExecutable(
ruleContext.getExecutablePrerequisite("$android_aapt_java_generator", Mode.HOST))
.addOutput(javaSourcesJar)
.setCommandLine(CommandLine.of(args, false))
.useParameterFile(ParameterFileType.UNQUOTED)
.setProgressMessage("Generating Java resources")
.setMnemonic("AndroidAapt");
if (rTxt != null) {
builder.addOutput(rTxt);
}
ruleContext.registerAction(builder.build(ruleContext));
}
/**
* Creates an Action that will invoke aapt to package the android resources
* into an apk file.
* @param apk Packed resources artifact to be generated by the aapt invocation.
*/
public void createGenerateApkAction(Artifact apk, String renameManifestPackage,
List<String> aaptOpts, List<String> densities) {
List<String> args;
if (renameManifestPackage == null) {
args = createAaptCommand("apk", apk, null, true, "-F", apk.getExecPathString());
} else {
args = createAaptCommand("apk", apk, null, true, "-F",
apk.getExecPathString(), "--rename-manifest-package", renameManifestPackage);
}
if (!densities.isEmpty()) {
args.add(0, "start_densities");
args.add(1, "end_densities");
args.addAll(1, densities);
}
args.addAll(aaptOpts);
ruleContext.registerAction(new SpawnAction.Builder()
.addInputs(getInputs())
.addTool(AndroidSdkProvider.fromRuleContext(ruleContext).getAapt())
.addOutput(apk)
.setExecutable(
ruleContext.getExecutablePrerequisite("$android_aapt_apk_generator", Mode.HOST))
.setCommandLine(CommandLine.of(args, false))
.useParameterFile(ParameterFileType.UNQUOTED)
.setProgressMessage("Generating apk resources")
.setMnemonic("AndroidAapt")
.build(ruleContext));
}
private List<String> createAaptCommand(String actionKind, Artifact output,
Artifact rTxtOutput, boolean inlineConstants, String... outputArgs) {
return createAaptCommand(
actionKind, output, rTxtOutput, inlineConstants, Arrays.asList(outputArgs));
}
private List<String> createAaptCommand(String actionKind, Artifact output,
Artifact rTxtOutput, boolean inlineConstants, Collection<String> outputArgs) {
List<String> args = new ArrayList<>();
args.addAll(getArgs(output, actionKind, ResourceType.RESOURCES));
args.addAll(getArgs(output, actionKind, ResourceType.ASSETS));
args.add(
AndroidSdkProvider.fromRuleContext(ruleContext).getAapt().getExecutable().getExecPathString());
args.add("package");
args.addAll(outputArgs);
// Allow overlay in case the same resource appears in more than one target,
// giving precedence to the order in which they are found. This is needed
// in order to support android library projects.
args.add("--auto-add-overlay");
if (rTxtOutput != null) {
args.add("--output-text-symbols");
args.add(rTxtOutput.getExecPath().getParentDirectory().getPathString());
}
if (!inlineConstants) {
args.add("--non-constant-id");
}
if (ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) {
args.add("--debug-mode");
}
args.add("-I");
args.add(AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar().getExecPathString());
args.add("-M");
args.add(manifest.getExecPathString());
args.addAll(getResourcesDirArg(output, actionKind, "-S", ResourceType.RESOURCES));
args.addAll(getResourcesDirArg(output, actionKind, "-A", ResourceType.ASSETS));
return args;
}
@VisibleForTesting
public List<String> getArgs(Artifact output, String actionKind, ResourceType resourceType) {
PathFragment outputPath = outputPath(output, actionKind, resourceType);
List<String> args = new ArrayList<>();
args.add("start_" + resourceType.getAttribute());
args.add(outputPath.getPathString());
// First make sure path elements are unique
Collection<String> paths = new LinkedHashSet<>();
for (AndroidResourcesProvider.ResourceContainer container : resourceContainers) {
for (Artifact artifact : container.getArtifacts(resourceType)) {
paths.add(artifact.getExecPathString());
}
}
// Than populate the command line
for (String path : paths) {
args.add(path);
args.add(path);
}
args.add("end_" + resourceType.getAttribute());
// if there is at least one artifact
if (args.size() > 3) {
return ImmutableList.copyOf(args);
} else {
return ImmutableList.<String>of();
}
}
/**
* Returns optional part of the <code>aapt</code> command line:
* optionName output_path.
*/
@VisibleForTesting
public List<String> getResourcesDirArg(Artifact output, String actionKind, String resourceArg,
ResourceType resourceType) {
PathFragment outputPath = outputPath(output, actionKind, resourceType);
List<String> dirArgs = new ArrayList<>();
Collection<String> paths = new LinkedHashSet<>();
// First make sure roots are unique
for (AndroidResourcesProvider.ResourceContainer container : resourceContainers) {
for (PathFragment root : container.getRoots(resourceType)) {
paths.add(outputPath.getRelative(root).getPathString());
}
}
// Than populate the command line
for (String path : paths) {
dirArgs.add(resourceArg);
dirArgs.add(path);
}
return ImmutableList.copyOf(dirArgs);
}
/**
* Returns a resourceType specific unique output location for the given action kind.
*/
private PathFragment outputPath(Artifact output, String actionKind, ResourceType resourceType) {
return output.getRoot().getExecPath().getRelative(ruleContext.getUniqueDirectory(
"_" + resourceType.getAttribute() + "_" + actionKind));
}
public void createGenerateProguardAction(
Artifact outputSpec, @Nullable Artifact outputMainDexSpec) {
ImmutableList.Builder<Artifact> outputs = ImmutableList.builder();
ImmutableList.Builder<String> aaptArgs = ImmutableList.builder();
outputs.add(outputSpec);
aaptArgs.add("-G").add(outputSpec.getExecPathString());
if (outputMainDexSpec != null) {
aaptArgs.add("-D").add(outputMainDexSpec.getExecPathString());
outputs.add(outputMainDexSpec);
}
List<String> aaptCommand =
createAaptCommand("proguard", outputSpec, null, true, aaptArgs.build());
ruleContext.registerAction(new SpawnAction.Builder()
.addInputs(getInputs())
.addTool(AndroidSdkProvider.fromRuleContext(ruleContext).getAapt())
.addOutputs(outputs.build())
.setExecutable(
ruleContext.getExecutablePrerequisite("$android_aapt_apk_generator", Mode.HOST))
.setCommandLine(CommandLine.of(aaptCommand, false))
.useParameterFile(ParameterFileType.UNQUOTED)
.setProgressMessage("Generating Proguard configuration for resources")
.setMnemonic("AndroidAapt")
.build(ruleContext));
}
}