/*
* Copyright 2016-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.jvm.java.autodeps;
import com.facebook.buck.android.AndroidLibraryDescription;
import com.facebook.buck.cli.BuckConfig;
import com.facebook.buck.jvm.java.JavaBuckConfig;
import com.facebook.buck.jvm.java.JavaFileParser;
import com.facebook.buck.jvm.java.JavaLibraryDescription;
import com.facebook.buck.jvm.java.JavaTestDescription;
import com.facebook.buck.jvm.java.JavacOptions;
import com.facebook.buck.jvm.java.PrebuiltJarDescription;
import com.facebook.buck.jvm.java.PrebuiltJarDescriptionArg;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.BuildEngine;
import com.facebook.buck.rules.BuildEngineBuildContext;
import com.facebook.buck.rules.BuildResult;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.step.ExecutionContext;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Set;
public class JavaDepsFinder {
private final JavaFileParser javaFileParser;
private final BuildEngineBuildContext buildContext;
private final ExecutionContext executionContext;
private final BuildEngine buildEngine;
public JavaDepsFinder(
JavaFileParser javaFileParser,
BuildEngineBuildContext buildContext,
ExecutionContext executionContext,
BuildEngine buildEngine) {
this.javaFileParser = javaFileParser;
this.buildContext = buildContext;
this.executionContext = executionContext;
this.buildEngine = buildEngine;
}
public JavaFileParser getJavaFileParser() {
return javaFileParser;
}
public static JavaDepsFinder createJavaDepsFinder(
BuckConfig buckConfig,
BuildEngineBuildContext buildContext,
ExecutionContext executionContext,
BuildEngine buildEngine) {
JavaBuckConfig javaBuckConfig = buckConfig.getView(JavaBuckConfig.class);
JavacOptions javacOptions = javaBuckConfig.getDefaultJavacOptions();
JavaFileParser javaFileParser = JavaFileParser.createJavaFileParser(javacOptions);
return new JavaDepsFinder(javaFileParser, buildContext, executionContext, buildEngine);
}
private static final Set<BuildRuleType> RULES_TO_VISIT =
ImmutableSet.of(
Description.getBuildRuleType(AndroidLibraryDescription.class),
Description.getBuildRuleType(JavaLibraryDescription.class),
Description.getBuildRuleType(JavaTestDescription.class),
Description.getBuildRuleType(PrebuiltJarDescription.class));
/** Java dependency information that is extracted from a {@link TargetGraph}. */
public static class DependencyInfo {
public final HashMultimap<String, TargetNode<?, ?>> symbolToProviders = HashMultimap.create();
}
public DependencyInfo findDependencyInfoForGraph(final TargetGraph graph) {
final DependencyInfo dependencyInfo = new DependencyInfo();
// Walk the graph and for each Java rule we record the Java entities it provides.
//
// Currently, we traverse the entire target graph using a single thread. However, the work to
// visit each node could be done in parallel, so long as the updates to the above collections
// were thread-safe.
for (TargetNode<?, ?> node : graph.getNodes()) {
if (!RULES_TO_VISIT.contains(Description.getBuildRuleType(node.getDescription()))) {
continue;
}
if (!(node.getConstructorArg() instanceof JavaLibraryDescription.CoreArg)
&& !(node.getConstructorArg() instanceof PrebuiltJarDescriptionArg)) {
throw new IllegalStateException("This rule is not supported by suggest: " + node);
}
Symbols symbols = getJavaFileFeatures(node);
for (String providedEntity : symbols.provided) {
dependencyInfo.symbolToProviders.put(providedEntity, node);
}
}
return dependencyInfo;
}
private Symbols getJavaFileFeatures(TargetNode<?, ?> node) {
// Build a JavaLibrarySymbolsFinder to create the JavaFileFeatures. By making use of Buck's
// build cache, we can often avoid running a Java parser.
BuildTarget buildTarget = node.getBuildTarget();
Object argForNode = node.getConstructorArg();
JavaSymbolsRule.SymbolsFinder symbolsFinder;
if (argForNode instanceof JavaLibraryDescription.CoreArg) {
JavaLibraryDescription.CoreArg arg = (JavaLibraryDescription.CoreArg) argForNode;
symbolsFinder = new JavaLibrarySymbolsFinder(arg.getSrcs(), javaFileParser);
} else {
PrebuiltJarDescriptionArg arg = (PrebuiltJarDescriptionArg) argForNode;
symbolsFinder = new PrebuiltJarSymbolsFinder(arg.getBinaryJar());
}
// Build the rule, leveraging Buck's build cache.
JavaSymbolsRule buildRule =
new JavaSymbolsRule(buildTarget, symbolsFinder, node.getFilesystem());
ListenableFuture<BuildResult> future =
buildEngine.build(buildContext, executionContext, buildRule).getResult();
BuildResult result = Futures.getUnchecked(future);
Symbols features;
if (result.getSuccess() != null) {
features = buildRule.getFeatures();
} else {
Throwable failure = result.getFailure();
Preconditions.checkNotNull(failure);
throw new RuntimeException("Failed to extract Java symbols for " + buildTarget, failure);
}
return features;
}
}