Java tutorial
/* * 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.HasAndroidResourceDeps; import com.facebook.buck.android.UberRDotJavaUtil; import com.facebook.buck.graph.TopologicalSort; import com.facebook.buck.java.abi.AbiWriterProtocol; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetPattern; import com.facebook.buck.rules.AbiRule; import com.facebook.buck.rules.AbstractBuildRuleBuilder; import com.facebook.buck.rules.AbstractBuildRuleBuilderParams; import com.facebook.buck.rules.AnnotationProcessingData; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildDependencies; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildRuleType; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.BuildableProperties; import com.facebook.buck.rules.DoNotUseAbstractBuildable; import com.facebook.buck.rules.ExportDependencies; import com.facebook.buck.rules.JavaPackageFinder; import com.facebook.buck.rules.OnDiskBuildInfo; import com.facebook.buck.rules.ResourcesAttributeBuilder; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.Sha1HashCode; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePaths; import com.facebook.buck.rules.SrcsAttributeBuilder; import com.facebook.buck.step.AbstractExecutionStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.MkdirAndSymlinkFileStep; import com.facebook.buck.util.BuckConstant; import com.facebook.buck.util.MorePaths; import com.facebook.buck.util.ProjectFilesystem; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.reflect.ClassPath; import java.io.File; 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.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 DefaultJavaLibraryRule extends DoNotUseAbstractBuildable implements JavaLibraryRule, AbiRule, HasJavaSrcs, HasClasspathEntries, ExportDependencies { private final static BuildableProperties OUTPUT_TYPE = new BuildableProperties(LIBRARY); private final ImmutableSortedSet<String> srcs; private final ImmutableSortedSet<SourcePath> resources; private final Optional<String> outputJar; private final List<String> inputsToConsiderForCachingPurposes; private final Optional<String> proguardConfig; private final ImmutableSortedSet<BuildRule> exportedDeps; private final Supplier<ImmutableSetMultimap<JavaLibraryRule, String>> outputClasspathEntriesSupplier; private final Supplier<ImmutableSetMultimap<JavaLibraryRule, String>> transitiveClasspathEntriesSupplier; private final Supplier<ImmutableSetMultimap<JavaLibraryRule, String>> declaredClasspathEntriesSupplier; private final JavacOptions javacOptions; /** * This returns the ABI key for this rule. This will be set <em>EITHER</em> as part of * {@link #initializeFromDisk(OnDiskBuildInfo)}, or while the build steps (in particular, the * javac step) for this rule are created. In the case of the latter, the {@link Supplier} is * guaranteed to be able to return (a possibly null) value after the build steps have been * executed. * <p> * This field should be set exclusively through {@link #setAbiKey(Supplier)} */ @Nullable private Supplier<Sha1HashCode> abiKeySupplier; /** * 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 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(); } }; /** * This is set in * {@link com.facebook.buck.rules.Buildable#getBuildSteps(com.facebook.buck.rules.BuildContext, BuildableContext)} * and is available to subclasses. */ protected ImmutableList<HasAndroidResourceDeps> androidResourceDeps; protected DefaultJavaLibraryRule(BuildRuleParams buildRuleParams, Set<String> srcs, Set<? extends SourcePath> resources, Optional<String> proguardConfig, Set<BuildRule> exportedDeps, JavacOptions javacOptions) { super(buildRuleParams); this.srcs = ImmutableSortedSet.copyOf(srcs); this.resources = ImmutableSortedSet.copyOf(resources); this.proguardConfig = Preconditions.checkNotNull(proguardConfig); this.exportedDeps = ImmutableSortedSet.copyOf(exportedDeps); this.javacOptions = Preconditions.checkNotNull(javacOptions); if (!srcs.isEmpty() || !resources.isEmpty()) { this.outputJar = Optional.of(getOutputJarPath(getBuildTarget())); } else { this.outputJar = Optional.absent(); } // Note that both srcs and resources are sorted so that the list order is consistent even if // the iteration order of the sets passed to the constructor changes. See // AbstractBuildRule.getInputsToCompareToOutput() for details. ImmutableList.Builder<String> builder = ImmutableList.<String>builder().addAll(this.srcs); builder.addAll(SourcePaths.filterInputsToCompareToOutput(resources)); inputsToConsiderForCachingPurposes = builder.build(); outputClasspathEntriesSupplier = Suppliers .memoize(new Supplier<ImmutableSetMultimap<JavaLibraryRule, String>>() { @Override public ImmutableSetMultimap<JavaLibraryRule, String> get() { ImmutableSetMultimap.Builder<JavaLibraryRule, String> outputClasspathBuilder = ImmutableSetMultimap .builder(); Iterable<JavaLibraryRule> javaExportedLibraryDeps = Iterables.filter(getExportedDeps(), JavaLibraryRule.class); for (JavaLibraryRule rule : javaExportedLibraryDeps) { outputClasspathBuilder.putAll(rule, rule.getOutputClasspathEntries().values()); // If we have any exported deps, add an entry mapping ourselves to to their, // classpaths so when suggesting libraries to add we know that adding this library // would pull in it's deps. outputClasspathBuilder.putAll(DefaultJavaLibraryRule.this, rule.getOutputClasspathEntries().values()); } if (outputJar.isPresent()) { outputClasspathBuilder.put(DefaultJavaLibraryRule.this, getPathToOutputFile()); } return outputClasspathBuilder.build(); } }); transitiveClasspathEntriesSupplier = Suppliers .memoize(new Supplier<ImmutableSetMultimap<JavaLibraryRule, String>>() { @Override public ImmutableSetMultimap<JavaLibraryRule, String> get() { final ImmutableSetMultimap.Builder<JavaLibraryRule, String> classpathEntries = ImmutableSetMultimap .builder(); ImmutableSetMultimap<JavaLibraryRule, String> classpathEntriesForDeps = Classpaths .getClasspathEntries(getDeps()); ImmutableSetMultimap<JavaLibraryRule, String> classpathEntriesForExportedsDeps = Classpaths .getClasspathEntries(getExportedDeps()); classpathEntries.putAll(classpathEntriesForDeps); // If we have any exported deps, add an entry mapping ourselves to to their classpaths, // so when suggesting libraries to add we know that adding this library would pull in // it's deps. if (!classpathEntriesForExportedsDeps.isEmpty()) { classpathEntries.putAll(DefaultJavaLibraryRule.this, classpathEntriesForExportedsDeps.values()); } // Only add ourselves to the classpath if there's a jar to be built. if (outputJar.isPresent()) { classpathEntries.putAll(DefaultJavaLibraryRule.this, getPathToOutputFile()); } return classpathEntries.build(); } }); declaredClasspathEntriesSupplier = Suppliers .memoize(new Supplier<ImmutableSetMultimap<JavaLibraryRule, String>>() { @Override public ImmutableSetMultimap<JavaLibraryRule, String> get() { final ImmutableSetMultimap.Builder<JavaLibraryRule, String> classpathEntries = ImmutableSetMultimap .builder(); Iterable<JavaLibraryRule> javaLibraryDeps = Iterables.filter(getDeps(), JavaLibraryRule.class); for (JavaLibraryRule rule : javaLibraryDeps) { classpathEntries.putAll(rule, rule.getOutputClasspathEntries().values()); } return classpathEntries.build(); } }); } /** * @param outputDirectory Directory to write class files to * @param transitiveClasspathEntries Classpaths of all transitive dependencies. * @param declaredClasspathEntries Classpaths of all declared dependencies. * @param javacOptions options to use when compiling code. * @param suggestBuildRules Function to convert from missing symbols to the suggested rules. * @param commands List of steps to add to. */ private void createCommandsForJavac(String outputDirectory, ImmutableSet<String> transitiveClasspathEntries, ImmutableSet<String> declaredClasspathEntries, JavacOptions javacOptions, BuildDependencies buildDependencies, Optional<JavacInMemoryStep.SuggestBuildRules> suggestBuildRules, ImmutableList.Builder<Step> commands) { // 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()) { final JavacInMemoryStep javac = new JavacInMemoryStep(outputDirectory, getJavaSrcs(), transitiveClasspathEntries, declaredClasspathEntries, javacOptions, Optional.of(getPathToAbiOutputFile()), Optional.of(getFullyQualifiedName()), buildDependencies, suggestBuildRules); commands.add(javac); // Create a supplier that extracts the ABI key from javac after it executes. setAbiKey(Suppliers.memoize(new Supplier<Sha1HashCode>() { @Override public Sha1HashCode get() { return createTotalAbiKey(javac.getAbiKey()); } })); } else { // When there are no .java files to compile, the ABI key should be a constant. setAbiKey(Suppliers.ofInstance(createTotalAbiKey(new Sha1HashCode(AbiWriterProtocol.EMPTY_ABI_KEY)))); } } /** * 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<JavaLibraryRule> 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 new Sha1HashCode(hasher.hash().toString()); } private String getPathToAbiOutputDir() { BuildTarget target = getBuildTarget(); return String.format("%s/%slib__%s__abi", BuckConstant.GEN_DIR, target.getBasePathWithSlash(), target.getShortName()); } private String getPathToAbiOutputFile() { return String.format("%s/abi", getPathToAbiOutputDir()); } private static String getOutputJarDirPath(BuildTarget target) { return String.format("%s/%slib__%s__output", BuckConstant.GEN_DIR, target.getBasePathWithSlash(), target.getShortName()); } private static String getOutputJarPath(BuildTarget target) { return String.format("%s/%s.jar", getOutputJarDirPath(target), target.getShortName()); } /** * @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 String getClassesDir(BuildTarget target) { return String.format("%s/%slib__%s__classes", BuckConstant.BIN_DIR, target.getBasePathWithSlash(), target.getShortName()); } /** * Finds all deps that implement JavaLibraryRule and hash their ABI keys together. */ @Override public Sha1HashCode getAbiKeyForDeps() { return new Sha1HashCode(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<JavaLibraryRule> getDepsForAbiKey() { SortedSet<JavaLibraryRule> rulesWithAbiToConsider = Sets.newTreeSet(); for (BuildRule dep : getDeps()) { if (dep instanceof JavaLibraryRule) { JavaLibraryRule javaRule = (JavaLibraryRule) dep; rulesWithAbiToConsider.addAll(javaRule.getOutputClasspathEntries().keys()); } } 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<JavaLibraryRule> rulesWithAbiToConsider) { Hasher hasher = Hashing.sha1().newHasher(); for (JavaLibraryRule ruleWithAbiToConsider : rulesWithAbiToConsider) { if (ruleWithAbiToConsider == this) { continue; } Sha1HashCode abiKey = ruleWithAbiToConsider.getAbiKey(); hasher.putUnencodedChars(abiKey.getHash()); } return hasher; } @Override public RuleKey.Builder appendToRuleKey(RuleKey.Builder builder) throws IOException { super.appendToRuleKey(builder).set("exportedDeps", exportedDeps).set("srcs", srcs) .setSourcePaths("resources", resources).set("proguard", proguardConfig); javacOptions.appendToRuleKey(builder); return builder; } @Override public BuildRuleType getType() { return BuildRuleType.JAVA_LIBRARY; } @Override public BuildableProperties getProperties() { return OUTPUT_TYPE; } @Override public ImmutableSortedSet<String> getJavaSrcs() { return srcs; } @Override public ImmutableSetMultimap<JavaLibraryRule, String> getTransitiveClasspathEntries() { return transitiveClasspathEntriesSupplier.get(); } @Override public ImmutableSetMultimap<JavaLibraryRule, String> getDeclaredClasspathEntries() { return declaredClasspathEntriesSupplier.get(); } @Override public ImmutableSetMultimap<JavaLibraryRule, String> getOutputClasspathEntries() { return outputClasspathEntriesSupplier.get(); } @Override public AnnotationProcessingData getAnnotationProcessingData() { return javacOptions.getAnnotationProcessingData(); } public Optional<String> getProguardConfig() { return proguardConfig; } @Override @Nullable public List<String> getInputsToCompareToOutput() { return inputsToConsiderForCachingPurposes; } @Override public ImmutableSortedSet<BuildRule> getExportedDeps() { return exportedDeps; } /** * Building a java_library() rule entails compiling the .java files specified in the srcs * attribute. They are compiled into a directory under {@link BuckConstant#BIN_DIR}. */ @Override public final List<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) throws IOException { ImmutableList.Builder<Step> commands = ImmutableList.builder(); BuildTarget buildTarget = getBuildTarget(); JavacOptions javacOptions = this.javacOptions; // Only override the bootclasspath if this rule is supposed to compile Android code. if (getProperties().is(ANDROID)) { javacOptions = JavacOptions.builder(this.javacOptions) .setBootclasspath(context.getAndroidBootclasspathSupplier().get()).build(); } // If this rule depends on AndroidResourceRules, then we need to generate the R.java files that // this rule needs in order to be able to compile itself. androidResourceDeps = UberRDotJavaUtil.getAndroidResourceDeps(this); boolean dependsOnAndroidResourceRules = !androidResourceDeps.isEmpty(); if (dependsOnAndroidResourceRules) { UberRDotJavaUtil.createDummyRDotJavaFiles(androidResourceDeps, buildTarget, commands); } ImmutableSetMultimap<JavaLibraryRule, String> transitiveClasspathEntries = getTransitiveClasspathEntries(); ImmutableSetMultimap<JavaLibraryRule, String> declaredClasspathEntries = getDeclaredClasspathEntries(); // If this rule depends on AndroidResourceRules, then we need to include the compiled R.java // files on the classpath when compiling this rule. if (dependsOnAndroidResourceRules) { ImmutableSetMultimap.Builder<JavaLibraryRule, String> transitiveClasspathEntriesWithRDotJava = ImmutableSetMultimap .builder(); transitiveClasspathEntriesWithRDotJava.putAll(transitiveClasspathEntries); ImmutableSetMultimap.Builder<JavaLibraryRule, String> declaredClasspathEntriesWithRDotJava = ImmutableSetMultimap .builder(); declaredClasspathEntriesWithRDotJava.putAll(declaredClasspathEntries); ImmutableSet<String> rDotJavaClasspath = ImmutableSet .of(UberRDotJavaUtil.getRDotJavaBinFolder(buildTarget)); transitiveClasspathEntriesWithRDotJava.putAll(this, rDotJavaClasspath); declaredClasspathEntriesWithRDotJava.putAll(this, rDotJavaClasspath); declaredClasspathEntries = declaredClasspathEntriesWithRDotJava.build(); transitiveClasspathEntries = transitiveClasspathEntriesWithRDotJava.build(); } // Javac requires that the root directory for generated sources already exist. String annotationGenFolder = javacOptions.getAnnotationProcessingData().getGeneratedSourceFolderName(); if (annotationGenFolder != null) { MakeCleanDirectoryStep mkdirGeneratedSources = new MakeCleanDirectoryStep(annotationGenFolder); commands.add(mkdirGeneratedSources); } // 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. String outputDirectory = getClassesDir(getBuildTarget()); commands.add(new MakeCleanDirectoryStep(outputDirectory)); Optional<JavacInMemoryStep.SuggestBuildRules> suggestBuildRule = createSuggestBuildFunction(context, transitiveClasspathEntries, declaredClasspathEntries, JAR_RESOLVER); // This adds the javac command, along with any supporting commands. createCommandsForJavac(outputDirectory, ImmutableSet.copyOf(transitiveClasspathEntries.values()), ImmutableSet.copyOf(declaredClasspathEntries.values()), javacOptions, context.getBuildDependencies(), suggestBuildRule, commands); // If there are resources, then link them to the appropriate place in the classes directory. addResourceCommands(context, commands, outputDirectory, context.getJavaPackageFinder()); if (outputJar.isPresent()) { commands.add(new MakeCleanDirectoryStep(getOutputJarDirPath(getBuildTarget()))); commands.add(new JarDirectoryStep(outputJar.get(), Collections.singleton(outputDirectory), /* mainClass */ null, /* manifestFile */ null)); } Preconditions.checkNotNull(abiKeySupplier, "abiKeySupplier must be set so that getAbiKey() will " + "return a non-null value if this rule builds successfully."); addStepsToRecordAbiToDisk(commands, buildableContext); return commands.build(); } /** * Assuming the build has completed successfully, the ABI should have been computed, and it should * be stored for subsequent builds. */ private void addStepsToRecordAbiToDisk(ImmutableList.Builder<Step> commands, final BuildableContext buildableContext) throws IOException { // Note that the parent directories for all of the files written by these steps should already // have been created by a previous step. Therefore, there is no reason to add a MkdirStep here. commands.add(new AbstractExecutionStep("recording ABI metadata") { @Override public int execute(ExecutionContext context) { Sha1HashCode abiKey = abiKeySupplier.get(); buildableContext.addMetadata(ABI_KEY_ON_DISK_METADATA, abiKey.getHash()); return 0; } }); buildableContext.addMetadata(ABI_KEY_FOR_DEPS_ON_DISK_METADATA, getAbiKeyForDeps().getHash()); } /** * @param transitiveNotDeclaredDep 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 transitiveNotDeclaredDep, Set<String> failedImports, JarResolver jarResolver) { if (!(transitiveNotDeclaredDep instanceof JavaLibraryRule)) { return false; } ImmutableSet<String> classPaths = ImmutableSet .copyOf(((JavaLibraryRule) transitiveNotDeclaredDep).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 (String classPath : classPaths) { ImmutableSet<String> topLevelSymbols = jarResolver.resolve(filesystem, Paths.get(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<JavacInMemoryStep.SuggestBuildRules> createSuggestBuildFunction(BuildContext context, ImmutableSetMultimap<JavaLibraryRule, String> transitiveClasspathEntries, ImmutableSetMultimap<JavaLibraryRule, String> declaredClasspathEntries, final JarResolver jarResolver) { if (context.getBuildDependencies() != BuildDependencies.WARN_ON_TRANSITIVE) { return Optional.absent(); } final Set<JavaLibraryRule> transitiveNotDeclaredDeps = Sets.difference(transitiveClasspathEntries.keySet(), Sets.union(ImmutableSet.of(this), declaredClasspathEntries.keySet())); final ImmutableList<BuildRule> sortedTransitiveNotDeclaredDeps = ImmutableList .copyOf(TopologicalSort.sort(context.getDependencyGraph(), new Predicate<BuildRule>() { @Override public boolean apply(BuildRule input) { return transitiveNotDeclaredDeps.contains(input); } })).reverse(); JavacInMemoryStep.SuggestBuildRules suggestBuildRuleFn = new JavacInMemoryStep.SuggestBuildRules() { @Override public ImmutableSet<String> suggest(ProjectFilesystem filesystem, ImmutableSet<String> failedImports) { ImmutableSet.Builder<String> suggestedDeps = ImmutableSet.builder(); Set<String> remainingImports = Sets.newHashSet(failedImports); for (BuildRule transitiveNotDeclaredDep : sortedTransitiveNotDeclaredDeps) { boolean ruleCanSeeDep = transitiveNotDeclaredDep .isVisibleTo(DefaultJavaLibraryRule.this.getBuildTarget()); if (ruleCanSeeDep && 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 void initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) { Optional<Sha1HashCode> abiKeyHash = onDiskBuildInfo.getHash(AbiRule.ABI_KEY_ON_DISK_METADATA); if (abiKeyHash.isPresent()) { setAbiKey(Suppliers.ofInstance(abiKeyHash.get())); } else { throw new IllegalStateException( String.format("Should not be initializing %s from disk if the ABI key is not written.", this)); } } @Override public Sha1HashCode getAbiKey() { Preconditions.checkState(isRuleBuilt(), "%s must be built before its ABI key can be returned.", this); return abiKeySupplier.get(); } private void setAbiKey(Supplier<Sha1HashCode> abiKeySupplier) { Preconditions.checkState(this.abiKeySupplier == null, "abiKeySupplier should be set only once"); this.abiKeySupplier = abiKeySupplier; } @VisibleForTesting void addResourceCommands(BuildContext context, ImmutableList.Builder<Step> commands, String outputDirectory, JavaPackageFinder javaPackageFinder) { if (!resources.isEmpty()) { String targetPackageDir = javaPackageFinder .findJavaPackageForPath(getBuildTarget().getBasePathWithSlash()) .replace('.', File.separatorChar); for (SourcePath rawResource : resources) { // If the path to the file defining this rule were: // "first-party/orca/lib-http/tests/com/facebook/orca/BUILD" // // And the value of resource were: // "first-party/orca/lib-http/tests/com/facebook/orca/protocol/base/batch_exception1.txt" // // Then javaPackageAsPath would be: // "com/facebook/orca/protocol/base/" // // And the path that we would want to copy to the classes directory would be: // "com/facebook/orca/protocol/base/batch_exception1.txt" // // Therefore, some path-wrangling is required to produce the correct string. Path resource = MorePaths.separatorsToUnix(rawResource.resolve(context)); String javaPackageAsPath = javaPackageFinder.findJavaPackageFolderForPath(resource.toString()); Path relativeSymlinkPath; if (resource.startsWith(BuckConstant.BUCK_OUTPUT_DIRECTORY) || resource.startsWith(BuckConstant.GEN_DIR) || resource.startsWith(BuckConstant.BIN_DIR) || resource.startsWith(BuckConstant.ANNOTATION_DIR)) { // Handle the case where we depend on the output of another BuildRule. In that case, just // grab the output and put in the same package as this target would be in. relativeSymlinkPath = Paths.get( String.format("%s/%s", targetPackageDir, rawResource.resolve(context).getFileName())); } else if ("".equals(javaPackageAsPath)) { // In this case, the project root is acting as the default package, so the resource path // works fine. relativeSymlinkPath = resource; } else { int lastIndex = resource.toString().lastIndexOf(javaPackageAsPath); Preconditions.checkState(lastIndex >= 0, "Resource path %s must contain %s", resource, javaPackageAsPath); relativeSymlinkPath = Paths.get(resource.toString().substring(lastIndex)); } String target = Paths.get(outputDirectory).resolve(relativeSymlinkPath).toString(); MkdirAndSymlinkFileStep link = new MkdirAndSymlinkFileStep(resource.toString(), target); commands.add(link); } } } @Override public String getPathToOutputFile() { return outputJar.orNull(); } public static Builder newJavaLibraryRuleBuilder(AbstractBuildRuleBuilderParams params) { return new Builder(params); } public static class Builder extends AbstractBuildRuleBuilder<DefaultJavaLibraryRule> implements SrcsAttributeBuilder, ResourcesAttributeBuilder { protected Set<String> srcs = Sets.newHashSet(); protected Set<SourcePath> resources = Sets.newHashSet(); protected final AnnotationProcessingParams.Builder annotationProcessingBuilder = new AnnotationProcessingParams.Builder(); protected Set<BuildTarget> exportedDeps = Sets.newHashSet(); protected JavacOptions.Builder javacOptions = JavacOptions.builder(); protected Optional<String> proguardConfig = Optional.absent(); protected Builder(AbstractBuildRuleBuilderParams params) { super(params); } @Override public DefaultJavaLibraryRule build(BuildRuleResolver ruleResolver) { BuildRuleParams buildRuleParams = createBuildRuleParams(ruleResolver); AnnotationProcessingParams processingParams = annotationProcessingBuilder.build(ruleResolver); javacOptions.setAnnotationProcessingData(processingParams); return new DefaultJavaLibraryRule(buildRuleParams, srcs, resources, proguardConfig, getBuildTargetsAsBuildRules(ruleResolver, exportedDeps), javacOptions.build()); } public AnnotationProcessingParams.Builder getAnnotationProcessingBuilder() { return annotationProcessingBuilder; } @Override public Builder setBuildTarget(BuildTarget buildTarget) { super.setBuildTarget(buildTarget); annotationProcessingBuilder.setOwnerTarget(buildTarget); return this; } @Override public Builder addDep(BuildTarget dep) { super.addDep(dep); return this; } @Override public Builder addSrc(String src) { srcs.add(src); return this; } @Override public Builder addResource(SourcePath relativePathToResource) { resources.add(relativePathToResource); return this; } @Override public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) { super.addVisibilityPattern(visibilityPattern); return this; } public Builder setProguardConfig(Optional<String> proguardConfig) { this.proguardConfig = Preconditions.checkNotNull(proguardConfig); return this; } public Builder setSourceLevel(String sourceLevel) { javacOptions.setSourceLevel(sourceLevel); return this; } public Builder setTargetLevel(String targetLevel) { javacOptions.setTargetLevel(targetLevel); return this; } public Builder addExportedDep(BuildTarget buildTarget) { this.exportedDeps.add(buildTarget); return this; } } }