// 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.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.collect.ImmutableMap; 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.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.util.Preconditions; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; /** * Builds Jack actions for a Java or Android target. * *

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 Jack documentation * @see JackLibraryProvider */ public final class JackCompilationHelper { private static final String PARTIAL_JACK_DIRECTORY = "_jill"; private static final String JACK_DIRECTORY = "_jack"; /** 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; /** 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 javaSources; /** Zip files of java sources for the rule's Jack library. */ private final ImmutableSet sourceJars; /** Java resources for the rule's Jack library. */ private final ImmutableMap resources; /** Jack libraries to be provided to depending rules on the classpath, from srcs and exports. */ private final NestedSet 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 exportedJars; /** Jack libraries to be provided only to this rule on the classpath, from srcs and deps. */ private final NestedSet 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 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 dexJacks; /** Jar libraries from dependency libraries to be included in dexing. */ private final ImmutableSet 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 processorClasspathJars; /** The names of classes to be used as annotation processors. */ private final ImmutableSet processorNames; /** Creates a new JackCompilationHelper. Called from {@link Builder#build()}. */ private JackCompilationHelper( RuleContext ruleContext, boolean useSanityChecks, FilesToRunProvider resourceExtractorBinary, FilesToRunProvider jackBinary, FilesToRunProvider jillBinary, Artifact androidJackLibrary, Artifact outputArtifact, ImmutableSet javaSources, ImmutableSet sourceJars, ImmutableMap resources, NestedSet processorClasspathJars, ImmutableSet processorNames, NestedSet exportedJacks, ImmutableSet exportedJars, NestedSet classpathJacks, ImmutableSet classpathJars, NestedSet dexJacks, ImmutableSet dexJars) { this.ruleContext = ruleContext; 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. * *

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 manualMainDexList, Collection 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 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.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 nonLibraryFileConverter = CacheBuilder.newBuilder() .initialCapacity(exportedJars.size() + classpathJars.size()) .build( new CacheLoader() { @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 transitiveClasspath = new NestedSetBuilder(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 classpath = new NestedSetBuilder(Order.NAIVE_LINK_ORDER) .add(androidBaseLibraryForJack) .addTransitive(transitiveClasspath) .build(); NestedSetBuilder exports = new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER); NestedSetBuilder 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. * *

Partial Jack format does not contain resources or pre-dex files. * * @see #postprocessPartialJackAndAddResources(Artifact,Artifact) */ private Artifact convertJarToPartialJack(Artifact jar) { Artifact result = ruleContext.getUniqueDirectoryArtifact( PARTIAL_JACK_DIRECTORY, FileSystemUtils.replaceExtension(jar.getRootRelativePath(), ".jack"), 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) { Artifact result = ruleContext.getUniqueDirectoryArtifact( PARTIAL_JACK_DIRECTORY, FileSystemUtils.replaceExtension(jar.getRootRelativePath(), "-resources.zip"), 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) { Artifact result = ruleContext.getUniqueDirectoryArtifact( JACK_DIRECTORY, partialJackLibrary.getRootRelativePath().relativeTo( ruleContext.getUniqueDirectory(PARTIAL_JACK_DIRECTORY)), 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 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 Mapping from library paths to 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 javaSources, Iterable sourceJars, Map resources, NestedSet 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 (Entry resource : resources.entrySet()) { 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.getValue().getExecPath(); PathFragment resourcePath = resource.getKey(); 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.values()) .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; /** Set of Android tools used to pick up the Jack tools. */ @Nullable private AndroidSdkProvider androidSdk; /** The destination for the Jack artifact to be created. */ @Nullable private Artifact outputArtifact; /** Java files for the rule's Jack library. */ private final LinkedHashSet javaSources = new LinkedHashSet<>(); /** Zip files of java sources for the rule's Jack library. */ private final LinkedHashSet sourceJars = new LinkedHashSet<>(); /** Map from paths within the Jack library to Java resources for the rule's Jack library. */ private final LinkedHashMap resources = new LinkedHashMap<>(); /** Jack libraries to be provided to depending rules on the classpath, from srcs and exports. */ private final NestedSetBuilder 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 exportedNonLibraryFiles = new LinkedHashSet<>(); /** Jack libraries to be provided only to this rule on the classpath, from srcs and deps. */ private final NestedSetBuilder 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 classpathNonLibraryFiles = new LinkedHashSet<>(); /** The names of classes to be used as annotation processors. */ private final LinkedHashSet processorNames = new LinkedHashSet<>(); /** Jar libraries to be provided as annotation processors' classpath, from plugin deps. */ private final NestedSetBuilder 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 dexJacks = new NestedSetBuilder<>(Order.NAIVE_LINK_ORDER); /** Jar libraries from dependency libraries to be included in dexing. */ private final LinkedHashSet dexJars = new LinkedHashSet<>(); /** * Sets the rule context in which this compilation helper will operate. * *

The compilation tools will be loaded automatically from the appropriate attributes: * Jack as $jack, Jill as $jill, and the resource extractor as $resource_extractor. * *

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 artifact the final Jack library should be output to. * *

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 tools bundle containing Jack, Jill, the resource extractor, and the Android base * library in Jack format. */ public JackCompilationHelper.Builder setAndroidSdk(AndroidSdkProvider androidSdk) { this.androidSdk = Preconditions.checkNotNull(androidSdk); return this; } /** * Adds a collection of Java source files to be compiled by Jack. */ public JackCompilationHelper.Builder addJavaSources(Collection 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 sourceJars) { this.sourceJars.addAll(Preconditions.checkNotNull(sourceJars)); return this; } /** * Adds a collection of jar files to be converted to Jack libraries. * *

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 compiledJars) { this.exportedNonLibraryFiles.addAll(Preconditions.checkNotNull(compiledJars)); this.classpathNonLibraryFiles.addAll(compiledJars); this.dexJars.addAll(compiledJars); return this; } /** * Adds Java resources as a map keyed by the paths where they will be added to the Jack package * and the final APK. The resource path for an artifact must be a suffix of its exec path. */ public JackCompilationHelper.Builder addResources(Map resources) { this.resources.putAll(Preconditions.checkNotNull(resources)); return this; } /** * Adds a set of class names which will be used as annotation processors. */ public JackCompilationHelper.Builder addProcessorNames(Collection 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 processorClasspathJars) { this.processorClasspathJars.addAll(Preconditions.checkNotNull(processorClasspathJars)); return this; } /** * Adds a set of normal dependencies. * *

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 deps) { return addClasspathDeps(deps).addRuntimeDeps(deps); } /** * Adds a set of dependencies which will be exported to Jack rules which depend on this one. * *

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 exports) { return addExportedDeps(exports).addRuntimeDeps(exports); } /** * Adds a set of dependencies which will be used in dexing. * *

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 runtimeDeps) { for (TransitiveInfoCollection dep : Preconditions.checkNotNull(runtimeDeps)) { JackLibraryProvider jackLibraryProvider = dep.getProvider(JackLibraryProvider.class); if (jackLibraryProvider != null) { dexJacks.addTransitive(jackLibraryProvider.getTransitiveJackLibrariesToLink()); } else { NestedSet 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. * *

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 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. * *

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 exportedDeps) { return addDependenciesInternal( Preconditions.checkNotNull(exportedDeps), exportedNonLibraryFiles, exportedJackLibraries); } /** * Adds all libraries from deps to nonLibraryFiles and jackLibs based on their type. * *

Those exporting JackLibraryProvider have their jackClasspathLibraries added to jackLibs. * Others will have any jars or jacks in their filesToBuild added to nonLibraryFiles. * *

{@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 deps, Collection nonLibraryFiles, NestedSetBuilder jackLibs) { for (TransitiveInfoCollection dep : deps) { JackLibraryProvider jackLibraryProvider = dep.getProvider(JackLibraryProvider.class); if (jackLibraryProvider != null) { jackLibs.addTransitive(jackLibraryProvider.getTransitiveJackClasspathLibraries()); } else { NestedSet 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. * *

It's not recommended to call build() more than once, as the resulting * JackCompilationHelpers will attempt to generate the same actions. */ public JackCompilationHelper build() { Preconditions.checkNotNull(ruleContext); Preconditions.checkNotNull(androidSdk); boolean useSanityChecks = ruleContext .getFragment(AndroidConfiguration.class) .isJackSanityChecked(); FilesToRunProvider jackBinary = androidSdk.getJack(); FilesToRunProvider jillBinary = androidSdk.getJill(); FilesToRunProvider resourceExtractorBinary = androidSdk.getResourceExtractor(); Artifact androidBaseLibraryForJack = androidSdk.getAndroidJack(); return new JackCompilationHelper( ruleContext, useSanityChecks, Preconditions.checkNotNull(resourceExtractorBinary), Preconditions.checkNotNull(jackBinary), Preconditions.checkNotNull(jillBinary), Preconditions.checkNotNull(androidBaseLibraryForJack), Preconditions.checkNotNull(outputArtifact), ImmutableSet.copyOf(javaSources), ImmutableSet.copyOf(sourceJars), ImmutableMap.copyOf(resources), processorClasspathJars.build(), ImmutableSet.copyOf(processorNames), exportedJackLibraries.build(), ImmutableSet.copyOf(exportedNonLibraryFiles), classpathJackLibraries.build(), ImmutableSet.copyOf(classpathNonLibraryFiles), dexJacks.build(), ImmutableSet.copyOf(dexJars)); } } }