diff options
author | Lukacs Berki <lberki@google.com> | 2015-05-19 09:28:06 +0000 |
---|---|---|
committer | Lukacs Berki <lberki@google.com> | 2015-05-19 09:34:35 +0000 |
commit | 89c2799dd890e6b93e2c3ddc63c54fddd988ea78 (patch) | |
tree | 8c2951c5e065ccc4c73755102851c36958f067a3 /src/main/java/com/google/devtools/build/lib | |
parent | d3461dba46b50719e238939946048cd1ca12755a (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')
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 <application> 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; + } +} |