| /* |
| * 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 com.facebook.buck.android.HasAndroidResourceDeps; |
| import com.facebook.buck.android.UberRDotJavaUtil; |
| import com.facebook.buck.graph.TopologicalSort; |
| import com.facebook.buck.model.AnnotationProcessingData; |
| import com.facebook.buck.model.BuildTarget; |
| import com.facebook.buck.model.BuildTargetPattern; |
| import com.facebook.buck.rules.AbstractCachingBuildRule; |
| import com.facebook.buck.rules.AbstractCachingBuildRuleBuilder; |
| import com.facebook.buck.rules.ArtifactCache; |
| import com.facebook.buck.rules.BuildContext; |
| import com.facebook.buck.rules.BuildDependencies; |
| import com.facebook.buck.rules.BuildRule; |
| import com.facebook.buck.rules.BuildRuleType; |
| import com.facebook.buck.rules.CachingBuildRuleParams; |
| import com.facebook.buck.rules.JavaPackageFinder; |
| import com.facebook.buck.rules.ResourcesAttributeBuilder; |
| import com.facebook.buck.rules.RuleKey; |
| import com.facebook.buck.rules.SrcsAttributeBuilder; |
| 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.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Function; |
| 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.reflect.ClassPath; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.logging.Logger; |
| |
| 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', |
| * ], |
| * ) |
| * </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 AbstractCachingBuildRule |
| implements JavaLibraryRule, HasJavaSrcs, HasClasspathEntries { |
| |
| private final ImmutableSortedSet<String> srcs; |
| |
| private final ImmutableSortedSet<String> resources; |
| |
| private final Optional<File> outputJar; |
| |
| private final List<String> inputsToConsiderForCachingPurposes; |
| |
| private final AnnotationProcessingParams annotationProcessingParams; |
| |
| @Nullable private final String proguardConfig; |
| |
| private final String sourceLevel; |
| |
| private final String targetLevel; |
| |
| private final boolean exportDeps; |
| |
| private final Supplier<ImmutableSet<String>> outputClasspathEntriesSupplier; |
| |
| private final Supplier<ImmutableSetMultimap<BuildRule, String>> |
| transitiveClasspathEntriesSupplier; |
| |
| private final Supplier<ImmutableSetMultimap<BuildRule, String>> |
| declaredClasspathEntriesSupplier; |
| |
| /** |
| * Function for opening a JAR and returning all symbols that can be referenced from inside of that |
| * jar. |
| */ |
| @VisibleForTesting |
| static interface JarResolver extends |
| Function<String, ImmutableSet<String>> {} |
| |
| private final JarResolver JAR_RESOLVER = |
| new JarResolver() { |
| @Override |
| public ImmutableSet<String> apply(String classPath) { |
| ImmutableSet.Builder<String> topLevelSymbolsBuilder = ImmutableSet.builder(); |
| try { |
| ClassLoader loader = URLClassLoader.newInstance( |
| new URL[]{new File(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 #buildInternal(com.facebook.buck.rules.BuildContext)} and is available to subclasses. |
| */ |
| protected ImmutableList<HasAndroidResourceDeps> androidResourceDeps; |
| |
| protected DefaultJavaLibraryRule(CachingBuildRuleParams cachingBuildRuleParams, |
| Set<String> srcs, |
| Set<String> resources, |
| @Nullable String proguardConfig, |
| AnnotationProcessingParams annotationProcessingParams, |
| boolean exportDeps) { |
| this( |
| cachingBuildRuleParams, |
| srcs, |
| resources, |
| proguardConfig, |
| annotationProcessingParams, |
| exportDeps, |
| JavacOptionsUtil.DEFAULT_SOURCE_LEVEL, |
| JavacOptionsUtil.DEFAULT_TARGET_LEVEL |
| ); |
| } |
| |
| |
| protected DefaultJavaLibraryRule(CachingBuildRuleParams cachingBuildRuleParams, |
| Set<String> srcs, |
| Set<String> resources, |
| @Nullable String proguardConfig, |
| AnnotationProcessingParams annotationProcessingParams, |
| boolean exportDeps, |
| String sourceLevel, |
| String targetLevel) { |
| super(cachingBuildRuleParams); |
| this.srcs = ImmutableSortedSet.copyOf(srcs); |
| this.resources = ImmutableSortedSet.copyOf(resources); |
| this.annotationProcessingParams = Preconditions.checkNotNull(annotationProcessingParams); |
| this.proguardConfig = proguardConfig; |
| this.sourceLevel = sourceLevel; |
| this.targetLevel = targetLevel; |
| this.exportDeps = exportDeps; |
| |
| if (!srcs.isEmpty() || !resources.isEmpty()) { |
| File file = new File(getOutputJarPath(getBuildTarget())); |
| this.outputJar = Optional.of(file); |
| } 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. |
| inputsToConsiderForCachingPurposes = ImmutableList.<String>builder() |
| .addAll(this.srcs) |
| .addAll(this.resources) |
| .build(); |
| |
| outputClasspathEntriesSupplier = |
| Suppliers.memoize(new Supplier<ImmutableSet<String>>() { |
| @Override |
| public ImmutableSet<String> get() { |
| ImmutableSet<String> outputClasspathEntries; |
| |
| // If this java_library exports its dependencies then just return the transitive |
| // dependencies. |
| if (DefaultJavaLibraryRule.this.exportDeps) { |
| outputClasspathEntries = ImmutableSet.copyOf( |
| getTransitiveClasspathEntries().values()); |
| } else if (outputJar.isPresent()) { |
| outputClasspathEntries = ImmutableSet.of(getOutput().getPath()); |
| } else { |
| outputClasspathEntries = ImmutableSet.of(); |
| } |
| |
| return outputClasspathEntries; |
| } |
| }); |
| |
| transitiveClasspathEntriesSupplier = |
| Suppliers.memoize(new Supplier<ImmutableSetMultimap<BuildRule, String>>() { |
| @Override |
| public ImmutableSetMultimap<BuildRule, String> get() { |
| final ImmutableSetMultimap.Builder<BuildRule, String> classpathEntries = |
| ImmutableSetMultimap.builder(); |
| ImmutableSetMultimap<BuildRule, String> classpathEntriesForDeps = |
| Classpaths.getClasspathEntries(getDeps()); |
| |
| classpathEntries.putAll(classpathEntriesForDeps); |
| |
| if (DefaultJavaLibraryRule.this.exportDeps) { |
| classpathEntries.putAll(DefaultJavaLibraryRule.this, |
| classpathEntriesForDeps.values()); |
| } |
| |
| // Only add ourselves to the classpath if there's a jar to be built. |
| if (outputJar.isPresent()) { |
| classpathEntries.putAll(DefaultJavaLibraryRule.this, |
| getOutput().getPath()); |
| } |
| |
| return classpathEntries.build(); |
| } |
| }); |
| |
| declaredClasspathEntriesSupplier = |
| Suppliers.memoize(new Supplier<ImmutableSetMultimap<BuildRule, String>>() { |
| @Override |
| public ImmutableSetMultimap<BuildRule, String> get() { |
| final ImmutableSetMultimap.Builder<BuildRule, String> classpathEntries = |
| ImmutableSetMultimap.builder(); |
| |
| Iterable<JavaLibraryRule> javaLibraryDeps = Iterables.filter( |
| Sets.union(getDeps(), ImmutableSet.of(DefaultJavaLibraryRule.this)), |
| JavaLibraryRule.class); |
| |
| for (JavaLibraryRule rule : javaLibraryDeps) { |
| classpathEntries.putAll(rule, rule.getOutputClasspathEntries()); |
| } |
| return classpathEntries.build(); |
| } |
| }); |
| } |
| |
| /** |
| * @param outputDirectory Directory to write class files to |
| * @param javaSourceFilePaths .java files to compile: may be empty |
| * @param transitiveClasspathEntries Classpaths of all transitive dependencies. |
| * @param declaredClasspathEntries Classpaths of all declared dependencies. |
| * @param annotationProcessingData to process JSR269 java annotations |
| * @param suggestBuildRules Function to convert from missing symbols to the suggested rules. |
| * @return commands to compile the specified inputs |
| */ |
| private static ImmutableList<Step> createCommandsForJavac( |
| String outputDirectory, |
| final SortedSet<String> javaSourceFilePaths, |
| ImmutableSet<String> transitiveClasspathEntries, |
| ImmutableSet<String> declaredClasspathEntries, |
| Supplier<String> bootclasspathSupplier, |
| AnnotationProcessingData annotationProcessingData, |
| Optional<String> invokingRule, |
| BuildDependencies buildDependencies, |
| Optional<DependencyCheckingJavacStep.SuggestBuildRules> suggestBuildRules, |
| String sourceLevel, |
| String targetLevel) { |
| ImmutableList.Builder<Step> commands = ImmutableList.builder(); |
| |
| // Only run javac if there are .java files to compile. |
| if (!javaSourceFilePaths.isEmpty()) { |
| Step javac = new DependencyCheckingJavacStep( |
| outputDirectory, |
| javaSourceFilePaths, |
| transitiveClasspathEntries, |
| declaredClasspathEntries, |
| bootclasspathSupplier, |
| annotationProcessingData, |
| invokingRule, |
| buildDependencies, |
| suggestBuildRules, |
| sourceLevel, |
| targetLevel); |
| |
| commands.add(javac); |
| } |
| |
| return commands.build(); |
| } |
| |
| private static String getOutputJarPath(BuildTarget target) { |
| return String.format( |
| "%s/%slib__%s__output/%s.jar", |
| BuckConstant.GEN_DIR, |
| target.getBasePathWithSlash(), |
| target.getShortName(), |
| 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()); |
| } |
| |
| @Override |
| public boolean isAndroidRule() { |
| return false; |
| } |
| |
| @Override |
| public boolean isLibrary() { |
| return true; |
| } |
| |
| @Override |
| protected RuleKey.Builder ruleKeyBuilder() { |
| return super.ruleKeyBuilder() |
| .set("srcs", srcs) |
| .set("resources", resources) |
| .set("classpathEntries", ImmutableSortedSet.copyOf(getDeclaredClasspathEntries().values())) |
| .set("isAndroidLibrary", isAndroidRule()) |
| .set("sourceLevel", sourceLevel) |
| .set("targetLevel", targetLevel) |
| .set("exportDeps", exportDeps); |
| } |
| |
| @Override |
| public BuildRuleType getType() { |
| return BuildRuleType.JAVA_LIBRARY; |
| } |
| |
| @Override |
| public ImmutableSortedSet<String> getJavaSrcs() { |
| return srcs; |
| } |
| |
| @Override |
| public ImmutableSetMultimap<BuildRule, String> getTransitiveClasspathEntries() { |
| return transitiveClasspathEntriesSupplier.get(); |
| } |
| |
| @Override |
| public ImmutableSetMultimap<BuildRule, String> getDeclaredClasspathEntries() { |
| return declaredClasspathEntriesSupplier.get(); |
| } |
| |
| @Override |
| public ImmutableSet<String> getOutputClasspathEntries() { |
| return outputClasspathEntriesSupplier.get(); |
| } |
| |
| @Override |
| public AnnotationProcessingData getAnnotationProcessingData() { |
| return annotationProcessingParams; |
| } |
| |
| @Nullable |
| public String getProguardConfig() { |
| return proguardConfig; |
| } |
| |
| @Override |
| @Nullable |
| protected List<String> getInputsToCompareToOutput(BuildContext context) { |
| return inputsToConsiderForCachingPurposes; |
| } |
| |
| @Override |
| public boolean getExportDeps() { |
| return exportDeps; |
| } |
| |
| /** |
| * Checks to see if all of the dependant rules are cached. By default, AbstractCachingBuildRule |
| * will consider a rule's deps uncached if any of its descendants were uncached. |
| */ |
| @VisibleForTesting |
| @Override |
| protected boolean depsCached(final BuildContext context, Logger logger) throws IOException { |
| if (context.getBuildDependencies() != BuildDependencies.FIRST_ORDER_ONLY) { |
| return super.depsCached(context, logger); |
| } |
| |
| for (BuildRule dep : getDeps()) { |
| if (dep instanceof DefaultJavaLibraryRule) { |
| DefaultJavaLibraryRule javaDep = (DefaultJavaLibraryRule)dep; |
| if (javaDep.getExportDeps()) { |
| if (javaDep.hasUncachedDescendants(context)) { |
| logger.info(String.format("%s not cached because java library %s exports its deps " + |
| "and has uncached descendants", |
| this, |
| dep.getFullyQualifiedName())); |
| return false; |
| } |
| } else if (!javaDep.ruleInputsCached(context, logger)) { |
| logger.info(String.format("%s not cached because java library %s's inputs changed", |
| this, |
| dep.getFullyQualifiedName())); |
| return false; |
| } |
| } else if (!dep.isCached(context)) { |
| logger.info(String.format("%s not cached because %s is not cached", |
| this, |
| dep.getFullyQualifiedName())); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * 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 |
| protected final List<Step> buildInternal(BuildContext context) throws IOException { |
| ImmutableList.Builder<Step> commands = ImmutableList.builder(); |
| BuildTarget buildTarget = getBuildTarget(); |
| |
| // 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, |
| context.getDependencyGraph()); |
| boolean dependsOnAndroidResourceRules = !androidResourceDeps.isEmpty(); |
| if (dependsOnAndroidResourceRules) { |
| UberRDotJavaUtil.createDummyRDotJavaFiles(androidResourceDeps, buildTarget, commands); |
| } |
| |
| ImmutableSetMultimap<BuildRule, String> transitiveClasspathEntries = |
| getTransitiveClasspathEntries(); |
| ImmutableSetMultimap<BuildRule, 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<BuildRule, String> transitiveClasspathEntriesWithRDotJava = |
| ImmutableSetMultimap.builder(); |
| transitiveClasspathEntriesWithRDotJava.putAll(transitiveClasspathEntries); |
| |
| ImmutableSetMultimap.Builder<BuildRule, 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(); |
| } |
| |
| // Only override the bootclasspath if this rule is supposed to compile Android code. |
| Supplier<String> bootclasspathSupplier; |
| if (isAndroidRule()) { |
| bootclasspathSupplier = context.getAndroidBootclasspathSupplier(); |
| } else { |
| bootclasspathSupplier = Suppliers.ofInstance(null); |
| } |
| |
| // Javac requires that the root directory for generated sources already exist. |
| String annotationGenFolder = annotationProcessingParams.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<DependencyCheckingJavacStep.SuggestBuildRules> suggestBuildRule = |
| createSuggestBuildFunction(context, |
| transitiveClasspathEntries, |
| declaredClasspathEntries, |
| JAR_RESOLVER); |
| |
| // This adds the javac command, along with any supporting commands. |
| List<Step> javac = createCommandsForJavac( |
| outputDirectory, |
| srcs, |
| ImmutableSet.copyOf(transitiveClasspathEntries.values()), |
| ImmutableSet.copyOf(declaredClasspathEntries.values()), |
| bootclasspathSupplier, |
| annotationProcessingParams, |
| Optional.of(getFullyQualifiedName()), |
| context.getBuildDependencies(), |
| suggestBuildRule, |
| sourceLevel, |
| targetLevel); |
| commands.addAll(javac); |
| |
| // If there are resources, then link them to the appropriate place in the classes directory. |
| addResourceCommands(commands, outputDirectory, context.getJavaPackageFinder()); |
| |
| if (outputJar.isPresent()) { |
| commands.add(new MakeCleanDirectoryStep(outputJar.get().getParent())); |
| commands.add(new JarDirectoryStep( |
| outputJar.get().getPath(), Collections.singleton(outputDirectory), null, null)); |
| } |
| |
| return commands.build(); |
| } |
| |
| /** |
| * @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(BuildRule transitiveNotDeclaredDep, |
| Set<String> failedImports, |
| JarResolver jarResolver) { |
| ImmutableSet<String> classPaths = getTransitiveClasspathEntries().get(transitiveNotDeclaredDep); |
| 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; |
| topLevelSymbols = jarResolver.apply(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<DependencyCheckingJavacStep.SuggestBuildRules> createSuggestBuildFunction( |
| BuildContext context, |
| ImmutableSetMultimap<BuildRule, String> transitiveClasspathEntries, |
| ImmutableSetMultimap<BuildRule, String> declaredClasspathEntries, |
| final JarResolver jarResolver) { |
| if (context.getBuildDependencies() != BuildDependencies.WARN_ON_TRANSITIVE) { |
| return Optional.absent(); |
| } |
| final Set<BuildRule> transitiveNotDeclaredDeps = Sets.difference( |
| transitiveClasspathEntries.keySet(), |
| 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(); |
| |
| DependencyCheckingJavacStep.SuggestBuildRules suggestBuildRuleFn = |
| new DependencyCheckingJavacStep.SuggestBuildRules() { |
| @Override |
| public ImmutableSet<String> apply(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(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); |
| } |
| |
| @VisibleForTesting |
| void addResourceCommands(ImmutableList.Builder<Step> commands, |
| String outputDirectory, |
| JavaPackageFinder javaPackageFinder) { |
| if (!resources.isEmpty()) { |
| for (String resource : 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. |
| String javaPackageAsPath = javaPackageFinder.findJavaPackageFolderForPath(resource); |
| String relativeSymlinkPath; |
| 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.lastIndexOf(javaPackageAsPath); |
| Preconditions.checkState(lastIndex >= 0, |
| "Resource path %s must contain %s", |
| resource, |
| javaPackageAsPath); |
| |
| relativeSymlinkPath = resource.substring(lastIndex); |
| } |
| String target = outputDirectory + '/' + relativeSymlinkPath; |
| |
| MkdirAndSymlinkFileStep link = new MkdirAndSymlinkFileStep(resource, target); |
| commands.add(link); |
| } |
| } |
| } |
| |
| @Override |
| public File getOutput() { |
| return outputJar.orNull(); |
| } |
| |
| public static Builder newJavaLibraryRuleBuilder() { |
| return new Builder(); |
| } |
| |
| public static class Builder extends AbstractCachingBuildRuleBuilder implements |
| SrcsAttributeBuilder, ResourcesAttributeBuilder { |
| |
| protected Set<String> srcs = Sets.newHashSet(); |
| protected Set<String> resources = Sets.newHashSet(); |
| protected final AnnotationProcessingParams.Builder annotationProcessingBuilder = |
| new AnnotationProcessingParams.Builder(); |
| protected String sourceLevel = JavacOptionsUtil.DEFAULT_SOURCE_LEVEL; |
| protected String targetLevel = JavacOptionsUtil.DEFAULT_TARGET_LEVEL; |
| protected boolean exportDeps = false; |
| |
| @Nullable |
| protected String proguardConfig = null; |
| |
| protected Builder() {} |
| |
| @Override |
| public DefaultJavaLibraryRule build(Map<String, BuildRule> buildRuleIndex) { |
| CachingBuildRuleParams cachingBuildRuleParams = createCachingBuildRuleParams(buildRuleIndex); |
| AnnotationProcessingParams processingParams = |
| annotationProcessingBuilder.build(buildRuleIndex); |
| |
| return new DefaultJavaLibraryRule( |
| cachingBuildRuleParams, |
| srcs, |
| resources, |
| proguardConfig, |
| processingParams, |
| exportDeps, |
| sourceLevel, |
| targetLevel); |
| } |
| |
| public AnnotationProcessingParams.Builder getAnnotationProcessingBuilder() { |
| return annotationProcessingBuilder; |
| } |
| |
| @Override |
| public Builder setBuildTarget(BuildTarget buildTarget) { |
| super.setBuildTarget(buildTarget); |
| annotationProcessingBuilder.setOwnerTarget(buildTarget); |
| return this; |
| } |
| |
| @Override |
| public Builder addDep(String dep) { |
| super.addDep(dep); |
| return this; |
| } |
| |
| @Override |
| public Builder addSrc(String src) { |
| srcs.add(src); |
| return this; |
| } |
| |
| @Override |
| public Builder addResource(String relativePathToResource) { |
| resources.add(relativePathToResource); |
| return this; |
| } |
| |
| @Override |
| public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) { |
| super.addVisibilityPattern(visibilityPattern); |
| return this; |
| } |
| |
| @Override |
| public Builder setArtifactCache(ArtifactCache artifactCache) { |
| super.setArtifactCache(artifactCache); |
| return this; |
| } |
| |
| public Builder setProguardConfig(String proguardConfig) { |
| this.proguardConfig = proguardConfig; |
| return this; |
| } |
| |
| public Builder setSourceLevel(String sourceLevel) { |
| this.sourceLevel = sourceLevel; |
| return this; |
| } |
| |
| public Builder setTargetLevel(String targetLevel) { |
| this.targetLevel = targetLevel; |
| return this; |
| } |
| |
| public Builder setExportDeps(boolean exportDeps) { |
| this.exportDeps = exportDeps; |
| return this; |
| } |
| } |
| } |