aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar Lukacs Berki <lberki@google.com>2015-05-19 09:28:06 +0000
committerGravatar Lukacs Berki <lberki@google.com>2015-05-19 09:34:35 +0000
commit89c2799dd890e6b93e2c3ddc63c54fddd988ea78 (patch)
tree8c2951c5e065ccc4c73755102851c36958f067a3 /src/main/java/com/google/devtools/build/lib
parentd3461dba46b50719e238939946048cd1ca12755a (diff)
Move the source code of the Android rules to the Bazel tree.
This is mostly a "[] mv", except for the extra constant that specifies the location of the Android SDK and moving the $zip attribute. They are minor enough to be included in this CL. Tested by creating a Bazel tree, compiling it and verifying that the Android classes are in libblaze.jar. I also eyeballed the source as a final check that nothing sensitive gets leaked. -- MOS_MIGRATED_REVID=93971892
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib')
-rw-r--r--src/main/java/com/google/devtools/build/lib/Constants.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java156
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java278
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java1229
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java139
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidCcLinkParamsProvider.java47
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java728
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java275
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java56
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java368
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryAarProvider.java43
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryBaseRule.java171
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java56
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidNativeLibraryProvider.java37
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverLinkLibrariesProvider.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceContainerBuilder.java100
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java324
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProvider.java194
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java758
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java68
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java113
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java70
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidTools.java319
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidToolsDefaultsJar.java88
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ApkProvider.java49
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java486
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java319
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java142
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ProguardMappingProvider.java35
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ProguardSpecProvider.java36
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceApk.java89
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/RuleConfigurationException.java18
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsAction.java143
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsActionContext.java41
34 files changed, 7021 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/Constants.java b/src/main/java/com/google/devtools/build/lib/Constants.java
index f976be85da..9e18b64544 100644
--- a/src/main/java/com/google/devtools/build/lib/Constants.java
+++ b/src/main/java/com/google/devtools/build/lib/Constants.java
@@ -86,4 +86,6 @@ public class Constants {
* Rule classes which specify iOS devices for running tests.
*/
public static final ImmutableSet<String> IOS_DEVICE_RULE_CLASSES = ImmutableSet.of("ios_device");
+
+ public static final String ANDROID_DEFAULT_SDK = "//tools/android:sdk";
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java
new file mode 100644
index 0000000000..a0b75d8d81
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java
@@ -0,0 +1,156 @@
+// Copyright 2015 Google Inc. 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.Functions;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.devtools.build.lib.actions.Action;
+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.actions.ActionConstructionContext;
+import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder for creating aar generator action.
+ */
+public class AarGeneratorBuilder {
+
+ private ResourceContainer primary;
+ private Artifact manifest;
+ private Artifact rTxt;
+ private Artifact classes;
+ private boolean strictMerge;
+
+ private Artifact aarOut;
+
+ private final AndroidTools androidTools;
+ private final RuleContext ruleContext;
+ private final SpawnAction.Builder builder;
+
+ /**
+ * Creates an {@link AarGeneratorBuilder}.
+ *
+ * @param androidTools A configured {@link AndroidTools} to retrieve the
+ * {@link FilesToRunProvider} for the AarGeneratorAction.
+ * @param ruleContext The {@link RuleContext} that is used to register the {@link Action}.
+ */
+ public AarGeneratorBuilder(AndroidTools androidTools, RuleContext ruleContext) {
+ this.androidTools = androidTools;
+ this.ruleContext = ruleContext;
+ this.builder = new SpawnAction.Builder();
+ }
+
+ public AarGeneratorBuilder withPrimary(ResourceContainer primary) {
+ this.primary = primary;
+ return this;
+ }
+
+ public AarGeneratorBuilder withManifest(Artifact manifest) {
+ this.manifest = manifest;
+ return this;
+ }
+
+ public AarGeneratorBuilder withRtxt(Artifact rTxt) {
+ this.rTxt = rTxt;
+ return this;
+ }
+
+ public AarGeneratorBuilder withClasses(Artifact classes) {
+ this.classes = classes;
+ return this;
+ }
+
+ public AarGeneratorBuilder setAAROut(Artifact aarOut) {
+ this.aarOut = aarOut;
+ return this;
+ }
+
+ public AarGeneratorBuilder strictResourceMerging() {
+ this.strictMerge = true;
+ return this;
+ }
+
+ public void build(ActionConstructionContext context) {
+ List<Artifact> outs = new ArrayList<>();
+ List<Artifact> ins = new ArrayList<>();
+ List<String> args = new ArrayList<>();
+
+ args.add("--mainData");
+ addPrimaryResourceContainer(ins, args, primary);
+
+ if (manifest != null) {
+ args.add("--manifest");
+ args.add(manifest.getExecPathString());
+ ins.add(manifest);
+ }
+
+ if (rTxt != null) {
+ args.add("--rtxt");
+ args.add(rTxt.getExecPathString());
+ ins.add(rTxt);
+ }
+
+ if (classes != null) {
+ args.add("--classes");
+ args.add(classes.getExecPathString());
+ ins.add(classes);
+ }
+
+ if (!strictMerge) {
+ args.add("--nostrictMerge");
+ }
+
+ args.add("--aarOutput");
+ args.add(aarOut.getExecPathString());
+ outs.add(aarOut);
+
+ ruleContext.registerAction(this.builder
+ .addInputs(ImmutableList.<Artifact>copyOf(ins))
+ .addOutputs(ImmutableList.<Artifact>copyOf(outs))
+ .setCommandLine(CommandLine.of(args, false))
+ .setExecutable(androidTools.getAarGenerator())
+ .setProgressMessage("Building AAR package")
+ .setMnemonic("AARGenerator")
+ .build(context));
+ }
+
+ private void addPrimaryResourceContainer(List<Artifact> inputs, List<String> args,
+ ResourceContainer container) {
+ Iterables.addAll(inputs, container.getArtifacts());
+ inputs.add(container.getManifest());
+
+ // no R.txt, because it will be generated from this action.
+ args.add(String.format("%s:%s:%s",
+ convertRoots(container, ResourceType.RESOURCES),
+ convertRoots(container, ResourceType.ASSETS),
+ container.getManifest().getExecPathString()
+ ));
+ }
+
+ private static String convertRoots(ResourceContainer container, ResourceType resourceType) {
+ return Joiner.on("#").join(
+ Iterators.transform(
+ container.getRoots(resourceType).iterator(), Functions.toStringFunction()));
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java
new file mode 100644
index 0000000000..7ba64f7354
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAaptActionHelper.java
@@ -0,0 +1,278 @@
+// Copyright 2015 Google Inc. 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.base.Preconditions;
+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.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
+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.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * 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 List<ResourceContainer> resourceContainers;
+ private final AndroidTools tools;
+
+ /**
+ * 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.
+ * @param tools AndroidTools used for creating the approriate actions.
+ */
+ public AndroidAaptActionHelper(RuleContext ruleContext, Artifact manifest,
+ List<ResourceContainer> resourceContainers, AndroidTools tools) {
+ this.ruleContext = ruleContext;
+ this.manifest = manifest;
+ this.resourceContainers = resourceContainers;
+ this.tools = tools;
+ }
+
+ /**
+ * Returns the artifacts needed as inputs to process the resources/assets.
+ */
+ private Iterable<Artifact> getInputs() {
+ if (inputs.isEmpty()) {
+ // TODO(bazel-team): When using getFilesToRun(), the middleman is
+ // not expanded. Fix by providing code to expand and use getFilesToRun here.
+ RunfilesSupport aaptRunnerRunfiles = tools.getToolRunner().getRunfilesSupport();
+ Preconditions.checkState(aaptRunnerRunfiles != null, tools.getToolRunner().getLabel());
+ // Note the below may be an overapproximation of the actual runfiles, due to "conditional
+ // artifacts" (see Runfiles.PruningManifest).
+ Iterables.addAll(inputs, aaptRunnerRunfiles.getRunfilesArtifactsWithoutMiddlemen());
+ inputs.add(tools.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(tools.getAapt())
+ .setExecutable(tools.getAaptJavaGenerator())
+ .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(tools.getAapt())
+ .addOutput(apk)
+ .setExecutable(tools.getApkGenerator())
+ .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) {
+ List<String> args = new ArrayList<>();
+ args.addAll(getArgs(output, actionKind, ResourceType.RESOURCES));
+ args.addAll(getArgs(output, actionKind, ResourceType.ASSETS));
+ args.add(tools.getToolRunner().getExecutable().getExecPathString());
+ args.add(tools.getAapt().getExecutable().getExecPathString());
+ args.add("package");
+ Collections.addAll(args, 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(tools.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) {
+ List<String> aaptCommand = createAaptCommand("proguard", outputSpec, null, true,
+ "-G", outputSpec.getExecPathString());
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .addInputs(getInputs())
+ .addTool(tools.getAapt())
+ .addOutput(outputSpec)
+ .setExecutable(tools.getApkGenerator())
+ .setCommandLine(CommandLine.of(aaptCommand, false))
+ .useParameterFile(ParameterFileType.UNQUOTED)
+ .setProgressMessage("Generating Proguard Configuration")
+ .setMnemonic("AndroidAapt")
+ .build(ruleContext));
+ }
+}
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
new file mode 100644
index 0000000000..f0a4778e00
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -0,0 +1,1229 @@
+// Copyright 2015 Google Inc. 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+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.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.CommandLine;
+import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+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.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.TriState;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
+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;
+import com.google.devtools.build.lib.rules.java.JavaCommon;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgs;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * An implementation for the "android_binary" rule.
+ */
+public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
+ static final String PROGUARD_SPECS = "proguard_specs";
+
+ protected abstract JavaSemantics createJavaSemantics();
+ protected abstract AndroidSemantics createAndroidSemantics();
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) {
+ JavaSemantics javaSemantics = createJavaSemantics();
+ AndroidSemantics androidSemantics = createAndroidSemantics();
+
+ NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder();
+ ImmutableList<TransitiveInfoCollection> deps = ImmutableList.<TransitiveInfoCollection>copyOf(
+ ruleContext.getPrerequisites("deps", Mode.TARGET));
+ JavaCommon javaCommon = new JavaCommon(
+ ruleContext, javaSemantics, deps, deps, deps);
+
+ AndroidCommon androidCommon = new AndroidCommon(
+ ruleContext, javaCommon, true /* asNeverLink */, true /* exportDeps */);
+ try {
+ RuleConfiguredTargetBuilder builder =
+ init(ruleContext, filesBuilder,
+ getTransitiveResourceContainers(ruleContext, ImmutableList.of("resources", "deps")),
+ javaCommon, androidCommon, javaSemantics, androidSemantics,
+ ImmutableList.<String>of("deps"));
+ return builder.build();
+ } catch (RuleConfigurationException e) {
+ Preconditions.checkArgument(ruleContext.hasErrors(),
+ "Exception caught but no errors reported:\n %s", Joiner.on("\n").join(e.getStackTrace()));
+ return null;
+ }
+ }
+
+ public static RuleConfiguredTargetBuilder init(
+ RuleContext ruleContext,
+ NestedSetBuilder<Artifact> filesBuilder,
+ NestedSet<ResourceContainer> resourceContainers,
+ JavaCommon javaCommon,
+ AndroidCommon androidCommon,
+ JavaSemantics javaSemantics,
+ AndroidSemantics androidSemantics,
+ List<String> depsAttributes) {
+ // TODO(bazel-team): Find a way to simplify this code.
+ // treeKeys() means that the resulting map sorts the entries by key, which is necessary to
+ // ensure determinism.
+ Multimap<String, TransitiveInfoCollection> depsByArchitecture =
+ MultimapBuilder.treeKeys().arrayListValues().build();
+ AndroidConfiguration config = ruleContext.getFragment(AndroidConfiguration.class);
+ if (config.isFatApk()) {
+ for (String depsAttribute : depsAttributes) {
+ for (Map.Entry<String, ? extends List<? extends TransitiveInfoCollection>> entry :
+ ruleContext.getSplitPrerequisites(depsAttribute).entrySet()) {
+ depsByArchitecture.putAll(entry.getKey(), entry.getValue());
+ }
+ }
+ } else {
+ for (String depsAttribute : depsAttributes) {
+ depsByArchitecture.putAll(
+ config.getCpu(), ruleContext.getPrerequisites(depsAttribute, Mode.TARGET));
+ }
+ }
+ Map<String, BuildConfiguration> configurationMap = new LinkedHashMap<>();
+ Map<String, CcToolchainProvider> toolchainMap = new LinkedHashMap<>();
+ if (config.isFatApk()) {
+ for (Map.Entry<String, ? extends List<? extends TransitiveInfoCollection>> entry :
+ ruleContext.getSplitPrerequisites(":cc_toolchain_split").entrySet()) {
+ TransitiveInfoCollection dep = Iterables.getOnlyElement(entry.getValue());
+ CcToolchainProvider toolchain = CppHelper.getToolchain(ruleContext, dep);
+ configurationMap.put(entry.getKey(), dep.getConfiguration());
+ toolchainMap.put(entry.getKey(), toolchain);
+ }
+ } else {
+ configurationMap.put(config.getCpu(), ruleContext.getConfiguration());
+ toolchainMap.put(config.getCpu(), CppHelper.getToolchain(ruleContext));
+ }
+
+ AndroidTools tools = AndroidTools.fromRuleContext(ruleContext);
+
+ NativeLibs nativeLibs = shouldLinkNativeDeps(ruleContext)
+ ? NativeLibs.fromLinkedNativeDeps(ruleContext, androidSemantics.getNativeDepsFileName(),
+ depsByArchitecture, toolchainMap, configurationMap)
+ : NativeLibs.fromPrecompiledObjects(ruleContext, depsByArchitecture);
+
+ // TODO(bazel-team): Resolve all the different cases of resource handling so this conditional
+ // can go away: recompile from android_resources, and recompile from
+ // android_binary attributes.
+ ApplicationManifest applicationManifest;
+ ResourceApk splitResourceApk;
+ ResourceApk incrementalResourceApk;
+ ResourceApk resourceApk;
+ if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) {
+ // Retrieve and compile the resources defined on the android_binary rule.
+ if (!LocalResourceContainer.validateRuleContext(ruleContext)) {
+ throw new RuleConfigurationException();
+ }
+ applicationManifest = androidSemantics.getManifestForRule(ruleContext)
+ .mergeWith(ruleContext, resourceContainers);
+ resourceApk = applicationManifest.packWithDataAndResources(
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK),
+ ruleContext,
+ resourceContainers,
+ tools,
+ null, /* Artifact rTxt */
+ ruleContext.getTokenizedStringListAttr("resource_configuration_filters"),
+ ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
+ ruleContext.getTokenizedStringListAttr("densities"),
+ ruleContext.attributes().get("application_id", Type.STRING),
+ getExpandedMakeVarsForAttr(ruleContext, "version_code"),
+ getExpandedMakeVarsForAttr(ruleContext, "version_name"),
+ false,
+ getProguardConfigArtifact(ruleContext, ""));
+ incrementalResourceApk = applicationManifest.addStubApplication(ruleContext)
+ .packWithDataAndResources(ruleContext
+ .getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK),
+ ruleContext,
+ resourceContainers,
+ tools,
+ null, /* Artifact rTxt */
+ ruleContext.getTokenizedStringListAttr("resource_configuration_filters"),
+ ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
+ ruleContext.getTokenizedStringListAttr("densities"),
+ ruleContext.attributes().get("application_id", Type.STRING),
+ getExpandedMakeVarsForAttr(ruleContext, "version_code"),
+ getExpandedMakeVarsForAttr(ruleContext, "version_name"),
+ true,
+ getProguardConfigArtifact(ruleContext, "incremental"));
+ splitResourceApk = applicationManifest
+ .createSplitManifest(ruleContext, "android_resources", false)
+ .packWithDataAndResources(getDxArtifact(ruleContext, "android_resources.ap_"),
+ ruleContext,
+ resourceContainers,
+ tools,
+ null, /* Artifact rTxt */
+ ruleContext.getTokenizedStringListAttr("resource_configuration_filters"),
+ ruleContext.getTokenizedStringListAttr("nocompress_extensions"),
+ ruleContext.getTokenizedStringListAttr("densities"),
+ ruleContext.attributes().get("application_id", Type.STRING),
+ getExpandedMakeVarsForAttr(ruleContext, "version_code"),
+ getExpandedMakeVarsForAttr(ruleContext, "version_name"),
+ true,
+ getProguardConfigArtifact(ruleContext, "incremental_split"));
+ } else {
+ // Retrieve the resources from the resources attribute on the android_binary rule
+ // and recompile them if necessary.
+ applicationManifest = ApplicationManifest.fromResourcesRule(ruleContext).mergeWith(
+ ruleContext, resourceContainers);
+ // Always recompiling resources causes AndroidTest to fail in certain circumstances.
+ if (shouldRegenerate(ruleContext, resourceContainers)) {
+ resourceApk = applicationManifest.packWithResources(
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK),
+ ruleContext,
+ resourceContainers,
+ tools,
+ true,
+ getProguardConfigArtifact(ruleContext, ""));
+ } else {
+ resourceApk = applicationManifest.useCurrentResources(ruleContext, tools,
+ getProguardConfigArtifact(ruleContext, ""));
+ }
+ incrementalResourceApk = applicationManifest
+ .addStubApplication(ruleContext)
+ .packWithResources(
+ ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK),
+ ruleContext,
+ resourceContainers,
+ tools,
+ false,
+ getProguardConfigArtifact(ruleContext, "incremental"));
+
+ splitResourceApk = applicationManifest
+ .createSplitManifest(ruleContext, "android_resources", false)
+ .packWithResources(getDxArtifact(ruleContext, "android_resources.ap_"),
+ ruleContext,
+ resourceContainers,
+ tools,
+ false,
+ getProguardConfigArtifact(ruleContext, "incremental_split"));
+ }
+
+ JavaTargetAttributes resourceClasses = androidCommon.init(
+ javaSemantics, androidSemantics, tools, resourceApk, AndroidIdlProvider.EMPTY,
+ ruleContext.getConfiguration().isCodeCoverageEnabled(), true);
+
+ return createAndroidBinary(ruleContext,
+ filesBuilder,
+ javaCommon,
+ androidCommon,
+ javaSemantics,
+ androidSemantics,
+ tools,
+ nativeLibs,
+ applicationManifest,
+ resourceApk,
+ incrementalResourceApk,
+ splitResourceApk,
+ resourceClasses,
+ ImmutableList.<Artifact>of(),
+ null);
+ }
+
+
+ public static RuleConfiguredTargetBuilder createAndroidBinary(RuleContext ruleContext,
+ NestedSetBuilder<Artifact> filesBuilder,
+ JavaCommon javaCommon,
+ AndroidCommon androidCommon,
+ JavaSemantics javaSemantics,
+ AndroidSemantics androidSemantics,
+ AndroidTools tools,
+ NativeLibs nativeLibs,
+ ApplicationManifest applicationManifest,
+ ResourceApk resourceApk,
+ ResourceApk incrementalResourceApk,
+ ResourceApk splitResourceApk,
+ JavaTargetAttributes resourceClasses,
+ ImmutableList<Artifact> apksUnderTest,
+ Artifact proguardMapping) {
+ Artifact deployJar = createDeployJar(ruleContext, javaSemantics, androidCommon, resourceClasses,
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR));
+
+ ProguardOutput proguardOutput = applyProguard(ruleContext,
+ androidCommon,
+ deployJar,
+ filesBuilder,
+ resourceApk,
+ applicationManifest,
+ ruleContext.getPrerequisiteArtifacts(PROGUARD_SPECS, Mode.TARGET).list(),
+ proguardMapping,
+ tools);
+ Artifact jarToDex = proguardOutput.outputJar;
+ Artifact debugKey = ruleContext.getHostPrerequisiteArtifact("debug_key");
+ DexingOutput dexingOutput = dex(ruleContext, androidSemantics, tools,
+ getMultidexMode(ruleContext),
+ ruleContext.getTokenizedStringListAttr("dexopts"),
+ deployJar,
+ jarToDex,
+ androidCommon,
+ resourceClasses);
+
+ Artifact unsignedApk =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
+ Artifact signedApk =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_SIGNED_APK);
+
+ ApkActionBuilder apkBuilder = new ApkActionBuilder(ruleContext, tools)
+ .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")
+ .signingKey(debugKey)
+ .build(signedApk));
+
+ Artifact zipAlignedApk = zipalignApk(
+ ruleContext,
+ tools,
+ signedApk,
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK));
+
+ // Don't add blacklistedApk, so it's only built if explicitly requested.
+ filesBuilder.add(deployJar);
+ filesBuilder.add(unsignedApk);
+ filesBuilder.add(zipAlignedApk);
+
+ NestedSet<Artifact> filesToBuild = filesBuilder.build();
+
+ NestedSet<Artifact> coverageMetadata = (androidCommon.getInstrumentedJar() != null)
+ ? NestedSetBuilder.create(Order.STABLE_ORDER, androidCommon.getInstrumentedJar())
+ : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER);
+
+ RuleConfiguredTargetBuilder builder =
+ new RuleConfiguredTargetBuilder(ruleContext);
+
+ Artifact incrementalApk =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_INCREMENTAL_APK);
+
+ Artifact fullDeployMarker =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.FULL_DEPLOY_MARKER);
+ Artifact incrementalDeployMarker =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.INCREMENTAL_DEPLOY_MARKER);
+ Artifact splitDeployMarker =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.SPLIT_DEPLOY_MARKER);
+
+ Artifact incrementalDexManifest =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEX_MANIFEST);
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setMnemonic("AndroidDexManifest")
+ .setProgressMessage("Generating incremental installation manifest for "
+ + ruleContext.getLabel())
+ .setExecutable(
+ ruleContext.getExecutablePrerequisite("$build_incremental_dexmanifest", Mode.HOST))
+ .addOutputArgument(incrementalDexManifest)
+ .addInputArguments(dexingOutput.shardDexZips)
+ .useParameterFile(ParameterFileType.UNQUOTED).build(ruleContext));
+
+ Artifact stubData = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.STUB_APPLICATION_DATA);
+ Artifact stubDex = getStubDex(ruleContext, javaSemantics, androidSemantics, tools);
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .classesDex(stubDex)
+ .resourceApk(incrementalResourceApk.getArtifact())
+ .javaResourceZip(dexingOutput.javaResourceJar)
+ .nativeLibs(nativeLibs)
+ .signingKey(debugKey)
+ .javaResourceFile(stubData)
+ .message("Generating incremental apk")
+ .build(incrementalApk));
+
+ Artifact argsArtifact =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.MOBILE_INSTALL_ARGS);
+ ruleContext.registerAction(
+ new WriteAdbArgsAction(ruleContext.getActionOwner(), argsArtifact));
+
+ createInstallAction(ruleContext, tools, false, fullDeployMarker, argsArtifact,
+ incrementalDexManifest, incrementalResourceApk.getArtifact(), incrementalApk, stubData);
+
+ createInstallAction(ruleContext, tools, true, incrementalDeployMarker,
+ argsArtifact,
+ incrementalDexManifest,
+ incrementalResourceApk.getArtifact(),
+ incrementalApk,
+ stubData);
+
+ NestedSetBuilder<Artifact> splitApkSetBuilder = NestedSetBuilder.stableOrder();
+
+ for (int i = 0; i < dexingOutput.shardDexZips.size(); i++) {
+ String splitName = "dex" + (i + 1);
+ Artifact splitApkResources = createSplitApkResources(
+ ruleContext, tools, applicationManifest, splitName, true);
+ Artifact splitApk = getDxArtifact(ruleContext, splitName + ".apk");
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .classesDex(dexingOutput.shardDexZips.get(i))
+ .resourceApk(splitApkResources)
+ .signingKey(debugKey)
+ .message("Generating split dex apk " + (i + 1))
+ .build(splitApk));
+ splitApkSetBuilder.add(splitApk);
+ }
+
+ Artifact nativeSplitApkResources = createSplitApkResources(
+ ruleContext, tools, applicationManifest, "native", false);
+ Artifact nativeSplitApk = getDxArtifact(ruleContext, "native.apk");
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .resourceApk(nativeSplitApkResources)
+ .signingKey(debugKey)
+ .message("Generating split native apk")
+ .nativeLibs(nativeLibs)
+ .build(nativeSplitApk));
+ splitApkSetBuilder.add(nativeSplitApk);
+
+ Artifact javaSplitApkResources = createSplitApkResources(
+ ruleContext, tools, applicationManifest, "java_resources", false);
+ Artifact javaSplitApk = getDxArtifact(ruleContext, "java_resources.apk");
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .resourceApk(javaSplitApkResources)
+ .javaResourceZip(dexingOutput.javaResourceJar)
+ .signingKey(debugKey)
+ .message("Generating split Java resource apk")
+ .build(javaSplitApk));
+ splitApkSetBuilder.add(javaSplitApk);
+
+ Artifact resourceSplitApk = getDxArtifact(ruleContext, "android_resources.apk");
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .resourceApk(splitResourceApk.getArtifact())
+ .signingKey(debugKey)
+ .message("Generating split Android resource apk")
+ .build(resourceSplitApk));
+ splitApkSetBuilder.add(resourceSplitApk);
+
+ Artifact splitMainApkResources = getDxArtifact(ruleContext, "split_main.ap_");
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setMnemonic("AndroidStripResources")
+ .setProgressMessage("Stripping resources from split main apk")
+ .setExecutable(ruleContext.getExecutablePrerequisite("$strip_resources", Mode.HOST))
+ .addArgument("--input_resource_apk")
+ .addInputArgument(resourceApk.getArtifact())
+ .addArgument("--output_resource_apk")
+ .addOutputArgument(splitMainApkResources)
+ .build(ruleContext));
+
+ NestedSet<Artifact> splitApks = splitApkSetBuilder.build();
+ Artifact splitMainApk = getDxArtifact(ruleContext, "split_main.apk");
+ ruleContext.registerAction(new ApkActionBuilder(ruleContext, tools)
+ .resourceApk(splitMainApkResources)
+ .classesDex(stubDex)
+ .signingKey(debugKey)
+ .message("Generating split main apk")
+ .build(splitMainApk));
+ splitApkSetBuilder.add(splitMainApk);
+ NestedSet<Artifact> allSplitApks = splitApkSetBuilder.build();
+
+ createSplitInstallAction(ruleContext, tools, splitDeployMarker, argsArtifact, splitMainApk,
+ splitApks, stubData);
+
+ NestedSet<Artifact> splitOutputGroup = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(allSplitApks)
+ .add(splitDeployMarker)
+ .build();
+
+ androidCommon.addTransitiveInfoProviders(builder, tools);
+ androidSemantics.addTransitiveInfoProviders(builder, ruleContext, javaCommon, androidCommon,
+ jarToDex, resourceApk, zipAlignedApk, apksUnderTest);
+
+ if (proguardOutput.mapping != null) {
+ builder.add(ProguardMappingProvider.class,
+ new ProguardMappingProvider(proguardOutput.mapping));
+ }
+
+ return builder
+ .setFilesToBuild(filesToBuild)
+ .add(RunfilesProvider.class, RunfilesProvider.simple(new Runfiles.Builder()
+ .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES)
+ .addTransitiveArtifacts(filesToBuild)
+ .build()))
+ .add(ApkProvider.class,
+ new ApkProvider(NestedSetBuilder.create(Order.STABLE_ORDER, zipAlignedApk),
+ coverageMetadata))
+ .addOutputGroup("mobile_install_full", fullDeployMarker)
+ .addOutputGroup("mobile_install_incremental", incrementalDeployMarker)
+ .addOutputGroup("mobile_install_split", splitOutputGroup);
+ }
+
+ private static void createSplitInstallAction(RuleContext ruleContext, AndroidTools tools,
+ Artifact marker, Artifact argsArtifact, Artifact splitMainApk, NestedSet<Artifact> splitApks,
+ Artifact stubDataFile) {
+ FilesToRunProvider adb = tools.getAdb();
+ SpawnAction.Builder builder = new SpawnAction.Builder()
+ .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST))
+ .addTool(adb)
+ .executeUnconditionally()
+ .setMnemonic("AndroidInstall")
+ .setProgressMessage("Installing " + ruleContext.getLabel() + " using split apks")
+ .setExecutionInfo(ImmutableMap.of("local", ""))
+ .addArgument("--output_marker")
+ .addOutputArgument(marker)
+ .addArgument("--stub_datafile")
+ .addInputArgument(stubDataFile)
+ .addArgument("--adb")
+ .addArgument(adb.getExecutable().getExecPathString())
+ .addTool(adb)
+ .addArgument("--flagfile")
+ .addInputArgument(argsArtifact)
+ .addArgument("--split_main_apk")
+ .addInputArgument(splitMainApk);
+
+ for (Artifact splitApk : splitApks) {
+ builder
+ .addArgument("--split_apk")
+ .addInputArgument(splitApk);
+ }
+
+ ruleContext.registerAction(builder.build(ruleContext));
+ }
+
+ private static void createInstallAction(RuleContext ruleContext,
+ AndroidTools tools, boolean incremental, Artifact marker, Artifact argsArtifact,
+ Artifact dexmanifest, Artifact resourceApk, Artifact apk, Artifact stubDataFile) {
+
+ SpawnAction.Builder builder = new SpawnAction.Builder()
+ .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST))
+ // We cannot know if the user connected a new device, uninstalled the app from the device
+ // or did anything strange to it, so we always run this action.
+ .executeUnconditionally()
+ .setMnemonic("AndroidInstall")
+ .setProgressMessage(
+ "Installing " + ruleContext.getLabel() + (incremental ? " incrementally" : ""))
+ .setExecutionInfo(ImmutableMap.of("local", ""))
+ .addArgument("--output_marker")
+ .addOutputArgument(marker)
+ .addArgument("--dexmanifest")
+ .addInputArgument(dexmanifest)
+ .addArgument("--resource_apk")
+ .addInputArgument(resourceApk)
+ .addArgument("--stub_datafile")
+ .addInputArgument(stubDataFile)
+ .addArgument("--adb")
+ .addArgument(tools.getAdb().getExecutable().getExecPathString())
+ .addTool(tools.getAdb())
+ .addArgument("--flagfile")
+ .addInputArgument(argsArtifact)
+ .addTool(tools.getAdb());
+
+ if (!incremental) {
+ builder
+ .addArgument("--apk")
+ .addInputArgument(apk);
+ }
+
+ ruleContext.registerAction(builder.build(ruleContext));
+ }
+
+ private static Artifact getStubDex(RuleContext ruleContext, JavaSemantics javaSemantics,
+ AndroidSemantics androidSemantics, AndroidTools tools) {
+ JavaCompilationArgs dep = ruleContext
+ .getPrerequisite(":incremental_stub_application", Mode.TARGET)
+ .getProvider(JavaCompilationArgsProvider.class)
+ .getJavaCompilationArgs();
+
+ JavaTargetAttributes attributes = new JavaTargetAttributes.Builder(javaSemantics)
+ .addRuntimeClassPathEntries(dep.getRuntimeJars())
+ .build();
+
+ Artifact stubDeployJar = getDxArtifact(ruleContext, "stub_deploy.jar");
+ new DeployArchiveBuilder(javaSemantics, ruleContext)
+ .setOutputJar(stubDeployJar)
+ .setAttributes(attributes)
+ .build();
+
+ Artifact stubDex = getDxArtifact(ruleContext, "stub_application.dex");
+ AndroidCommon.createDexAction(
+ ruleContext,
+ androidSemantics,
+ tools,
+ stubDeployJar,
+ stubDex,
+ ImmutableList.<String>of(),
+ false,
+ null);
+
+ return stubDex;
+ }
+
+ /** Generates an uncompressed _deploy.jar of all the runtime jars. */
+ private static Artifact createDeployJar(
+ RuleContext ruleContext, JavaSemantics javaSemantics, AndroidCommon common,
+ JavaTargetAttributes attributes, Artifact deployJar) {
+ new DeployArchiveBuilder(javaSemantics, ruleContext)
+ .setOutputJar(deployJar)
+ .setAttributes(attributes)
+ .addRuntimeJars(common.getRuntimeJars())
+ .build();
+ return deployJar;
+ }
+
+ private static String getExpandedMakeVarsForAttr(RuleContext context, String attr) {
+ final String value = context.attributes().get(attr, Type.STRING);
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ return context.expandMakeVariables(attr, value);
+ }
+
+ @Immutable
+ private static final class ProguardOutput {
+ private final Artifact outputJar;
+ @Nullable private final Artifact mapping;
+
+ private ProguardOutput(Artifact outputJar, Artifact mapping) {
+ this.outputJar = outputJar;
+ this.mapping = mapping;
+ }
+ }
+
+ /** Applies the proguard specifications, and creates a ProguardedJar. */
+ public static ProguardOutput applyProguard(RuleContext ruleContext,
+ AndroidCommon common,
+ Artifact deployJarArtifact,
+ NestedSetBuilder<Artifact> filesBuilder,
+ ResourceApk resourceApk,
+ ApplicationManifest applicationManifest,
+ ImmutableList<Artifact> proguardSpecs,
+ Artifact proguardMapping,
+ AndroidTools tools) {
+ // Proguard will be only used for binaries which specify a proguard_spec
+ if (proguardSpecs.isEmpty()) {
+ return new ProguardOutput(deployJarArtifact, null);
+ }
+
+ ImmutableSortedSet.Builder<Artifact> builder =
+ ImmutableSortedSet.<Artifact>orderedBy(Artifact.EXEC_PATH_COMPARATOR).addAll(proguardSpecs);
+ for (ProguardSpecProvider dep : ruleContext.getPrerequisites("deps", Mode.TARGET,
+ ProguardSpecProvider.class)) {
+ builder.addAll(dep.getTransitiveProguardSpecs());
+ }
+ Artifact output = resourceApk.getResourceProguardConfig();
+ builder.add(output);
+ proguardSpecs = builder.build().asList();
+ Artifact proguardOutputJar =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR);
+ return createProguardAction(ruleContext, common, tools.getProguard(), deployJarArtifact,
+ proguardSpecs, proguardMapping, tools.getAndroidJar(), proguardOutputJar, filesBuilder);
+ }
+
+ public static ProguardOutput createProguardAction(RuleContext ruleContext, AndroidCommon common,
+ FilesToRunProvider proguard, Artifact jar, ImmutableList<Artifact> proguardSpecs,
+ Artifact proguardMapping, Artifact androidJar, Artifact proguardOutputJar,
+ NestedSetBuilder<Artifact> filesBuilder) {
+ Iterable<Artifact> libraryJars = NestedSetBuilder.<Artifact>naiveLinkOrder()
+ .add(androidJar)
+ .addTransitive(common.getTransitiveNeverLinkLibraries())
+ .build();
+
+ Builder builder = new SpawnAction.Builder()
+ .addInput(jar)
+ .addInputs(libraryJars)
+ .addInputs(proguardSpecs)
+ .addOutput(proguardOutputJar)
+ .setExecutable(proguard)
+ .setProgressMessage("Trimming binary with proguard")
+ .setMnemonic("Proguard")
+ .addArgument("-injars")
+ .addArgument(jar.getExecPathString());
+
+ for (Artifact libraryJar : libraryJars) {
+ builder.addArgument("-libraryjars")
+ .addArgument(libraryJar.getExecPathString());
+ }
+
+ filesBuilder.add(proguardOutputJar);
+
+ if (proguardMapping != null) {
+ builder.addInput(proguardMapping)
+ .addArgument("-applymapping")
+ .addArgument(proguardMapping.getExecPathString());
+ }
+
+ Artifact proguardOutputMap = null;
+ if (ruleContext.attributes().get("proguard_generate_mapping", Type.BOOLEAN)) {
+ proguardOutputMap = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_BINARY_PROGUARD_MAP);
+
+ builder.addOutput(proguardOutputMap)
+ .addArgument("-printmapping")
+ .addArgument(proguardOutputMap.getExecPathString());
+ filesBuilder.add(proguardOutputMap);
+ }
+
+ builder.addArgument("-outjars")
+ .addArgument(proguardOutputJar.getExecPathString());
+
+ for (Artifact proguardSpec : proguardSpecs) {
+ builder.addArgument("@" + proguardSpec.getExecPathString());
+ }
+
+ ruleContext.registerAction(builder.build(ruleContext));
+ return new ProguardOutput(proguardOutputJar, proguardOutputMap);
+ }
+
+ @Immutable
+ private static final class DexingOutput {
+ private final Artifact classesDexZip;
+ private final Artifact javaResourceJar;
+ private final ImmutableList<Artifact> shardDexZips;
+
+ private DexingOutput(
+ Artifact classesDexZip, Artifact javaResourceJar, Iterable<Artifact> shardDexZips) {
+ this.classesDexZip = classesDexZip;
+ this.javaResourceJar = javaResourceJar;
+ this.shardDexZips = ImmutableList.copyOf(shardDexZips);
+ }
+ }
+
+ /** Dexes the ProguardedJar to generate ClassesDex that has a reference classes.dex. */
+ static DexingOutput dex(RuleContext ruleContext, AndroidSemantics semantics,
+ AndroidTools tools, MultidexMode multidexMode, List<String> dexopts, Artifact deployJar,
+ Artifact proguardedJar, AndroidCommon common, JavaTargetAttributes attributes) {
+ Artifact classesDex = AndroidBinary.getDxArtifact(ruleContext,
+ getMultidexMode(ruleContext).getOutputDexFilename());
+ if (!AndroidBinary.supportsMultidexMode(tools, multidexMode)) {
+ ruleContext.ruleError("Multidex mode \"" + multidexMode.getAttributeValue()
+ + "\" not supported by this version of the Android SDK");
+ throw new RuleConfigurationException();
+ }
+
+ int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER);
+ if (dexShards > 1 && multidexMode == MultidexMode.OFF) {
+ ruleContext.ruleError(".dex sharding is only available in multidex mode");
+ throw new RuleConfigurationException();
+ }
+
+ Artifact mainDexList = ruleContext.getPrerequisiteArtifact("main_dex_list", Mode.TARGET);
+ if ((mainDexList != null && multidexMode != MultidexMode.MANUAL_MAIN_DEX)
+ || (mainDexList == null && multidexMode == MultidexMode.MANUAL_MAIN_DEX)) {
+ ruleContext.ruleError(
+ "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified.");
+ throw new RuleConfigurationException();
+ }
+
+ if (multidexMode == MultidexMode.OFF) {
+ // Single dex mode: generate classes.dex directly from the input jar.
+ AndroidCommon.createDexAction(
+ ruleContext, semantics, tools, proguardedJar, classesDex, dexopts, false, null);
+ return new DexingOutput(classesDex, deployJar, ImmutableList.of(classesDex));
+ } else {
+ // Multidex mode: generate classes.dex.zip, where the zip contains [classes.dex,
+ // classes2.dex, ... classesN.dex]. Because the dexer also places resources into this zip,
+ // we also need to create a cleanup action that removes all non-.dex files before staging
+ // for apk building.
+ if (multidexMode == MultidexMode.LEGACY) {
+ // For legacy multidex, we need to generate a list for the dexer's --main-dex-list flag.
+ mainDexList = createMainDexListAction(ruleContext, tools, proguardedJar);
+ }
+
+ if (dexShards > 1) {
+ List<Artifact> shardJars = new ArrayList<>(dexShards);
+ for (int i = 1; i <= dexShards; i++) {
+ shardJars.add(getDxArtifact(ruleContext, "shard" + i + ".jar"));
+ }
+
+ CustomCommandLine.Builder shardCommandLine = CustomCommandLine.builder()
+ .addBeforeEachExecPath("--output_jar", shardJars);
+
+ Artifact javaResourceJar =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.JAVA_RESOURCES_JAR);
+
+ if (mainDexList != null) {
+ shardCommandLine.addExecPath("--main_dex_filter", mainDexList);
+ }
+
+ // If we need to run Proguard, all the class files will be in the Proguarded jar and the
+ // deploy jar will already have been built (since it's the input of Proguard) and it will
+ // contain all the Java resources. Otherwise, we don't want to have deploy jar creation on
+ // the critical path, so we put all the jar files that constitute it on the inputs of the
+ // jar shuffler.
+ if (proguardedJar != deployJar) {
+ shardCommandLine.addExecPath("--input_jar", proguardedJar);
+ } else {
+ shardCommandLine
+ .addBeforeEachExecPath("--input_jar", common.getRuntimeJars())
+ .addBeforeEachExecPath("--input_jar", attributes.getRuntimeClassPathForArchive());
+ }
+
+ shardCommandLine.addExecPath("--output_resources", javaResourceJar);
+
+ SpawnAction.Builder shardAction = new SpawnAction.Builder()
+ .setMnemonic("ShardClassesToDex")
+ .setProgressMessage("Sharding classes for dexing for " + ruleContext.getLabel())
+ .setExecutable(ruleContext.getExecutablePrerequisite("$shuffle_jars", Mode.HOST))
+ .addOutputs(shardJars)
+ .addOutput(javaResourceJar)
+ .setCommandLine(shardCommandLine.build());
+
+ if (mainDexList != null) {
+ shardAction.addInput(mainDexList);
+ }
+ if (proguardedJar != deployJar) {
+ shardAction.addInput(proguardedJar);
+ } else {
+ shardAction
+ .addInputs(common.getRuntimeJars())
+ .addInputs(attributes.getRuntimeClassPathForArchive());
+ }
+
+ ruleContext.registerAction(shardAction.build(ruleContext));
+
+ List<Artifact> shardDexes = new ArrayList<>(dexShards);
+ for (int i = 1; i <= dexShards; i++) {
+ Artifact shardJar = shardJars.get(i - 1);
+ Artifact shard = getDxArtifact(ruleContext, "shard" + i + ".dex.zip");
+ shardDexes.add(shard);
+ AndroidCommon.createDexAction(
+ ruleContext, semantics, tools, shardJar, shard, dexopts, true, null);
+ }
+
+ CommandLine mergeCommandLine = CustomCommandLine.builder()
+ .addBeforeEachExecPath("--input_zip", shardDexes)
+ .addExecPath("--output_zip", classesDex)
+ .build();
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setMnemonic("MergeDexZips")
+ .setProgressMessage("Merging dex shards for " + ruleContext.getLabel())
+ .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips", Mode.HOST))
+ .addInputs(shardDexes)
+ .addOutput(classesDex)
+ .setCommandLine(mergeCommandLine)
+ .build(ruleContext));
+ return new DexingOutput(classesDex, javaResourceJar, shardDexes);
+ } else {
+ // Create an artifact for the intermediate zip output that includes non-.dex files.
+ PathFragment dexPath = classesDex.getRootRelativePath();
+ Artifact classesDexIntermediate = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ dexPath.getParentDirectory().getRelative("intermediate_" + dexPath.getBaseName()),
+ ruleContext.getBinOrGenfilesDirectory());
+
+ // Have the dexer generate the intermediate file and the "cleaner" action consume this to
+ // generate the final archive with only .dex files.
+ AndroidCommon.createDexAction(ruleContext, semantics, tools, proguardedJar,
+ classesDexIntermediate, dexopts, true, mainDexList);
+ createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex);
+ return new DexingOutput(classesDex, deployJar, ImmutableList.of(classesDex));
+ }
+ }
+ }
+
+ /**
+ * Creates an action that copies a .zip file to a specified path, filtering all non-.dex files
+ * out of the output.
+ */
+ static void createCleanDexZipAction(RuleContext ruleContext, Artifact inputZip,
+ Artifact outputZip) {
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setExecutable(ruleContext.getExecutablePrerequisite("$zip", Mode.HOST))
+ .addInput(inputZip)
+ .addOutput(outputZip)
+ .addArgument(inputZip.getExecPathString())
+ .addArgument("--out")
+ .addArgument(outputZip.getExecPathString())
+ .addArgument("--copy")
+ .addArgument("classes*.dex")
+ .setProgressMessage("Trimming " + inputZip.getExecPath().getBaseName())
+ .setMnemonic("TrimDexZip")
+ .build(ruleContext));
+ }
+
+ /**
+ * Creates an action that generates a list of classes to be passed to the dexer's
+ * --main-dex-list flag (which specifies the classes that need to be directly in classes.dex).
+ * Returns the file containing the list.
+ */
+ static Artifact createMainDexListAction(
+ RuleContext ruleContext, AndroidTools tools, Artifact jar) {
+ // Process the input jar through Proguard into an intermediate, streamlined jar.
+ Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar");
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .addOutput(strippedJar)
+ .setExecutable(tools.getProguard())
+ .setProgressMessage("Generating streamlined input jar for main dex classes list")
+ .setMnemonic("MainDexClassesIntermediate")
+ .addArgument("-injars")
+ .addInputArgument(jar)
+ .addArgument("-libraryjars")
+ .addInputArgument(tools.getShrinkedAndroidJar())
+ .addArgument("-outjars")
+ .addArgument(strippedJar.getExecPathString())
+ .addArgument("-dontwarn")
+ .addArgument("-dontnote")
+ .addArgument("-forceprocessing")
+ .addArgument("-dontoptimize")
+ .addArgument("-dontobfuscate")
+ .addArgument("-dontpreverify")
+ .addArgument("-include")
+ .addInputArgument(tools.getMainDexClasses())
+ .build(ruleContext));
+
+ // Create the main dex classes list.
+ Artifact mainDexList = AndroidBinary.getDxArtifact(ruleContext, "main_dex_list.txt");
+ ruleContext.registerAction(tools.mainDexListAction(jar, strippedJar, mainDexList));
+ return mainDexList;
+ }
+
+ private static Artifact createSplitApkResources(RuleContext ruleContext, AndroidTools tools,
+ ApplicationManifest mainManifest, String splitName, boolean hasCode) {
+ Artifact splitManifest = mainManifest.createSplitManifest(ruleContext, splitName, hasCode)
+ .getManifest();
+ Artifact splitResources = getDxArtifact(ruleContext, "split_" + splitName + ".ap_");
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setExecutable(tools.getAapt())
+ .setMnemonic("AndroidAapt")
+ .setProgressMessage("Generating resource apk for split " + splitName)
+ .addArgument("package")
+ .addArgument("-F")
+ .addOutputArgument(splitResources)
+ .addArgument("-M")
+ .addInputArgument(splitManifest)
+ .addArgument("-I")
+ .addInputArgument(tools.getAndroidJar())
+ .build(ruleContext));
+
+ return splitResources;
+ }
+
+ /**
+ * 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 AndroidTools tools;
+
+ private String message;
+ private Artifact signingKey;
+ 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, AndroidTools tools) {
+ this.ruleContext = ruleContext;
+ this.tools = tools;
+ }
+
+ /**
+ * 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 the key to be used for signing the APK.
+ *
+ * <p>If set to null (the default), the APK will not be signed.
+ */
+ public ApkActionBuilder signingKey(Artifact signingKey) {
+ this.signingKey = signingKey;
+ return this;
+ }
+
+ /**
+ * Creates a generating action for {@code outApk} that builds the APK specified.
+ */
+ public Action[] build(Artifact outApk) {
+ Builder actionBuilder = tools.apkBuilderAction()
+ .setProgressMessage(message)
+ .setMnemonic("AndroidApkBuilder")
+ .addOutputArgument(outApk);
+
+ if (javaResourceZip != null) {
+ actionBuilder
+ .addArgument("-rj")
+ .addInputArgument(javaResourceZip);
+ }
+
+ for (Map.Entry<String, Iterable<Artifact>> entry : nativeLibs.getMap().entrySet()) {
+ for (Artifact library : entry.getValue()) {
+ actionBuilder
+ .addArgument("-nl")
+ .addArgument(entry.getKey())
+ .addInputArgument(library);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /** Last step in buildings an apk: align the zip boundaries by 4 bytes. */
+ static Artifact zipalignApk(RuleContext ruleContext, AndroidTools tools,
+ 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(tools.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.
+ *
+ * <p>The resources should be regenerated (using aapt) if any of the following are true:
+ * <ul>
+ * <li>There is more than one resource container
+ * <li>There are densities to filter by.
+ * <li>There are resource configuration filters.
+ * <li>There are extensions that should be compressed.
+ * </ul>
+ */
+ public static boolean shouldRegenerate(RuleContext ruleContext,
+ Iterable<ResourceContainer> resourceContainers) {
+ return Iterables.size(resourceContainers) > 1
+ || ruleContext.attributes().isAttributeValueExplicitlySpecified("densities")
+ || ruleContext.attributes().isAttributeValueExplicitlySpecified(
+ "resource_configuration_filters")
+ || ruleContext.attributes().isAttributeValueExplicitlySpecified("nocompress_extensions");
+ }
+
+ /**
+ * Returns whether to use NativeDepsHelper to link native dependencies.
+ */
+ public static boolean shouldLinkNativeDeps(RuleContext ruleContext) {
+ TriState attributeValue = ruleContext.attributes().get("legacy_native_support", Type.TRISTATE);
+ if (attributeValue == TriState.AUTO) {
+ return !ruleContext.getFragment(AndroidConfiguration.class).getLegacyNativeSupport();
+ } else {
+ return attributeValue == TriState.NO;
+ }
+ }
+
+ /**
+ * Returns the multidex mode to apply to this target.
+ */
+ public static MultidexMode getMultidexMode(RuleContext ruleContext) {
+ if (ruleContext.getRule().isAttrDefined("multidex", Type.STRING)) {
+ return Preconditions.checkNotNull(
+ MultidexMode.fromValue(ruleContext.attributes().get("multidex", Type.STRING)));
+ } else {
+ return MultidexMode.OFF;
+ }
+ }
+
+ /**
+ * List of Android SDKs that contain runtimes that do not support the native multidexing
+ * introduced in Android L. If someone tries to build an android_binary that has multidex=native
+ * set with an old SDK, we will exit with an error to alert the developer that his application
+ * might not run on devices that the used SDK still supports.
+ */
+ private static final Set<String> RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING = ImmutableSet.of(
+ "/android_sdk_linux/platforms/android_10/", "/android_sdk_linux/platforms/android_13/",
+ "/android_sdk_linux/platforms/android_15/", "/android_sdk_linux/platforms/android_16/",
+ "/android_sdk_linux/platforms/android_17/", "/android_sdk_linux/platforms/android_18/",
+ "/android_sdk_linux/platforms/android_19/", "/android_sdk_linux/platforms/android_20/");
+
+ /**
+ * Returns true if the runtime contained in the Android SDK used to build this rule supports the
+ * given version of multidex mode specified, false otherwise.
+ */
+ public static boolean supportsMultidexMode(AndroidTools tools, MultidexMode mode) {
+ if (mode == MultidexMode.NATIVE) {
+ // Native mode is not supported by Android devices running Android before v21.
+ String runtime = tools.getAndroidJar().getExecPathString();
+ for (String blacklistedRuntime : RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING) {
+ if (runtime.contains(blacklistedRuntime)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns an intermediate artifact used to support dex generation.
+ */
+ public static Artifact getDxArtifact(RuleContext ruleContext, String baseName) {
+ return ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getUniqueDirectory("_dx").getRelative(baseName),
+ ruleContext.getBinOrGenfilesDirectory());
+ }
+
+ /**
+ * Returns an intermediate artifact used to support dex generation.
+ */
+ public static Artifact getProguardConfigArtifact(RuleContext ruleContext, String prefix) {
+ // TODO(bazel-team): Remove the redundant inclusion of the rule name, as getUniqueDirectory
+ // includes the rulename as well.
+ return Preconditions.checkNotNull(
+ ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getUniqueDirectory("proguard").getRelative(
+ Joiner.on("_").join(prefix, ruleContext.getLabel().getName(), "proguard.cfg")),
+ ruleContext.getBinOrGenfilesDirectory()));
+ }
+
+ /**
+ * Class that builds the main dex classes list.
+ */
+ @VisibleForTesting
+ public static final String MAIN_DEX_CLASS_BUILDER =
+ "com.android.multidex.ClassReferenceListBuilder";
+
+ public static NestedSet<ResourceContainer> getTransitiveResourceContainers(
+ RuleContext ruleContext, List<String> attributesWithTransitiveResources) {
+ // Traverse through all android_library targets looking for resources
+ NestedSetBuilder<ResourceContainer> resourcesBuilder = NestedSetBuilder.naiveLinkOrder();
+
+ for (String attribute : attributesWithTransitiveResources) {
+ for (AndroidResourcesProvider resources :
+ ruleContext.getPrerequisites(attribute, Mode.TARGET, AndroidResourcesProvider.class)) {
+ resourcesBuilder.addTransitive(resources.getTransitiveAndroidResources());
+ }
+ }
+
+ return resourcesBuilder.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java
new file mode 100644
index 0000000000..4056c784cf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java
@@ -0,0 +1,139 @@
+// Copyright 2015 Google Inc. 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 static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
+
+/**
+ * Attributes for {@code android_binary} that are not present on {@code android_test}.
+ */
+public final class AndroidBinaryOnlyRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
+ return builder
+// /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(application_id) -->
+// A full Java-language-style package name for the application. The name should be unique.
+// The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers, and
+// underscores ('_'). However, individual package name parts may only start with letters.
+// The package name serves as a unique identifier for the application. It's also the default
+// name for the application process (see the &lt;application&gt; element's process attribute)
+// and the default task affinity of an activity.
+//
+// This overrides the value declared in the manifest.
+// ${SYNOPSIS}
+// <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("application_id", STRING).undocumented("not ready for production use"))
+// /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(version_code) -->
+// An internal version number. This number is used only to determine whether one version is
+// more recent than another, with higher numbers indicating more recent versions. This is not
+// the version number shown to users; that number is set by the version_name attribute.
+// The value must be set as an integer, such as "100". Each successive version must have a
+// higher number.
+// This overrides the value declared in the manifest.
+//
+// Subject to <a href="#make_variables">"Make" variable</a> substitution.
+// ${SYNOPSIS}
+// Suggested practice is to declare a varrdef and reference it here so that a particular build
+// invocation will be used to generate apks for release.
+// <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("version_code", STRING).undocumented("not ready for production use"))
+// /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(version_name) -->
+// The version number shown to users. The string has no other purpose than to be displayed to
+// users. The version_code attribute holds the significant version number used internally.
+// This overrides the value declared in the manifest.
+//
+// Subject to <a href="#make_variables">"Make" variable</a> substitution.
+// ${SYNOPSIS}
+// Suggested practice is to declare a varrdef and reference it here so that a particular build
+// invocation will be used to generate apks for release.
+// <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("version_name", STRING).undocumented("not ready for production use"))
+ /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(nocompress_extensions) -->
+ A list of file extension to leave uncompressed in apk.
+ ${SYNOPSIS}
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("nocompress_extensions", STRING_LIST))
+ /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(resource_configuration_filters) -->
+ A list of resource configuration filters, such 'en' that will limit the resources in the
+ apk to only the ones in the 'en' configuration.
+ ${SYNOPSIS}
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("resource_configuration_filters", STRING_LIST))
+ /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(densities) -->
+ Densities to filter for when building the apk.
+ ${SYNOPSIS}
+ This will strip out raster drawable resources that would not be loaded by a device with
+ the specified screen densities, to reduce APK size.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("densities", STRING_LIST))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(resources) -->
+ The <code>android_resources</code> target corresponding to this binary.
+ ${SYNOPSIS}
+ The target describing the manifest, resources and assets used by this
+ binary.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("resources", LABEL).allowedFileTypes().allowedRuleClasses("android_resources"))
+ .add(attr("$android_manifest_merge_tool", LABEL)
+ .cfg(HOST)
+ .exec()
+ .value(env.getLabel(AndroidRuleClasses.MANIFEST_MERGE_TOOL_LABEL)))
+
+ /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(multidex) -->
+ Whether to split code into multiple dex files.
+ ${SYNOPSIS}
+ Possible values:
+ <ul>
+ <li><code>native</code>: Split code into multiple dex files when the
+ dex 64K index limit is exceeded.
+ Assumes native platform support for loading multidex classes at
+ runtime. <em class="harmful">This only works with Android L and
+ newer</em>.</li>
+ <li><code>legacy</code>: Split code into multiple dex files when the
+ dex 64K index limit is exceeded. Assumes multidex classes are
+ loaded through application code (i.e. no platform support).</li>
+ <li><code>manual_main_dex</code>: Split code into multiple dex files when the
+ dex 64K index limit is exceeded. The content of the main dex file
+ needs to be specified by providing a list of classes in a text file
+ using the <a href="#android_binary.main_dex_list">main_dex_list</a>.</li>
+ <li><code>off</code>: Compile all code to a single dex file, even if
+ if exceeds the index limit.</li>
+ </ul>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("multidex", STRING)
+ .allowedValues(new AllowedValueSet(MultidexMode.getValidValues()))
+ .value(MultidexMode.OFF.getAttributeValue()))
+ .removeAttribute("data")
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$android_binary_only")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(AndroidRuleClasses.AndroidBinaryBaseRule.class)
+ .build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCcLinkParamsProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCcLinkParamsProvider.java
new file mode 100644
index 0000000000..effb2a6af9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCcLinkParamsProvider.java
@@ -0,0 +1,47 @@
+// Copyright 2015 Google Inc. 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.Function;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore.CcLinkParamsStoreImpl;
+
+/**
+ * A target that provides C++ libraries to be linked into Android targets.
+ */
+@Immutable
+public final class AndroidCcLinkParamsProvider implements TransitiveInfoProvider {
+ private final CcLinkParamsStoreImpl store;
+
+ public AndroidCcLinkParamsProvider(CcLinkParamsStore store) {
+ this.store = new CcLinkParamsStoreImpl(store);
+ }
+
+ public CcLinkParamsStore getLinkParams() {
+ return store;
+ }
+
+ public static final Function<TransitiveInfoCollection, CcLinkParamsStore> TO_LINK_PARAMS =
+ new Function<TransitiveInfoCollection, CcLinkParamsStore>() {
+ @Override
+ public CcLinkParamsStore apply(TransitiveInfoCollection input) {
+ AndroidCcLinkParamsProvider provider = input.getProvider(
+ AndroidCcLinkParamsProvider.class);
+ return provider == null ? null : provider.getLinkParams();
+ }
+ };
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
new file mode 100644
index 0000000000..08984b49ce
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java
@@ -0,0 +1,728 @@
+// Copyright 2015 Google Inc. 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 static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.DEFAULT;
+import static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.ERROR;
+import static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.STRICT;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.MiddlemanFactory;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParams;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore;
+import com.google.devtools.build.lib.rules.cpp.CcNativeLibraryProvider;
+import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
+import com.google.devtools.build.lib.rules.cpp.LinkerInput;
+import com.google.devtools.build.lib.rules.java.ClasspathConfiguredFragment;
+import com.google.devtools.build.lib.rules.java.JavaCcLinkParamsProvider;
+import com.google.devtools.build.lib.rules.java.JavaCommon;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgs;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts;
+import com.google.devtools.build.lib.rules.java.JavaCompilationHelper;
+import com.google.devtools.build.lib.rules.java.JavaNativeLibraryProvider;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
+import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A helper class for android rules.
+ *
+ * <p>Helps create the java compilation as well as handling the exporting of the java compilation
+ * artifacts to the other rules.
+ */
+public class AndroidCommon {
+ private final RuleContext ruleContext;
+ private final JavaCommon javaCommon;
+
+ private NestedSet<Artifact> compileTimeDependencyArtifacts;
+ private NestedSet<Artifact> filesToBuild;
+ private NestedSet<Artifact> transitiveNeverlinkLibraries =
+ NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ private Iterable<Artifact> topLevelSourceJars = ImmutableList.of();
+ private NestedSet<Artifact> transitiveSourceJars = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ private JavaCompilationArgs javaCompilationArgs = JavaCompilationArgs.EMPTY_ARGS;
+ private JavaCompilationArgs recursiveJavaCompilationArgs = JavaCompilationArgs.EMPTY_ARGS;
+ private Artifact srcJar;
+ private Artifact classJar;
+ private Artifact gensrcJar;
+
+ private Collection<Artifact> idls;
+ private AndroidIdlProvider transitiveIdlImportData;
+ private NestedSet<ResourceContainer> transitiveResources;
+ private Map<Artifact, Artifact> translatedIdlSources = ImmutableMap.of();
+ private boolean asNeverLink;
+ private boolean exportDeps;
+
+ public AndroidCommon(RuleContext ruleContext, JavaCommon javaCommon) {
+ this.ruleContext = ruleContext;
+ this.javaCommon = javaCommon;
+ this.asNeverLink = this.javaCommon.isNeverLink();
+ }
+
+ /**
+ * Creates a new AndroidCommon.
+ * @param ruleContext The rule context associated with this instance.
+ * @param common the JavaCommon instance
+ * @param asNeverLink Boolean to indicate if this rule should be treated as a compile time dep
+ * by consuming rules.
+ * @param exportDeps Boolean to indicate if the dependencies should be treated as "exported" deps.
+ */
+ public AndroidCommon(
+ RuleContext ruleContext, JavaCommon common, boolean asNeverLink, boolean exportDeps) {
+ this.ruleContext = ruleContext;
+ this.asNeverLink = asNeverLink;
+ this.exportDeps = exportDeps;
+ this.javaCommon = common;
+ }
+
+ /**
+ * Collects the transitive neverlink dependencies.
+ */
+ public static NestedSet<Artifact> collectTransitiveNeverlinkLibraries(
+ RuleContext ruleContext, JavaCommon javaCommon) {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.naiveLinkOrder();
+ for (AndroidNeverLinkLibrariesProvider dep :
+ javaCommon.getDependencies(AndroidNeverLinkLibrariesProvider.class)) {
+ builder.addTransitive(dep.getTransitiveNeverLinkLibraries());
+ }
+ if (javaCommon.isNeverLink()) {
+ builder.addAll(javaCommon.getJavaCompilationArtifacts().getRuntimeJars());
+ for (TransitiveInfoCollection dep : javaCommon.getDependencies()) {
+ if (dep.getProvider(AndroidNeverLinkLibrariesProvider.class) != null) {
+ JavaCompilationArgsProvider javaArgs = dep.getProvider(JavaCompilationArgsProvider.class);
+ Preconditions.checkState(javaArgs != null, ruleContext.getLabel());
+ builder.addTransitive(javaArgs.getRecursiveJavaCompilationArgs().getRuntimeJars());
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Creates an action that converts {@code jarToDex} to a dex file. The output will be stored in
+ * the {@link com.google.devtools.build.lib.actions.Artifact} {@code dxJar}.
+ */
+ public static void createDexAction(
+ RuleContext ruleContext, AndroidSemantics semantics, AndroidTools tools, Artifact jarToDex,
+ Artifact classesDex, List<String> dexOptions, boolean multidex, Artifact mainDexList) {
+ List<String> args = new ArrayList<>();
+ args.add("--dex");
+ // Add --no-locals to coverage builds. Otherwise local variable debug information is not
+ // preserved, which leads to runtime errors.
+ if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
+ args.add("--no-locals");
+ }
+
+ // Multithreaded dex does not work when using --multi-dex.
+ if (!multidex) {
+ // Multithreaded dex tends to run faster, but only up to about 5 threads (at which point the
+ // law of diminishing returns kicks in). This was determined experimentally, with 5-thread dex
+ // performing about 25% faster than 1-thread dex.
+ args.add("--num-threads=5");
+ }
+
+ args.addAll(dexOptions);
+ if (multidex) {
+ args.add("--multi-dex");
+ if (mainDexList != null) {
+ args.add("--main-dex-list=" + mainDexList.getExecPathString());
+ }
+ }
+ args.add("--output=" + classesDex.getExecPathString());
+ args.add(jarToDex.getExecPathString());
+
+ SpawnAction.Builder builder = tools.dxAction(semantics)
+ .addInput(jarToDex)
+ .addOutput(classesDex)
+ .addArguments(args)
+ .setProgressMessage("Converting " + jarToDex.getExecPathString() + " to dex format")
+ .setMnemonic("AndroidDexer")
+ .setResources(ResourceSet.createWithRamCpuIo(4096.0, 5.0, 0.0));
+ if (mainDexList != null) {
+ builder.addInput(mainDexList);
+ }
+ ruleContext.registerAction(builder.build(ruleContext));
+ }
+
+ private void compileResources(
+ JavaSemantics javaSemantics,
+ JavaCompilationArtifacts.Builder artifactsBuilder, JavaTargetAttributes.Builder attributes,
+ NestedSet<ResourceContainer> resourceContainers, ResourceContainer updatedResources) {
+ Artifact binaryResourcesJar =
+ ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_CLASS_JAR);
+ compileResourceJar(javaSemantics, binaryResourcesJar, updatedResources.getJavaSourceJar());
+ // combined resource constants needs to come even before own classes that may contain
+ // local resource constants
+ artifactsBuilder.addRuntimeJar(binaryResourcesJar);
+ // Repackages the R.java for each dependency package and places the resultant jars
+ // before the dependency libraries to ensure that the generated resource ids are
+ // correct.
+ createRepackerActions(attributes, resourceContainers,
+ updatedResources.getJavaPackage(),
+ binaryResourcesJar);
+ }
+
+ private void compileResourceJar(
+ JavaSemantics javaSemantics, Artifact binaryResourcesJar, Artifact javaSourceJar) {
+ JavaCompilationArtifacts.Builder javaArtifactsBuilder =
+ new JavaCompilationArtifacts.Builder();
+ JavaTargetAttributes.Builder javacJarAttributes =
+ new JavaTargetAttributes.Builder(javaSemantics);
+ javacJarAttributes.addSourceJar(javaSourceJar);
+ JavaCompilationHelper javacHelper = new JavaCompilationHelper(
+ ruleContext, javaSemantics, getJavacOpts(), javacJarAttributes);
+ Artifact outputDepsProto =
+ javacHelper.createOutputDepsProtoArtifact(binaryResourcesJar, javaArtifactsBuilder);
+
+ javacHelper.createCompileActionWithInstrumentation(binaryResourcesJar, null /* gensrcJar */,
+ outputDepsProto, javaArtifactsBuilder);
+ }
+
+ private void createRepackerActions(
+ JavaTargetAttributes.Builder attributes,
+ Iterable<ResourceContainer> resourceContainers,
+ String originalPackage,
+ Artifact binaryResourcesJar) {
+ // Now use jarjar for the rest of the resources. We need to make a copy
+ // of the final generated resources for each of the targets included in
+ // the transitive closure of this binary.
+ for (ResourceContainer otherContainer : resourceContainers) {
+ if (otherContainer.getLabel().equals(ruleContext.getLabel())) {
+ continue;
+ }
+
+ // Since the Java sources are generated by combining all resources with the
+ // ones included in the binary, the path of the artifact has to be unique
+ // per binary and per library (not only per library).
+ String jarName = otherContainer.getLabel().getName() + ".jar";
+ PathFragment jarRelativePath =
+ otherContainer.getLabel().getPackageFragment().getRelative(jarName);
+ Artifact resourcesJar = ruleContext.getAnalysisEnvironment()
+ .getDerivedArtifact(ruleContext.getUniqueDirectory(
+ "resource_jars").getRelative(jarRelativePath), ruleContext.getBinOrGenfilesDirectory());
+ // combined resource constants copy needs to come before library classes that may contain
+ // their local resource constants
+ attributes.addRuntimeClassPathEntry(resourcesJar);
+ FilesToRunProvider repackager =
+ ruleContext.getExecutablePrerequisite("$android_jar_repackager", Mode.HOST);
+ createRepackagerAction(ruleContext, binaryResourcesJar, resourcesJar, originalPackage,
+ otherContainer.getJavaPackage(), repackager);
+ }
+ }
+
+ private static Artifact createRepackagerAction(
+ RuleContext ruleContext,
+ Artifact inputJar,
+ Artifact outputJar,
+ String originalPackage,
+ String finalPackage,
+ FilesToRunProvider repackager) {
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .setExecutable(repackager)
+ .addInputArgument(inputJar)
+ .addOutputArgument(outputJar)
+ .addArgument(originalPackage)
+ .addArgument(finalPackage)
+ .setProgressMessage("Repackaging jar")
+ .setMnemonic("JarRepackager")
+ .build(ruleContext));
+ return outputJar;
+ }
+
+ public JavaTargetAttributes init(
+ JavaSemantics javaSemantics, AndroidSemantics androidSemantics,
+ AndroidTools tools, ResourceApk resourceApk, AndroidIdlProvider transitiveIdlImportData,
+ boolean addCoverageSupport, boolean collectJavaCompilationArgs) {
+ ImmutableList<Artifact> extraSources =
+ resourceApk.isLegacy() || resourceApk.getResourceJavaSrcJar() == null
+ ? ImmutableList.<Artifact>of()
+ : ImmutableList.of(resourceApk.getResourceJavaSrcJar());
+ JavaTargetAttributes.Builder attributes = init(
+ tools, androidSemantics,
+ transitiveIdlImportData,
+ resourceApk.getTransitiveResources(),
+ extraSources);
+ JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder();
+ if (resourceApk.isLegacy()) {
+ compileResources(javaSemantics, artifactsBuilder, attributes,
+ resourceApk.getTransitiveResources(), resourceApk.getPrimaryResource());
+ }
+
+ JavaCompilationHelper helper = initAttributes(attributes, javaSemantics);
+
+ if (addCoverageSupport) {
+ androidSemantics.addCoverageSupport(ruleContext, this, javaSemantics, true,
+ attributes, artifactsBuilder);
+ }
+
+ initJava(
+ helper,
+ artifactsBuilder,
+ collectJavaCompilationArgs,
+ resourceApk.getResourceJavaSrcJar());
+ return helper.getAttributes();
+ }
+
+ private JavaTargetAttributes.Builder init(
+ AndroidTools tools,
+ AndroidSemantics androidSemantics,
+ AndroidIdlProvider transitiveIdlImportData,
+ NestedSet<AndroidResourcesProvider.ResourceContainer> transitiveResources,
+ Collection<Artifact> extraArtifacts) {
+ this.transitiveIdlImportData = transitiveIdlImportData;
+ this.transitiveResources = transitiveResources;
+
+ ImmutableList.Builder<Artifact> extraSrcsBuilder =
+ new ImmutableList.Builder<Artifact>().addAll(extraArtifacts);
+
+ idls = getIdlSrcs(ruleContext);
+ if (!idls.isEmpty() && !ruleContext.hasErrors()) {
+ translatedIdlSources = generateTranslatedIdlArtifacts(ruleContext, idls);
+ }
+
+ javaCommon.initializeJavacOpts(androidSemantics.getJavacArguments());
+ JavaTargetAttributes.Builder attributes = javaCommon.initCommon(
+ extraSrcsBuilder.addAll(translatedIdlSources.values()).build());
+
+ attributes.setBootClassPath(ImmutableList.of(tools.getAndroidJar()));
+ return attributes;
+ }
+
+ private JavaCompilationHelper initAttributes(
+ JavaTargetAttributes.Builder attributes, JavaSemantics semantics) {
+ JavaCompilationHelper helper = new JavaCompilationHelper(
+ ruleContext, semantics, javaCommon.getJavacOpts(), attributes);
+ Iterable<? extends TransitiveInfoCollection> deps =
+ javaCommon.targetsTreatedAsDeps(ClasspathType.BOTH);
+ helper.addLibrariesToAttributes(deps);
+ helper.addProvidersToAttributes(javaCommon.compilationArgsFromSources(), asNeverLink);
+ attributes.setStrictJavaDeps(getStrictAndroidDeps());
+ attributes.setRuleKind(ruleContext.getRule().getRuleClass());
+ attributes.setTargetLabel(ruleContext.getLabel());
+
+ JavaCommon.validateConstraint(ruleContext, "android", deps);
+ ruleContext.checkSrcsSamePackage(true);
+ return helper;
+ }
+
+ private StrictDepsMode getStrictAndroidDeps() {
+ // Get command line strict_android_deps option
+ StrictDepsMode strict = ruleContext.getFragment(AndroidConfiguration.class).getStrictDeps();
+ // Use option if anything but DEFAULT, which is now equivalent to ERROR.
+ return (strict != DEFAULT && strict != STRICT) ? strict : ERROR;
+ }
+
+ private void initJava(JavaCompilationHelper helper,
+ JavaCompilationArtifacts.Builder javaArtifactsBuilder, boolean collectJavaCompilationArgs,
+ @Nullable Artifact additionalSourceJar) {
+ NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.<Artifact>stableOrder();
+ if (additionalSourceJar != null) {
+ filesBuilder.add(additionalSourceJar);
+ }
+
+ JavaTargetAttributes attributes = helper.getAttributes();
+ if (ruleContext.hasErrors()) {
+ // Avoid leaving filesToBuild set to null, otherwise we'll get a NullPointerException masking
+ // the real error.
+ filesToBuild = filesBuilder.build();
+ return;
+ }
+
+ if (attributes.hasJarFiles()) {
+ // This rule is repackaging some source jars as a java library
+
+ javaArtifactsBuilder.addRuntimeJars(attributes.getJarFiles());
+ javaArtifactsBuilder.addCompileTimeJars(attributes.getCompileTimeJarFiles());
+
+ filesBuilder.addAll(attributes.getJarFiles());
+ }
+
+ Artifact jar = null;
+ classJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR);
+ if (attributes.hasSourceFiles() || attributes.hasSourceJars() || attributes.hasResources()) {
+ // We only want to add a jar to the classpath of a dependent rule if it has content.
+ javaArtifactsBuilder.addRuntimeJar(classJar);
+ jar = classJar;
+ }
+
+ filesBuilder.add(classJar);
+
+ // The gensrcJar is only created if the target uses annotation processing. Otherwise,
+ // it is null, and the source jar action will not depend on the compile action.
+ gensrcJar = helper.createGensrcJar(classJar);
+
+ srcJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_SOURCE_JAR);
+ helper.createSourceJarAction(srcJar, gensrcJar);
+
+ NestedSetBuilder<Artifact> compileTimeDependenciesBuilder = NestedSetBuilder.stableOrder();
+ Artifact outputDepsProto = helper.createOutputDepsProtoArtifact(classJar, javaArtifactsBuilder);
+ if (outputDepsProto != null) {
+ compileTimeDependenciesBuilder.add(outputDepsProto);
+ }
+ helper.createCompileActionWithInstrumentation(classJar, gensrcJar, outputDepsProto,
+ javaArtifactsBuilder);
+
+ compileTimeDependencyArtifacts = compileTimeDependenciesBuilder.build();
+ filesToBuild = filesBuilder.build();
+
+ if ((attributes.hasSourceFiles() || attributes.hasSourceJars()) && jar != null) {
+ helper.createCompileTimeJarAction(jar, outputDepsProto, javaArtifactsBuilder);
+ }
+ javaCommon.setJavaCompilationArtifacts(javaArtifactsBuilder.build());
+
+ javaCommon.setClassPathFragment(new ClasspathConfiguredFragment(
+ javaCommon.getJavaCompilationArtifacts(), attributes, asNeverLink));
+
+ transitiveNeverlinkLibraries = collectTransitiveNeverlinkLibraries(ruleContext, javaCommon);
+ topLevelSourceJars = ImmutableList.of(srcJar);
+ transitiveSourceJars = javaCommon.collectTransitiveSourceJars(srcJar);
+
+ if (collectJavaCompilationArgs) {
+ this.javaCompilationArgs = collectJavaCompilationArgs(
+ ruleContext, exportDeps, asNeverLink, attributes.hasSourceFiles());
+ this.recursiveJavaCompilationArgs = collectJavaCompilationArgs(
+ ruleContext, true, asNeverLink, /* hasSources */ true);
+ }
+ }
+
+ public RuleConfiguredTargetBuilder addTransitiveInfoProviders(
+ RuleConfiguredTargetBuilder builder, AndroidTools tools) {
+ if (!idls.isEmpty()) {
+ generateAndroidIdlActions(
+ ruleContext, tools, idls, transitiveIdlImportData, translatedIdlSources);
+ }
+
+ Runfiles runfiles = new Runfiles.Builder()
+ .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES)
+ .build();
+
+ javaCommon.addTransitiveInfoProviders(builder, filesToBuild, classJar);
+
+ return builder
+ .setFilesToBuild(filesToBuild)
+ .add(RunfilesProvider.class, RunfilesProvider.simple(runfiles))
+ .add(AndroidResourcesProvider.class, new AndroidResourcesProvider(
+ ruleContext.getLabel(), transitiveResources))
+ .add(AndroidIdlProvider.class, transitiveIdlImportData)
+ .add(JavaCompilationArgsProvider.class, new JavaCompilationArgsProvider(
+ javaCompilationArgs, recursiveJavaCompilationArgs,
+ compileTimeDependencyArtifacts,
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)))
+ .add(AndroidNeverLinkLibrariesProvider.class, new AndroidNeverLinkLibrariesProvider(
+ transitiveNeverlinkLibraries))
+ .addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL,
+ collectHiddenTopLevelArtifacts(ruleContext))
+ .addOutputGroup(JavaSemantics.SOURCE_JARS_OUTPUT_GROUP, transitiveSourceJars);
+ }
+
+ public static PathFragment getAssetDir(RuleContext ruleContext) {
+ return new PathFragment(ruleContext.attributes().get(
+ AndroidResourcesProvider.ResourceType.ASSETS.getAttribute() + "_dir",
+ Type.STRING));
+ }
+
+ public static ImmutableList<Artifact> getIdlParcelables(RuleContext ruleContext) {
+ return ruleContext.getRule().isAttrDefined("idl_parcelables", Type.LABEL_LIST)
+ ? ImmutableList.copyOf(ruleContext.getPrerequisiteArtifacts(
+ "idl_parcelables", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list())
+ : ImmutableList.<Artifact>of();
+ }
+
+ public static Collection<Artifact> getIdlSrcs(RuleContext ruleContext) {
+ if (!ruleContext.getRule().isAttrDefined("idl_srcs", Type.LABEL_LIST)) {
+ return ImmutableList.of();
+ }
+ checkIdlSrcsSamePackage(ruleContext);
+ return ruleContext.getPrerequisiteArtifacts(
+ "idl_srcs", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list();
+ }
+
+ public static void checkIdlSrcsSamePackage(RuleContext ruleContext) {
+ PathFragment packageName = ruleContext.getLabel().getPackageFragment();
+ Collection<Artifact> idls = ruleContext
+ .getPrerequisiteArtifacts("idl_srcs", Mode.TARGET)
+ .filter(AndroidRuleClasses.ANDROID_IDL)
+ .list();
+ for (Artifact idl : idls) {
+ Label idlLabel = idl.getOwner();
+ if (!packageName.equals(idlLabel.getPackageFragment())) {
+ ruleContext.attributeError("idl_srcs", "do not import '" + idlLabel + "' directly. "
+ + "You should either move the file to this package or depend on "
+ + "an appropriate rule there");
+ }
+ }
+ }
+
+ public static NestedSet<LinkerInput> collectTransitiveNativeLibraries(
+ Iterable<? extends TransitiveInfoCollection> deps) {
+ NestedSetBuilder<LinkerInput> builder = NestedSetBuilder.stableOrder();
+ for (TransitiveInfoCollection dep : deps) {
+ AndroidNativeLibraryProvider android = dep.getProvider(AndroidNativeLibraryProvider.class);
+ if (android != null) {
+ builder.addTransitive(android.getTransitiveAndroidNativeLibraries());
+ continue;
+ }
+
+ JavaNativeLibraryProvider java = dep.getProvider(JavaNativeLibraryProvider.class);
+ if (java != null) {
+ builder.addTransitive(java.getTransitiveJavaNativeLibraries());
+ continue;
+ }
+
+ CcNativeLibraryProvider cc = dep.getProvider(CcNativeLibraryProvider.class);
+ if (cc != null) {
+ for (LinkerInput input : cc.getTransitiveCcNativeLibraries()) {
+ Artifact library = input.getOriginalLibraryArtifact();
+ String name = library.getFilename();
+ if (CppFileTypes.SHARED_LIBRARY.matches(name)
+ || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) {
+ builder.add(input);
+ }
+ }
+ continue;
+ }
+ }
+
+ return builder.build();
+ }
+
+ public static AndroidResourcesProvider getAndroidResources(RuleContext ruleContext) {
+ TransitiveInfoCollection prerequisite = ruleContext.getPrerequisite("resources", Mode.TARGET);
+ return prerequisite != null
+ ? prerequisite.getProvider(AndroidResourcesProvider.class)
+ : null;
+ }
+
+ public static NestedSet<Artifact> getApplicationApks(RuleContext ruleContext) {
+ NestedSetBuilder<Artifact> applicationApksBuilder = NestedSetBuilder.stableOrder();
+ for (ApkProvider dep : ruleContext.getPrerequisites("deps", Mode.TARGET, ApkProvider.class)) {
+ applicationApksBuilder.addTransitive(dep.getTransitiveApks());
+ }
+ return applicationApksBuilder.build();
+ }
+
+ private ImmutableMap<Artifact, Artifact> generateTranslatedIdlArtifacts(
+ RuleContext ruleContext, Collection<Artifact> idls) {
+ ImmutableMap.Builder<Artifact, Artifact> outputJavaSources = ImmutableMap.builder();
+ PathFragment rulePackage = ruleContext.getRule().getLabel().getPackageFragment();
+ String ruleName = ruleContext.getRule().getName();
+ // for each aidl file use aggregated preprocessed files to generate Java code
+ for (Artifact idl : idls) {
+ // Reconstruct the package tree under <rule>_aidl to avoid a name conflict
+ // if the same AIDL files are used in multiple targets.
+ PathFragment javaOutputPath = FileSystemUtils.replaceExtension(
+ rulePackage.getRelative(ruleName + "_aidl").getRelative(idl.getRootRelativePath()),
+ ".java");
+ Artifact output = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ javaOutputPath, ruleContext.getConfiguration().getGenfilesDirectory());
+ outputJavaSources.put(idl, output);
+ }
+ return outputJavaSources.build();
+ }
+
+ private JavaCompilationArgs collectJavaCompilationArgs(RuleContext ruleContext,
+ boolean recursive, boolean neverLink, boolean hasSrcs) {
+ JavaCompilationArgs.Builder builder = JavaCompilationArgs.builder()
+ .merge(javaCommon.getJavaCompilationArtifacts(), neverLink);
+ if (recursive || !hasSrcs) {
+ builder.addTransitiveTargets(ruleContext.getPrerequisites("deps", Mode.TARGET), recursive,
+ neverLink ? ClasspathType.COMPILE_ONLY : ClasspathType.BOTH);
+ }
+ return builder.build();
+ }
+
+ private void generateAndroidIdlActions(RuleContext ruleContext, AndroidTools tools,
+ Collection<Artifact> idls, AndroidIdlProvider transitiveIdlImportData,
+ Map<Artifact, Artifact> translatedIdlSources) {
+ FilesToRunProvider toolRunner =
+ ruleContext.getExecutablePrerequisite("$android_tool_runner", Mode.HOST);
+ Set<Artifact> preprocessedIdls = new LinkedHashSet<>();
+ List<String> preprocessedArgs = new ArrayList<>();
+
+ // add imports
+ for (String idlImport : transitiveIdlImportData.getTransitiveIdlImportRoots()) {
+ preprocessedArgs.add("-I" + idlImport);
+ }
+
+ // preprocess each aidl file
+ preprocessedArgs.add("-p" + tools.getFrameworkAidl().getExecPathString());
+ PathFragment rulePackage = ruleContext.getRule().getLabel().getPackageFragment();
+ String ruleName = ruleContext.getRule().getName();
+ for (Artifact idl : idls) {
+ // Reconstruct the package tree under <rule>_aidl to avoid a name conflict
+ // if the source AIDL files are also generated.
+ PathFragment preprocessedPath = rulePackage.getRelative(ruleName + "_aidl")
+ .getRelative(idl.getRootRelativePath());
+ Artifact preprocessed = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ preprocessedPath, ruleContext.getConfiguration().getGenfilesDirectory());
+ preprocessedIdls.add(preprocessed);
+ preprocessedArgs.add("-p" + preprocessed.getExecPathString());
+
+ createAndroidIdlPreprocessAction(ruleContext, tools, toolRunner, idl, preprocessed);
+ }
+
+ // aggregate all preprocessed aidl files
+ MiddlemanFactory middlemanFactory = ruleContext.getAnalysisEnvironment().getMiddlemanFactory();
+ Artifact preprocessedIdlsMiddleman = middlemanFactory.createAggregatingMiddleman(
+ ruleContext.getActionOwner(), "AndroidIDLMiddleman", preprocessedIdls,
+ ruleContext.getConfiguration().getMiddlemanDirectory());
+
+ for (Artifact idl : translatedIdlSources.keySet()) {
+ createAndroidIdlAction(ruleContext, tools, toolRunner, idl,
+ transitiveIdlImportData.getTransitiveIdlImports(),
+ preprocessedIdlsMiddleman, translatedIdlSources.get(idl), preprocessedArgs);
+ }
+ }
+
+ private void createAndroidIdlPreprocessAction(RuleContext ruleContext, AndroidTools tools,
+ FilesToRunProvider toolRunner, Artifact idl, Artifact preprocessed) {
+ RunfilesSupport toolRunnerRunfiles = toolRunner.getRunfilesSupport();
+ Preconditions.checkState(toolRunnerRunfiles != null, toolRunner.getLabel());
+ ruleContext.registerAction(tools.aidlAction()
+ // Note the below may be an overapproximation of the actual runfiles, due to "conditional
+ // artifacts" (see Runfiles.PruningManifest).
+ // TODO(bazel-team): When using getFilesToRun(), the middleman is
+ // not expanded. Fix by providing code to expand and use getFilesToRun here.
+ .addInput(idl)
+ .addOutput(preprocessed)
+ .addArgument("--preprocess")
+ .addArgument(preprocessed.getExecPathString())
+ .addArgument(idl.getExecPathString())
+ .setProgressMessage("Android IDL preprocessing")
+ .setMnemonic("AndroidIDLPreprocess")
+ .build(ruleContext));
+ }
+
+ private void createAndroidIdlAction(RuleContext ruleContext, AndroidTools tools,
+ FilesToRunProvider toolRunner,
+ Artifact idl, Iterable<Artifact> idlImports, Artifact preprocessedIdls,
+ Artifact output, List<String> preprocessedArgs) {
+ RunfilesSupport toolRunnerRunfiles = toolRunner.getRunfilesSupport();
+ Preconditions.checkState(toolRunnerRunfiles != null, toolRunner.getLabel());
+ ruleContext.registerAction(tools.aidlAction()
+ .addInput(idl)
+ .addInputs(idlImports)
+ .addInput(preprocessedIdls)
+ .addInput(tools.getFrameworkAidl())
+ .addOutput(output)
+ .addArgument("-b") // Fail if trying to compile a parcelable.
+ .addArguments(preprocessedArgs)
+ .addArgument(idl.getExecPathString())
+ .addArgument(output.getExecPathString())
+ .setProgressMessage("Android IDL generation")
+ .setMnemonic("AndroidIDLGnerate")
+ .build(ruleContext));
+ }
+
+ public ImmutableList<String> getJavacOpts() {
+ return javaCommon.getJavacOpts();
+ }
+
+ @Nullable public Artifact getGensrcJar() {
+ return gensrcJar;
+ }
+
+ public ImmutableList<Artifact> getRuntimeJars() {
+ return javaCommon.getJavaCompilationArtifacts().getRuntimeJars();
+ }
+
+ public Artifact getInstrumentedJar() {
+ return javaCommon.getJavaCompilationArtifacts().getInstrumentedJar();
+ }
+
+ public NestedSet<Artifact> getTransitiveNeverLinkLibraries() {
+ return transitiveNeverlinkLibraries;
+ }
+
+ public Iterable<Artifact> getTopLevelSourceJars() {
+ return topLevelSourceJars;
+ }
+
+ public NestedSet<Artifact> getTransitiveSourceJars() {
+ return transitiveSourceJars;
+ }
+
+ public JavaSourceJarsProvider getJavaSourceJarsProvider() {
+ return new JavaSourceJarsProvider(getTransitiveSourceJars(), getTopLevelSourceJars());
+ }
+
+ public boolean isNeverLink() {
+ return asNeverLink;
+ }
+
+ public CcLinkParamsStore getCcLinkParamsStore() {
+ return getCcLinkParamsStore(javaCommon.targetsTreatedAsDeps(ClasspathType.BOTH));
+ }
+
+ public static CcLinkParamsStore getCcLinkParamsStore(
+ final Iterable<? extends TransitiveInfoCollection> deps) {
+ return new CcLinkParamsStore() {
+ @Override
+ protected void collect(CcLinkParams.Builder builder, boolean linkingStatically,
+ boolean linkShared) {
+ builder.addTransitiveTargets(deps,
+ // Link in Java-specific C++ code in the transitive closure
+ JavaCcLinkParamsProvider.TO_LINK_PARAMS,
+ // Link in Android-specific C++ code (e.g., android_libraries) in the transitive closure
+ AndroidCcLinkParamsProvider.TO_LINK_PARAMS,
+ // Link in non-language-specific C++ code in the transitive closure
+ CcLinkParamsProvider.TO_LINK_PARAMS);
+ }
+ };
+ }
+
+ private NestedSet<Artifact> collectHiddenTopLevelArtifacts(RuleContext ruleContext) {
+ NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+ for (OutputGroupProvider provider :
+ ruleContext.getPrerequisites("deps", Mode.TARGET, OutputGroupProvider.class)) {
+ builder.addTransitive(provider.getOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL));
+ }
+ return builder.build();
+ }
+}
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
new file mode 100644
index 0000000000..ea420a754c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
@@ -0,0 +1,275 @@
+// Copyright 2015 Google Inc. 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.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.Constants;
+import com.google.devtools.build.lib.analysis.RedirectChaser;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.LabelConverter;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsConverter;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.common.options.Converters;
+import com.google.devtools.common.options.Option;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Configuration fragment for Android rules.
+ */
+public class AndroidConfiguration extends BuildConfiguration.Fragment {
+ /**
+ * Android configuration options.
+ */
+ public static class Options extends FragmentOptions {
+ @Option(name = "android_cpu",
+ defaultValue = "armeabi",
+ category = "semantics",
+ help = "The Android target CPU.")
+ public String cpu;
+
+ @Option(name = "strict_android_deps",
+ allowMultiple = false,
+ defaultValue = "default",
+ converter = StrictDepsConverter.class,
+ category = "semantics",
+ help = "If true, checks that an Android target explicitly declares all directly used "
+ + "targets as dependencies.")
+ public StrictDepsMode strictDeps;
+
+ // Label of filegroup combining all Android tools used as implicit dependencies of
+ // android_* rules
+ @Option(name = "android_sdk",
+ defaultValue = Constants.ANDROID_DEFAULT_SDK,
+ category = "version",
+ converter = LabelConverter.class,
+ help = "Specifies Android SDK/platform that is used to build Android applications.")
+ public Label sdk;
+
+ @Option(name = "proguard_top",
+ defaultValue = "null",
+ category = "version",
+ converter = LabelConverter.class,
+ help = "Specifies which version of ProGuard to use for code removal when building an "
+ + "Android binary.")
+ public Label proguard;
+
+ @Option(name = "legacy_android_native_support",
+ defaultValue = "true",
+ category = "semantics",
+ help = "Switches back to old native support for android_binaries. Disable to link together "
+ + "native deps of android_binaries into a single .so by default.")
+ public boolean legacyNativeSupport;
+
+ // TODO(bazel-team): Maybe merge this with --android_cpu above.
+ @Option(name = "fat_apk_cpu",
+ converter = Converters.CommaSeparatedOptionListConverter.class,
+ allowMultiple = true,
+ defaultValue = "",
+ category = "undocumented",
+ help = "Setting this option enables fat APKs, which contain native binaries for all "
+ + "specified target architectures, e.g., --fat_apk_cpu=x86,armeabi-v7a. Note that "
+ + "you will also at least need to select an Android-compatible crosstool. "
+ + "If this flag is specified, then --android_cpu is ignored for dependencies of "
+ + "android_binary rules.")
+ public List<String> fatApkCpus;
+
+ @Override
+ public void addAllLabels(Multimap<String, Label> labelMap) {
+ if (proguard != null) {
+ labelMap.put("android_proguard", proguard);
+ }
+
+ labelMap.put("android_sdk", sdk);
+ labelMap.put("android_incremental_stub_application",
+ AndroidRuleClasses.DEFAULT_INCREMENTAL_STUB_APPLICATION);
+ labelMap.put("android_incremental_split_stub_application",
+ AndroidRuleClasses.DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION);
+ labelMap.put("android_resources_processor", AndroidRuleClasses.DEFAULT_RESOURCES_PROCESSOR);
+ labelMap.put("android_aar_generator", AndroidRuleClasses.DEFAULT_AAR_GENERATOR);
+ }
+
+ @Override
+ public Map<String, Set<Label>> getDefaultsLabels(BuildConfiguration.Options commonOptions) {
+ Map<String, Set<Label>> result = new TreeMap<>();
+ addLabel(result, "ANDROID_AIDL_TOOL", "static_aidl_tool");
+ addLabel(result, "ANDROID_AIDL_FRAMEWORK", "aidl_framework");
+ addLabel(result, "ANDROID_AAPT", "static_aapt_tool");
+ addLabel(result, "ANDROID_ADB", "static_adb_tool");
+ addLabel(result, "ANDROID_APKBUILDER", "apkbuilder_tool");
+ addLabel(result, "ANDROID_DX_JAR", "dx_jar");
+ return result;
+ }
+
+ @Override
+ public ImmutableList<String> getDefaultsRules() {
+ return ImmutableList.of("android_tools_defaults_jar(name = 'android_jar')");
+ }
+
+ private void addLabel(Map<String, Set<Label>> map, String key, String localLabel) {
+ try {
+ map.put(key, ImmutableSet.of(sdk.getLocalTargetLabel(localLabel)));
+ } catch (SyntaxException e) {
+ throw new IllegalStateException("Invalid label for " + key + ": " + localLabel, e);
+ }
+ }
+
+ @Override
+ public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() {
+ return ImmutableList.of(AndroidRuleClasses.ANDROID_SPLIT_TRANSITION);
+ }
+ }
+
+ /**
+ * Configuration loader for the Android fragment.
+ */
+ public static class Loader implements ConfigurationFragmentFactory {
+ @Override
+ public Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions)
+ throws InvalidConfigurationException {
+ Options options = buildOptions.get(Options.class);
+ Label sdk = RedirectChaser.followRedirects(env, options.sdk, "android_sdk");
+ Label incrementalStubApplication = RedirectChaser.followRedirects(env,
+ AndroidRuleClasses.DEFAULT_INCREMENTAL_STUB_APPLICATION,
+ "android_incremental_stub_application");
+ Label incrementalSplitStubApplication = RedirectChaser.followRedirects(env,
+ AndroidRuleClasses.DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION,
+ "android_incremental_split_stub_application");
+ Label resourcesProcessor = RedirectChaser.followRedirects(env,
+ AndroidRuleClasses.DEFAULT_RESOURCES_PROCESSOR,
+ "android_resources_processor");
+ Label aarGenerator = RedirectChaser.followRedirects(env,
+ AndroidRuleClasses.DEFAULT_AAR_GENERATOR,
+ "android_aar_generator");
+
+ if (incrementalStubApplication == null) {
+ return null;
+ }
+
+ return new AndroidConfiguration(
+ options, sdk, incrementalStubApplication, incrementalSplitStubApplication,
+ resourcesProcessor, aarGenerator);
+ }
+
+ @Override
+ public Class<? extends Fragment> creates() {
+ return AndroidConfiguration.class;
+ }
+ }
+
+ private final Label sdk;
+ private final Label incrementalStubApplication;
+ private final Label incrementalSplitStubApplication;
+ private final Label resourcesProcessor;
+ private final Label aarGenerator;
+ private final StrictDepsMode strictDeps;
+ private final boolean legacyNativeSupport;
+ private final String cpu;
+ private final boolean fatApk;
+ private final Label proguard;
+
+ AndroidConfiguration(Options options, Label sdk, Label incrementalStubApplication,
+ Label incrementalSplitStubApplication, Label resourcesProcessor, Label aarGenerator) {
+ this.sdk = sdk;
+ this.incrementalStubApplication = incrementalStubApplication;
+ this.incrementalSplitStubApplication = incrementalSplitStubApplication;
+ this.resourcesProcessor = resourcesProcessor;
+ this.aarGenerator = aarGenerator;
+ this.strictDeps = options.strictDeps;
+ this.legacyNativeSupport = options.legacyNativeSupport;
+ this.cpu = options.cpu;
+ this.fatApk = !options.fatApkCpus.isEmpty();
+ this.proguard = options.proguard;
+ }
+
+ @Override
+ public String getName() {
+ return "Android";
+ }
+
+ @Override
+ public String cacheKey() {
+ return cpu + "-" + strictDeps.toString().toLowerCase();
+ }
+
+ public String getCpu() {
+ return cpu;
+ }
+
+ public Label getSdk() {
+ return sdk;
+ }
+
+ public boolean getLegacyNativeSupport() {
+ return legacyNativeSupport;
+ }
+
+ public StrictDepsMode getStrictDeps() {
+ return strictDeps;
+ }
+
+ public Label getIncrementalStubApplication() {
+ return incrementalStubApplication;
+ }
+
+ public Label getIncrementalSplitStubApplication() {
+ return incrementalSplitStubApplication;
+ }
+
+ public Label getResourcesProcessor() {
+ return resourcesProcessor;
+ }
+
+ public Label getAarGenerator() {
+ return aarGenerator;
+ }
+
+ public boolean isFatApk() {
+ return fatApk;
+ }
+
+ @Override
+ public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) {
+ globalMakeEnvBuilder.put("ANDROID_CPU", cpu);
+ }
+
+ @Override
+ public String getOutputDirectoryName() {
+ return fatApk ? "fat-apk" : null;
+ }
+
+ @Override
+ public String getConfigurationNameSuffix() {
+ return fatApk ? "fat-apk" : null;
+ }
+
+ public Label getProguardLabel() {
+ return proguard;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java
new file mode 100644
index 0000000000..24078be5b9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java
@@ -0,0 +1,56 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Configured targets implementing this provider can contribute Android IDL information to the
+ * compilation.
+ */
+@Immutable
+public final class AndroidIdlProvider implements TransitiveInfoProvider {
+
+ public static final AndroidIdlProvider EMPTY = new AndroidIdlProvider(
+ NestedSetBuilder.<String>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER));
+
+ private final NestedSet<String> transitiveIdlImportRoots;
+ private final NestedSet<Artifact> transitiveIdlImports;
+
+ public AndroidIdlProvider(NestedSet<String> transitiveIdlImportRoots,
+ NestedSet<Artifact> transitiveIdlImports) {
+ this.transitiveIdlImportRoots = transitiveIdlImportRoots;
+ this.transitiveIdlImports = transitiveIdlImports;
+ }
+
+ /**
+ * The set of IDL import roots need for compiling the IDL sources in the transitive closure.
+ */
+ public NestedSet<String> getTransitiveIdlImportRoots() {
+ return transitiveIdlImportRoots;
+ }
+
+ /**
+ * The IDL files in the transitive closure.
+ */
+ public NestedSet<Artifact> getTransitiveIdlImports() {
+ return transitiveIdlImports;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java
new file mode 100644
index 0000000000..ad259e7b4a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java
@@ -0,0 +1,368 @@
+// Copyright 2015 Google Inc. 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.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.OutputGroupProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+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.rules.cpp.LinkerInput;
+import com.google.devtools.build.lib.rules.java.JavaCommon;
+import com.google.devtools.build.lib.rules.java.JavaNeverlinkInfoProvider;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
+import com.google.devtools.build.lib.rules.java.JavaUtil;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An implementation for the "android_library" rule.
+ */
+public abstract class AndroidLibrary implements RuleConfiguredTargetFactory {
+
+ protected abstract JavaSemantics createJavaSemantics();
+ protected abstract AndroidSemantics createAndroidSemantics();
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) {
+ JavaSemantics javaSemantics = createJavaSemantics();
+ AndroidSemantics androidSemantics = createAndroidSemantics();
+
+ List<? extends TransitiveInfoCollection> deps =
+ ruleContext.getPrerequisites("deps", Mode.TARGET);
+ checkResourceInlining(ruleContext);
+ checkIdlRootImport(ruleContext);
+ NestedSet<AndroidResourcesProvider.ResourceContainer> transitiveResources =
+ collectTransitiveResources(ruleContext);
+ NestedSet<LinkerInput> transitiveNativeLibraries =
+ AndroidCommon.collectTransitiveNativeLibraries(deps);
+ NestedSet<Artifact> transitiveProguardConfigs =
+ collectTransitiveProguardConfigs(ruleContext);
+ AndroidIdlProvider transitiveIdlImportData = collectTransitiveIdlImports(ruleContext);
+ AndroidTools tools = AndroidTools.fromRuleContext(ruleContext);
+ if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) {
+ try {
+ if (!LocalResourceContainer.validateRuleContext(ruleContext)) {
+ throw new RuleConfigurationException();
+ }
+ JavaCommon javaCommon = new JavaCommon(ruleContext, javaSemantics);
+ AndroidCommon androidCommon = new AndroidCommon(ruleContext, javaCommon);
+
+ ApplicationManifest applicationManifest = androidSemantics.getManifestForRule(ruleContext);
+ ResourceApk resourceApk = applicationManifest.packWithDataAndResources(
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK),
+ ruleContext, transitiveResources, tools,
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT),
+ ImmutableList.<String>of(), /* configurationFilters */
+ ImmutableList.<String>of(), /* uncompressedExtensions */
+ ImmutableList.<String>of(), /* densities */
+ null /* applicationId */,
+ null /* versionCode */,
+ null /* versionName */,
+ false,
+ null /* proguardCfgOut */);
+
+ androidCommon.init(javaSemantics, androidSemantics, tools,
+ resourceApk, transitiveIdlImportData, false, true);
+
+ Artifact classesJar = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR);
+
+ Artifact aarOut = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_LIBRARY_AAR);
+
+ new AarGeneratorBuilder(tools, ruleContext)
+ .withPrimary(resourceApk.getPrimaryResource())
+ .withManifest(resourceApk.getPrimaryResource().getManifest())
+ .withRtxt(resourceApk.getPrimaryResource().getRTxt())
+ .withClasses(classesJar)
+ .strictResourceMerging()
+ .setAAROut(aarOut)
+ .build(ruleContext);
+
+ RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext);
+ androidCommon.addTransitiveInfoProviders(builder, tools);
+ androidSemantics.addTransitiveInfoProviders(
+ builder, ruleContext, javaCommon, androidCommon,
+ null, resourceApk, null, ImmutableList.<Artifact>of());
+
+ return builder
+ .add(AndroidNativeLibraryProvider.class,
+ new AndroidNativeLibraryProvider(transitiveNativeLibraries))
+ .add(JavaSourceJarsProvider.class, new JavaSourceJarsProvider(
+ androidCommon.getTransitiveSourceJars(),
+ androidCommon.getTopLevelSourceJars()))
+ .add(JavaNeverlinkInfoProvider.class,
+ new JavaNeverlinkInfoProvider(androidCommon.isNeverLink()))
+ .add(AndroidCcLinkParamsProvider.class,
+ new AndroidCcLinkParamsProvider(androidCommon.getCcLinkParamsStore()))
+ .add(ProguardSpecProvider.class, new ProguardSpecProvider(transitiveProguardConfigs))
+ .add(AndroidLibraryAarProvider.class, new AndroidLibraryAarProvider(aarOut,
+ applicationManifest.getManifest()))
+ .addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, transitiveProguardConfigs)
+ .build();
+ } catch (RuleConfigurationException e) {
+ // RuleConfigurations exceptions will only be thrown after the RuleContext is updated.
+ // So, exit.
+ return null;
+ }
+ } else {
+ JavaCommon javaCommon = new JavaCommon(ruleContext, javaSemantics);
+ AndroidCommon androidCommon = new AndroidCommon(ruleContext, javaCommon);
+ ResourceApk resourceApk = ResourceApk.fromTransitiveResources(transitiveResources);
+ androidCommon.init(javaSemantics, androidSemantics, tools,
+ resourceApk, transitiveIdlImportData, false, true);
+
+
+ RuleConfiguredTargetBuilder targetBuilder = androidCommon.addTransitiveInfoProviders(
+ new RuleConfiguredTargetBuilder(ruleContext), tools);
+
+ androidSemantics.addTransitiveInfoProviders(
+ targetBuilder, ruleContext, javaCommon, androidCommon,
+ null, null, null, ImmutableList.<Artifact>of());
+ targetBuilder
+ .add(AndroidNativeLibraryProvider.class,
+ new AndroidNativeLibraryProvider(transitiveNativeLibraries))
+ .add(JavaSourceJarsProvider.class, androidCommon.getJavaSourceJarsProvider())
+ .add(AndroidCcLinkParamsProvider.class,
+ new AndroidCcLinkParamsProvider(androidCommon.getCcLinkParamsStore()))
+ .add(JavaNeverlinkInfoProvider.class,
+ new JavaNeverlinkInfoProvider(androidCommon.isNeverLink()))
+ .add(ProguardSpecProvider.class, new ProguardSpecProvider(transitiveProguardConfigs))
+ .addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, transitiveProguardConfigs);
+
+ Artifact aarOut = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_LIBRARY_AAR);
+
+ Artifact classesJar = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR);
+
+ ResourceContainer primaryResources;
+
+ if (AndroidCommon.getAndroidResources(ruleContext) != null) {
+ primaryResources = Iterables.getOnlyElement(
+ AndroidCommon.getAndroidResources(ruleContext).getTransitiveAndroidResources());
+ targetBuilder.add(AndroidLibraryAarProvider.class, new AndroidLibraryAarProvider(
+ aarOut, primaryResources.getManifest()));
+ } else {
+ // there are no local resources and resources attribute was not specified either
+ ApplicationManifest applicationManifest =
+ ApplicationManifest.generatedManifest(ruleContext);
+
+ Artifact apk = ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_RESOURCES_APK);
+
+ String javaPackage;
+ if (apk.getExecPath().getFirstSegment(ImmutableSet.of("java", "javatests"))
+ != PathFragment.INVALID_SEGMENT) {
+ javaPackage = JavaUtil.getJavaPackageName(apk.getExecPath());
+ } else {
+ // This is a workaround for libraries that don't follow the standard Bazel package format
+ javaPackage = apk.getRootRelativePath().getPathString().replace('/', '.');
+ }
+ if (ruleContext.attributes().isAttributeValueExplicitlySpecified("custom_package")) {
+ javaPackage = ruleContext.attributes().get("custom_package", Type.STRING);
+ }
+
+ primaryResources = new ResourceContainer(ruleContext.getLabel(),
+ javaPackage, null /* renameManifestPackage */, false /* inlinedConstants */,
+ apk, applicationManifest.getManifest(),
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR),
+ ImmutableList.<Artifact>of(), ImmutableList.<Artifact>of(),
+ ImmutableList.<PathFragment>of(), ImmutableList.<PathFragment>of(),
+ ruleContext.attributes().get("exports_manifest", Type.BOOLEAN),
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT));
+
+ primaryResources = new AndroidResourcesProcessorBuilder(tools, ruleContext)
+ .setApkOut(apk)
+ .setRTxtOut(primaryResources.getRTxt())
+ .setSourceJarOut(primaryResources.getJavaSourceJar())
+ .setJavaPackage(primaryResources.getJavaPackage())
+ .withPrimary(primaryResources)
+ .withDependencies(transitiveResources)
+ .setDebug(
+ ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
+ .setWorkingDirectory(ruleContext.getUniqueDirectory("_resources"))
+ .build(ruleContext);
+ }
+
+ new AarGeneratorBuilder(tools, ruleContext)
+ .withPrimary(primaryResources)
+ .withManifest(primaryResources.getManifest())
+ .withRtxt(primaryResources.getRTxt())
+ .withClasses(classesJar)
+ .setAAROut(aarOut)
+ .build(ruleContext);
+
+ return targetBuilder.build();
+ }
+ }
+
+ private AndroidIdlProvider collectTransitiveIdlImports(RuleContext ruleContext) {
+ NestedSetBuilder<String> rootsBuilder = NestedSetBuilder.naiveLinkOrder();
+ NestedSetBuilder<Artifact> importsBuilder = NestedSetBuilder.naiveLinkOrder();
+
+ for (AndroidIdlProvider dep : ruleContext.getPrerequisites(
+ "deps", Mode.TARGET, AndroidIdlProvider.class)) {
+ rootsBuilder.addTransitive(dep.getTransitiveIdlImportRoots());
+ importsBuilder.addTransitive(dep.getTransitiveIdlImports());
+ }
+
+ Collection<Artifact> idlImports = getIdlImports(ruleContext);
+ if (!hasExplicitlySpecifiedIdlImportRoot(ruleContext)) {
+ for (Artifact idlImport : idlImports) {
+ PathFragment javaRoot = JavaUtil.getJavaRoot(idlImport.getExecPath());
+ if (javaRoot == null) {
+ ruleContext.ruleError("Cannot determine java/javatests root for import "
+ + idlImport.getExecPathString());
+ } else {
+ rootsBuilder.add(javaRoot.toString());
+ }
+ }
+ } else {
+ PathFragment pkgFragment = ruleContext.getLabel().getPackageFragment();
+ Set<PathFragment> idlImportRoots = new HashSet<>();
+ for (Artifact idlImport : idlImports) {
+ idlImportRoots.add(idlImport.getRoot().getExecPath()
+ .getRelative(pkgFragment)
+ .getRelative(getIdlImportRoot(ruleContext)));
+ }
+ for (PathFragment idlImportRoot : idlImportRoots) {
+ rootsBuilder.add(idlImportRoot.toString());
+ }
+ }
+ importsBuilder.addAll(idlImports);
+
+ return new AndroidIdlProvider(rootsBuilder.build(), importsBuilder.build());
+ }
+
+ private void checkIdlRootImport(RuleContext ruleContext) {
+ if (hasExplicitlySpecifiedIdlImportRoot(ruleContext)
+ && !hasExplicitlySpecifiedIdlSrcsOrParcelables(ruleContext)) {
+ ruleContext.attributeError("idl_import_root",
+ "Neither idl_srcs nor idl_parcelables were specified, "
+ + "but 'idl_import_root' attribute was set");
+ }
+ }
+
+ private void checkResourceInlining(RuleContext ruleContext) {
+ AndroidResourcesProvider resources = AndroidCommon.getAndroidResources(ruleContext);
+ if (resources == null) {
+ return;
+ }
+
+ ResourceContainer container = Iterables.getOnlyElement(
+ resources.getTransitiveAndroidResources());
+
+ if (container.getConstantsInlined()
+ && !container.getArtifacts(ResourceType.RESOURCES).isEmpty()) {
+ ruleContext.ruleError("This android library has some resources assigned, so the target '"
+ + resources.getLabel() + "' should have the attribute inline_constants set to 0");
+ }
+ }
+
+ private NestedSet<ResourceContainer> collectTransitiveResources(RuleContext ruleContext) {
+ NestedSetBuilder<ResourceContainer> builder = NestedSetBuilder.naiveLinkOrder();
+ for (AndroidResourcesProvider resource : Iterables.concat(
+ ruleContext.getPrerequisites("resources", Mode.TARGET, AndroidResourcesProvider.class),
+ ruleContext.getPrerequisites("deps", Mode.TARGET, AndroidResourcesProvider.class))) {
+ builder.addTransitive(resource.getTransitiveAndroidResources());
+ }
+
+ return builder.build();
+ }
+
+ private boolean hasExplicitlySpecifiedIdlImportRoot(RuleContext ruleContext) {
+ return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_import_root");
+ }
+
+ private boolean hasExplicitlySpecifiedIdlSrcsOrParcelables(RuleContext ruleContext) {
+ return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_srcs")
+ || ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_parcelables");
+ }
+
+ private String getIdlImportRoot(RuleContext ruleContext) {
+ return ruleContext.attributes().get("idl_import_root", Type.STRING);
+ }
+
+ /**
+ * Returns the union of "idl_srcs" and "idl_parcelables", i.e. all .aidl files
+ * provided by this library that contribute to .aidl --> .java compilation.
+ */
+ private static Collection<Artifact> getIdlImports(RuleContext ruleContext) {
+ return ImmutableList.<Artifact>builder()
+ .addAll(AndroidCommon.getIdlParcelables(ruleContext))
+ .addAll(AndroidCommon.getIdlSrcs(ruleContext))
+ .build();
+ }
+
+ private NestedSet<Artifact> collectTransitiveProguardConfigs(RuleContext ruleContext) {
+ NestedSetBuilder<Artifact> specsBuilder = NestedSetBuilder.naiveLinkOrder();
+
+ for (ProguardSpecProvider dep : ruleContext.getPrerequisites(
+ "deps", Mode.TARGET, ProguardSpecProvider.class)) {
+ specsBuilder.addTransitive(dep.getTransitiveProguardSpecs());
+ }
+
+ // Pass our local proguard configs through the validator, which checks a whitelist.
+ if (!getProguardConfigs(ruleContext).isEmpty()) {
+ FilesToRunProvider proguardWhitelister = ruleContext
+ .getExecutablePrerequisite("$proguard_whitelister", Mode.HOST);
+ for (Artifact specToValidate : getProguardConfigs(ruleContext)) {
+ //If we're validating j/a/b/testapp/proguard.cfg, the output will be:
+ //j/a/b/testapp/proguard.cfg_valid
+ Artifact output = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ specToValidate.getRootRelativePath()
+ .replaceName(specToValidate.getFilename() + "_valid"),
+ ruleContext.getBinOrGenfilesDirectory());
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .addInput(specToValidate)
+ .setExecutable(proguardWhitelister)
+ .setProgressMessage("Validating proguard configuration")
+ .setMnemonic("ValidateProguard")
+ .addArgument("--path")
+ .addArgument(specToValidate.getExecPathString())
+ .addArgument("--output")
+ .addArgument(output.getExecPathString())
+ .addOutput(output)
+ .build(ruleContext));
+ specsBuilder.add(output);
+ }
+ }
+ return specsBuilder.build();
+ }
+
+ private Collection<Artifact> getProguardConfigs(RuleContext ruleContext) {
+ return ruleContext.getPrerequisiteArtifacts("proguard_specs", Mode.TARGET).list();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryAarProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryAarProvider.java
new file mode 100644
index 0000000000..59402defd8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryAarProvider.java
@@ -0,0 +1,43 @@
+// Copyright 2015 Google Inc. 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.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A target that can provide the aar artifact of Android libraries and all the manifests that are
+ * merged into the main aar manifest.
+ */
+@Immutable
+public final class AndroidLibraryAarProvider implements TransitiveInfoProvider {
+
+ private final Artifact aar;
+ private final Artifact manifest;
+
+ public AndroidLibraryAarProvider(Artifact aar, Artifact manifest) {
+ this.aar = Preconditions.checkNotNull(aar);
+ this.manifest = Preconditions.checkNotNull(manifest);
+ }
+
+ public Artifact getAar() {
+ return aar;
+ }
+
+ public Artifact getManifest() {
+ return manifest;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryBaseRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryBaseRule.java
new file mode 100644
index 0000000000..81f50156fc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibraryBaseRule.java
@@ -0,0 +1,171 @@
+// Copyright 2015 Google Inc. 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 static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.AndroidAaptBaseRule;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.AndroidBaseRule;
+import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.AndroidResourceSupportRule;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+
+/**
+ * Rule definition for the android_library rule.
+ */
+public final class AndroidLibraryBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
+ return builder
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(srcs) -->
+ The list of source files that are processed to create the target.
+ ${SYNOPSIS}
+ <p><code>srcs</code> files of type <code>.java</code> are compiled.
+ <em>For readability's sake</em>, it is not good to put the name of a
+ generated <code>.java</code> source file into the <code>srcs</code>.
+ Instead, put the depended-on rule name in the <code>srcs</code>, as
+ described below.
+ </p>
+ <p><code>srcs</code> files of type <code>.srcjar</code> are unpacked and
+ compiled. (This is useful if you need to generate a set of .java files with
+ a genrule or build extension.)
+ </p>
+ <p>This rule currently forces source and class compatibility with Java 7,
+ although try with resources is not supported.
+ </p>
+ <p><code>srcs</code> files of type <code>.jar</code> are linked in.
+ (This is useful if you have third-party <code>.jar</code> files
+ with no source.)
+ </p>
+ <p>If <code>srcs</code> is omitted, then any dependency specified in
+ <code>deps</code> is exported from this rule (see
+ <a href="#java_library.exports">java_library's exports</a> for more
+ information about exporting dependencies). However, this behavior will be
+ deprecated soon; try not to rely on it.
+ </p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("srcs", LABEL_LIST)
+ .direct_compile_time_input()
+ .allowedFileTypes(JavaSemantics.JAVA_SOURCE, JavaSemantics.JAR,
+ JavaSemantics.SOURCE_JAR))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(deps) -->
+ The list of other libraries to link against.
+ ${SYNOPSIS}
+ Permitted library types are: <code>android_library</code>,
+ <code>java_library</code> with <code>android</code> constraint and
+ <code>cc_library</code> wrapping or producing <code>.so</code> native libraries
+ for the Android target platform.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .override(builder.copy("deps")
+ .allowedRuleClasses(AndroidRuleClasses.ALLOWED_DEPENDENCIES)
+ .allowedFileTypes())
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(resources) -->
+ The <code>android_resources</code> target assigned to this library.
+ ${SYNOPSIS}
+ If specified, the resources will be added to any <code>android_binary</code>
+ depending on this library.
+ <p>Only an <code>android_resource</code> rule with the attribute
+ <code>inline_constants</code> set to 0 can be used in this case.</p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("resources", LABEL)
+ .allowedFileTypes()
+ .allowedRuleClasses("android_resources"))
+ .add(attr("alwayslink", BOOLEAN).undocumented("purely informational for now"))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(neverlink) -->
+ Only use this library for compilation and not at runtime.
+ ${SYNOPSIS}
+ The outputs of a rule marked as <code>neverlink</code> will not be used in
+ <code>.apk</code> creation. Useful if the library will be provided by the
+ runtime environment during execution.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("neverlink", BOOLEAN).value(false))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(idl_import_root) -->
+ Package-relative path to the root of the java package tree containing idl
+ sources included in this library.
+ ${SYNOPSIS}
+ This path will be used as the import root when processing idl sources that
+ depend on this library. (See
+ <a href="#android_library_examples.idl_import_root">examples</a>.)
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("idl_import_root", STRING))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(idl_srcs) -->
+ List of Android IDL definitions to translate to Java interfaces.
+ ${SYNOPSIS}
+ After the Java interfaces are generated, they will be compiled together
+ with the contents of <code>srcs</code>.
+ <p>These files will be made available as imports for any
+ <code>android_library</code> target that depends on this library, directly
+ or via its transitive closure.</p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("idl_srcs", LABEL_LIST).direct_compile_time_input()
+ .allowedFileTypes(AndroidRuleClasses.ANDROID_IDL))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(idl_parcelables) -->
+ List of Android IDL definitions to supply as imports.
+ ${SYNOPSIS}
+ These files will be made available as imports for any
+ <code>android_library</code> target that depends on this library, directly
+ or via its transitive closure, but will not be translated to Java
+ or compiled.
+ <p>Only <code>.aidl</code> files that correspond directly to
+ <code>.java</code> sources in this library should be included (e.g., custom
+ implementations of Parcelable), otherwise <code>idl_srcs</code> should be
+ used.</p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("idl_parcelables", LABEL_LIST).direct_compile_time_input()
+ .allowedFileTypes(AndroidRuleClasses.ANDROID_IDL))
+ /* <!-- #BLAZE_RULE(android_library).ATTRIBUTE(proguard_specs) -->
+ Files to be used as Proguard specification.
+ ${SYNOPSIS}
+ These will describe the set of specifications to be used by Proguard. If specified,
+ they will be added to any <code>android_binary</code> target depending on this library.
+
+ The files included here must only have idempotent rules, namely -dontnote, -dontwarn,
+ assumenosideeffects, and rules that start with -keep. Other options can only appear in
+ <code>android_binary</code>'s proguard_specs, to ensure non-tautological merges.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("proguard_specs", LABEL_LIST).legacyAllowAnyFileType())
+ .add(attr("$proguard_whitelister", LABEL).cfg(HOST).exec().value(
+ new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.isAttributeValueExplicitlySpecified("proguard_specs")
+ ? env.getLabel("//tools/android/build:proguard_whitelister")
+ : null;
+ }
+ }))
+ .add(attr("$android_manifest_merge_tool", LABEL).cfg(HOST).exec().value(env.getLabel(
+ AndroidRuleClasses.MANIFEST_MERGE_TOOL_LABEL)))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$android_library_base")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(AndroidBaseRule.class, AndroidAaptBaseRule.class,
+ AndroidResourceSupportRule.class)
+ .build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java
new file mode 100644
index 0000000000..684f7eeeb2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifestMergeHelper.java
@@ -0,0 +1,56 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+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.SpawnAction;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public final class AndroidManifestMergeHelper {
+
+ private AndroidManifestMergeHelper() {}
+
+ public static void createMergeManifestAction(RuleContext ruleContext,
+ Artifact merger, Iterable<Artifact> mergees,
+ Collection<String> excludePermissions, Artifact mergedManifest) {
+ List<String> args = new ArrayList<>();
+ args.add("--merger=" + merger.getExecPathString());
+
+ for (Artifact mergee : mergees) {
+ args.add("--mergee=" + mergee.getExecPathString());
+ }
+
+ for (String excludePermission : excludePermissions) {
+ args.add("--exclude_permission=" + excludePermission);
+ }
+
+ args.add("--output=" + mergedManifest.getExecPathString());
+
+ ruleContext.registerAction(new SpawnAction.Builder()
+ .addInput(merger)
+ .addInputs(mergees)
+ .addOutput(mergedManifest)
+ .setExecutable(ruleContext.getPrerequisite("$android_manifest_merge_tool", Mode.HOST))
+ .addArguments(args)
+ .setProgressMessage("Merging Android Manifests")
+ .setMnemonic("AndroidManifestMerger")
+ .build(ruleContext));
+ }
+}
+
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNativeLibraryProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNativeLibraryProvider.java
new file mode 100644
index 0000000000..9a5dcdc50c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNativeLibraryProvider.java
@@ -0,0 +1,37 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.cpp.LinkerInput;
+
+/**
+ * A target that can provide native libraries (they are always dynamic ones) to
+ * Android binaries.
+ */
+@Immutable
+public final class AndroidNativeLibraryProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<LinkerInput> transitiveAndroidNativeLibraries;
+
+ public AndroidNativeLibraryProvider(NestedSet<LinkerInput> transitiveAndroidNativeLibraries) {
+ this.transitiveAndroidNativeLibraries = transitiveAndroidNativeLibraries;
+ }
+
+ public NestedSet<LinkerInput> getTransitiveAndroidNativeLibraries() {
+ return transitiveAndroidNativeLibraries;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverLinkLibrariesProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverLinkLibrariesProvider.java
new file mode 100644
index 0000000000..1b997d78bd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverLinkLibrariesProvider.java
@@ -0,0 +1,44 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+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 com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
+
+/**
+ * A target that can provide neverlink libraries for Android targets.
+ *
+ * <p>All targets implementing this interface must also implement
+ * {@link JavaCompilationArgsProvider}.
+ */
+@Immutable
+public final class AndroidNeverLinkLibrariesProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> transitiveNeverLinkLibraries;
+
+ public AndroidNeverLinkLibrariesProvider(
+ NestedSet<Artifact> transitiveNeverLinkLibraries) {
+ this.transitiveNeverLinkLibraries = transitiveNeverLinkLibraries;
+ }
+
+ /**
+ * Returns the set of neverlink libraries in the transitive closure.
+ */
+ public NestedSet<Artifact> getTransitiveNeverLinkLibraries() {
+ return transitiveNeverLinkLibraries;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceContainerBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceContainerBuilder.java
new file mode 100644
index 0000000000..0a83753ded
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceContainerBuilder.java
@@ -0,0 +1,100 @@
+// Copyright 2015 Google Inc. 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.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
+import com.google.devtools.build.lib.rules.java.JavaUtil;
+
+import javax.annotation.Nullable;
+
+/**
+ * Encapsulates the process of building the AndroidResourceContainer.
+ */
+public final class AndroidResourceContainerBuilder {
+ private LocalResourceContainer data;
+ private Artifact manifest;
+ private Artifact rOutput;
+ private boolean inlineConstants = false;
+
+ /** Provides the resources and assets for the ResourceContainer. */
+ public AndroidResourceContainerBuilder withData(LocalResourceContainer data) {
+ this.data = data;
+ return this;
+ }
+
+ public AndroidResourceContainerBuilder withManifest(Artifact manifest) {
+ this.manifest = manifest;
+ return this;
+ }
+
+ public AndroidResourceContainerBuilder withROutput(Artifact rOutput) {
+ this.rOutput = rOutput;
+ return this;
+ }
+
+ public AndroidResourceContainerBuilder withInlinedConstants(boolean inlineContants) {
+ this.inlineConstants = inlineContants;
+ return this;
+ }
+
+ /** Creates a {@link ResourceContainer} from a {@link RuleContext}. */
+ public ResourceContainer buildFromRule(RuleContext ruleContext, Artifact apk) {
+ Preconditions.checkNotNull(this.manifest);
+ Preconditions.checkNotNull(this.data);
+ return new AndroidResourcesProvider.ResourceContainer(
+ ruleContext.getLabel(),
+ getJavaPackage(ruleContext, apk),
+ getRenameManifestPackage(ruleContext),
+ inlineConstants,
+ apk,
+ manifest,
+ ruleContext.getImplicitOutputArtifact(
+ AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR),
+ data.getAssets(),
+ data.getResources(),
+ data.getAssetRoots(),
+ data.getResourceRoots(),
+ ruleContext.attributes().get("exports_manifest", Type.BOOLEAN),
+ rOutput);
+ }
+
+ private String getJavaPackage(RuleContext ruleContext, Artifact apk) {
+ if (hasCustomPackage(ruleContext)) {
+ return ruleContext.attributes().get("custom_package", Type.STRING);
+ }
+ // TODO(bazel-team): JavaUtil.getJavaPackageName does not check to see if the path is valid.
+ // So we need to check for the JavaRoot.
+ if (JavaUtil.getJavaRoot(apk.getExecPath()) == null) {
+ ruleContext.ruleError("You must place your code under a directory named 'java' or "
+ + "'javatests' for blaze to work. That directory (java,javatests) will be treated as "
+ + "your java source root. Alternatively, you can set the 'custom_package' attribute.");
+ }
+ return JavaUtil.getJavaPackageName(apk.getExecPath());
+ }
+
+ private boolean hasCustomPackage(RuleContext ruleContext) {
+ return ruleContext.attributes().isAttributeValueExplicitlySpecified("custom_package");
+ }
+
+ @Nullable
+ private String getRenameManifestPackage(RuleContext ruleContext) {
+ return ruleContext.attributes().isAttributeValueExplicitlySpecified("rename_manifest_package")
+ ? ruleContext.attributes().get("rename_manifest_package", Type.STRING)
+ : null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
new file mode 100644
index 0000000000..a7928042aa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java
@@ -0,0 +1,324 @@
+// Copyright 2015 Google Inc. 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.Functions;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+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.Collections;
+import java.util.List;
+
+/**
+ * Builder for creating resource processing action.
+ */
+public class AndroidResourcesProcessorBuilder {
+
+ private ResourceContainer primary;
+ private List<ResourceContainer> dependencies = Collections.emptyList();
+ private Artifact proguardOut;
+ private Artifact rTxtOut;
+ private Artifact sourceJarOut;
+ private boolean debug = false;
+ private List<String> resourceConfigs = Collections.emptyList();
+ private List<String> uncompressedExtensions = Collections.emptyList();
+ private Artifact apkOut;
+ private final AndroidTools androidTools;
+ private List<String> assetsToIgnore = Collections.emptyList();
+ private SpawnAction.Builder spawnActionBuilder;
+ private PathFragment workingDirectory;
+ private List<String> densities = Collections.emptyList();
+ private String customJavaPackage;
+ private final RuleContext ruleContext;
+ private String versionCode;
+ private String applicationId;
+ private String versionName;
+
+ /**
+ * @param androidTools A configured AndroidTools for the AndroidResourcesProcessor action.
+ * @param ruleContext The RuleContext that was used to create the SpawnAction.Builder.
+ */
+ public AndroidResourcesProcessorBuilder(AndroidTools androidTools, RuleContext ruleContext) {
+ this.androidTools = androidTools;
+ this.ruleContext = ruleContext;
+ this.spawnActionBuilder = new SpawnAction.Builder();
+ }
+
+ /**
+ * The primary resource for merging. This resource will overwrite any resource or data
+ * value in the transitive closure.
+ */
+ public AndroidResourcesProcessorBuilder withPrimary(ResourceContainer primary) {
+ this.primary = primary;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder withDependencies(Iterable<ResourceContainer> nestedSet) {
+ this.dependencies = ImmutableList.copyOf(nestedSet);
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setUncompressedExtensions(
+ List<String> uncompressedExtensions) {
+ this.uncompressedExtensions = uncompressedExtensions;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setDensities(List<String> densities) {
+ this.densities = densities;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setConfigurationFilters(List<String> resourceConfigs) {
+ this.resourceConfigs = resourceConfigs;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setDebug(boolean debug) {
+ this.debug = debug;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setProguardOut(Artifact proguardCfg) {
+ this.proguardOut = proguardCfg;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setRTxtOut(Artifact rTxtOut) {
+ this.rTxtOut = rTxtOut;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setApkOut(Artifact apkOut) {
+ this.apkOut = apkOut;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setSourceJarOut(Artifact sourceJarOut) {
+ this.sourceJarOut = sourceJarOut;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setAssetsToIgnore(List<String> assetsToIgnore) {
+ this.assetsToIgnore = assetsToIgnore;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setWorkingDirectory(PathFragment workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ return this;
+ }
+
+ private void addResourceContainer(List<Artifact> inputs, List<String> args,
+ ResourceContainer container) {
+ Iterables.addAll(inputs, container.getArtifacts());
+ inputs.add(container.getManifest());
+ inputs.add(container.getRTxt());
+
+ args.add(String.format("%s:%s:%s:%s",
+ convertRoots(container, ResourceType.RESOURCES),
+ convertRoots(container, ResourceType.ASSETS),
+ container.getManifest().getExecPathString(),
+ container.getRTxt() == null ? "" : container.getRTxt().getExecPath()
+ ));
+ }
+
+ private void addPrimaryResourceContainer(List<Artifact> inputs, List<String> args,
+ ResourceContainer container) {
+ Iterables.addAll(inputs, container.getArtifacts());
+ inputs.add(container.getManifest());
+
+ // no R.txt, because it will be generated from this action.
+ args.add(String.format("%s:%s:%s",
+ convertRoots(container, ResourceType.RESOURCES),
+ convertRoots(container, ResourceType.ASSETS),
+ container.getManifest().getExecPathString()
+ ));
+ }
+
+ @VisibleForTesting
+ public static String convertRoots(ResourceContainer container, ResourceType resourceType) {
+ return Joiner.on("#").join(
+ Iterators.transform(
+ container.getRoots(resourceType).iterator(), Functions.toStringFunction()));
+ }
+
+ public ResourceContainer build(ActionConstructionContext context) {
+ List<Artifact> outs = new ArrayList<>();
+ List<Artifact> ins = new ArrayList<>();
+ List<String> args = new ArrayList<>();
+
+ args.add("--aapt");
+ args.add(androidTools.getAapt().getExecutable().getExecPathString());
+
+ Iterables.addAll(ins, androidTools.getAndroidResourceProcessor().getRunfilesSupport()
+ .getRunfilesArtifactsWithoutMiddlemen());
+
+ args.add("--annotationJar");
+ args.add(androidTools.getAnnotationsJar().getExecPathString());
+ ins.add(androidTools.getAnnotationsJar());
+ args.add("--androidJar");
+ args.add(androidTools.getAndroidJar().getExecPathString());
+ ins.add(androidTools.getAndroidJar());
+
+ args.add("--primaryData");
+ addPrimaryResourceContainer(ins, args, primary);
+ if (!dependencies.isEmpty()) {
+ args.add("--data");
+ List<String> data = new ArrayList<>();
+ for (ResourceContainer container : dependencies) {
+ addResourceContainer(ins, data, container);
+ }
+ args.add(Joiner.on(",").join(data));
+ }
+
+ if (rTxtOut != null || sourceJarOut != null) {
+ args.add("--generatedSourcePath");
+ args.add(workingDirectory.toString());
+ }
+
+ if (rTxtOut != null) {
+ args.add("--rOutput");
+ args.add(rTxtOut.getExecPathString());
+ outs.add(rTxtOut);
+ // If R.txt is not null, dependency R.javas will not be regenerated from the R.txt found in
+ // the deps, which means the resource processor needs to be told it is creating a library so
+ // that it will generate the R.txt.
+ args.add("--packageType");
+ args.add("LIBRARY");
+ }
+ if (sourceJarOut != null) {
+ args.add("--srcJarOutput");
+ args.add(sourceJarOut.getExecPathString());
+ outs.add(sourceJarOut);
+ }
+ if (proguardOut != null) {
+ args.add("--proguardOutput");
+ args.add(proguardOut.getExecPathString());
+ outs.add(proguardOut);
+ }
+ if (apkOut != null) {
+ args.add("--packagePath");
+ args.add(apkOut.getExecPathString());
+ outs.add(apkOut);
+ }
+ if (!resourceConfigs.isEmpty()) {
+ args.add("--resourceConfigs");
+ args.add(Joiner.on(',').join(resourceConfigs));
+ }
+ if (!densities.isEmpty()) {
+ args.add("--densities");
+ args.add(Joiner.on(',').join(densities));
+ }
+ if (!uncompressedExtensions.isEmpty()) {
+ args.add("--uncompressedExtensions");
+ args.add(Joiner.on(',').join(uncompressedExtensions));
+ }
+ if (!assetsToIgnore.isEmpty()) {
+ args.add("--assetsToIgnore");
+ args.add(
+ Joiner.on(',').join(assetsToIgnore));
+ }
+ if (debug) {
+ args.add("--debug");
+ }
+
+ if (versionCode != null) {
+ args.add("--versionCode");
+ args.add(versionCode);
+ }
+
+ if (versionName != null) {
+ args.add("--versionName");
+ args.add(versionName);
+ }
+
+ if (applicationId != null) {
+ args.add("--applicationId");
+ args.add(applicationId);
+ }
+
+ if (!Strings.isNullOrEmpty(customJavaPackage)) {
+ // Sets an alternative java package for the generated R.java
+ // this is allows android rules to generate resources outside of the java{,tests} tree.
+ args.add("--packageForR");
+ args.add(customJavaPackage);
+ }
+
+ // Create the spawn action.
+ ruleContext.registerAction(this.spawnActionBuilder
+ .addTool(androidTools.getAapt())
+ .addInputs(ImmutableList.<Artifact>copyOf(ins))
+ .addOutputs(ImmutableList.<Artifact>copyOf(outs))
+ .addArguments(ImmutableList.<String>copyOf(args))
+ .setExecutable(androidTools.getAndroidResourceProcessor())
+ .setProgressMessage("Processing resources")
+ .setMnemonic("AndroidAapt")
+ .build(context));
+
+ // Return the full set of processed transitive dependencies.
+ return new ResourceContainer(
+ primary.getLabel(),
+ primary.getJavaPackage(),
+ primary.getRenameManifestPackage(),
+ primary.getConstantsInlined(),
+ // If there is no apk to be generated, use the apk from the primary resources.
+ // All ResourceContainers have to have an apk, but if a new one is not requested to be built
+ // for this resource processing action (in case of just creating an R.txt or
+ // proguard merging), reuse the primary resource from the dependencies.
+ apkOut != null ? apkOut : primary.getApk(),
+ primary.getManifest(),
+ sourceJarOut,
+ primary.getArtifacts(ResourceType.ASSETS),
+ primary.getArtifacts(ResourceType.RESOURCES),
+ primary.getRoots(ResourceType.ASSETS),
+ primary.getRoots(ResourceType.RESOURCES),
+ primary.isManifestExported(),
+ rTxtOut);
+ }
+
+ public AndroidResourcesProcessorBuilder setJavaPackage(String newManifestPackage) {
+ this.customJavaPackage = newManifestPackage;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setVersionCode(String versionCode) {
+ this.versionCode = versionCode;
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setApplicationId(String applicationId) {
+ if (applicationId != null && !applicationId.isEmpty()) {
+ this.applicationId = applicationId;
+ }
+ return this;
+ }
+
+ public AndroidResourcesProcessorBuilder setVersionName(String versionName) {
+ this.versionName = versionName;
+ return this;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProvider.java
new file mode 100644
index 0000000000..c0e788e3b9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProvider.java
@@ -0,0 +1,194 @@
+// Copyright 2015 Google Inc. 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.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+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 com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+/**
+ * A provider that supplies Android resources from its transitive closure.
+ */
+@Immutable
+public final class AndroidResourcesProvider implements TransitiveInfoProvider {
+
+ private final Label label;
+ private final NestedSet<ResourceContainer> transitiveAndroidResources;
+
+ public AndroidResourcesProvider(Label label,
+ NestedSet<ResourceContainer> transitiveAndroidResources) {
+ this.label = label;
+ this.transitiveAndroidResources = transitiveAndroidResources;
+ }
+
+ /**
+ * Returns the label that is associated with this piece of information.
+ *
+ * <p>
+ * This is usually the label of the target that provides the information.
+ */
+ public Label getLabel() {
+ return label;
+ }
+
+ /**
+ * Returns transitive Android resources (APK, assets, etc.).
+ */
+ public NestedSet<ResourceContainer> getTransitiveAndroidResources() {
+ return transitiveAndroidResources;
+ }
+
+
+ /**
+ * The type of resource in question: either asset or a resource.
+ */
+ public enum ResourceType {
+ ASSETS("assets"), RESOURCES("resources");
+
+ private final String attribute;
+
+ private ResourceType(String attribute) {
+ this.attribute = attribute;
+ }
+
+ public String getAttribute() {
+ return attribute;
+ }
+ }
+
+
+ /**
+ * The resources contributed by a single target.
+ */
+ @Immutable
+ public static final class ResourceContainer {
+
+ private final Label label;
+ private final String javaPackage;
+ private final String renameManifestPackage;
+ private final boolean constantsInlined;
+ private final Artifact apk;
+ private final Artifact manifest;
+ private final ImmutableList<Artifact> assets;
+ private final ImmutableList<Artifact> resources;
+ private final ImmutableList<PathFragment> assetsRoots;
+ private final ImmutableList<PathFragment> resourcesRoots;
+ private final boolean manifestExported;
+ private final Artifact javaSourceJar;
+ private final Artifact rTxt;
+
+ public ResourceContainer(Label label,
+ String javaPackage,
+ @Nullable String renameManifestPackage,
+ boolean constantsInlined,
+ Artifact apk,
+ Artifact manifest,
+ Artifact javaSourceJar,
+ ImmutableList<Artifact> assets,
+ ImmutableList<Artifact> resources,
+ ImmutableList<PathFragment> assetsRoots,
+ ImmutableList<PathFragment> resourcesRoots,
+ boolean manifestExported,
+ Artifact rTxt) {
+ this.javaSourceJar = javaSourceJar;
+ this.manifestExported = manifestExported;
+ this.label = Preconditions.checkNotNull(label);
+ this.javaPackage = Preconditions.checkNotNull(javaPackage);
+ this.renameManifestPackage = renameManifestPackage;
+ this.constantsInlined = constantsInlined;
+ this.apk = Preconditions.checkNotNull(apk);
+ this.manifest = Preconditions.checkNotNull(manifest);
+ this.assets = Preconditions.checkNotNull(assets);
+ this.resources = Preconditions.checkNotNull(resources);
+ this.assetsRoots = Preconditions.checkNotNull(assetsRoots);
+ this.resourcesRoots = Preconditions.checkNotNull(resourcesRoots);
+ this.rTxt = rTxt;
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ public String getJavaPackage() {
+ return javaPackage;
+ }
+
+ public String getRenameManifestPackage() {
+ return renameManifestPackage;
+ }
+
+ public boolean getConstantsInlined() {
+ return constantsInlined;
+ }
+
+ public Artifact getApk() {
+ return apk;
+ }
+
+ public Artifact getJavaSourceJar() {
+ return javaSourceJar;
+ }
+
+ public Artifact getManifest() {
+ return manifest;
+ }
+
+ public boolean isManifestExported() {
+ return manifestExported;
+ }
+
+ public ImmutableList<Artifact> getArtifacts(ResourceType resourceType) {
+ return resourceType == ResourceType.ASSETS ? assets : resources;
+ }
+
+ public Iterable<Artifact> getArtifacts() {
+ return Iterables.concat(assets, resources);
+ }
+
+ public Artifact getRTxt() {
+ return rTxt;
+ }
+
+ public ImmutableList<PathFragment> getRoots(ResourceType resourceType) {
+ return resourceType == ResourceType.ASSETS ? assetsRoots : resourcesRoots;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(label);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ResourceContainer)) {
+ return false;
+ }
+ ResourceContainer other = (ResourceContainer) obj;
+ return label.equals(other.label);
+ }
+ }
+}
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
new file mode 100644
index 0000000000..d428a92432
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -0,0 +1,758 @@
+// Copyright 2015 Google Inc. 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 static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.INTEGER;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
+import static com.google.devtools.build.lib.packages.Type.TRISTATE;
+import static com.google.devtools.build.lib.util.FileTypeSet.ANY_FILE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.Constants;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.packages.TriState;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Rule definitions for Android rules.
+ */
+public final class AndroidRuleClasses {
+ public static final SafeImplicitOutputsFunction ANDROID_JAVA_SOURCE_JAR =
+ fromTemplates("%{name}.srcjar");
+ public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_SOURCE_JAR =
+ JavaSemantics.JAVA_LIBRARY_SOURCE_JAR;
+ public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_CLASS_JAR =
+ JavaSemantics.JAVA_LIBRARY_CLASS_JAR;
+ public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_AAR =
+ fromTemplates("%{name}.aar");
+ public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_APK =
+ fromTemplates("%{name}.ap_");
+ public static final SafeImplicitOutputsFunction ANDROID_INCREMENTAL_RESOURCES_APK =
+ fromTemplates("%{name}_files/incremental.ap_");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_APK =
+ fromTemplates("%{name}.apk");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_INCREMENTAL_APK =
+ fromTemplates("%{name}_incremental.apk");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_UNSIGNED_APK =
+ fromTemplates("%{name}_unsigned.apk");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_SIGNED_APK =
+ fromTemplates("%{name}_signed.apk");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_DEPLOY_JAR =
+ fromTemplates("%{name}_deploy.jar");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_PROGUARD_JAR =
+ fromTemplates("%{name}_proguard.jar");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_PROGUARD_MAP =
+ fromTemplates("%{name}_proguard.map");
+ public static final SafeImplicitOutputsFunction ANDROID_BINARY_INSTRUMENTED_JAR =
+ fromTemplates("%{name}_instrumented.jar");
+ public static final SafeImplicitOutputsFunction ANDROID_R_TXT =
+ fromTemplates("%{name}_symbols/R.txt");
+ public static final SafeImplicitOutputsFunction STUB_APPLICATON_MANIFEST =
+ fromTemplates("%{name}_files/stub/AndroidManifest.xml");
+ public static final SafeImplicitOutputsFunction FULL_DEPLOY_MARKER =
+ fromTemplates("%{name}_files/full_deploy_marker");
+ public static final SafeImplicitOutputsFunction INCREMENTAL_DEPLOY_MARKER =
+ fromTemplates("%{name}_files/incremental_deploy_marker");
+ public static final SafeImplicitOutputsFunction SPLIT_DEPLOY_MARKER =
+ fromTemplates("%{name}_files/split_deploy_marker");
+ public static final SafeImplicitOutputsFunction MOBILE_INSTALL_ARGS =
+ fromTemplates("%{name}_files/mobile_install_args");
+
+ // This needs to be in its own directory because ApkBuilder only has a function (-rf) for source
+ // folders but not source files, and it's easiest to guarantee that nothing gets put beside this
+ // file in the ApkBuilder invocation in this manner
+ public static final SafeImplicitOutputsFunction STUB_APPLICATION_DATA =
+ fromTemplates("%{name}_files/stub_application_data/stub_application_data.txt");
+ public static final SafeImplicitOutputsFunction DEX_MANIFEST =
+ fromTemplates("%{name}_files/dexmanifest.txt");
+ public static final SafeImplicitOutputsFunction JAVA_RESOURCES_JAR =
+ fromTemplates("%{name}_files/java_resources.jar");
+ public static final String MANIFEST_MERGE_TOOL_LABEL =
+ "//tools/android/build:merge_manifests.par";
+ public static final String BUILD_INCREMENTAL_DEXMANIFEST_LABEL =
+ "//tools/android:build_incremental_dexmanifest";
+ public static final String STUBIFY_MANIFEST_LABEL = "//tools/android:stubify_manifest";
+ public static final String INCREMENTAL_INSTALL_LABEL = "//tools/android:incremental_install";
+ public static final String BUILD_SPLIT_MANIFEST_LABEL = "//tools/android:build_split_manifest";
+ public static final String STRIP_RESOURCES_LABEL = "//tools/android:strip_resources";
+
+ public static final Label DEFAULT_ANDROID_SDK =
+ Label.parseAbsoluteUnchecked(Constants.ANDROID_DEFAULT_SDK);
+ public static final Label DEFAULT_INCREMENTAL_STUB_APPLICATION =
+ Label.parseAbsoluteUnchecked("//tools/android:incremental_stub_application");
+ public static final Label DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION =
+ Label.parseAbsoluteUnchecked("//tools/android:incremental_split_stub_application");
+ public static final Label DEFAULT_RESOURCES_PROCESSOR =
+ Label.parseAbsoluteUnchecked("//tools/android:resources_processor");
+ public static final Label DEFAULT_AAR_GENERATOR =
+ Label.parseAbsoluteUnchecked("//tools/android:aar_generator");
+
+ public static final LateBoundLabel<BuildConfiguration> INCREMENTAL_STUB_APPLICATION =
+ new LateBoundLabel<BuildConfiguration>(DEFAULT_INCREMENTAL_STUB_APPLICATION) {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return
+ configuration.getFragment(AndroidConfiguration.class).getIncrementalStubApplication();
+ }
+ };
+
+ static final LateBoundLabel<BuildConfiguration> INCREMENTAL_SPLIT_STUB_APPLICATION =
+ new LateBoundLabel<BuildConfiguration>(DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION) {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration
+ .getFragment(AndroidConfiguration.class)
+ .getIncrementalSplitStubApplication();
+ }
+ };
+
+ public static final LateBoundLabel<BuildConfiguration> RESOURCES_PROCESSOR =
+ new LateBoundLabel<BuildConfiguration>(DEFAULT_RESOURCES_PROCESSOR) {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration
+ .getFragment(AndroidConfiguration.class)
+ .getResourcesProcessor();
+ }
+ };
+
+ public static final LateBoundLabel<BuildConfiguration> AAR_GENERATOR =
+ new LateBoundLabel<BuildConfiguration>(DEFAULT_AAR_GENERATOR) {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration
+ .getFragment(AndroidConfiguration.class)
+ .getAarGenerator();
+ }
+ };
+
+ /**
+ * Implementation for the :proguard attribute.
+ */
+ static final LateBoundLabel<BuildConfiguration> PROGUARD =
+ new LateBoundLabel<BuildConfiguration>() {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ // If --proguard_top is not specified, null is returned. AndroidSdk will take care of using
+ // android_sdk.proguard then.
+ return configuration.getFragment(AndroidConfiguration.class).getProguardLabel();
+ }
+ };
+
+ public static final LateBoundLabel<BuildConfiguration> ANDROID_SDK =
+ new LateBoundLabel<BuildConfiguration>(DEFAULT_ANDROID_SDK) {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration.getFragment(AndroidConfiguration.class).getSdk();
+ }
+ };
+
+ public static final SplitTransition<BuildOptions> ANDROID_SPLIT_TRANSITION =
+ new SplitTransition<BuildOptions>() {
+ @Override
+ public boolean defaultsToSelf() {
+ return true;
+ }
+
+ @Override
+ public List<BuildOptions> split(BuildOptions buildOptions) {
+ AndroidConfiguration.Options androidOptions =
+ buildOptions.get(AndroidConfiguration.Options.class);
+ if (androidOptions.fatApkCpus.isEmpty()) {
+ return ImmutableList.of();
+ }
+
+ List<BuildOptions> result = new ArrayList<>();
+ for (String cpu : ImmutableSortedSet.copyOf(androidOptions.fatApkCpus)) {
+ BuildOptions splitOptions = buildOptions.clone();
+ // Disable fat APKs for the child configurations.
+ splitOptions.get(AndroidConfiguration.Options.class).fatApkCpus = ImmutableList.of();
+
+ // Set the cpu & android_cpu.
+ // TODO(bazel-team): --android_cpu doesn't follow --cpu right now; it should.
+ splitOptions.get(AndroidConfiguration.Options.class).cpu = cpu;
+ splitOptions.get(BuildConfiguration.Options.class).cpu = cpu;
+ result.add(splitOptions);
+ }
+ return result;
+ }
+ };
+
+ public static final FileType ANDROID_IDL = FileType.of(".aidl");
+
+ public static final String[] ALLOWED_DEPENDENCIES = {
+ "android_library",
+ "cc_library",
+ "java_import",
+ "java_library",
+ "proto_library"};
+
+ public static final ImplicitOutputsFunction ANDROID_BINARY_IMPLICIT_OUTPUTS =
+ new ImplicitOutputsFunction() {
+
+ @Override
+ public Iterable<String> getImplicitOutputs(AttributeMap rule) {
+ boolean mapping = rule.get("proguard_generate_mapping", Type.BOOLEAN);
+ List<SafeImplicitOutputsFunction> functions = Lists.newArrayListWithCapacity(6);
+ functions.add(AndroidRuleClasses.ANDROID_BINARY_APK);
+ functions.add(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
+ functions.add(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR);
+
+ // The below is a hack to support configurable attributes (proguard_specs seems like
+ // too valuable an attribute to make nonconfigurable, and we don't currently
+ // have the ability to know the configuration when determining implicit outputs).
+ // An IllegalArgumentException gets triggered if the attribute instance is configurable.
+ // We assume, heuristically, that means every configurable value is a non-empty list.
+ //
+ // TODO(bazel-team): find a stronger approach for this. One simple approach is to somehow
+ // receive 'rule' as an AggregatingAttributeMapper instead of a RawAttributeMapper,
+ // check that all possible values are non-empty, and simply don't support configurable
+ // instances that mix empty and non-empty lists. A more ambitious approach would be
+ // to somehow determine implicit outputs after the configuration is known. A third
+ // approach is to refactor the Android rule logic to avoid these dependencies in the
+ // first place.
+ boolean hasProguardSpecs;
+ try {
+ hasProguardSpecs = !rule.get("proguard_specs", LABEL_LIST).isEmpty();
+ } catch (IllegalArgumentException e) {
+ // We assume at this point the attribute instance is configurable.
+ hasProguardSpecs = true;
+ }
+
+ if (hasProguardSpecs) {
+ functions.add(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR);
+ if (mapping) {
+ functions.add(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_MAP);
+ }
+ }
+ return fromFunctions(functions).getImplicitOutputs(rule);
+ }
+ };
+
+ public static final ImplicitOutputsFunction ANDROID_LIBRARY_IMPLICIT_OUTPUTS =
+ new ImplicitOutputsFunction() {
+ @Override
+ public Iterable<String> getImplicitOutputs(AttributeMap attributes) {
+ if (LocalResourceContainer.definesAndroidResources(attributes)) {
+ return fromFunctions(
+ AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR,
+ AndroidRuleClasses.ANDROID_RESOURCES_APK,
+ AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR,
+ AndroidRuleClasses.ANDROID_LIBRARY_SOURCE_JAR,
+ AndroidRuleClasses.ANDROID_LIBRARY_AAR,
+ AndroidRuleClasses.ANDROID_R_TXT).getImplicitOutputs(attributes);
+ }
+ return fromFunctions(
+ AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR,
+ AndroidRuleClasses.ANDROID_LIBRARY_SOURCE_JAR,
+ AndroidRuleClasses.ANDROID_LIBRARY_AAR).getImplicitOutputs(attributes);
+ }
+ };
+
+ /**
+ * Definition of the {@code android_sdk} rule.
+ */
+ public static final class AndroidSdkRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+ return builder
+ .setUndocumented()
+ // This is the Proguard that comes from the --proguard_top attribute.
+ .add(attr(":proguard", LABEL).cfg(HOST).value(PROGUARD).exec())
+ // This is the Proguard in the BUILD file that contains the android_sdk rule. Used when
+ // --proguard_top is not specified.
+ .add(attr("proguard", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("aapt", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("dx", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("main_dex_list_creator", LABEL)
+ .mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("adb", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("framework_aidl", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE))
+ .add(attr("aidl", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .add(attr("android_jar", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE))
+ .add(attr("shrinked_android_jar", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE))
+ .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("zipalign", LABEL).mandatory().cfg(HOST).allowedFileTypes(ANY_FILE).exec())
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("android_sdk")
+ .ancestors(BaseRuleClasses.BaseRule.class)
+ .factoryClass(AndroidSdk.class)
+ .build();
+ }
+ }
+
+ /**
+ * Definition of the {@code android_tools_defaults_jar} rule.
+ */
+ public static final class AndroidToolsDefaultsJarRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+ return builder
+ .setUndocumented()
+ .add(attr(":android_sdk", LABEL)
+ .allowedRuleClasses("android_sdk", "filegroup")
+ .value(ANDROID_SDK))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("android_tools_defaults_jar")
+ .ancestors(BaseRuleClasses.BaseRule.class)
+ .factoryClass(AndroidToolsDefaultsJar.class)
+ .build();
+ }
+ }
+
+ /**
+ * Base class for rule definitions using AAPT.
+ */
+ public static final class AndroidAaptBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .add(attr(":android_resources_processor", LABEL).cfg(HOST).exec().value(
+ AndroidRuleClasses.RESOURCES_PROCESSOR))
+ .add(attr(":android_aar_generator", LABEL).cfg(HOST).exec().value(
+ AndroidRuleClasses.AAR_GENERATOR))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return Metadata.builder()
+ .name("$android_aapt_base")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(AndroidRuleClasses.AndroidBaseRule.class)
+ .build();
+ }
+ }
+
+ /**
+ * Base class for rule definitions that support resource declarations.
+ */
+ public static final class AndroidResourceSupportRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder.setUndocumented()
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(manifest) -->
+ The name of the Android manifest file, normally <code>AndroidManifest.xml</code>.
+ Must be defined if resource_files or assets are defined.
+ ${SYNOPSIS}
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("manifest", LABEL).legacyAllowAnyFileType())
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(exports_manifest) -->
+ Whether to export manifest entries to <code>android_binary</code> targets
+ that depend on this target. <code>uses-permissions</code> attributes are never exported.
+ ${SYNOPSIS}
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("exports_manifest", BOOLEAN).value(false))
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(resource_files) -->
+ The list of resources to be packaged.
+ ${SYNOPSIS}
+ This is typically a <code>glob</code> of all files under the
+ <code>res</code> directory.
+ <br/>
+ Generated files (from genrules) can be referenced by
+ <a href="build-ref.html#labels">Label</a> here as well. The only restriction is that the
+ generated outputs must be under the same "<code>res</code>" directory as any other
+ resource files that are included.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("resource_files", LABEL_LIST).legacyAllowAnyFileType())
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(assets_dir) -->
+ The string giving the path to the files in <code>assets</code>.
+ ${SYNOPSIS}
+ The pair <code>assets</code> and <code>assets_dir</code> describe packaged
+ assets and either both attributes should be provided or none of them.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("assets_dir", STRING))
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(assets) -->
+ The list of assets to be packaged.
+ ${SYNOPSIS}
+ This is typically a <code>glob</code> of all files under the
+ <code>assets</code> directory. You can also reference other rules (any rule that produces
+ files) or exported files in the other packages, as long as all those files are under the
+ <code>assets_dir</code> directory in the corresponding package.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("assets", LABEL_LIST).legacyAllowAnyFileType())
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(inline_constants) -->
+ Let the compiler inline the constants defined in the generated java sources.
+ ${SYNOPSIS}
+ This attribute must be set to 0 for all <code>android_library</code> rules
+ used directly by an <code>android_binary</code>,
+ and for any <code>android_binary</code> that has an <code>android_library</code>
+ in its transitive closure.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("inline_constants", BOOLEAN).value(false))
+ /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(custom_package) -->
+ Java package for which java sources will be generated.
+ ${SYNOPSIS}
+ By default the package is inferred from the directory where the BUILD file
+ containing the rule is. You can specify a different package but this is
+ highly discouraged since it can introduce classpath conflicts with other
+ libraries that will only be detected at runtime.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("custom_package", STRING))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$android_resource_support")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(AndroidRuleClasses.AndroidBaseRule.class)
+ .build();
+ }
+ }
+
+ /**
+ * Base class for Android rule definitions.
+ */
+ public static final class AndroidBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .add(attr(":android_sdk", LABEL)
+ .allowedRuleClasses("android_sdk", "filegroup")
+ .value(ANDROID_SDK))
+ /* <!-- #BLAZE_RULE($android_base).ATTRIBUTE(plugins) -->
+ Java compiler plugins to run at compile-time.
+ ${SYNOPSIS}
+ Every <code>java_plugin</code> specified in
+ the plugins attribute will be run whenever
+ this target is built. Resources generated by
+ the plugin will be included in the result jar of
+ the target.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("plugins", LABEL_LIST).cfg(HOST).allowedRuleClasses("java_plugin")
+ .legacyAllowAnyFileType())
+ .add(attr(":java_plugins", LABEL_LIST)
+ .cfg(HOST)
+ .allowedRuleClasses("java_plugin")
+ .silentRuleClassFilter()
+ .value(JavaSemantics.JAVA_PLUGINS))
+ /* <!-- #BLAZE_RULE($android_base).ATTRIBUTE(javacopts) -->
+ Extra compiler options for this target.
+ ${SYNOPSIS}
+ Subject to <a href="#make_variables">"Make variable"</a> substitution and
+ <a href="#sh-tokenization">Bourne shell tokenization</a>.
+ <p>
+ These compiler options are passed to javac after the global compiler options.</p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("javacopts", STRING_LIST))
+ // $android_jar must be in the target configuration because it points to an
+ // android_tools_defaults_jar rule, and that needs the configuration to fetch the actual
+ // android.jar .
+ .add(attr("$android_jar", LABEL)
+ .value(env.getLabel("//tools/defaults:android_jar")))
+ .add(attr("$android_dx_jar", LABEL).cfg(HOST)
+ .value(env.getLabel("//tools/defaults:android_dx_jar")))
+ .add(attr("$android_jar_repackager", LABEL).cfg(HOST).exec()
+ .value(env.getLabel("//tools/android:repackage_jar")))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$android_base")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .build();
+ }
+ }
+
+ /**
+ * Base class for Android rule definitions that produce binaries.
+ */
+ public static final class AndroidBinaryBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
+ return builder
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(srcs) -->
+ The list of source files that are processed to create the target.
+ ${SYNOPSIS}
+ <p><code>srcs</code> files of type <code>.java</code> are compiled.
+ <em>For readability's sake</em>, it is not good to put the name of a
+ generated <code>.java</code> source file into the <code>srcs</code>.
+ Instead, put the depended-on rule name in the <code>srcs</code>, as
+ described below.
+ </p>
+ <p><code>srcs</code> files of type <code>.srcjar</code> are unpacked and
+ compiled. (This is useful if you need to generate a set of .java files with
+ a genrule or build extension.)
+ </p>
+ <p>This rule currently forces source and class compatibility with Java 6.
+ </p>
+ <p><code>srcs</code> files of type <code>.jar</code> are linked in.
+ (This is useful if you have third-party <code>.jar</code> files
+ with no source.)
+ </p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("srcs", LABEL_LIST)
+ .direct_compile_time_input()
+ .allowedFileTypes(JavaSemantics.JAVA_SOURCE, JavaSemantics.JAR,
+ JavaSemantics.SOURCE_JAR))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(deps) -->
+ The list of other libraries to be linked in to the binary target.
+ ${SYNOPSIS}
+ Permitted library types are: <code>android_library</code>,
+ <code>java_library</code> with <code>android</code> constraint and
+ <code>cc_library</code> wrapping or producing <code>.so</code> native libraries for the
+ Android target platform.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .override(builder.copy("deps").cfg(ANDROID_SPLIT_TRANSITION)
+ .allowedRuleClasses(ALLOWED_DEPENDENCIES).allowedFileTypes())
+ // Proguard rule specifying master list of classes to keep during legacy multidexing.
+ .add(attr("$build_incremental_dexmanifest", LABEL).cfg(HOST).exec()
+ .value(env.getLabel(AndroidRuleClasses.BUILD_INCREMENTAL_DEXMANIFEST_LABEL)))
+ .add(attr("$stubify_manifest", LABEL).cfg(HOST).exec()
+ .value(env.getLabel(AndroidRuleClasses.STUBIFY_MANIFEST_LABEL)))
+ .add(attr("$shuffle_jars", LABEL).cfg(HOST).exec()
+ .value(env.getLabel("//tools/android:shuffle_jars")))
+ .add(attr("$merge_dexzips", LABEL).cfg(HOST).exec()
+ .value(env.getLabel("//tools/android:merge_dexzips")))
+ .add(attr("$incremental_install", LABEL).cfg(HOST).exec()
+ .value(env.getLabel(INCREMENTAL_INSTALL_LABEL)))
+ .add(attr("$build_split_manifest", LABEL).cfg(HOST).exec()
+ .value(env.getLabel(BUILD_SPLIT_MANIFEST_LABEL)))
+ .add(attr("$strip_resources", LABEL).cfg(HOST).exec()
+ .value(env.getLabel(AndroidRuleClasses.STRIP_RESOURCES_LABEL)))
+ .add(attr(":incremental_stub_application", LABEL)
+ .value(AndroidRuleClasses.INCREMENTAL_STUB_APPLICATION))
+ .add(attr(":incremental_split_stub_application", LABEL)
+ .value(AndroidRuleClasses.INCREMENTAL_SPLIT_STUB_APPLICATION))
+
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(debug_key) -->
+ File containing debug keystore to be used to sign debug apk.
+ ${SYNOPSIS}
+ Points to a location of debug keystore file that is different than default
+ debug key. Usually you do not want to use key other than default key, so
+ this attribute should be omitted.
+ <p><em class="harmful">WARNING: Do not use your production keys, they should be
+ strictly safeguarded and not kept in your source tree</em>.</p>
+ <p>This keystore must contain a single key named "AndroidDebugKey", and
+ have a keystore password of "android".
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("debug_key", LABEL).cfg(HOST).legacyAllowAnyFileType()
+ .value(env.getLabel("//tools/android:debug_keystore")))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(dexopts) -->
+ Additional command-line flags for the dx tool when generating classes.dex.
+ ${SYNOPSIS}
+ Subject to <a href="#make_variables">"Make variable"</a> substitution and
+ <a href="#sh-tokenization">Bourne shell tokenization</a>.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("dexopts", STRING_LIST))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(dex_shards) -->
+ Number of shards dexing should be decomposed into.
+ ${SYNOPSIS}
+ This is makes dexing much faster at the expense of app installation and startup time. The
+ larger the binary, the more shards should be used. 25 is a good value to start
+ experimenting with.
+ <p>
+ Note that each shard will result in at least one dex in the final app. For this reason,
+ setting this to more than 1 is not recommended for release binaries.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("dex_shards", INTEGER).value(1))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(main_dex_list_opts) -->
+ Command line options to pass to the main dex list builder.
+ ${SYNOPSIS}
+ Use this option to affect the classes included in the main dex list.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("main_dex_list_opts", STRING_LIST))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(main_dex_list) -->
+
+ A text file contains a list of class file names. Classes defined by those class files are
+ put in the primary classes.dex. e.g.:<pre class="code">
+android/support/multidex/MultiDex$V19.class
+android/support/multidex/MultiDex.class
+android/support/multidex/MultiDexApplication.class
+com/google/common/base/Objects.class
+ </pre>
+ ${SYNOPSIS}
+ Must be used with <code>multidex="manual_main_dex"</code>.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("main_dex_list", LABEL).legacyAllowAnyFileType())
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_specs) -->
+ Files to be used as Proguard specification.
+ ${SYNOPSIS}
+ This file will describe the set of specifications to be used by Proguard.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("proguard_specs", LABEL_LIST).legacyAllowAnyFileType())
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_generate_mapping) -->
+ Whether to generate Proguard mapping file.
+ ${SYNOPSIS}
+ The mapping file will be generated only if <code>proguard_specs</code> is
+ specified. This file will list the mapping between the original and
+ obfuscated class, method, and field names.
+ <p><em class="harmful">WARNING: If you use this attribute, your Proguard specification
+ should contain neither <code>-dontobfuscate</code> nor <code>-printmapping</code>.
+ </em>.</p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("proguard_generate_mapping", BOOLEAN).value(false)
+ .nonconfigurable("value is referenced in an ImplicitOutputsFunction"))
+ /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(legacy_native_support) -->
+ Enables legacy native support, where pre-compiled native libraries are copied
+ directly into the APK.
+ ${SYNOPSIS}
+ Possible values:
+ <ul>
+ <li><code>legacy_native_support = 1</code>: Pre-built .so files found in the
+ dependencies of cc_libraries in the transitive closure will be copied into
+ the APK without being modified in any way. All cc_libraries in the transitive
+ closure of this rule must wrap .so files. (<em class="harmful">deprecated</em> -
+ legacy_native_support = 0 will become the default and this attribute will be
+ removed in a future Blaze release.)</li>
+ <li><code>legacy_native_support = 0</code>: Native dependencies in the transitive
+ closure will be linked together into a single lib[ruleName].so
+ before being placed in the APK. This ensures that, e.g., only one copy of
+ //base will be loaded into memory. This lib[ruleName].so can be loaded
+ via System.loadLibrary as normal.</li>
+ <li><code>legacy_native_support = -1</code>: Linking is controlled by the
+ <a href="blaze-user-manual.html#flag--legacy_android_native_support">
+ --[no]legacy_android_native_support</a> Blaze flag.</li>
+ </ul>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("legacy_native_support", TRISTATE).value(TriState.AUTO))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$android_binary_base")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(AndroidRuleClasses.AndroidBaseRule.class, AndroidAaptBaseRule.class,
+ AndroidResourceSupportRule.class)
+ .build();
+ }
+ }
+
+ /**
+ * Semantic options for the dexer's multidex behavior.
+ */
+ public static enum MultidexMode {
+ // Build dexes with multidex, assuming native platform support for multidex.
+ NATIVE("native"),
+ // Build dexes with multidex and implement support at the application level.
+ LEGACY("legacy"),
+ // Build dexes with multidex, main dex list needs to be manually specified.
+ MANUAL_MAIN_DEX("legacy"),
+ // Build all dex code into a single classes.dex file.
+ OFF("none");
+
+ @Nullable private final String jackFlagValue;
+
+ private MultidexMode(String jackFlagValue) {
+ this.jackFlagValue = jackFlagValue;
+ }
+
+ /**
+ * Returns the attribute value that specifies this mode.
+ */
+ public String getAttributeValue() {
+ return toString().toLowerCase();
+ }
+
+ /**
+ * Returns whether or not this multidex mode can be passed to Jack.
+ */
+ public boolean isSupportedByJack() {
+ return jackFlagValue != null;
+ }
+
+ /**
+ * Returns the value that should be passed to Jack's --multi-dex flag.
+ *
+ * @throws UnsupportedOperationException if the dex mode is not supported by Jack
+ * ({@link #isSupportedByJack()} returns false)
+ */
+ public String getJackFlagValue() {
+ if (!isSupportedByJack()) {
+ throw new UnsupportedOperationException();
+ }
+ return jackFlagValue;
+ }
+
+ /**
+ * Returns the name of the output dex classes file. In multidex mode, this is an archive
+ * of (possibly) multiple files.
+ */
+ public String getOutputDexFilename() {
+ return this == OFF ? "classes.dex" : "classes.dex.zip";
+ }
+
+ /**
+ * Converts an attribute value to a corresponding mode. Returns null on no match.
+ */
+ public static MultidexMode fromValue(String value) {
+ for (MultidexMode mode : values()) {
+ if (mode.getAttributeValue().equals(value)) {
+ return mode;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Enumerates valid values for the "multidex" attribute.
+ */
+ public static List<String> getValidValues() {
+ List<String> ans = Lists.newArrayList();
+ for (MultidexMode mode : MultidexMode.values()) {
+ ans.add(mode.getAttributeValue());
+ }
+ return ans;
+ }
+ }
+}
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
new file mode 100644
index 0000000000..2afaacaede
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdk.java
@@ -0,0 +1,68 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/**
+ * Implementation of the {@code android_sdk} rule.
+ */
+public class AndroidSdk implements RuleConfiguredTargetFactory {
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
+ // If the user didn't specify --proguard_top, go with the proguard attribute in the android_sdk
+ // rule. Otherwise, use what she told us to.
+ FilesToRunProvider proguard =
+ ruleContext.getFragment(AndroidConfiguration.class).getProguardLabel() == null
+ ? ruleContext.getExecutablePrerequisite("proguard", Mode.HOST)
+ : ruleContext.getExecutablePrerequisite(":proguard", Mode.HOST);
+
+ FilesToRunProvider aidl = ruleContext.getExecutablePrerequisite("aidl", Mode.HOST);
+ FilesToRunProvider aapt = ruleContext.getExecutablePrerequisite("aapt", Mode.HOST);
+ FilesToRunProvider apkBuilder = ruleContext.getExecutablePrerequisite(
+ "apkbuilder", Mode.HOST);
+ FilesToRunProvider adb = ruleContext.getExecutablePrerequisite("adb", Mode.HOST);
+ FilesToRunProvider dx = ruleContext.getExecutablePrerequisite("dx", Mode.HOST);
+ FilesToRunProvider mainDexListCreator = ruleContext.getExecutablePrerequisite(
+ "main_dex_list_creator", Mode.HOST);
+ FilesToRunProvider zipalign = ruleContext.getExecutablePrerequisite("zipalign", Mode.HOST);
+ Artifact frameworkAidl = ruleContext.getPrerequisiteArtifact("framework_aidl", Mode.HOST);
+ Artifact androidJar = ruleContext.getPrerequisiteArtifact("android_jar", Mode.HOST);
+ Artifact shrinkedAndroidJar =
+ ruleContext.getPrerequisiteArtifact("shrinked_android_jar", Mode.HOST);
+ Artifact annotationsJar = ruleContext.getPrerequisiteArtifact("annotations_jar", Mode.HOST);
+ Artifact mainDexClasses = ruleContext.getPrerequisiteArtifact("main_dex_classes", Mode.HOST);
+
+ if (ruleContext.hasErrors()) {
+ return null;
+ }
+
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .add(AndroidSdkProvider.class, new AndroidSdkProvider(
+ frameworkAidl, androidJar, shrinkedAndroidJar, annotationsJar, mainDexClasses,
+ adb, dx, mainDexListCreator, aidl, aapt, apkBuilder, proguard, zipalign))
+ .add(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .setFilesToBuild(NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER))
+ .build();
+ }
+}
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
new file mode 100644
index 0000000000..c8883568ff
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSdkProvider.java
@@ -0,0 +1,113 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Description of the tools Blaze needs from an Android SDK.
+ */
+@Immutable
+public final class AndroidSdkProvider implements TransitiveInfoProvider {
+ private final Artifact frameworkAidl;
+ private final Artifact androidJar;
+ private final Artifact shrinkedAndroidJar;
+ private final Artifact annotationsJar;
+ private final Artifact mainDexClasses;
+ private final FilesToRunProvider adb;
+ private final FilesToRunProvider dx;
+ private final FilesToRunProvider mainDexListCreator;
+ private final FilesToRunProvider aidl;
+ private final FilesToRunProvider aapt;
+ private final FilesToRunProvider apkBuilder;
+ private final FilesToRunProvider proguard;
+ private final FilesToRunProvider zipalign;
+
+ public AndroidSdkProvider(
+ Artifact frameworkAidl, Artifact androidJar, Artifact shrinkedAndroidJar,
+ Artifact annotationsJar, Artifact mainDexClasses,
+ FilesToRunProvider adb, FilesToRunProvider dx,
+ FilesToRunProvider mainDexListCreator,
+ FilesToRunProvider aidl, FilesToRunProvider aapt, FilesToRunProvider apkBuilder,
+ FilesToRunProvider proguard, FilesToRunProvider zipalign) {
+ this.frameworkAidl = frameworkAidl;
+ this.androidJar = androidJar;
+ this.shrinkedAndroidJar = shrinkedAndroidJar;
+ this.annotationsJar = annotationsJar;
+ this.mainDexClasses = mainDexClasses;
+ this.adb = adb;
+ this.dx = dx;
+ this.mainDexListCreator = mainDexListCreator;
+ this.aidl = aidl;
+ this.aapt = aapt;
+ this.apkBuilder = apkBuilder;
+ this.proguard = proguard;
+ this.zipalign = zipalign;
+ }
+
+ public Artifact getFrameworkAidl() {
+ return frameworkAidl;
+ }
+
+ public Artifact getAndroidJar() {
+ return androidJar;
+ }
+
+ public Artifact getShrinkedAndroidJar() {
+ return shrinkedAndroidJar;
+ }
+
+ public Artifact getAnnotationsJar() {
+ return annotationsJar;
+ }
+
+ public Artifact getMainDexClasses() {
+ return mainDexClasses;
+ }
+
+ public FilesToRunProvider getAdb() {
+ return adb;
+ }
+
+ public FilesToRunProvider getDx() {
+ return dx;
+ }
+
+ public FilesToRunProvider getMainDexListCreator() {
+ return mainDexListCreator;
+ }
+
+ public FilesToRunProvider getAidl() {
+ return aidl;
+ }
+
+ public FilesToRunProvider getAapt() {
+ return aapt;
+ }
+
+ public FilesToRunProvider getApkBuilder() {
+ return apkBuilder;
+ }
+
+ public FilesToRunProvider getProguard() {
+ return proguard;
+ }
+
+ public FilesToRunProvider getZipalign() {
+ return zipalign;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java
new file mode 100644
index 0000000000..9af02cec88
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java
@@ -0,0 +1,70 @@
+// Copyright 2015 Google Inc. 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.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.rules.java.JavaCommon;
+import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.rules.java.JavaTargetAttributes;
+
+/**
+ * Pluggable semantics for Android rules.
+ *
+ * <p>A new instance of this class is created for each configured target, therefore, it is allowed
+ * to keep state.
+ */
+public interface AndroidSemantics {
+ /**
+ * Adds transitive info providers for {@code android_binary} and {@code android_library} rules.
+ */
+ void addTransitiveInfoProviders(RuleConfiguredTargetBuilder builder,
+ RuleContext ruleContext, JavaCommon javaCommon, AndroidCommon androidCommon,
+ Artifact jarWithAllClasses, ResourceApk resourceApk, Artifact zipAlignedApk,
+ Iterable<Artifact> apksUnderTest);
+
+ /**
+ * Returns the manifest to be used when compiling a given rule.
+ */
+ ApplicationManifest getManifestForRule(RuleContext ruleContext);
+
+ /**
+ * Returns the name of the file in which the file names of native dependencies are listed.
+ */
+ String getNativeDepsFileName();
+
+ /**
+ * Returns the command line options to be used when compiling Java code for {@code android_*}
+ * rules.
+ *
+ * <p>These will come after the default options specified by the toolchain and the ones in the
+ * {@code javacopts} attribute.
+ */
+ ImmutableList<String> getJavacArguments();
+
+ /**
+ * JVM arguments to be passed to the command line of dx.
+ */
+ ImmutableList<String> getDxJvmArguments();
+
+ /**
+ * Add coverage instrumentation to the Java compilation of an Android binary.
+ */
+ void addCoverageSupport(RuleContext ruleContext, AndroidCommon common,
+ JavaSemantics javaSemantics, boolean forAndroidTest,
+ JavaTargetAttributes.Builder attributes, JavaCompilationArtifacts.Builder artifactsBuilder);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidTools.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidTools.java
new file mode 100644
index 0000000000..b7a057dc0c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidTools.java
@@ -0,0 +1,319 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.java.BaseJavaCompilationHelper;
+import com.google.devtools.build.lib.rules.java.JavaCompilationHelper;
+import com.google.devtools.build.lib.rules.java.Jvm;
+
+import java.util.List;
+
+/** A common interface for all the tools used by {@code android_*} rules. */
+public class AndroidTools {
+ private final RuleContext ruleContext;
+ private final Artifact dxJar;
+ private final FilesToRunProvider aapt;
+ private final Artifact apkBuilderTool;
+ private final Artifact aidlTool;
+ private final Artifact frameworkAidl;
+ private final FilesToRunProvider adb;
+ private final FilesToRunProvider toolRunner;
+ private final FilesToRunProvider aaptJavaGenerator;
+ private final FilesToRunProvider apkGenerator;
+ private final FilesToRunProvider resourceProcessor;
+ private final FilesToRunProvider aarGenerator;
+ private final FilesToRunProvider zipalign;
+ private final FilesToRunProvider proguard;
+ private final Artifact androidJar;
+ private final Artifact shrinkedAndroidJar;
+ private final Artifact annotationsJar;
+ private final Artifact mainDexClasses;
+ private final AndroidSdkProvider androidSdk;
+
+ public static AndroidTools fromRuleContext(RuleContext ruleContext) {
+ TransitiveInfoCollection androidSdkDep =
+ ruleContext.getPrerequisite(":android_sdk", Mode.TARGET);
+ AndroidSdkProvider androidSdk = androidSdkDep == null
+ ? null
+ : androidSdkDep.getProvider(AndroidSdkProvider.class);
+
+ return new AndroidTools(
+ ruleContext,
+ androidSdk,
+ getOptionalArtifact(ruleContext, "$android_dx_jar"),
+ getOptionalArtifact(ruleContext, "$android_jar", Mode.TARGET),
+ getOptionalArtifact(ruleContext, "$shrinked_android"),
+ getOptionalArtifact(ruleContext, "$android_annotations_jar"),
+ getOptionalArtifact(ruleContext, "$multidex_keep_classes"),
+ getOptionalArtifact(ruleContext, "$android_apkbuilder_tool"),
+ getOptionalArtifact(ruleContext, "$android_aidl_tool"),
+ getOptionalArtifact(ruleContext, "$android_aidl_framework"),
+ getOptionalToolFromArtifact(ruleContext, "$android_aapt"),
+ getOptionalToolFromArtifact(ruleContext, "$adb"),
+ getOptionalTool(ruleContext, "$android_tool_runner"),
+ getOptionalTool(ruleContext, "$android_aapt_java_generator"),
+ getOptionalTool(ruleContext, "$android_aapt_apk_generator"),
+ getOptionalTool(ruleContext, ":android_resources_processor"),
+ getOptionalTool(ruleContext, ":android_aar_generator"),
+ getOptionalTool(ruleContext, "$zipalign_tool"),
+ getOptionalTool(ruleContext, ":proguard"));
+ }
+
+ private static Artifact getOptionalArtifact(RuleContext ruleContext, String attribute) {
+ return getOptionalArtifact(ruleContext, attribute, Mode.HOST);
+ }
+
+ private static Artifact getOptionalArtifact(
+ RuleContext ruleContext, String attribute, Mode mode) {
+ if (!ruleContext.getRule().isAttrDefined(attribute, Type.LABEL)) {
+ return null;
+ }
+
+ List<Artifact> prerequisites =
+ ruleContext.getPrerequisiteArtifacts(attribute, mode).list();
+
+ if (prerequisites.isEmpty()) {
+ return null;
+ } else if (prerequisites.size() == 1) {
+ return prerequisites.get(0);
+ } else {
+ ruleContext.attributeError(attribute, "expected a single artifact");
+ return null;
+ }
+ }
+
+ private static FilesToRunProvider getOptionalToolFromArtifact(
+ RuleContext ruleContext, String attribute) {
+ if (!ruleContext.getRule().isAttrDefined(attribute, Type.LABEL)) {
+ return null;
+ }
+
+ Artifact artifact = getOptionalArtifact(ruleContext, attribute);
+ if (artifact == null) {
+ return null;
+ }
+
+ return FilesToRunProvider.fromSingleArtifact(
+ ruleContext.attributes().get(attribute, Type.LABEL),
+ artifact);
+ }
+
+ private static FilesToRunProvider getOptionalTool(RuleContext ruleContext, String attribute) {
+ if (!ruleContext.getRule().isAttrDefined(attribute, Type.LABEL)) {
+ return null;
+ }
+
+ TransitiveInfoCollection prerequisite = ruleContext.getPrerequisite(attribute, Mode.HOST);
+ if (prerequisite == null) {
+ return null;
+ }
+
+ return prerequisite.getProvider(FilesToRunProvider.class);
+ }
+
+ public AndroidTools(
+ RuleContext ruleContext,
+ AndroidSdkProvider androidSdk,
+ Artifact dxJar,
+ Artifact androidJar,
+ Artifact shrinkedAndroidJar,
+ Artifact annotationsJar,
+ Artifact mainDexClasses,
+ Artifact apkBuilderTool,
+ Artifact aidlTool,
+ Artifact frameworkAidl,
+ FilesToRunProvider aapt,
+ FilesToRunProvider adb,
+ FilesToRunProvider toolRunner,
+ FilesToRunProvider aaptJavaGenerator,
+ FilesToRunProvider apkGenerator,
+ FilesToRunProvider resourceProcessor,
+ FilesToRunProvider aarGenerator,
+ FilesToRunProvider zipalign,
+ FilesToRunProvider proguard) {
+ this.ruleContext = ruleContext;
+ this.androidSdk = androidSdk;
+ this.dxJar = dxJar;
+ this.androidJar = androidJar;
+ this.shrinkedAndroidJar = shrinkedAndroidJar;
+ this.mainDexClasses = mainDexClasses;
+ this.aapt = aapt;
+ this.annotationsJar = annotationsJar;
+ this.apkBuilderTool = apkBuilderTool;
+ this.aidlTool = aidlTool;
+ this.frameworkAidl = frameworkAidl;
+ this.adb = adb;
+ this.toolRunner = toolRunner;
+ this.aaptJavaGenerator = aaptJavaGenerator;
+ this.apkGenerator = apkGenerator;
+ this.resourceProcessor = resourceProcessor;
+ this.aarGenerator = aarGenerator;
+ this.zipalign = zipalign;
+ this.proguard = proguard;
+ }
+
+ public Artifact getFrameworkAidl() {
+ return androidSdk != null ? androidSdk.getFrameworkAidl() : frameworkAidl;
+ }
+
+ public Artifact getAndroidJar() {
+ return androidSdk != null ? androidSdk.getAndroidJar() : androidJar;
+ }
+
+ public Artifact getShrinkedAndroidJar() {
+ return androidSdk != null ? androidSdk.getShrinkedAndroidJar() : shrinkedAndroidJar;
+ }
+
+ public Artifact getAnnotationsJar() {
+ return androidSdk != null ? androidSdk.getAnnotationsJar() : annotationsJar;
+ }
+
+ public Artifact getMainDexClasses() {
+ return androidSdk != null ? androidSdk.getMainDexClasses() : mainDexClasses;
+ }
+
+ public FilesToRunProvider getAapt() {
+ return androidSdk != null ? androidSdk.getAapt() : aapt;
+ }
+
+ public FilesToRunProvider getAdb() {
+ return androidSdk != null ? androidSdk.getAdb() : adb;
+ }
+
+ public FilesToRunProvider getToolRunner() {
+ return toolRunner;
+ }
+
+ public FilesToRunProvider getAaptJavaGenerator() {
+ return aaptJavaGenerator;
+ }
+
+ public FilesToRunProvider getApkGenerator() {
+ return apkGenerator;
+ }
+
+ public FilesToRunProvider getAndroidResourceProcessor() {
+ return resourceProcessor;
+ }
+
+ public FilesToRunProvider getAarGenerator() {
+ return aarGenerator;
+ }
+
+ public FilesToRunProvider getZipalign() {
+ return androidSdk != null ? androidSdk.getZipalign() : zipalign;
+ }
+
+ public FilesToRunProvider getProguard() {
+ return androidSdk != null ? androidSdk.getProguard() : proguard;
+ }
+
+ /**
+ * Creates a {@link com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder} that has
+ * its executable already set to invoke dx.
+ */
+ public SpawnAction.Builder dxAction(AndroidSemantics semantics) {
+ return androidSdk != null
+ ? new SpawnAction.Builder()
+ .setExecutable(androidSdk.getDx())
+ : new SpawnAction.Builder()
+ .addTransitiveInputs(BaseJavaCompilationHelper.getHostJavabaseInputs(ruleContext))
+ .setExecutable(ruleContext.getHostConfiguration()
+ .getFragment(Jvm.class).getJavaExecutable())
+ .addArguments(semantics.getDxJvmArguments())
+ .addArgument("-jar")
+ .addInputArgument(dxJar);
+ }
+
+ /**
+ * Creates a {@link com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder} that has
+ * its executable already set to invoke the main dex list creator.
+ */
+ public Action[] mainDexListAction(Artifact jar, Artifact strippedJar, Artifact mainDexList) {
+ if (androidSdk != null) {
+ return new SpawnAction.Builder()
+ .setExecutable(androidSdk.getMainDexListCreator())
+ .addOutputArgument(mainDexList)
+ .addInputArgument(strippedJar)
+ .addInputArgument(jar)
+ .build(ruleContext);
+ } else {
+ StringBuilder shellCommandBuilder = new StringBuilder()
+ .append(ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable()
+ .getPathString())
+ .append(" -cp ").append(dxJar.getExecPathString())
+ .append(" ").append(AndroidBinary.MAIN_DEX_CLASS_BUILDER);
+ for (String opt : ruleContext.getTokenizedStringListAttr("main_dex_list_opts")) {
+ shellCommandBuilder.append(" ").append(opt);
+ }
+ shellCommandBuilder
+ .append(" ").append(strippedJar.getExecPathString())
+ .append(" ").append(jar.getExecPathString())
+ .append(" >").append(mainDexList.getExecPathString());
+
+ return new SpawnAction.Builder()
+ .addInput(strippedJar)
+ .addInput(jar)
+ .addInput(dxJar)
+ .addTransitiveInputs(BaseJavaCompilationHelper.getHostJavabaseInputs(ruleContext))
+ .addOutput(mainDexList)
+ .setProgressMessage("Generating main dex classes list")
+ .setMnemonic("MainDexClasses")
+ .setShellCommand(shellCommandBuilder.toString())
+ .build(ruleContext);
+ }
+ }
+
+ /**
+ * Creates a {@link com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder} that has
+ * its executable already set to invoke apkbuilder.
+ */
+ public SpawnAction.Builder apkBuilderAction() {
+ return androidSdk != null
+ ? new SpawnAction.Builder()
+ .setExecutable(androidSdk.getApkBuilder())
+ : new SpawnAction.Builder()
+ .setExecutable(
+ ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable())
+ .addTransitiveInputs(JavaCompilationHelper.getHostJavabaseInputs(ruleContext))
+ .addArgument("-jar")
+ .addInputArgument(apkBuilderTool);
+ }
+
+ /**
+ * Creates a {@link com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder} that has
+ * its executable already set to invoke aidl.
+ */
+ public SpawnAction.Builder aidlAction() {
+ return androidSdk != null
+ ? new SpawnAction.Builder()
+ .setExecutable(androidSdk.getAidl())
+ : new SpawnAction.Builder()
+ // Note the below may be an overapproximation of the actual runfiles, due to
+ // "conditional artifacts" (see Runfiles.PruningManifest).
+ // TODO(bazel-team): When using getFilesToRun(), the middleman is
+ // not expanded. Fix by providing code to expand and use getFilesToRun here.
+ .addInputs(toolRunner.getRunfilesSupport().getRunfilesArtifactsWithoutMiddlemen())
+ .setExecutable(toolRunner.getExecutable())
+ .addInputArgument(aidlTool);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidToolsDefaultsJar.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidToolsDefaultsJar.java
new file mode 100644
index 0000000000..de4ae447ee
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidToolsDefaultsJar.java
@@ -0,0 +1,88 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+import java.util.regex.Pattern;
+
+/**
+ * Implementation for the {@code android_tools_defaults_jar} rule.
+ *
+ * <p>This rule is a sad, sad way to let people depend on {@code android.jar} when an
+ * {@code android_sdk} rule is used. In an ideal world, people would say "depend on
+ * the android_jar output group of $config.android_sdk", but, alas, neither depending on labels in
+ * the configuration nor depending on a specified output group works.
+ *
+ * <p>So all this needs to be implemented manually. This rule is injected into the defaults package
+ * from {@link AndroidConfiguration.Options#getDefaultsRules()}.
+ */
+public class AndroidToolsDefaultsJar implements RuleConfiguredTargetFactory {
+ private static final Pattern ANDROID_JAR_BASENAME_RX =
+ Pattern.compile("android[a-zA-Z0-9_]*\\.jar$");
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
+ if (!ruleContext.getLabel().getPackageName().equals("tools/defaults")) {
+ // Guard against extraordinarily inquisitive individuals.
+ ruleContext.ruleError("The android_tools_defaults_jar rule should not be used in BUILD files."
+ + " It is a rule internal to the build tool.");
+ return null;
+ }
+
+ TransitiveInfoCollection androidSdk = ruleContext.getPrerequisite(":android_sdk", Mode.TARGET);
+ AndroidSdkProvider sdkProvider = androidSdk.getProvider(AndroidSdkProvider.class);
+ Artifact androidJar = sdkProvider != null
+ ? sdkProvider.getAndroidJar()
+ : findAndroidJar(androidSdk.getProvider(FileProvider.class).getFilesToBuild());
+
+ NestedSet<Artifact> filesToBuild = androidJar == null
+ ? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)
+ : NestedSetBuilder.create(Order.STABLE_ORDER, androidJar);
+
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .add(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .setFilesToBuild(filesToBuild)
+ .build();
+ }
+
+ private static Artifact findAndroidJar(Iterable<Artifact> fullSdk) {
+ // We need to do this by sifting through all the files in the Android SDK because we need to
+ // handle the case when --android_sdk points to a plain filegroup.
+ //
+ // We can't avoid adding an android_tools_defaults_jar rule to the defaults package when this is
+ // the case, because the defaults package is constructed very early and it's not possible to get
+ // information about the rule class of the target pointed to by --android_sdk earlier, and it's
+ // doubly impossible to do redirect chasing then.
+ for (Artifact artifact : fullSdk) {
+
+ if (ANDROID_JAR_BASENAME_RX.matcher(artifact.getExecPath().getBaseName()).matches()) {
+ return artifact;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApkProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApkProvider.java
new file mode 100644
index 0000000000..c418cb4708
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApkProvider.java
@@ -0,0 +1,49 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+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;
+
+/**
+ * A provider for targets that can build .apk files. Currently used for coverage collection.
+ */
+@Immutable
+public final class ApkProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> transitiveApks;
+
+ private final NestedSet<Artifact> coverageMetadata;
+
+ public ApkProvider(NestedSet<Artifact> transitiveApks, NestedSet<Artifact> coverageMetdata) {
+ this.transitiveApks = transitiveApks;
+ this.coverageMetadata = coverageMetdata;
+ }
+
+ /**
+ * Returns the APK files generated in the transitive closure.
+ */
+ public NestedSet<Artifact> getTransitiveApks() {
+ return transitiveApks;
+ }
+
+ /**
+ * Returns the coverage metadata artifacts generated in the transitive closure.
+ */
+ public NestedSet<Artifact> getCoverageMetadata() {
+ return coverageMetadata;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
new file mode 100644
index 0000000000..c5aae12d47
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java
@@ -0,0 +1,486 @@
+// Copyright 2015 Google Inc. 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.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.Type;
+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.rules.android.LocalResourceContainer.Builder.InvalidAssetPath;
+import com.google.devtools.build.lib.rules.android.LocalResourceContainer.Builder.InvalidResourcePath;
+import com.google.devtools.build.lib.rules.java.JavaUtil;
+
+import java.util.List;
+
+/** Represents a AndroidManifest, that may have been merged from dependencies. */
+public final class ApplicationManifest {
+ public static ApplicationManifest fromResourcesRule(RuleContext ruleContext) {
+ final AndroidResourcesProvider resources = AndroidCommon.getAndroidResources(ruleContext);
+ if (resources == null) {
+ ruleContext.attributeError("manifest",
+ "a resources or manifest attribute is mandatory.");
+ throw new RuleConfigurationException();
+ }
+ return new ApplicationManifest(Iterables.getOnlyElement(
+ resources.getTransitiveAndroidResources())
+ .getManifest());
+ }
+
+ public ApplicationManifest createSplitManifest(
+ RuleContext ruleContext, String splitName, boolean hasCode) {
+ // aapt insists that manifests be called AndroidManifest.xml, even though they have to be
+ // explicitly designated as manifests on the command line
+ Artifact result = AndroidBinary.getDxArtifact(
+ ruleContext, "split_" + splitName + "/AndroidManifest.xml");
+ SpawnAction.Builder builder = new SpawnAction.Builder()
+ .setExecutable(ruleContext.getExecutablePrerequisite("$build_split_manifest", Mode.HOST))
+ .setProgressMessage("Creating manifest for split " + splitName)
+ .setMnemonic("AndroidBuildSplitManifest")
+ .addArgument("--main_manifest")
+ .addInputArgument(manifest)
+ .addArgument("--split_manifest")
+ .addOutputArgument(result)
+ .addArgument("--split")
+ .addArgument(splitName)
+ .addArgument(hasCode ? "--hascode" : "--nohascode");
+
+ String overridePackage = getOverridePackage(ruleContext);
+ if (overridePackage != null) {
+ builder
+ .addArgument("--override_package")
+ .addArgument(overridePackage);
+ }
+
+ ruleContext.registerAction(builder.build(ruleContext));
+ return new ApplicationManifest(result);
+ }
+
+ private String getOverridePackage(RuleContext ruleContext) {
+ // It seems that we sometimes rename the app for God-knows-what reason. If that is the case,
+ // pass this information to the stubifier script.
+ TransitiveInfoCollection resourcesPrerequisite =
+ ruleContext.getPrerequisite("resources", Mode.TARGET);
+ if (resourcesPrerequisite != null) {
+ ResourceContainer resourceContainer = Iterables.getOnlyElement(
+ resourcesPrerequisite.getProvider(AndroidResourcesProvider.class)
+ .getTransitiveAndroidResources());
+ return resourceContainer.getRenameManifestPackage();
+ } else {
+ return null;
+ }
+ }
+
+ public ApplicationManifest addStubApplication(RuleContext ruleContext) {
+
+ Artifact stubManifest =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.STUB_APPLICATON_MANIFEST);
+
+ SpawnAction.Builder builder = new SpawnAction.Builder()
+ .setExecutable(ruleContext.getExecutablePrerequisite("$stubify_manifest", Mode.HOST))
+ .setProgressMessage("Injecting stub application")
+ .setMnemonic("InjectStubApplication")
+ .addArgument("--input_manifest")
+ .addInputArgument(manifest)
+ .addArgument("--output_manifest")
+ .addOutputArgument(stubManifest)
+ .addArgument("--output_datafile")
+ .addOutputArgument(
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.STUB_APPLICATION_DATA));
+
+ String overridePackage = getOverridePackage(ruleContext);
+ if (overridePackage != null) {
+ builder.addArgument("--override_package");
+ builder.addArgument(overridePackage);
+ }
+
+ ruleContext.registerAction(builder.build(ruleContext));
+
+ return new ApplicationManifest(stubManifest);
+ }
+
+ public static ApplicationManifest fromRule(RuleContext ruleContext) {
+ return new ApplicationManifest(ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET));
+ }
+
+ public static ApplicationManifest fromExplicitManifest(Artifact manifest) {
+ return new ApplicationManifest(manifest);
+ }
+
+ /**
+ * Generates an empty manifest for a rule that does not directly specify resources.
+ *
+ * <p><strong>Note:</strong> This generated manifest can then be used as the primary manifest
+ * when merging with dependencies.
+ *
+ * @return the generated ApplicationManifest
+ */
+ public static ApplicationManifest generatedManifest(RuleContext ruleContext) {
+ Artifact generatedManifest = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getUniqueDirectory(ruleContext.getRule().getName() + "_generated")
+ .getChild("AndroidManifest.xml"),
+ ruleContext.getBinOrGenfilesDirectory());
+
+ String manifestPackage;
+ if (ruleContext.attributes().isAttributeValueExplicitlySpecified("custom_package")) {
+ manifestPackage = ruleContext.attributes().get("custom_package", Type.STRING);
+ } else {
+ manifestPackage = JavaUtil.getJavaFullClassname(
+ ruleContext.getRule().getPackage().getNameFragment());
+ }
+ String contents = Joiner.on("\n").join(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>",
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"",
+ " package=\"" + manifestPackage + "\">",
+ " <application>",
+ " </application>",
+ "</manifest>");
+ ruleContext.getAnalysisEnvironment().registerAction(new FileWriteAction(
+ ruleContext.getActionOwner(), generatedManifest, contents, false /* makeExecutable */));
+ return new ApplicationManifest(generatedManifest);
+ }
+
+ private final Artifact manifest;
+
+ private ApplicationManifest(Artifact manifest) {
+ this.manifest = manifest;
+ }
+
+ public ApplicationManifest mergeWith(RuleContext ruleContext,
+ Iterable<ResourceContainer> resourceContainers) {
+ if (!Iterables.isEmpty(getMergeeManifests(resourceContainers))) {
+ Iterable<Artifact> exportedManifests = getMergeeManifests(resourceContainers);
+ Artifact outputManifest = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getUniqueDirectory(
+ ruleContext.getRule().getName() + "_merged").getChild("AndroidManifest.xml"),
+ ruleContext.getBinOrGenfilesDirectory());
+ AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(),
+ exportedManifests, ImmutableList.of("all"), outputManifest);
+ return new ApplicationManifest(outputManifest);
+ }
+ return this;
+ }
+
+ private static Iterable<Artifact> getMergeeManifests(
+ Iterable<ResourceContainer> resourceContainers) {
+ ImmutableSortedSet.Builder<Artifact> builder =
+ ImmutableSortedSet.orderedBy(Artifact.EXEC_PATH_COMPARATOR);
+ for (ResourceContainer r : resourceContainers) {
+ if (r.isManifestExported()) {
+ builder.add(r.getManifest());
+ }
+ }
+ return builder.build();
+ }
+
+ /** Packages up the manifest with assets from the rule and dependent resources. */
+ public ResourceApk packWithAssets(
+ Artifact resourceApk,
+ RuleContext ruleContext,
+ NestedSet<ResourceContainer> resourceContainers,
+ AndroidTools tools,
+ Artifact rTxt,
+ boolean incremental,
+ Artifact proguardCfg) {
+ try {
+ LocalResourceContainer data = new LocalResourceContainer.Builder()
+ .withAssets(
+ AndroidCommon.getAssetDir(ruleContext),
+ ruleContext.getPrerequisites(
+ // TODO(bazel-team): Remove the ResourceType construct.
+ ResourceType.ASSETS.getAttribute(),
+ Mode.TARGET,
+ FileProvider.class)).build();
+
+ return createApk(resourceApk,
+ ruleContext,
+ resourceContainers,
+ tools,
+ rTxt,
+ ImmutableList.<String>of(), /* configurationFilters */
+ ImmutableList.<String>of(), /* uncompressedExtensions */
+ ImmutableList.<String>of(), /* densities */
+ null, /* String applicationId */
+ null, /* String versionCode */
+ null, /* String versionName */
+ incremental,
+ data,
+ proguardCfg);
+ } catch (InvalidAssetPath e) {
+ ruleContext.attributeError(ResourceType.ASSETS.getAttribute(), e.getMessage());
+ throw new RuleConfigurationException();
+ }
+ }
+
+ /** Packages up the manifest with resource and assets from the rule and dependent resources. */
+ public ResourceApk packWithDataAndResources(
+ Artifact resourceApk,
+ RuleContext ruleContext,
+ NestedSet<ResourceContainer> resourceContainers,
+ AndroidTools tools,
+ Artifact rTxt,
+ List<String> configurationFilters,
+ List<String> uncompressedExtensions,
+ List<String> densities,
+ String applicationId,
+ String versionCode,
+ String versionName,
+ boolean incremental,
+ Artifact proguardCfg) {
+ try {
+ LocalResourceContainer data = new LocalResourceContainer.Builder()
+ .withAssets(
+ AndroidCommon.getAssetDir(ruleContext),
+ ruleContext.getPrerequisites(
+ // TODO(bazel-team): Remove the ResourceType construct.
+ ResourceType.ASSETS.getAttribute(),
+ Mode.TARGET,
+ FileProvider.class))
+ .withResources(
+ ruleContext.getPrerequisites(
+ "resource_files",
+ Mode.TARGET,
+ FileProvider.class)).build();
+
+ return createApk(resourceApk,
+ ruleContext,
+ resourceContainers,
+ tools,
+ rTxt,
+ configurationFilters,
+ uncompressedExtensions,
+ densities,
+ applicationId,
+ versionCode,
+ versionName,
+ incremental,
+ data,
+ proguardCfg);
+ } catch (InvalidAssetPath e) {
+ ruleContext.attributeError(ResourceType.ASSETS.getAttribute(), e.getMessage());
+ throw new RuleConfigurationException();
+ } catch (InvalidResourcePath e) {
+ ruleContext.attributeError("resource_files", e.getMessage());
+ throw new RuleConfigurationException();
+ }
+ }
+
+ private ResourceApk createApk(Artifact resourceApk,
+ RuleContext ruleContext,
+ NestedSet<ResourceContainer> resourceContainers,
+ AndroidTools tools,
+ Artifact rTxt,
+ List<String> configurationFilters,
+ List<String> uncompressedExtensions,
+ List<String> densities,
+ String applicationId,
+ String versionCode,
+ String versionName,
+ boolean incremental,
+ LocalResourceContainer data,
+ Artifact proguardCfg) {
+ ResourceContainer resourceContainer = checkForInlinedResources(
+ new AndroidResourceContainerBuilder()
+ .withData(data)
+ .withManifest(getManifest())
+ .withROutput(rTxt)
+ .buildFromRule(ruleContext, resourceApk),
+ resourceContainers,
+ ruleContext);
+
+ AndroidResourcesProcessorBuilder builder =
+ new AndroidResourcesProcessorBuilder(tools, ruleContext)
+ .setApkOut(resourceContainer.getApk())
+ .setConfigurationFilters(configurationFilters)
+ .setUncompressedExtensions(uncompressedExtensions)
+ .setJavaPackage(resourceContainer.getJavaPackage())
+ .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT)
+ .withPrimary(resourceContainer)
+ .withDependencies(resourceContainers)
+ .setWorkingDirectory(ruleContext.getUniqueDirectory("_resources"))
+ .setDensities(densities)
+ .setProguardOut(proguardCfg)
+ .setApplicationId(applicationId)
+ .setVersionCode(versionCode)
+ .setVersionName(versionName);
+
+ if (!incremental) {
+ builder
+ .setRTxtOut(resourceContainer.getRTxt())
+ .setSourceJarOut(resourceContainer.getJavaSourceJar());
+ }
+
+ ResourceContainer processed = builder.build(ruleContext);
+ NestedSet<ResourceContainer> transitiveResources =
+ NestedSetBuilder.<ResourceContainer>naiveLinkOrder()
+ // TODO(bazel-team): If this is replaced with .addTransitive(), a few tests fail.
+ // Investigate.
+ .addAll(resourceContainers)
+ .add(processed)
+ .build();
+
+ return new ResourceApk(
+ resourceApk, processed.getJavaSourceJar(), transitiveResources, processed, manifest,
+ proguardCfg, false);
+ }
+
+ private static ResourceContainer checkForInlinedResources(ResourceContainer resourceContainer,
+ Iterable<ResourceContainer> resourceContainers, RuleContext ruleContext) {
+ // Dealing with Android library projects
+ if (Iterables.size(resourceContainers) > 1) {
+ if (resourceContainer.getConstantsInlined()
+ && !resourceContainer.getArtifacts(ResourceType.RESOURCES).isEmpty()) {
+ ruleContext.ruleError("This android binary depends on an android "
+ + "library project, so the resources '"
+ + AndroidCommon.getAndroidResources(ruleContext).getLabel()
+ + "' should have the attribute inline_constants set to 0");
+ throw new RuleConfigurationException();
+ }
+ }
+ return resourceContainer;
+ }
+
+ /** Uses the resource apk from the resources attribute, as opposed to recompiling. */
+ public ResourceApk useCurrentResources(RuleContext ruleContext, AndroidTools tools,
+ Artifact proguardCfg) {
+ ResourceContainer resourceContainer = Iterables.getOnlyElement(
+ AndroidCommon.getAndroidResources(ruleContext).getTransitiveAndroidResources());
+ NestedSet<ResourceContainer> resourceContainers =
+ NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
+
+ NestedSet<ResourceContainer> transitiveResources =
+ NestedSetBuilder.<ResourceContainer>naiveLinkOrder()
+ .addAll(resourceContainers)
+ .add(resourceContainer)
+ .build();
+
+ new AndroidAaptActionHelper(
+ ruleContext,
+ resourceContainer.getManifest(),
+ Lists.newArrayList(resourceContainer),
+ tools).createGenerateProguardAction(proguardCfg);
+
+ return new ResourceApk(
+ resourceContainer.getApk(),
+ null /* javaSrcJar */,
+ transitiveResources,
+ resourceContainer,
+ manifest,
+ proguardCfg,
+ false);
+ }
+
+ /**
+ * Packages up the manifest with resources, and generates the R.java.
+ *
+ * @deprecated in favor of {@link ApplicationManifest#packWithDataAndResources}.
+ */
+ @Deprecated
+ public ResourceApk packWithResources(
+ Artifact resourceApk,
+ RuleContext ruleContext,
+ NestedSet<ResourceContainer> resourceContainers,
+ AndroidTools tools,
+ boolean createSource,
+ Artifact proguardCfg) {
+
+ TransitiveInfoCollection resourcesPrerequisite =
+ ruleContext.getPrerequisite("resources", Mode.TARGET);
+ ResourceContainer resourceContainer = Iterables.getOnlyElement(
+ resourcesPrerequisite.getProvider(AndroidResourcesProvider.class)
+ .getTransitiveAndroidResources());
+
+ // Dealing with Android library projects
+ if (Iterables.size(resourceContainers) > 1) {
+ if (resourceContainer.getConstantsInlined()
+ && !resourceContainer.getArtifacts(ResourceType.RESOURCES).isEmpty()) {
+ ruleContext.ruleError("This android_binary depends on an android_library, so the"
+ + " resources '" + AndroidCommon.getAndroidResources(ruleContext).getLabel()
+ + "' should have the attribute inline_constants set to 0");
+ throw new RuleConfigurationException();
+ }
+ }
+
+ // This binary depends on a library project, so we need to regenerate the
+ // resources. The resulting sources and apk will combine all the resources
+ // contained in the transitive closure of the binary.
+ AndroidAaptActionHelper aaptActionHelper = new AndroidAaptActionHelper(ruleContext,
+ getManifest(), Lists.newArrayList(resourceContainers), tools);
+
+ List<String> resourceConfigurationFilters =
+ ruleContext.getTokenizedStringListAttr("resource_configuration_filters");
+ List<String> uncompressedExtensions =
+ ruleContext.getTokenizedStringListAttr("nocompress_extensions");
+
+ ImmutableList.Builder<String> additionalAaptOpts = ImmutableList.<String>builder();
+
+ for (String extension : uncompressedExtensions) {
+ additionalAaptOpts.add("-0").add(extension);
+ }
+ if (!resourceConfigurationFilters.isEmpty()) {
+ additionalAaptOpts.add("-c").add(Joiner.on(",").join(resourceConfigurationFilters));
+ }
+
+ Artifact javaSourcesJar = null;
+
+ if (createSource) {
+ javaSourcesJar =
+ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR);
+ aaptActionHelper.createGenerateResourceSymbolsAction(
+ javaSourcesJar, null, resourceContainer.getJavaPackage(), true);
+ }
+
+ List<String> densities = ruleContext.getTokenizedStringListAttr("densities");
+ aaptActionHelper.createGenerateApkAction(resourceApk,
+ resourceContainer.getRenameManifestPackage(), additionalAaptOpts.build(), densities);
+
+ ResourceContainer updatedResources = new ResourceContainer(
+ ruleContext.getLabel(),
+ resourceContainer.getJavaPackage(),
+ resourceContainer.getRenameManifestPackage(),
+ resourceContainer.getConstantsInlined(),
+ resourceApk,
+ getManifest(),
+ javaSourcesJar,
+ resourceContainer.getArtifacts(ResourceType.ASSETS),
+ resourceContainer.getArtifacts(ResourceType.RESOURCES),
+ resourceContainer.getRoots(ResourceType.ASSETS),
+ resourceContainer.getRoots(ResourceType.RESOURCES),
+ resourceContainer.isManifestExported(),
+ resourceContainer.getRTxt());
+
+ aaptActionHelper.createGenerateProguardAction(proguardCfg);
+
+ return new ResourceApk(resourceApk, updatedResources.getJavaSourceJar(),
+ resourceContainers, updatedResources, manifest, proguardCfg, true);
+ }
+
+ public Artifact getManifest() {
+ return manifest;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
new file mode 100644
index 0000000000..9d43fd7201
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
@@ -0,0 +1,319 @@
+// Copyright 2015 Google Inc. 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.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+/**
+ * The collected resources and assets artifacts and roots.
+ *
+ * <p>This is used to encapsulate the logic and the data associated with the resources and assets
+ * derived from an appropriate android rule in a reusable instance.
+ */
+@Immutable
+public final class LocalResourceContainer {
+ public static final String[] RESOURCES_ATTRIBUTES = new String[] {
+ "manifest",
+ "resource_files",
+ "assets",
+ "assets_dir",
+ "inline_constants",
+ "exports_manifest",
+ "application_id",
+ "version_name",
+ "version_code"
+ };
+
+ /**
+ * Determines if the attributes contain resource and asset attributes.
+ */
+ public static boolean definesAndroidResources(AttributeMap attributes) {
+ for (String attribute : RESOURCES_ATTRIBUTES) {
+ if (attributes.isAttributeValueExplicitlySpecified(attribute)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks validity of a RuleContext to produce an AndroidData.
+ */
+ public static boolean validateRuleContext(RuleContext ruleContext) {
+ boolean valid = validateAssetsAndAssetsDir(ruleContext);
+ valid = valid && validateNoResourcesAttribute(ruleContext);
+ valid = valid && validateNoAndroidResourcesInSources(ruleContext);
+ valid = valid && validateManifest(ruleContext);
+ return valid;
+ }
+
+ private static boolean validateAssetsAndAssetsDir(RuleContext ruleContext) {
+ if (ruleContext.attributes().isAttributeValueExplicitlySpecified("assets")
+ ^ ruleContext.attributes().isAttributeValueExplicitlySpecified("assets_dir")) {
+ ruleContext.ruleError(
+ "'assets' and 'assets_dir' should be either both empty or both non-empty");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Validates that there are no resources defined if there are resource attribute defined.
+ */
+ private static boolean validateNoResourcesAttribute(RuleContext ruleContext) {
+ if (ruleContext.attributes().isAttributeValueExplicitlySpecified("resources")) {
+ ruleContext.attributeError("resources",
+ String.format("cannot be set when any of %s are defined.",
+ Joiner.on(", ").join(RESOURCES_ATTRIBUTES)));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Validates that there are no android_resources srcjars in the srcs, as android_resource rules
+ * should not be used with the Android data logic.
+ */
+ private static boolean validateNoAndroidResourcesInSources(RuleContext ruleContext) {
+ Iterable<AndroidResourcesProvider> resources =
+ ruleContext.getPrerequisites("srcs", Mode.TARGET, AndroidResourcesProvider.class);
+ for (AndroidResourcesProvider provider : resources) {
+ ruleContext.attributeError("srcs",
+ String.format("should not contain android_resource label %s", provider.getLabel()));
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean validateManifest(RuleContext ruleContext) {
+ if (ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET) == null) {
+ ruleContext.attributeError("manifest",
+ "is required when resource_files or assets are defined.");
+ return false;
+ }
+ return true;
+ }
+
+ public static class Builder {
+ public static final class InvalidAssetPath extends RuntimeException {
+ InvalidAssetPath(String message) {
+ super(message);
+ }
+ }
+
+ public static final class InvalidResourcePath extends RuntimeException {
+ InvalidResourcePath(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Set of allowable android directories prefixes.
+ */
+ // TODO(bazel-team): Pull from AOSP constant?
+ public static final ImmutableSet<String> RESOURCE_DIRECTORY_TYPES = ImmutableSet.of(
+ "animator",
+ "anim",
+ "color",
+ "drawable",
+ "interpolator",
+ "layout",
+ "menu",
+ "mipmap",
+ "raw",
+ "transition",
+ "values",
+ "xml");
+
+ public static final String INCORRECT_RESOURCE_LAYOUT_MESSAGE = String.format(
+ "'%%s' is not in the expected resource directory structure of "
+ + "<resource directory>/{%s}/<file>", Joiner.on(',').join(RESOURCE_DIRECTORY_TYPES));
+
+ private Collection<Artifact> assets = new LinkedHashSet<>();
+ private Collection<Artifact> resources = new LinkedHashSet<>();
+ private Collection<PathFragment> resourceRoots = new LinkedHashSet<>();
+ private Collection<PathFragment> assetRoots = new LinkedHashSet<>();
+
+ /**
+ * Retrieves the asset {@link Artifact} and asset root {@link PathFragment}.
+ * @param assetsDir The common directory for the assets, used to get the directory roots and
+ * verify the artifacts are located beneath the assetsDir
+ * @param targets {@link FileProvider}s for a given set of assets.
+ * @return The Builder.
+ * @throws InvalidAssetPath when a path does not reside under the correct directory.
+ */
+ public LocalResourceContainer.Builder withAssets(
+ PathFragment assetsDir, Iterable<FileProvider> targets) {
+ for (FileProvider target : targets) {
+ for (Artifact file : target.getFilesToBuild()) {
+ PathFragment packageFragment = file.getArtifactOwner().getLabel().getPackageFragment();
+ PathFragment packageRelativePath =
+ file.getRootRelativePath().relativeTo(packageFragment);
+ if (packageRelativePath.startsWith(assetsDir)) {
+ PathFragment relativePath = packageRelativePath.relativeTo(assetsDir);
+ assetRoots.add(trimTail(file.getExecPath(), relativePath));
+ } else {
+ throw new InvalidAssetPath(String.format(
+ "'%s' (generated by '%s') is not beneath '%s'",
+ file.getRootRelativePath(), target.getLabel(), assetsDir));
+ }
+ assets.add(file);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Retrieves the resource {@link Artifact} and resource root {@link PathFragment}.
+ * @param targets {@link FileProvider}s for a given set of resource.
+ * @return The Builder.
+ * @throws InvalidResourcePath when a path does not reside under the correct directory.
+ */
+ public LocalResourceContainer.Builder withResources(Iterable<FileProvider> targets) {
+ PathFragment lastResourceDir = null;
+ Artifact lastFile = null;
+ for (FileProvider target : targets) {
+ for (Artifact file : target.getFilesToBuild()) {
+ PathFragment packageFragment = file.getArtifactOwner().getLabel().getPackageFragment();
+ PathFragment packageRelativePath =
+ file.getRootRelativePath().relativeTo(packageFragment);
+ PathFragment resourceDir = findResourceDir(file);
+ if (lastResourceDir == null || resourceDir.equals(lastResourceDir)) {
+ resourceRoots.add(
+ trimTail(file.getExecPath(), makeRelativeTo(resourceDir, packageRelativePath)));
+ } else {
+ throw new InvalidResourcePath(String.format(
+ "'%s' (generated by '%s') is not in the same directory '%s' (derived from %s)."
+ + " All resources must share a common directory.",
+ file.getRootRelativePath(), file.getArtifactOwner().getLabel(), lastResourceDir,
+ lastFile.getRootRelativePath()));
+ }
+ resources.add(file);
+ lastFile = file;
+ lastResourceDir = resourceDir;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Finds and validates the resource directory PathFragment from the artifact Path.
+ *
+ * <p>If the artifact is not a Fileset, the resource directory is presumed to be the second
+ * directory from the end. Filesets are expect to have the last directory as the resource
+ * directory.
+ *
+ */
+ public static PathFragment findResourceDir(Artifact artifact) {
+ PathFragment fragment = artifact.getPath().asFragment();
+ int segmentCount = fragment.segmentCount();
+ if (segmentCount < 3) {
+ throw new InvalidResourcePath(String.format(
+ INCORRECT_RESOURCE_LAYOUT_MESSAGE, artifact.getRootRelativePath()));
+ }
+ // TODO(bazel-team): Expand Fileset to verify, or remove Fileset as an option for resources.
+ if (artifact.isFileset()) {
+ return fragment.subFragment(segmentCount - 1, segmentCount);
+ }
+
+ // Check the resource folder type layout.
+ // get the prefix of the parent folder of the fragment.
+ String parentDirectory = fragment.getSegment(segmentCount - 2);
+ int dashIndex = parentDirectory.indexOf('-');
+ String androidFolder =
+ dashIndex == -1 ? parentDirectory : parentDirectory.substring(0, dashIndex);
+ if (!RESOURCE_DIRECTORY_TYPES.contains(androidFolder)) {
+ throw new InvalidResourcePath(String.format(
+ INCORRECT_RESOURCE_LAYOUT_MESSAGE, artifact.getRootRelativePath()));
+ }
+
+ return fragment.subFragment(segmentCount - 3, segmentCount - 2);
+ }
+
+ /**
+ * Returns the root-part of a given path by trimming off the end specified by
+ * a given tail. Assumes that the tail is known to match, and simply relies on
+ * the segment lengths.
+ */
+ private static PathFragment trimTail(PathFragment path, PathFragment tail) {
+ return path.subFragment(0, path.segmentCount() - tail.segmentCount());
+ }
+
+ private static PathFragment makeRelativeTo(PathFragment ancestor, PathFragment path) {
+ String cutAtSegment = ancestor.getSegment(ancestor.segmentCount() - 1);
+ int totalPathSegments = path.segmentCount() - 1;
+ for (int i = totalPathSegments; i >= 0; i--) {
+ if (path.getSegment(i).equals(cutAtSegment)) {
+ return path.subFragment(i, totalPathSegments);
+ }
+ }
+ throw new IllegalArgumentException("PathFragment " + path
+ + " is not beneath " + ancestor);
+ }
+
+ public LocalResourceContainer build() {
+ return new LocalResourceContainer(
+ ImmutableList.copyOf(resources),
+ ImmutableList.copyOf(resourceRoots),
+ ImmutableList.copyOf(assets),
+ ImmutableList.copyOf(assetRoots));
+ }
+ }
+
+ private final ImmutableList<Artifact> resources;
+ private final ImmutableList<Artifact> assets;
+ private final ImmutableList<PathFragment> assetRoots;
+ private final ImmutableList<PathFragment> resourceRoots;
+
+ @VisibleForTesting
+ public LocalResourceContainer(
+ ImmutableList<Artifact> resources,
+ ImmutableList<PathFragment> resourceRoots,
+ ImmutableList<Artifact> assets,
+ ImmutableList<PathFragment> assetRoots) {
+ this.resources = resources;
+ this.resourceRoots = resourceRoots;
+ this.assets = assets;
+ this.assetRoots = assetRoots;
+ }
+
+ public ImmutableList<Artifact> getResources() {
+ return resources;
+ }
+
+ public ImmutableList<Artifact> getAssets() {
+ return assets;
+ }
+
+ public ImmutableList<PathFragment> getAssetRoots() {
+ return assetRoots;
+ }
+
+ public ImmutableList<PathFragment> getResourceRoots() {
+ return resourceRoots;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java b/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java
new file mode 100644
index 0000000000..3acf1fc350
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java
@@ -0,0 +1,142 @@
+// Copyright 2015 Google Inc. 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.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.rules.cpp.CcLinkParams;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
+import com.google.devtools.build.lib.rules.cpp.LinkerInput;
+import com.google.devtools.build.lib.rules.nativedeps.NativeDepsHelper;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/** Represents the collection of native libraries (.so) to be installed in the APK. */
+public final class NativeLibs {
+ public static final NativeLibs EMPTY =
+ new NativeLibs(ImmutableMap.<String, Iterable<Artifact>>of(), null);
+
+ public static NativeLibs fromPrecompiledObjects(
+ RuleContext ruleContext, Multimap<String, TransitiveInfoCollection> depsByArchitecture) {
+ ImmutableMap.Builder<String, Iterable<Artifact>> builder = ImmutableMap.builder();
+ for (Map.Entry<String, Collection<TransitiveInfoCollection>> entry :
+ depsByArchitecture.asMap().entrySet()) {
+ NestedSet<LinkerInput> nativeLibraries =
+ AndroidCommon.collectTransitiveNativeLibraries(entry.getValue());
+ builder.put(entry.getKey(), checkUniqueBaseNames(ruleContext, nativeLibraries));
+ }
+ return new NativeLibs(builder.build(), null);
+ }
+
+ public static NativeLibs fromLinkedNativeDeps(
+ RuleContext ruleContext,
+ String nativeDepsFileName,
+ Multimap<String, TransitiveInfoCollection> depsByArchitecture,
+ Map<String, CcToolchainProvider> toolchainMap,
+ Map<String, BuildConfiguration> configurationMap) {
+ Map<String, Iterable<Artifact>> result = new LinkedHashMap<>();
+ for (Map.Entry<String, Collection<TransitiveInfoCollection>> entry :
+ depsByArchitecture.asMap().entrySet()) {
+ CcLinkParams linkParams = AndroidCommon.getCcLinkParamsStore(entry.getValue())
+ .get(/* linkingStatically */ true, /* linkShared */ true);
+ Artifact nativeDepsLibrary = NativeDepsHelper.maybeCreateAndroidNativeDepsAction(
+ ruleContext, linkParams, configurationMap.get(entry.getKey()),
+ toolchainMap.get(entry.getKey()));
+ if (nativeDepsLibrary != null) {
+ result.put(entry.getKey(), ImmutableList.of(nativeDepsLibrary));
+ }
+ }
+ if (result.isEmpty()) {
+ return NativeLibs.EMPTY;
+ } else {
+ Artifact anyNativeLibrary =
+ result.entrySet().iterator().next().getValue().iterator().next();
+ // The native deps name file must be the only file in its directory because ApkBuilder does
+ // not have an option to add a particular file to the .apk, only one to add every file in a
+ // particular directory.
+ Artifact nativeDepsName = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getUniqueDirectory("nativedeps_filename").getRelative(nativeDepsFileName),
+ ruleContext.getBinOrGenfilesDirectory());
+ ruleContext.registerAction(new FileWriteAction(ruleContext.getActionOwner(), nativeDepsName,
+ anyNativeLibrary.getExecPath().getBaseName(), false));
+
+ return new NativeLibs(ImmutableMap.copyOf(result), nativeDepsName);
+ }
+ }
+
+ // Map from architecture (CPU folder to place the library in) to libraries for that CPU
+ private final Map<String, Iterable<Artifact>> nativeLibs;
+ private final Artifact nativeLibsName;
+
+ private NativeLibs(Map<String, Iterable<Artifact>> nativeLibs, Artifact nativeLibsName) {
+ this.nativeLibs = nativeLibs;
+ this.nativeLibsName = nativeLibsName;
+ }
+
+ /**
+ * Returns a map from the name of the architecture (CPU folder to place the library in) to the
+ * set of libraries for that architecture.
+ */
+ public Map<String, Iterable<Artifact>> getMap() {
+ return nativeLibs;
+ }
+
+ /**
+ * Returns the artifact containing the names of the native libraries or null if it does not exist.
+ *
+ * <p>This artifact will be put in the root directory of the .apk and can be used to load the
+ * libraries programmatically without knowing their names.
+ */
+ @Nullable
+ public Artifact getName() {
+ return nativeLibsName;
+ }
+
+ private static Iterable<Artifact> checkUniqueBaseNames(
+ RuleContext ruleContext, NestedSet<LinkerInput> libraries) {
+ Map<String, Artifact> basenames = new HashMap<>();
+ Set<Artifact> artifacts = new HashSet<>();
+ for (LinkerInput linkerInput : libraries) {
+ Artifact artifact = linkerInput.getOriginalLibraryArtifact();
+ if (!artifacts.add(artifact)) {
+ // We have already reached this library, e.g., through a different solib symlink.
+ continue;
+ }
+ String basename = artifact.getExecPath().getBaseName();
+ Artifact oldArtifact = basenames.put(basename, artifact);
+ if (oldArtifact != null) {
+ // Without linking, there may be name collisions in the libraries which were provided, so
+ // check for this at this step.
+ ruleContext.ruleError("Each library in the transitive closure must have a unique "
+ + "basename, but two libraries had the basename '" + basename + "': "
+ + artifact.prettyPrint() + " and " + oldArtifact.prettyPrint());
+ }
+ }
+ return artifacts;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ProguardMappingProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/ProguardMappingProvider.java
new file mode 100644
index 0000000000..154067fb07
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ProguardMappingProvider.java
@@ -0,0 +1,35 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A target that can provide a proguard obfuscation mapping to Android binaries or tests.
+ */
+@Immutable
+public final class ProguardMappingProvider implements TransitiveInfoProvider {
+
+ private final Artifact proguardMapping;
+
+ public ProguardMappingProvider(Artifact proguardMapping) {
+ this.proguardMapping = proguardMapping;
+ }
+
+ public Artifact getProguardMapping() {
+ return proguardMapping;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ProguardSpecProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/ProguardSpecProvider.java
new file mode 100644
index 0000000000..ec83539925
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ProguardSpecProvider.java
@@ -0,0 +1,36 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+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;
+
+/**
+ * A target that can provide proguard specifications to Android binaries.
+ */
+@Immutable
+public final class ProguardSpecProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> transitiveProguardSpecs;
+
+ public ProguardSpecProvider(NestedSet<Artifact> transitiveProguardSpecs) {
+ this.transitiveProguardSpecs = transitiveProguardSpecs;
+ }
+
+ public NestedSet<Artifact> getTransitiveProguardSpecs() {
+ return transitiveProguardSpecs;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceApk.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceApk.java
new file mode 100644
index 0000000000..b495918316
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceApk.java
@@ -0,0 +1,89 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
+
+import javax.annotation.Nullable;
+
+/**
+ * The ResourceApk represents the packaged resources that serve as the basis for the signed and the
+ * unsigned APKs.
+ */
+@Immutable
+public class ResourceApk {
+ // TODO(bazel-team): The only field that is legitimately nullable is javaSrcJar. The rest is
+ // marked as such due to .fromTransitiveResources(). It seems like there should be a better way
+ // to do this.
+ @Nullable private final Artifact resourceApk; // The .ap_ file
+ @Nullable private final Artifact resourceJavaSrcJar; // Source jar containing R.java and friends
+ private final NestedSet<ResourceContainer> transitiveResources;
+ @Nullable private final ResourceContainer primaryResource;
+ @Nullable private final Artifact manifest; // The non-binary XML version of AndroidManifest.xml
+ @Nullable private final Artifact resourceProguardConfig;
+ private final boolean legacy;
+
+ public ResourceApk(
+ @Nullable Artifact resourceApk,
+ @Nullable Artifact resourceJavaSrcJar,
+ NestedSet<ResourceContainer> transitiveResources,
+ @Nullable ResourceContainer primaryResource,
+ @Nullable Artifact manifest,
+ @Nullable Artifact resourceProguardConfig,
+ boolean legacy) {
+ this.resourceApk = resourceApk;
+ this.resourceJavaSrcJar = resourceJavaSrcJar;
+ this.transitiveResources = transitiveResources;
+ this.primaryResource = primaryResource;
+ this.manifest = manifest;
+ this.resourceProguardConfig = resourceProguardConfig;
+ this.legacy = legacy;
+ }
+
+ public Artifact getArtifact() {
+ return resourceApk;
+ }
+
+ public ResourceContainer getPrimaryResource() {
+ return primaryResource;
+ }
+
+ public Artifact getManifest() {
+ return manifest;
+ }
+
+ public Artifact getResourceJavaSrcJar() {
+ return resourceJavaSrcJar;
+ }
+
+ public boolean isLegacy() {
+ return legacy;
+ }
+
+ public NestedSet<ResourceContainer> getTransitiveResources() {
+ return transitiveResources;
+ }
+
+ public static ResourceApk fromTransitiveResources(
+ NestedSet<ResourceContainer> transitiveResources) {
+ return new ResourceApk(null, null, transitiveResources, null, null, null, false);
+ }
+
+ public Artifact getResourceProguardConfig() {
+ return resourceProguardConfig;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/RuleConfigurationException.java b/src/main/java/com/google/devtools/build/lib/rules/android/RuleConfigurationException.java
new file mode 100644
index 0000000000..2cd365bfdd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/RuleConfigurationException.java
@@ -0,0 +1,18 @@
+// Copyright 2015 Google Inc. 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;
+
+/** Represents an invalid state in configuring the AndroidBinary rule. */
+public final class RuleConfigurationException extends RuntimeException {
+} \ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsAction.java b/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsAction.java
new file mode 100644
index 0000000000..41b2111ed4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsAction.java
@@ -0,0 +1,143 @@
+// Copyright 2015 Google Inc. 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.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * An action that writes the a parameter file to {@code incremental_install.py} based on the command
+ * line arguments to {@code blaze mobile-install}.
+ */
+public class WriteAdbArgsAction extends AbstractFileWriteAction {
+ private static final String GUID = "16720416-3c01-4b0a-a543-ead7e563a1ca";
+
+ /**
+ * Options of the {@code mobile-install} command pertaining to the way {@code adb} is invoked.
+ */
+ public static final class Options extends OptionsBase {
+ @Option(name = "adb",
+ category = "mobile-install",
+ defaultValue = "",
+ help = "adb binary to use for the 'mobile-install' command. If unspecified, the one in "
+ + "the Android SDK specified by the --android_sdk command line option (or the default "
+ + "SDK if --android_sdk is not specified) is used.")
+ public String adb;
+
+ @Option(name = "adb_arg",
+ category = "mobile-install",
+ allowMultiple = true,
+ defaultValue = "",
+ help = "Extra arguments to pass to adb. Usually used to designate a device to install to.")
+ public List<String> adbArgs;
+
+ @Option(name = "adb_jobs",
+ category = "mobile-install",
+ defaultValue = "2",
+ help = "The number of instances of adb to use in parallel to update files on the device")
+ public int adbJobs;
+
+ @Option(name = "incremental_install_verbosity",
+ category = "mobile-install",
+ defaultValue = "",
+ help = "The verbosity for incremental install. Set to 1 for debug logging.")
+ public String incrementalInstallVerbosity;
+
+ @Option(name = "start_app",
+ category = "mobile-install",
+ defaultValue = "false",
+ help = "Whether to start the app after installing it.")
+ public boolean startApp;
+ }
+
+ public WriteAdbArgsAction(ActionOwner owner, Artifact outputFile) {
+ super(owner, ImmutableList.<Artifact>of(), outputFile, false);
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor)
+ throws IOException, InterruptedException, ExecException {
+ Options options = executor.getOptions().getOptions(Options.class);
+ final List<String> args = options.adbArgs;
+ final String adb = options.adb;
+ final int adbJobs = options.adbJobs;
+ final String incrementalInstallVerbosity = options.incrementalInstallVerbosity;
+ final boolean startApp = options.startApp;
+ final String userHomeDirectory = executor.getContext(
+ WriteAdbArgsActionContext.class).getUserHomeDirectory();
+
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ PrintStream ps = new PrintStream(out, false, "UTF-8");
+
+ if (!adb.isEmpty()) {
+ ps.printf("--adb=%s\n", adb);
+ }
+
+ for (String arg : args) {
+ ps.printf("--extra_adb_arg=%s\n", arg);
+ }
+
+ ps.printf("--adb_jobs=%d\n", adbJobs);
+
+ if (!incrementalInstallVerbosity.isEmpty()) {
+ ps.printf("--verbosity=%s\n", incrementalInstallVerbosity);
+ }
+
+ ps.printf("--start_app=%s\n", startApp);
+
+ if (userHomeDirectory != null) {
+ ps.printf("--user_home_dir=%s\n", userHomeDirectory);
+ }
+
+ ps.flush();
+ }
+ };
+ }
+
+ @Override
+ public boolean isVolatile() {
+ return true;
+ }
+
+ @Override
+ public boolean executeUnconditionally() {
+ // In theory, we only need to re-execute if the --adb_args command line arg changes, but we
+ // cannot express this. We also can't put the ADB args in the configuration, because that would
+ // mean re-analysis on every change, and then the "build" command would also have this argument,
+ // which is not optimal.
+ return true;
+ }
+
+ @Override
+ protected String computeKey() {
+ return new Fingerprint()
+ .addString(GUID)
+ .hexDigestAndReset();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsActionContext.java b/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsActionContext.java
new file mode 100644
index 0000000000..0f96a28a50
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/WriteAdbArgsActionContext.java
@@ -0,0 +1,41 @@
+// Copyright 2015 Google Inc. 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.devtools.build.lib.actions.ExecutionStrategy;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link ActionContext} for {@link WriteAdbArgsAction}.
+ */
+@ExecutionStrategy(contextType = WriteAdbArgsActionContext.class)
+public final class WriteAdbArgsActionContext implements ActionContext {
+
+ private final String userHomeDirectory;
+
+ public WriteAdbArgsActionContext(String userHomeDirectory) {
+ this.userHomeDirectory = userHomeDirectory;
+ }
+
+ /**
+ * @return The user's home directory as set in the environment in which the Blaze client is called
+ * or null if the HOME environment variable has not been set.
+ */
+ @Nullable
+ public String getUserHomeDirectory() {
+ return userHomeDirectory;
+ }
+}