blob: 74996e0b4e2506eeb87fc5ee81a374666d76bb00 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.java;
import static com.facebook.buck.rules.BuildableProperties.Kind.ANDROID;
import static com.facebook.buck.rules.BuildableProperties.Kind.LIBRARY;
import com.facebook.buck.android.AndroidPackageable;
import com.facebook.buck.android.AndroidPackageableCollector;
import com.facebook.buck.graph.TopologicalSort;
import com.facebook.buck.graph.TraversableGraph;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildDependencies;
import com.facebook.buck.rules.BuildOutputInitializer;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.BuildableProperties;
import com.facebook.buck.rules.ExportDependencies;
import com.facebook.buck.rules.ImmutableSha1HashCode;
import com.facebook.buck.rules.InitializableFromDisk;
import com.facebook.buck.rules.OnDiskBuildInfo;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.shell.BashStep;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.TouchStep;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.Optionals;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.reflect.ClassPath;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.annotation.Nullable;
/**
* Suppose this were a rule defined in <code>src/com/facebook/feed/BUILD</code>:
* <pre>
* java_library(
* name = 'feed',
* srcs = [
* 'FeedStoryRenderer.java',
* ],
* deps = [
* '//src/com/facebook/feed/model:model',
* '//third-party/java/guava:guava',
* ],
* )
* </pre>
* Then this would compile {@code FeedStoryRenderer.java} against Guava and the classes generated
* from the {@code //src/com/facebook/feed/model:model} rule.
*/
public class DefaultJavaLibrary extends AbstractBuildRule
implements JavaLibrary, AbiRule, HasClasspathEntries, ExportDependencies,
InitializableFromDisk<JavaLibrary.Data>, AndroidPackageable {
private static final BuildableProperties OUTPUT_TYPE = new BuildableProperties(LIBRARY);
private final ImmutableSortedSet<SourcePath> srcs;
private final ImmutableSortedSet<SourcePath> resources;
private final Optional<Path> outputJar;
private final Optional<Path> proguardConfig;
private final ImmutableList<String> postprocessClassesCommands;
private final ImmutableSortedSet<BuildRule> exportedDeps;
private final ImmutableSortedSet<BuildRule> providedDeps;
// Some classes need to override this when enhancing deps (see AndroidLibrary).
private final ImmutableSet<Path> additionalClasspathEntries;
private final Supplier<ImmutableSetMultimap<JavaLibrary, Path>>
outputClasspathEntriesSupplier;
private final Supplier<ImmutableSetMultimap<JavaLibrary, Path>>
transitiveClasspathEntriesSupplier;
private final Supplier<ImmutableSetMultimap<JavaLibrary, Path>>
declaredClasspathEntriesSupplier;
private final BuildOutputInitializer<Data> buildOutputInitializer;
private final Optional<Path> resourcesRoot;
// TODO(jacko): This really should be final, but we need to refactor how we get the
// AndroidPlatformTarget first before it can be.
private JavacOptions javacOptions;
/**
* Function for opening a JAR and returning all symbols that can be referenced from inside of that
* jar.
*/
@VisibleForTesting
static interface JarResolver {
public ImmutableSet<String> resolve(ProjectFilesystem filesystem, Path relativeClassPath);
}
private static final JarResolver JAR_RESOLVER =
new JarResolver() {
@Override
public ImmutableSet<String> resolve(ProjectFilesystem filesystem, Path relativeClassPath) {
ImmutableSet.Builder<String> topLevelSymbolsBuilder = ImmutableSet.builder();
try {
Path classPath = filesystem.getFileForRelativePath(relativeClassPath).toPath();
ClassLoader loader = URLClassLoader.newInstance(
new URL[]{classPath.toUri().toURL()},
/* parent */ null);
// For every class contained in that jar, check to see if the package name
// (e.g. com.facebook.foo), the simple name (e.g. ImmutableSet) or the name
// (e.g com.google.common.collect.ImmutableSet) is one of the missing symbols.
for (ClassPath.ClassInfo classInfo : ClassPath.from(loader).getTopLevelClasses()) {
topLevelSymbolsBuilder.add(classInfo.getPackageName(),
classInfo.getSimpleName(),
classInfo.getName());
}
} catch (IOException e) {
// Since this simply is a heuristic, return an empty set if we fail to load a jar.
return topLevelSymbolsBuilder.build();
}
return topLevelSymbolsBuilder.build();
}
};
public DefaultJavaLibrary(
BuildRuleParams params,
SourcePathResolver resolver,
Set<? extends SourcePath> srcs,
Set<? extends SourcePath> resources,
Optional<Path> proguardConfig,
ImmutableList<String> postprocessClassesCommands,
ImmutableSortedSet<BuildRule> exportedDeps,
ImmutableSortedSet<BuildRule> providedDeps,
ImmutableSet<Path> additionalClasspathEntries,
JavacOptions javacOptions,
Optional<Path> resourcesRoot) {
super(params, resolver);
// Exported deps are meant to be forwarded onto the CLASSPATH for dependents,
// and so only make sense for java library types.
for (BuildRule dep : exportedDeps) {
if (!(dep instanceof JavaLibrary)) {
throw new HumanReadableException(
params.getBuildTarget() + ": exported dep " +
dep.getBuildTarget() + " (" + dep.getType() + ") " +
"must be a type of java library.");
}
}
this.srcs = ImmutableSortedSet.copyOf(srcs);
this.resources = ImmutableSortedSet.copyOf(resources);
this.proguardConfig = proguardConfig;
this.postprocessClassesCommands = postprocessClassesCommands;
this.exportedDeps = exportedDeps;
this.providedDeps = providedDeps;
this.additionalClasspathEntries = additionalClasspathEntries;
this.javacOptions = javacOptions;
this.resourcesRoot = resourcesRoot;
if (!srcs.isEmpty() || !resources.isEmpty()) {
this.outputJar = Optional.of(getOutputJarPath(getBuildTarget()));
} else {
this.outputJar = Optional.absent();
}
this.outputClasspathEntriesSupplier =
Suppliers.memoize(new Supplier<ImmutableSetMultimap<JavaLibrary, Path>>() {
@Override
public ImmutableSetMultimap<JavaLibrary, Path> get() {
return JavaLibraryClasspathProvider.getOutputClasspathEntries(
DefaultJavaLibrary.this,
outputJar);
}
});
this.transitiveClasspathEntriesSupplier =
Suppliers.memoize(new Supplier<ImmutableSetMultimap<JavaLibrary, Path>>() {
@Override
public ImmutableSetMultimap<JavaLibrary, Path> get() {
return JavaLibraryClasspathProvider.getTransitiveClasspathEntries(
DefaultJavaLibrary.this,
outputJar);
}
});
this.declaredClasspathEntriesSupplier =
Suppliers.memoize(new Supplier<ImmutableSetMultimap<JavaLibrary, Path>>() {
@Override
public ImmutableSetMultimap<JavaLibrary, Path> get() {
return JavaLibraryClasspathProvider.getDeclaredClasspathEntries(
DefaultJavaLibrary.this);
}
});
this.buildOutputInitializer = new BuildOutputInitializer<>(params.getBuildTarget(), this);
}
/**
* @param outputDirectory Directory to write class files to
* @param transitiveClasspathEntries Classpaths of all transitive dependencies.
* @param declaredClasspathEntries Classpaths of all declared dependencies.
* @param javacOptions javac configuration.
* @param suggestBuildRules Function to convert from missing symbols to the suggested rules.
* @param commands List of steps to add to.
*/
@VisibleForTesting
void createCommandsForJavac(
Path outputDirectory,
ImmutableSet<Path> transitiveClasspathEntries,
ImmutableSet<Path> declaredClasspathEntries,
JavacOptions javacOptions,
BuildDependencies buildDependencies,
Optional<JavacStep.SuggestBuildRules> suggestBuildRules,
ImmutableList.Builder<Step> commands,
BuildTarget target) {
// Make sure that this directory exists because ABI information will be written here.
Step mkdir = new MakeCleanDirectoryStep(getPathToAbiOutputDir());
commands.add(mkdir);
// Only run javac if there are .java files to compile.
if (!getJavaSrcs().isEmpty()) {
Path pathToSrcsList = BuildTargets.getGenPath(getBuildTarget(), "__%s__srcs");
commands.add(new MkdirStep(pathToSrcsList.getParent()));
Optional<Path> workingDirectory;
if (getJavac().isUsingWorkspace()) {
Path scratchDir = BuildTargets.getGenPath(target, "lib__%s____working_directory");
commands.add(new MakeCleanDirectoryStep(scratchDir));
workingDirectory = Optional.of(scratchDir);
} else {
workingDirectory = Optional.absent();
}
JavacStep javacStep = new JavacStep(
outputDirectory,
workingDirectory,
getJavaSrcs(),
Optional.of(pathToSrcsList),
transitiveClasspathEntries,
declaredClasspathEntries,
javacOptions,
target,
buildDependencies,
suggestBuildRules);
commands.add(javacStep);
}
}
/**
* Creates the total ABI key for this rule. If export_deps is true, the total key is computed by
* hashing the ABI keys of the dependencies together with the ABI key of this rule. If export_deps
* is false, the standalone ABI key for this rule is used as the total key.
* @param abiKey the standalone ABI key for this rule.
* @return total ABI key containing also the ABI keys of the dependencies.
*/
protected Sha1HashCode createTotalAbiKey(Sha1HashCode abiKey) {
if (getExportedDeps().isEmpty()) {
return abiKey;
}
SortedSet<HasBuildTarget> depsForAbiKey = getDepsForAbiKey();
// Hash the ABI keys of all dependencies together with ABI key for the current rule.
Hasher hasher = createHasherWithAbiKeyForDeps(depsForAbiKey);
hasher.putUnencodedChars(abiKey.getHash());
return ImmutableSha1HashCode.of(hasher.hash().toString());
}
private Path getPathToAbiOutputDir() {
return BuildTargets.getGenPath(getBuildTarget(), "lib__%s__abi");
}
private static Path getOutputJarDirPath(BuildTarget target) {
return BuildTargets.getGenPath(target, "lib__%s__output");
}
private static Path getOutputJarPath(BuildTarget target) {
return Paths.get(
String.format(
"%s/%s.jar",
getOutputJarDirPath(target),
target.getShortNameAndFlavorPostfix()));
}
/**
* @return directory path relative to the project root where .class files will be generated.
* The return value does not end with a slash.
*/
private static Path getClassesDir(BuildTarget target) {
return BuildTargets.getBinPath(target, "lib__%s__classes");
}
/**
* Finds all deps that implement JavaLibraryRule and hash their ABI keys together.
*/
@Override
public Sha1HashCode getAbiKeyForDeps() {
return ImmutableSha1HashCode.of(
createHasherWithAbiKeyForDeps(getDepsForAbiKey()).hash().toString());
}
/**
* Returns a sorted set containing the dependencies which will be hashed in the final ABI key.
* @return the dependencies to be hashed in the final ABI key.
*/
private SortedSet<HasBuildTarget> getDepsForAbiKey() {
SortedSet<HasBuildTarget> rulesWithAbiToConsider = Sets.newTreeSet(BUILD_TARGET_COMPARATOR);
for (BuildRule dep : Iterables.concat(getDepsForTransitiveClasspathEntries(), providedDeps)) {
// This looks odd. DummyJavaAbiRule contains a Buildable that isn't a JavaAbiRule.
if (dep instanceof HasJavaAbi) {
if (dep instanceof JavaLibrary) {
JavaLibrary javaRule = (JavaLibrary) dep;
rulesWithAbiToConsider.addAll(javaRule.getOutputClasspathEntries().keys());
} else {
rulesWithAbiToConsider.add(dep);
}
}
}
// We also need to iterate over inputs that are SourcePaths, since they're only listed as
// compile-time deps and not in the "deps" field. If any of these change, we should recompile
// the library, since we will (at least) need to repack it.
rulesWithAbiToConsider.addAll(
getResolver().filterBuildRuleInputs(Iterables.concat(srcs, resources)));
return rulesWithAbiToConsider;
}
/**
* Creates a Hasher containing the ABI keys of the dependencies.
* @param rulesWithAbiToConsider a sorted set containing the dependencies whose ABI key will be
* added to the hasher.
* @return a Hasher containing the ABI keys of the dependencies.
*/
private Hasher createHasherWithAbiKeyForDeps(SortedSet<HasBuildTarget> rulesWithAbiToConsider) {
Hasher hasher = Hashing.sha1().newHasher();
for (HasBuildTarget candidate : rulesWithAbiToConsider) {
if (candidate == this) {
continue;
}
if (candidate instanceof HasJavaAbi) {
Sha1HashCode abiKey = ((HasJavaAbi) candidate).getAbiKey();
hasher.putUnencodedChars(abiKey.getHash());
} else if (candidate instanceof BuildRule) {
HashCode hashCode = ((BuildRule) candidate).getRuleKey().getHashCode();
hasher.putBytes(hashCode.asBytes());
}
}
return hasher;
}
@Override
public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
builder
.setReflectively("postprocessClassesCommands", postprocessClassesCommands)
.setReflectively("resources", resources)
.setReflectively("resources_root", resourcesRoot.toString())
// provided_deps are already included in the rule key, but we need to explicitly call them
// out as "provided" because changing a dep from provided to transtitive should result in a
// re-build (otherwise, we'd get a rule key match).
.setReflectively("provided_deps", providedDeps);
return javacOptions.appendToRuleKey(builder, "javacOptions");
}
@Override
public BuildableProperties getProperties() {
return OUTPUT_TYPE;
}
@Override
public ImmutableSortedSet<Path> getJavaSrcs() {
return ImmutableSortedSet.copyOf(getResolver().getAllPaths(srcs));
}
@Override
public ImmutableSortedSet<BuildRule> getDepsForTransitiveClasspathEntries() {
return ImmutableSortedSet.copyOf(Sets.union(getDeclaredDeps(), exportedDeps));
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getTransitiveClasspathEntries() {
return transitiveClasspathEntriesSupplier.get();
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getDeclaredClasspathEntries() {
return declaredClasspathEntriesSupplier.get();
}
@Override
public ImmutableSetMultimap<JavaLibrary, Path> getOutputClasspathEntries() {
return outputClasspathEntriesSupplier.get();
}
@Override
public AnnotationProcessingParams getAnnotationProcessingParams() {
return javacOptions.getAnnotationProcessingParams();
}
@Override
public ImmutableCollection<Path> getInputsToCompareToOutput() {
ImmutableList.Builder<Path> builder = ImmutableList.builder();
builder.addAll(getResolver().filterInputsToCompareToOutput(this.srcs));
builder.addAll(getResolver().filterInputsToCompareToOutput(this.resources));
Optionals.addIfPresent(this.proguardConfig, builder);
return builder.build();
}
@Override
public ImmutableSortedSet<BuildRule> getExportedDeps() {
return exportedDeps;
}
public JavacOptions getJavacOptions() {
return javacOptions;
}
/**
* Building a java_library() rule entails compiling the .java files specified in the srcs
* attribute. They are compiled into a directory under
* {@link com.facebook.buck.util.BuckConstant#BIN_DIR}.
*/
@Override
public final ImmutableList<Step> getBuildSteps(
BuildContext context,
BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
// Only override the bootclasspath if this rule is supposed to compile Android code.
if (getProperties().is(ANDROID)) {
this.javacOptions = JavacOptions.builder(javacOptions)
.setBootclasspath(context.getAndroidBootclasspathSupplier().get())
.build();
}
ImmutableSetMultimap<JavaLibrary, Path> transitiveClasspathEntries =
ImmutableSetMultimap.<JavaLibrary, Path>builder()
.putAll(getTransitiveClasspathEntries())
.putAll(this, additionalClasspathEntries)
.build();
ImmutableSetMultimap<JavaLibrary, Path> declaredClasspathEntries =
ImmutableSetMultimap.<JavaLibrary, Path>builder()
.putAll(getDeclaredClasspathEntries())
.putAll(this, additionalClasspathEntries)
.build();
// Javac requires that the root directory for generated sources already exist.
Path annotationGenFolder =
javacOptions.getAnnotationProcessingParams().getGeneratedSourceFolderName();
if (annotationGenFolder != null) {
MakeCleanDirectoryStep mkdirGeneratedSources =
new MakeCleanDirectoryStep(annotationGenFolder);
steps.add(mkdirGeneratedSources);
buildableContext.recordArtifactsInDirectory(annotationGenFolder);
}
// Always create the output directory, even if there are no .java files to compile because there
// might be resources that need to be copied there.
BuildTarget target = getBuildTarget();
Path outputDirectory = getClassesDir(target);
steps.add(new MakeCleanDirectoryStep(outputDirectory));
Optional<JavacStep.SuggestBuildRules> suggestBuildRule =
createSuggestBuildFunction(context,
transitiveClasspathEntries,
declaredClasspathEntries,
JAR_RESOLVER);
// We don't want to add these to the declared or transitive deps, since they're only used at
// compile time.
Collection<Path> provided = JavaLibraryClasspathProvider.getJavaLibraryDeps(providedDeps)
.transformAndConcat(
new Function<JavaLibrary, Collection<Path>>() {
@Override
public Collection<Path> apply(JavaLibrary input) {
return input.getOutputClasspathEntries().values();
}
})
.filter(Predicates.notNull())
.toSet();
ImmutableSet<Path> transitive = ImmutableSet.<Path>builder()
.addAll(transitiveClasspathEntries.values())
.addAll(provided)
.build();
ImmutableSet<Path> declared = ImmutableSet.<Path>builder()
.addAll(declaredClasspathEntries.values())
.addAll(provided)
.build();
// This adds the javac command, along with any supporting commands.
createCommandsForJavac(
outputDirectory,
transitive,
declared,
javacOptions,
context.getBuildDependencies(),
suggestBuildRule,
steps,
target);
addPostprocessClassesCommands(steps, postprocessClassesCommands, outputDirectory);
// If there are resources, then link them to the appropriate place in the classes directory.
JavaPackageFinder finder = context.getJavaPackageFinder();
if (resourcesRoot.isPresent()) {
finder = new ResourcesRootPackageFinder(resourcesRoot.get(), finder);
}
steps.add(
new CopyResourcesStep(
getResolver(),
target,
resources,
outputDirectory,
finder));
steps.add(new MakeCleanDirectoryStep(getOutputJarDirPath(target)));
Path abiJar = getOutputJarDirPath(target)
.resolve(String.format("%s-abi.jar", target.getShortNameAndFlavorPostfix()));
steps.add(new MkdirStep(abiJar.getParent()));
if (outputJar.isPresent()) {
Path output = outputJar.get();
steps.add(new JarDirectoryStep(
output,
Collections.singleton(outputDirectory),
/* mainClass */ null,
/* manifestFile */ null));
buildableContext.recordArtifact(output);
// Calculate the ABI.
steps.add(new CalculateAbiStep(buildableContext, output, abiJar));
} else {
Path scratch = BuildTargets.getBinPath(
target,
String.format("%%s/%s-temp-abi.jar", target.getShortNameAndFlavorPostfix()));
steps.add(new MakeCleanDirectoryStep(scratch.getParent()));
steps.add(new TouchStep(scratch));
steps.add(new CalculateAbiStep(buildableContext, scratch, abiJar));
}
JavaLibraryRules.addAccumulateClassNamesStep(this, buildableContext, steps);
return steps.build();
}
/**
* @param transitiveNotDeclaredRule A {@link BuildRule} that is contained in the transitive
* dependency list but is not declared as a dependency.
* @param failedImports A Set of remaining failed imports. This function will mutate this set
* and remove any imports satisfied by {@code transitiveNotDeclaredDep}.
* @return whether or not adding {@code transitiveNotDeclaredDep} as a dependency to this build
* rule would have satisfied one of the {@code failedImports}.
*/
private boolean isMissingBuildRule(ProjectFilesystem filesystem,
BuildRule transitiveNotDeclaredRule,
Set<String> failedImports,
JarResolver jarResolver) {
if (!(transitiveNotDeclaredRule instanceof JavaLibrary)) {
return false;
}
ImmutableSet<Path> classPaths =
ImmutableSet.copyOf(
((JavaLibrary) transitiveNotDeclaredRule).getOutputClasspathEntries().values());
boolean containsMissingBuildRule = false;
// Open the output jar for every jar contained as the output of transitiveNotDeclaredDep. With
// the exception of rules that export their dependencies, this will result in a single
// classpath.
for (Path classPath : classPaths) {
ImmutableSet<String> topLevelSymbols = jarResolver.resolve(filesystem, classPath);
for (String symbolName : topLevelSymbols) {
if (failedImports.contains(symbolName)) {
failedImports.remove(symbolName);
containsMissingBuildRule = true;
// If we've found all of the missing imports, bail out early.
if (failedImports.isEmpty()) {
return true;
}
}
}
}
return containsMissingBuildRule;
}
/**
* @return A function that takes a list of failed imports from a javac invocation and returns a
* set of rules to suggest that the developer import to satisfy those imports.
*/
@VisibleForTesting
Optional<JavacStep.SuggestBuildRules> createSuggestBuildFunction(
BuildContext context,
ImmutableSetMultimap<JavaLibrary, Path> transitiveClasspathEntries,
ImmutableSetMultimap<JavaLibrary, Path> declaredClasspathEntries,
final JarResolver jarResolver) {
if (context.getBuildDependencies() != BuildDependencies.WARN_ON_TRANSITIVE) {
return Optional.absent();
}
final Set<JavaLibrary> transitiveNotDeclaredDeps = Sets.difference(
transitiveClasspathEntries.keySet(),
Sets.union(ImmutableSet.of(this), declaredClasspathEntries.keySet()));
TraversableGraph<BuildRule> graph = context.getActionGraph();
final ImmutableList<JavaLibrary> sortedTransitiveNotDeclaredDeps = FluentIterable
.from(TopologicalSort.sort(graph, Predicates.<BuildRule>alwaysTrue()))
.filter(JavaLibrary.class)
.filter(Predicates.in(transitiveNotDeclaredDeps))
.toList()
.reverse();
JavacStep.SuggestBuildRules suggestBuildRuleFn =
new JavacStep.SuggestBuildRules() {
@Override
public ImmutableSet<String> suggest(ProjectFilesystem filesystem,
ImmutableSet<String> failedImports) {
ImmutableSet.Builder<String> suggestedDeps = ImmutableSet.builder();
Set<String> remainingImports = Sets.newHashSet(failedImports);
for (JavaLibrary transitiveNotDeclaredDep : sortedTransitiveNotDeclaredDeps) {
if (isMissingBuildRule(filesystem,
transitiveNotDeclaredDep,
remainingImports,
jarResolver)) {
suggestedDeps.add(transitiveNotDeclaredDep.getFullyQualifiedName());
}
// If we've wiped out all remaining imports, break the loop looking for them.
if (remainingImports.isEmpty()) {
break;
}
}
return suggestedDeps.build();
}
};
return Optional.of(suggestBuildRuleFn);
}
/**
* Instructs this rule to report the ABI it has on disk as its current ABI.
*/
@Override
public JavaLibrary.Data initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
return JavaLibraryRules.initializeFromDisk(getBuildTarget(), onDiskBuildInfo);
}
@Override
public BuildOutputInitializer<Data> getBuildOutputInitializer() {
return buildOutputInitializer;
}
@Override
public Sha1HashCode getAbiKey() {
return buildOutputInitializer.getBuildOutput().getAbiKey();
}
@Override
public ImmutableSortedMap<String, HashCode> getClassNamesToHashes() {
return buildOutputInitializer.getBuildOutput().getClassNamesToHashes();
}
/**
* Adds a BashStep for each postprocessClasses command that runs the command followed by the
* outputDirectory of javac outputs.
*
* The expectation is that the command will inspect and update the directory by
* modifying, adding, and deleting the .class files in the directory.
*
* The outputDirectory should be a valid java root. I.e., if outputDirectory
* is buck-out/bin/java/abc/lib__abc__classes/, then a contained class abc.AbcModule
* should be at buck-out/bin/java/abc/lib__abc__classes/abc/AbcModule.class
*
* @param commands the list of Steps we are building.
* @param postprocessClassesCommands the list of commands to post-process .class files.
* @param outputDirectory the directory that will contain all the javac output.
*/
@VisibleForTesting
static void addPostprocessClassesCommands(
ImmutableList.Builder<Step> commands,
List<String> postprocessClassesCommands,
Path outputDirectory) {
for (final String postprocessClassesCommand : postprocessClassesCommands) {
BashStep bashStep = new BashStep(postprocessClassesCommand + " " + outputDirectory);
commands.add(bashStep);
}
}
@VisibleForTesting
public Javac getJavac() {
return javacOptions.getJavac();
}
@Override
@Nullable
public Path getPathToOutputFile() {
return outputJar.orNull();
}
@Override
public Iterable<AndroidPackageable> getRequiredPackageables() {
return AndroidPackageableCollector.getPackageableRules(ImmutableSortedSet.copyOf(
Sets.difference(
Sets.union(getDeclaredDeps(), exportedDeps),
providedDeps)));
}
@Override
public void addToCollector(AndroidPackageableCollector collector) {
if (outputJar.isPresent()) {
collector.addClasspathEntry(this, outputJar.get());
}
if (proguardConfig.isPresent()) {
collector.addProguardConfig(getBuildTarget(), proguardConfig.get());
}
}
}