diff options
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.java | 849 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/android/JackLibraryProvider.java | 84 |
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()); + } + } +} |