aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android
diff options
context:
space:
mode:
authorGravatar Michael Staib <mstaib@google.com>2015-06-05 16:15:47 +0000
committerGravatar Laurent Le Brun <laurentlb@google.com>2015-06-08 12:53:13 +0000
commit84a7c337b457681e59816391feda71ac79577117 (patch)
tree665a86d374891de8e7f383a95eb059fc243d8b6d /src/main/java/com/google/devtools/build/lib/rules/android
parent5cef3485740be54819809f19a4aea04d61da7be6 (diff)
Add the JackCompilationHelper to Bazel.
This class contains the compilation logic for Jack: building Java files/resources into Jack libraries, and converting jar files using Jill to convert Java code and a resource extraction script to pull resources out. The JackLibraryProvider is exported by rules which can compile to Jack. Android library rules will export this provider, while an aspect will be used to add it to Java rules. -- MOS_MIGRATED_REVID=95296935
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/android')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/JackCompilationHelper.java849
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/JackLibraryProvider.java84
2 files changed, 933 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/JackCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/JackCompilationHelper.java
new file mode 100644
index 0000000000..fafa1f8626
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/JackCompilationHelper.java
@@ -0,0 +1,849 @@
+// 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.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+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.FileProvider;
+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.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+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.android.AndroidRuleClasses.MultidexMode;
+import com.google.devtools.build.lib.rules.java.JavaSemantics;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nullable;
+
+/**
+ * Builds Jack actions for a Java or Android target.
+ *
+ * <p>Jack is the new Android toolchain which integrates proguard-compatible code minification
+ * et al. and has an intermediate library format to front-load the dexing work.
+ *
+ * @see <a href="http://tools.android.com/tech-docs/jackandjill">Jack documentation</a>
+ * @see JackLibraryProvider
+ */
+public final class JackCompilationHelper {
+
+ /** Filetype for the intermediate library created by Jack. */
+ public static final FileType JACK_LIBRARY_TYPE = FileType.of(".jack");
+
+ /** Flag to indicate that the next argument is a Jack property. */
+ static final String JACK_PROPERTY = "-D";
+ /** Flag to indicate that resource conflicts should be resolved by taking the first element. */
+ static final String PROPERTY_KEEP_FIRST_RESOURCE = "jack.import.resource.policy=keep-first";
+ /** Flag to indicate that type conflicts should be resolved by taking the first element. */
+ static final String PROPERTY_KEEP_FIRST_TYPE = "jack.import.type.policy=keep-first";
+ /** Flag to turn on/off sanity checks in Jack. */
+ static final String SANITY_CHECKS = "--sanity-checks";
+ /** Value of the sanity checks flag which disables sanity checks. */
+ static final String SANITY_CHECKS_OFF = "off";
+ /** Value of the sanity checks flag which enables sanity checks. */
+ static final String SANITY_CHECKS_ON = "on";
+
+ /** Flag to indicate the classpath of Jack libraries, separated by semicolons. */
+ static final String CLASSPATH = "-cp";
+ /** Flag to load a Jack library into the Jack compiler so it will be part of the output. */
+ static final String IMPORT_JACK_LIBRARY = "--import";
+ /** Flag to import a zip file of Java sources into a Jack library. */
+ static final String IMPORT_SOURCE_ZIP = "--import-source-zip";
+ /** Flag to import a single file into a Jack library's resources. */
+ static final String IMPORT_RESOURCE_FILE = "--import-resource-file";
+ /** Flag to add a zip file full of resources to the Jack library. */
+ static final String IMPORT_RESOURCE_ZIP = "--import-resource-zip";
+ /** Flag to add the names of annotation processors. */
+ static final String PROCESSOR_NAMES = "--processor";
+ /** Flag to add the classpath of annotation processors. */
+ static final String PROCESSOR_CLASSPATH = "--processorpath";
+ /** Flag to include a Proguard configuration. */
+ static final String CONFIG_PROGUARD = "--config-proguard";
+ /** Flag to set the multidex mode when compiling to dex with Jack. */
+ static final String MULTI_DEX = "--multi-dex";
+ /** Flag to specify the path to the main dex list in manual main dex mode. */
+ static final String MAIN_DEX_LIST = "--main-dex-list";
+
+ /** Flag indicating the filename Jill should output the converted jar to. */
+ static final String JILL_OUTPUT = "--output";
+ /** Flag to output a jack library. */
+ static final String OUTPUT_JACK = "--output-jack";
+ /** Flag to output a zip file containing dex files and resources for packaging. */
+ static final String OUTPUT_DEX_ZIP = "--output-dex-zip";
+ /** Name of the zip file containing the dex files and java resources for packaging. */
+ static final String ZIP_OUTPUT_FILENAME = "classes.dex.zip";
+
+ /** Rule context used to build and register actions. */
+ private final RuleContext ruleContext;
+ /** JavaSemantics used to determine the resource root for Java resources. */
+ private final JavaSemantics javaSemantics;
+
+ /** True to use Jack's internal sanity checks, trading speed for crash-on-bugs. */
+ private final boolean useSanityChecks;
+
+ /** Binary used to extract resources from a jar file. */
+ private final FilesToRunProvider resourceExtractorBinary;
+ /** Binary used to build Jack libraries and dex files. */
+ private final FilesToRunProvider jackBinary;
+ /** Binary used to convert jars to Jack libraries. */
+ private final FilesToRunProvider jillBinary;
+ /** Jack library containing Android base classes. This will be placed first on the classpath. */
+ private final Artifact androidBaseLibraryForJack;
+
+ /** The destination for the Jack artifact to be created. */
+ private final Artifact outputArtifact;
+
+ /** Java files for the rule's Jack library. */
+ private final ImmutableSet<Artifact> javaSources;
+ /** Zip files of java sources for the rule's Jack library. */
+ private final ImmutableSet<Artifact> sourceJars;
+
+ /** Java resources for the rule's Jack library. */
+ private final ImmutableSet<Artifact> resources;
+
+ /** Jack libraries to be provided to depending rules on the classpath, from srcs and exports. */
+ private final NestedSet<Artifact> exportedJacks;
+ /**
+ * Jar libraries to be provided to depending rules on the classpath, from srcs and exports.
+ * These will be placed after the jack files from exportedJacks and before this rule's jack file.
+ */
+ private final ImmutableSet<Artifact> exportedJars;
+
+ /** Jack libraries to be provided only to this rule on the classpath, from srcs and deps. */
+ private final NestedSet<Artifact> classpathJacks;
+ /**
+ * Jar libraries to be provided only to this rule on the classpath, from srcs and deps.
+ * These will be placed after the jack files from classpathJacks.
+ */
+ private final ImmutableSet<Artifact> classpathJars;
+
+ /**
+ * Jack libraries from dependency libraries to be included in dexing.
+ * Does not include the library generated by this rule or any reached only through neverlink libs.
+ */
+ private final NestedSet<Artifact> dexJacks;
+ /** Jar libraries from dependency libraries to be included in dexing. */
+ private final ImmutableSet<Artifact> dexJars;
+
+ /** Minimal state used only to give nice error messages in case of oopses. */
+ private JackLibraryProvider alreadyCompiledLibrary;
+
+ private boolean wasDexBuilt;
+
+ /** The classpath to be used for annotation processors. */
+ private final NestedSet<Artifact> processorClasspathJars;
+
+ /** The names of classes to be used as annotation processors. */
+ private final ImmutableSet<String> processorNames;
+
+ /** Creates a new JackCompilationHelper. Called from {@link Builder#build()}. */
+ private JackCompilationHelper(
+ RuleContext ruleContext,
+ JavaSemantics javaSemantics,
+ boolean useSanityChecks,
+ FilesToRunProvider resourceExtractorBinary,
+ FilesToRunProvider jackBinary,
+ FilesToRunProvider jillBinary,
+ Artifact androidJackLibrary,
+ Artifact outputArtifact,
+ ImmutableSet<Artifact> javaSources,
+ ImmutableSet<Artifact> sourceJars,
+ ImmutableSet<Artifact> resources,
+ NestedSet<Artifact> processorClasspathJars,
+ ImmutableSet<String> processorNames,
+ NestedSet<Artifact> exportedJacks,
+ ImmutableSet<Artifact> exportedJars,
+ NestedSet<Artifact> classpathJacks,
+ ImmutableSet<Artifact> classpathJars,
+ NestedSet<Artifact> dexJacks,
+ ImmutableSet<Artifact> dexJars) {
+ this.ruleContext = ruleContext;
+ this.javaSemantics = javaSemantics;
+ this.useSanityChecks = useSanityChecks;
+ this.resourceExtractorBinary = resourceExtractorBinary;
+ this.jackBinary = jackBinary;
+ this.jillBinary = jillBinary;
+ this.androidBaseLibraryForJack = androidJackLibrary;
+ this.outputArtifact = outputArtifact;
+ this.javaSources = javaSources;
+ this.sourceJars = sourceJars;
+ this.resources = resources;
+ this.processorClasspathJars = processorClasspathJars;
+ this.processorNames = processorNames;
+ this.exportedJacks = exportedJacks;
+ this.exportedJars = exportedJars;
+ this.classpathJacks = classpathJacks;
+ this.classpathJars = classpathJars;
+ this.dexJacks = dexJacks;
+ this.dexJars = dexJars;
+ }
+
+ /**
+ * Builds one or more dex files from the jack libraries in the transitive closure of this rule.
+ *
+ * <p>This method should only be called once, as it will generate the same artifact each time.
+ * It will fail if called a second time.
+ *
+ * @param multidexMode The multidex flag to send to Jack.
+ * @param manualMainDexList Iff multidexMode is MANUAL_MAIN_DEX, an artifact representing the file
+ * with the list of class filenames which should go in the main dex. Else, absent.
+ * @param proguardSpecs A collection of Proguard configuration files to be used to process the
+ * Jack libraries before building a dex out of them.
+ * @returns A zip file containing the dex file(s) and Java resource(s) generated by Jack.
+ */
+ // TODO(bazel-team): this method (compile to jack library, compile all transitive jacks to dex)
+ // may be too much overhead for manydex (dex per library) mode.
+ // Instead, consider running jack --output-dex right on the source files, bypassing the
+ // intermediate jack library format.
+ public Artifact compileAsDex(
+ MultidexMode multidexMode,
+ Optional<Artifact> manualMainDexList,
+ Collection<Artifact> proguardSpecs) {
+ Preconditions.checkNotNull(multidexMode);
+ Preconditions.checkNotNull(manualMainDexList);
+ Preconditions.checkNotNull(proguardSpecs);
+ Preconditions.checkArgument(
+ multidexMode.isSupportedByJack(),
+ "Multidex mode '%s' is not supported by Jack",
+ multidexMode);
+ Preconditions.checkArgument(
+ manualMainDexList.isPresent() == (multidexMode == MultidexMode.MANUAL_MAIN_DEX),
+ "The main dex list must be supplied if and only if the multidex mode is 'manual_main_dex'");
+ Preconditions.checkState(!wasDexBuilt, "A dex file has already been built.");
+
+ Artifact outputZip = AndroidBinary.getDxArtifact(ruleContext, ZIP_OUTPUT_FILENAME);
+
+ NestedSet<Artifact> transitiveJackLibraries =
+ compileAsLibrary().getTransitiveJackLibrariesToLink();
+ CustomCommandLine.Builder builder =
+ CustomCommandLine.builder()
+ // Have jack double-check its behavior and crash rather than producing invalid output
+ .add(SANITY_CHECKS)
+ .add(useSanityChecks ? SANITY_CHECKS_ON : SANITY_CHECKS_OFF)
+ // Have jack take the first match in the event of a class or resource name collision.
+ .add(JACK_PROPERTY)
+ .add(PROPERTY_KEEP_FIRST_RESOURCE)
+ .add(JACK_PROPERTY)
+ .add(PROPERTY_KEEP_FIRST_TYPE);
+
+ for (Artifact jackLibrary : transitiveJackLibraries) {
+ builder.addExecPath(IMPORT_JACK_LIBRARY, jackLibrary);
+ }
+ for (Artifact proguardSpec : proguardSpecs) {
+ builder.addExecPath(CONFIG_PROGUARD, proguardSpec);
+ }
+ builder.add(MULTI_DEX).add(multidexMode.getJackFlagValue());
+ if (manualMainDexList.isPresent()) {
+ builder.addExecPath(MAIN_DEX_LIST, manualMainDexList.get());
+ }
+ builder.addExecPath(OUTPUT_DEX_ZIP, outputZip);
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(jackBinary)
+ .addInputs(transitiveJackLibraries)
+ .addInputs(proguardSpecs)
+ .addInputs(manualMainDexList.asSet())
+ .addOutput(outputZip)
+ .setCommandLine(builder.build())
+ .setProgressMessage("Dexing " + ruleContext.getLabel() + " with Jack")
+ .setMnemonic("AndroidJackDex")
+ .build(ruleContext));
+ return outputZip;
+ }
+
+ /**
+ * Constructs the actions to compile a jack library for a neverlink lib.
+ *
+ * @returns a {@link JackLibraryProvider} containing the resulting transitive jack libraries.
+ */
+ public JackLibraryProvider compileAsNeverlinkLibrary() {
+ JackLibraryProvider nonNeverlink = compileAsLibrary();
+ return new JackLibraryProvider(
+ /* transitiveJackLibrariesToLink */
+ NestedSetBuilder.<Artifact>emptySet(Order.NAIVE_LINK_ORDER),
+ nonNeverlink.getTransitiveJackClasspathLibraries());
+ }
+
+ /**
+ * Constructs the actions to compile a jack library for a non-neverlink lib.
+ *
+ * @returns a {@link JackLibraryProvider} containing the resulting transitive jack libraries.
+ */
+ public JackLibraryProvider compileAsLibrary() {
+ if (alreadyCompiledLibrary != null) {
+ // Because the JackCompilationHelper is immutable, compileAsLibrary will always produce the
+ // same result for the life of the helper. The resulting library may be needed by clients
+ // which also need to build a dex, e.g., AndroidBinary.
+ return alreadyCompiledLibrary;
+ }
+ Function<Artifact, Artifact> nonLibraryFileConverter =
+ CacheBuilder.newBuilder()
+ .initialCapacity(exportedJars.size() + classpathJars.size())
+ .build(
+ new CacheLoader<Artifact, Artifact>() {
+ @Override
+ public Artifact load(Artifact artifact) throws Exception {
+ if (JavaSemantics.JAR.matches(artifact.getFilename())) {
+ return postprocessPartialJackAndAddResources(
+ convertJarToPartialJack(artifact), extractResourcesFromJar(artifact));
+ } else if (JACK_LIBRARY_TYPE.matches(artifact.getFilename())) {
+ return artifact;
+ }
+ throw new AssertionError("Invalid type for library file: " + artifact);
+ }
+ });
+
+ NestedSet<Artifact> transitiveClasspath =
+ new NestedSetBuilder<Artifact>(Order.NAIVE_LINK_ORDER)
+ .addAll(Iterables.transform(classpathJars, nonLibraryFileConverter))
+ .addTransitive(classpathJacks)
+ .build();
+
+ // android.jack needs to be first in the set's iteration order, as it's the base library.
+ // Then any jars or jack files specified directly, then dependencies from providers.
+ NestedSet<Artifact> classpath =
+ new NestedSetBuilder<Artifact>(Order.NAIVE_LINK_ORDER)
+ .add(androidBaseLibraryForJack)
+ .addTransitive(transitiveClasspath)
+ .build();
+
+ NestedSetBuilder<Artifact> exports = new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+ NestedSetBuilder<Artifact> dexContents = new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+
+ if (javaSources.isEmpty() && sourceJars.isEmpty() && resources.isEmpty()) {
+ // We still have to create SOMETHING to fulfill the artifact, but man, screw it
+ buildEmptyJackAction();
+ } else {
+ buildJackAction(javaSources, sourceJars, resources, classpath);
+ exports.add(outputArtifact);
+ dexContents.add(outputArtifact);
+ }
+
+ // These need to be added now so that they can be after the outputArtifact (if present).
+ exports
+ .addAll(Iterables.transform(exportedJars, nonLibraryFileConverter))
+ .addTransitive(exportedJacks)
+ .addTransitive(transitiveClasspath);
+ dexContents
+ .addAll(Iterables.transform(dexJars, nonLibraryFileConverter))
+ .addTransitive(dexJacks);
+
+ alreadyCompiledLibrary = new JackLibraryProvider(dexContents.build(), exports.build());
+ return alreadyCompiledLibrary;
+ }
+
+ /**
+ * Generates an action which converts the jar to partial Jack format and returns the Jack file.
+ *
+ * <p>Partial Jack format does not contain resources or pre-dex files.
+ *
+ * @see #postprocessPartialJackAndAddResources(Artifact,Artifact)
+ */
+ private Artifact convertJarToPartialJack(Artifact jar) {
+ PathFragment outputPath =
+ FileSystemUtils.replaceExtension(
+ getPartialJackRoot().getRelative(jar.getRootRelativePath()), ".jack");
+ Artifact result =
+ ruleContext
+ .getAnalysisEnvironment()
+ .getDerivedArtifact(outputPath, ruleContext.getBinOrGenfilesDirectory());
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(jillBinary)
+ .addArgument(JILL_OUTPUT)
+ .addOutputArgument(result)
+ .addInputArgument(jar)
+ .setProgressMessage(
+ "Converting " + jar.getExecPath().getBaseName() + " to Jack library with Jill")
+ .setMnemonic("AndroidJill")
+ .build(ruleContext));
+ return result;
+ }
+
+ /**
+ * Generates an action which creates a zip file from the contents of the input jar, filtering out
+ * non-resource files and returning a zip file containing only resources.
+ */
+ private Artifact extractResourcesFromJar(Artifact jar) {
+ PathFragment outputPath =
+ FileSystemUtils.replaceExtension(
+ getPartialJackRoot().getRelative(jar.getRootRelativePath()), "-resources.zip");
+ Artifact result =
+ ruleContext
+ .getAnalysisEnvironment()
+ .getDerivedArtifact(outputPath, ruleContext.getBinOrGenfilesDirectory());
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(resourceExtractorBinary)
+ .addInputArgument(jar)
+ .addOutputArgument(result)
+ .setProgressMessage("Extracting resources from " + jar.getExecPath().getBaseName())
+ .setMnemonic("AndroidJillResources")
+ .build(ruleContext));
+ return result;
+ }
+
+ /**
+ * Generates an action to finish processing a partial Jack library generated by
+ * {@link #convertJarToPartialJack(Artifact)} and add resources from
+ * {@link #extractResourcesFromJar(Artifact)}, then returns the final library.
+ */
+ private Artifact postprocessPartialJackAndAddResources(
+ Artifact partialJackLibrary, Artifact resources) {
+ PathFragment outputPath =
+ getFinalizedJackRoot()
+ .getRelative(partialJackLibrary.getRootRelativePath().relativeTo(getPartialJackRoot()));
+ Artifact result =
+ ruleContext
+ .getAnalysisEnvironment()
+ .getDerivedArtifact(outputPath, ruleContext.getBinOrGenfilesDirectory());
+ CustomCommandLine.Builder builder =
+ CustomCommandLine.builder()
+ // Have jack double-check its behavior and crash rather than producing invalid output
+ .add(SANITY_CHECKS)
+ .add(useSanityChecks ? SANITY_CHECKS_ON : SANITY_CHECKS_OFF)
+ .addExecPath(IMPORT_JACK_LIBRARY, partialJackLibrary)
+ .addExecPath(IMPORT_RESOURCE_ZIP, resources)
+ .addExecPath(OUTPUT_JACK, result);
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(jackBinary)
+ .addInput(partialJackLibrary)
+ .addInput(resources)
+ .addOutput(result)
+ .setCommandLine(builder.build())
+ .setProgressMessage(
+ "Processing " + partialJackLibrary.getExecPath().getBaseName() + " as Jack library")
+ .setMnemonic("AndroidJillPostprocess")
+ .build(ruleContext));
+ return result;
+ }
+
+ /**
+ * Creates an intermediate directory to store partially-converted Jack libraries.
+ *
+ * @see #convertJarToPartialJack(Artifact)
+ */
+ private PathFragment getPartialJackRoot() {
+ PathFragment rulePath = ruleContext.getLabel().toPathFragment();
+ return rulePath.replaceName(rulePath.getBaseName() + "_jill");
+ }
+
+ /**
+ * Creates an intermediate directory to store fully-converted Jack libraries.
+ *
+ * @see #postprocessPartialJackAndAddResources(Artifact,Artifact)
+ */
+ private PathFragment getFinalizedJackRoot() {
+ PathFragment rulePath = ruleContext.getLabel().toPathFragment();
+ return rulePath.replaceName(rulePath.getBaseName() + "_jack");
+ }
+
+ /**
+ * Creates an action to build an empty jack library given by outputArtifact.
+ */
+ private void buildEmptyJackAction() {
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(jackBinary)
+ .addArgument(OUTPUT_JACK)
+ .addOutputArgument(outputArtifact)
+ .setProgressMessage("Compiling " + ruleContext.getLabel() + " as Jack library")
+ .setMnemonic("AndroidJackLibraryNull")
+ .build(ruleContext));
+ }
+
+ /**
+ * Creates an action to compile the given sources as a jack library given by outputArtifact.
+ *
+ * @param javaSources Iterable of .java files to compile using jack.
+ * @param sourceJars Iterable of .srcjar files to unpack and compile using jack.
+ * @param resources Iterable of resource files to be imported into the jack library.
+ * @param classpathJackLibraries Libraries used for compilation.
+ * @returns An artifact representing the combined jack library, or null if none was created.
+ */
+ private void buildJackAction(
+ Iterable<Artifact> javaSources,
+ Iterable<Artifact> sourceJars,
+ Iterable<Artifact> resources,
+ NestedSet<Artifact> classpathJackLibraries) {
+ CustomCommandLine.Builder builder =
+ CustomCommandLine.builder()
+ // Have jack double-check its behavior and crash rather than producing invalid output
+ .add(SANITY_CHECKS)
+ .add(useSanityChecks ? SANITY_CHECKS_ON : SANITY_CHECKS_OFF)
+ .addExecPath(OUTPUT_JACK, outputArtifact)
+ .addJoinExecPaths(CLASSPATH, ":", classpathJackLibraries);
+ if (!processorNames.isEmpty()) {
+ builder.add(PROCESSOR_NAMES).add(Joiner.on(',').join(processorNames));
+ }
+ if (!processorClasspathJars.isEmpty()) {
+ builder.addJoinExecPaths(PROCESSOR_CLASSPATH, ":", processorClasspathJars);
+ }
+ for (Artifact resource : resources) {
+ builder.add(IMPORT_RESOURCE_FILE);
+ // Splits paths at the appropriate root (java root, if present; source/genfiles/etc. if not).
+ // The part of the path after the : is used as the path to the resource within the jack/apk,
+ // while the part of the path before the : is the remainder of the path to the resource file.
+ // In cases where the path to the file and the path within the jack/apk are the same,
+ // such as when a source file is not under a java root, this prefix will be empty.
+ PathFragment execPath = resource.getExecPath();
+ PathFragment resourcePath = javaSemantics.getJavaResourcePath(resource.getRootRelativePath());
+ if (execPath.equals(resourcePath)) {
+ builder.addPaths(":%s", resourcePath);
+ } else {
+ // execPath must end with resourcePath in all cases
+ PathFragment rootPrefix =
+ execPath.subFragment(0, execPath.segmentCount() - resourcePath.segmentCount());
+ builder.addPaths("%s:%s", rootPrefix, resourcePath);
+ }
+ }
+ builder.addBeforeEachExecPath(IMPORT_SOURCE_ZIP, sourceJars).addExecPaths(javaSources);
+ ruleContext.registerAction(
+ new SpawnAction.Builder()
+ .setExecutable(jackBinary)
+ .addInputs(classpathJackLibraries)
+ .addOutput(outputArtifact)
+ .addInputs(processorClasspathJars)
+ .addInputs(resources)
+ .addInputs(sourceJars)
+ .addInputs(javaSources)
+ .setCommandLine(builder.build())
+ .setProgressMessage("Compiling " + ruleContext.getLabel() + " as Jack library")
+ .setMnemonic("AndroidJackLibrary")
+ .build(ruleContext));
+ }
+
+ /**
+ * Builder for JackCompilationHelper to configure all of its tools and sources.
+ */
+ public static final class Builder {
+
+ /** Rule context used to build and register actions. */
+ @Nullable private RuleContext ruleContext;
+ /** JavaSemantics used to determine the resource root for Java resources. */
+ @Nullable private JavaSemantics javaSemantics;
+
+ /** Jack library containing Android base classes. */
+ @Nullable private Artifact androidBaseLibraryForJack;
+
+ /** The destination for the Jack artifact to be created. */
+ @Nullable private Artifact outputArtifact;
+
+ /** Java files for the rule's Jack library. */
+ private final LinkedHashSet<Artifact> javaSources = new LinkedHashSet<>();
+ /** Zip files of java sources for the rule's Jack library. */
+ private final LinkedHashSet<Artifact> sourceJars = new LinkedHashSet<>();
+ /** Java resources for the rule's Jack library. */
+ private final LinkedHashSet<Artifact> resources = new LinkedHashSet<>();
+
+ /** Jack libraries to be provided to depending rules on the classpath, from srcs and exports. */
+ private final NestedSetBuilder<Artifact> exportedJackLibraries =
+ new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+ /**
+ * Jar libraries to be provided to depending rules on the classpath, from srcs and exports.
+ * These will be placed after the jack files from exportedJacks and before this rule's jack
+ * file.
+ */
+ private final LinkedHashSet<Artifact> exportedNonLibraryFiles = new LinkedHashSet<>();
+
+ /** Jack libraries to be provided only to this rule on the classpath, from srcs and deps. */
+ private final NestedSetBuilder<Artifact> classpathJackLibraries =
+ new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+ /**
+ * Jar libraries to be provided only to this rule on the classpath, from srcs and deps.
+ * These will be placed after the jack files from classpathJacks.
+ */
+ private final LinkedHashSet<Artifact> classpathNonLibraryFiles = new LinkedHashSet<>();
+
+ /** The names of classes to be used as annotation processors. */
+ private final LinkedHashSet<String> processorNames = new LinkedHashSet<>();
+
+ /** Jar libraries to be provided as annotation processors' classpath, from plugin deps. */
+ private final NestedSetBuilder<Artifact> processorClasspathJars =
+ new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+
+ /**
+ * Jack libraries from dependency libraries to be included in dexing.
+ * Does not include the library generated by this rule or any reached only through neverlink
+ * libs.
+ */
+ private final NestedSetBuilder<Artifact> dexJacks =
+ new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER);
+ /** Jar libraries from dependency libraries to be included in dexing. */
+ private final LinkedHashSet<Artifact> dexJars = new LinkedHashSet<>();
+
+ /**
+ * Sets the rule context in which this compilation helper will operate.
+ *
+ * <p>The compilation tools will be loaded automatically from the appropriate attributes:
+ * Jack as $jack, Jill as $jill, and the resource extractor as $resource_extractor.
+ *
+ * <p>Jack's sanity checks will be enabled or disabled according to the AndroidConfiguration
+ * accessed through this context.
+ */
+ public JackCompilationHelper.Builder setRuleContext(RuleContext ruleContext) {
+ this.ruleContext = Preconditions.checkNotNull(ruleContext);
+ return this;
+ }
+
+ /**
+ * Sets the java semantics used to determine resource placement.
+ */
+ public JackCompilationHelper.Builder setJavaSemantics(JavaSemantics javaSemantics) {
+ this.javaSemantics = Preconditions.checkNotNull(javaSemantics);
+ return this;
+ }
+
+ /**
+ * Sets the artifact the final Jack library should be output to.
+ *
+ * <p>The artifact specified will always be generated, although it may be empty if there are no
+ * sources.
+ */
+ public JackCompilationHelper.Builder setOutputArtifact(Artifact outputArtifact) {
+ this.outputArtifact = Preconditions.checkNotNull(outputArtifact);
+ return this;
+ }
+
+ /**
+ * Sets the artifact representing android.jack, to supply base libraries to Jack compilation.
+ */
+ public JackCompilationHelper.Builder setAndroidBaseLibraryForJack(
+ Artifact androidBaseLibraryForJack) {
+ this.androidBaseLibraryForJack = Preconditions.checkNotNull(androidBaseLibraryForJack);
+ return this;
+ }
+
+ /**
+ * Adds a collection of Java source files to be compiled by Jack.
+ */
+ public JackCompilationHelper.Builder addJavaSources(Collection<Artifact> javaSources) {
+ this.javaSources.addAll(Preconditions.checkNotNull(javaSources));
+ return this;
+ }
+
+ /**
+ * Adds a collection of zip files containing Java sources to be compiled by Jack.
+ */
+ public JackCompilationHelper.Builder addSourceJars(Collection<Artifact> sourceJars) {
+ this.sourceJars.addAll(Preconditions.checkNotNull(sourceJars));
+ return this;
+ }
+
+ /**
+ * Adds a collection of jar files to be converted to Jack libraries.
+ *
+ * <p>The Jack libraries created from these jar files will be used both as
+ * dependencies on the classpath of this rule and exports to the classpath of depending rules,
+ * as with jar files in the sources of a Java rule.
+ * They will also be available to the compilation of the final dex file(s).
+ * It has an identical effect as if these jars were specified in both deps and exports.
+ */
+ public JackCompilationHelper.Builder addCompiledJars(Collection<Artifact> compiledJars) {
+ this.exportedNonLibraryFiles.addAll(Preconditions.checkNotNull(compiledJars));
+ this.classpathNonLibraryFiles.addAll(compiledJars);
+ this.dexJars.addAll(compiledJars);
+ return this;
+ }
+
+ /**
+ * Adds a set of Java resources which will be added to the Jack package and the final APK.
+ */
+ public JackCompilationHelper.Builder addResources(Collection<Artifact> resources) {
+ this.resources.addAll(Preconditions.checkNotNull(resources));
+ return this;
+ }
+
+ /**
+ * Adds a set of class names which will be used as annotation processors.
+ */
+ public JackCompilationHelper.Builder addProcessorNames(Collection<String> processorNames) {
+ this.processorNames.addAll(Preconditions.checkNotNull(processorNames));
+ return this;
+ }
+
+ /**
+ * Adds a set of jars which will be used as the classpath for annotation processors.
+ */
+ public JackCompilationHelper.Builder addProcessorClasspathJars(
+ Iterable<Artifact> processorClasspathJars) {
+ this.processorClasspathJars.addAll(Preconditions.checkNotNull(processorClasspathJars));
+ return this;
+ }
+
+ /**
+ * Adds a set of normal dependencies.
+ *
+ * <p>These dependencies will be considered direct dependencies of this rule,
+ * and indirect dependencies of any rules depending on this one.
+ * They will also be available to the compilation of the final dex file(s).
+ */
+ public JackCompilationHelper.Builder addDeps(
+ Iterable<? extends TransitiveInfoCollection> deps) {
+ return addClasspathDeps(deps).addRuntimeDeps(deps);
+ }
+
+ /**
+ * Adds a set of dependencies which will be exported to Jack rules which depend on this one.
+ *
+ * <p>These dependencies will be considered direct dependencies of rules depending on this one,
+ * but not of this rule itself, in line with Java rule semantics.
+ * They will also be available to the compilation of the final dex file(s).
+ */
+ public JackCompilationHelper.Builder addExports(
+ Iterable<? extends TransitiveInfoCollection> exports) {
+ return addExportedDeps(exports).addRuntimeDeps(exports);
+ }
+
+ /**
+ * Adds a set of dependencies which will be used in dexing.
+ *
+ * <p>Unless {@link #addClasspathDeps} or {@link #addExportedDeps} are also called,
+ * runtimeDeps will not be provided on the classpath of this rule or rules which depend on it.
+ */
+ public JackCompilationHelper.Builder addRuntimeDeps(
+ Iterable<? extends TransitiveInfoCollection> runtimeDeps) {
+ for (TransitiveInfoCollection dep : Preconditions.checkNotNull(runtimeDeps)) {
+ JackLibraryProvider jackLibraryProvider = dep.getProvider(JackLibraryProvider.class);
+ if (jackLibraryProvider != null) {
+ dexJacks.addTransitive(jackLibraryProvider.getTransitiveJackLibrariesToLink());
+ } else {
+ NestedSet<Artifact> filesToBuild = dep.getProvider(FileProvider.class).getFilesToBuild();
+ for (Artifact file :
+ FileType.filter(filesToBuild, JavaSemantics.JAR, JACK_LIBRARY_TYPE)) {
+ dexJars.add(file);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds a set of dependencies which will be placed on the classpath of this rule.
+ *
+ * <p>Unless {@link #addRuntimeDeps} is also called, classpathDeps will only be used for
+ * compilation of this rule and will not be built into the final dex.
+ *
+ * @see #addDeps
+ */
+ public JackCompilationHelper.Builder addClasspathDeps(
+ Iterable<? extends TransitiveInfoCollection> classpathDeps) {
+ return addDependenciesInternal(
+ Preconditions.checkNotNull(classpathDeps),
+ classpathNonLibraryFiles,
+ classpathJackLibraries);
+ }
+
+ /**
+ * Adds a set of dependencies to be placed on the classpath of rules depending on this rule.
+ *
+ * <p>Unless {@link #addRuntimeDeps} is also called, exportedDeps will only be used for
+ * compilation of depending rules and will not be built into the final dex.
+ *
+ * @see #addExports
+ */
+ public JackCompilationHelper.Builder addExportedDeps(
+ Iterable<? extends TransitiveInfoCollection> exportedDeps) {
+ return addDependenciesInternal(
+ Preconditions.checkNotNull(exportedDeps), exportedNonLibraryFiles, exportedJackLibraries);
+ }
+
+ /**
+ * Adds all libraries from deps to nonLibraryFiles and jackLibs based on their type.
+ *
+ * <p>Those exporting JackLibraryProvider have their jackClasspathLibraries added to jackLibs.
+ * Others will have any jars or jacks in their filesToBuild added to nonLibraryFiles.
+ *
+ * <p>{@link #addRuntimeDeps} should also be called on deps for dependencies which will be built
+ * into the final dex file(s).
+ */
+ private JackCompilationHelper.Builder addDependenciesInternal(
+ Iterable<? extends TransitiveInfoCollection> deps,
+ Collection<Artifact> nonLibraryFiles,
+ NestedSetBuilder<Artifact> jackLibs) {
+ for (TransitiveInfoCollection dep : deps) {
+ JackLibraryProvider jackLibraryProvider = dep.getProvider(JackLibraryProvider.class);
+ if (jackLibraryProvider != null) {
+ jackLibs.addTransitive(jackLibraryProvider.getTransitiveJackClasspathLibraries());
+ } else {
+ NestedSet<Artifact> filesToBuild = dep.getProvider(FileProvider.class).getFilesToBuild();
+ for (Artifact file :
+ FileType.filter(filesToBuild, JavaSemantics.JAR, JACK_LIBRARY_TYPE)) {
+ nonLibraryFiles.add(file);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Constructs the JackCompilationHelper.
+ *
+ * <p>It's not recommended to call build() more than once, as the resulting
+ * JackCompilationHelpers will attempt to generate the same actions.
+ */
+ public JackCompilationHelper build() {
+ boolean useSanityChecks =
+ ruleContext
+ .getConfiguration()
+ .getFragment(AndroidConfiguration.class)
+ .isJackSanityChecked();
+ FilesToRunProvider jackBinary =
+ Preconditions.checkNotNull(ruleContext.getExecutablePrerequisite("$jack", Mode.HOST));
+ FilesToRunProvider jillBinary =
+ Preconditions.checkNotNull(ruleContext.getExecutablePrerequisite("$jill", Mode.HOST));
+ FilesToRunProvider resourceExtractorBinary =
+ Preconditions.checkNotNull(
+ ruleContext.getExecutablePrerequisite("$resource_extractor", Mode.HOST));
+
+ return new JackCompilationHelper(
+ Preconditions.checkNotNull(ruleContext),
+ Preconditions.checkNotNull(javaSemantics),
+ useSanityChecks,
+ resourceExtractorBinary,
+ jackBinary,
+ jillBinary,
+ Preconditions.checkNotNull(androidBaseLibraryForJack),
+ Preconditions.checkNotNull(outputArtifact),
+ ImmutableSet.copyOf(javaSources),
+ ImmutableSet.copyOf(sourceJars),
+ ImmutableSet.copyOf(resources),
+ processorClasspathJars.build(),
+ ImmutableSet.copyOf(processorNames),
+ exportedJackLibraries.build(),
+ ImmutableSet.copyOf(exportedNonLibraryFiles),
+ classpathJackLibraries.build(),
+ ImmutableSet.copyOf(classpathNonLibraryFiles),
+ dexJacks.build(),
+ ImmutableSet.copyOf(dexJars));
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/JackLibraryProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/JackLibraryProvider.java
new file mode 100644
index 0000000000..0b8f39426f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/JackLibraryProvider.java
@@ -0,0 +1,84 @@
+// 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.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 Jack libraries to the compilation of
+ * an Android APK using the Jack toolchain or another Jack library.
+ *
+ * @see <a href="http://tools.android.com/tech-docs/jackandjill">Jack documentation</a>
+ * @see JackCompilationHelper
+ */
+@Immutable
+public final class JackLibraryProvider implements TransitiveInfoProvider {
+ public static final JackLibraryProvider EMPTY =
+ new JackLibraryProvider(
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER));
+
+ private final NestedSet<Artifact> transitiveJackLibrariesToLink;
+ private final NestedSet<Artifact> transitiveJackClasspathLibraries;
+
+ public JackLibraryProvider(
+ NestedSet<Artifact> transitiveJackLibrariesToLink,
+ NestedSet<Artifact> transitiveJackClasspathLibraries) {
+ this.transitiveJackLibrariesToLink = Preconditions.checkNotNull(transitiveJackLibrariesToLink);
+ this.transitiveJackClasspathLibraries =
+ Preconditions.checkNotNull(transitiveJackClasspathLibraries);
+ }
+
+ /**
+ * Gets the Jack libraries in the transitive closure which should be added to the final dex file.
+ */
+ public NestedSet<Artifact> getTransitiveJackLibrariesToLink() {
+ return transitiveJackLibrariesToLink;
+ }
+
+ /**
+ * Gets the Jack libraries which should be added to the classpath of any Jack action depending on
+ * this provider.
+ */
+ public NestedSet<Artifact> getTransitiveJackClasspathLibraries() {
+ return transitiveJackClasspathLibraries;
+ }
+
+ /**
+ * Builder class to combine multiple JackLibraryProviders into a single one.
+ */
+ public static final class Builder {
+ private final NestedSetBuilder<Artifact> transitiveJackLibrariesToLink =
+ NestedSetBuilder.<Artifact>stableOrder();
+ private final NestedSetBuilder<Artifact> transitiveJackClasspathLibraries =
+ NestedSetBuilder.<Artifact>stableOrder();
+
+ public Builder merge(JackLibraryProvider other) {
+ transitiveJackLibrariesToLink.addTransitive(other.getTransitiveJackLibrariesToLink());
+ transitiveJackClasspathLibraries.addTransitive(other.getTransitiveJackClasspathLibraries());
+ return this;
+ }
+
+ public JackLibraryProvider build() {
+ return new JackLibraryProvider(
+ transitiveJackLibrariesToLink.build(), transitiveJackClasspathLibraries.build());
+ }
+ }
+}