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