blob: ddad1fbfce877877e66891120efe5e568ebaf6c8 [file] [log] [blame]
/*
* Copyright 2014-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.cli;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.BuckEventListener;
import com.facebook.buck.event.MissingSymbolEvent;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.JavaSymbolFinder;
import com.facebook.buck.java.JavacOptions;
import com.facebook.buck.java.SrcRootsFinder;
import com.facebook.buck.json.DefaultProjectBuildFileParserFactory;
import com.facebook.buck.json.ProjectBuildFileParserFactory;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.ParserConfig;
import com.facebook.buck.rules.BuildEvent;
import com.facebook.buck.rules.Description;
import com.facebook.buck.util.Console;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.Set;
public class MissingSymbolsHandler {
private final Console console;
private final JavaSymbolFinder javaSymbolFinder;
private final String buildFileName;
private MissingSymbolsHandler(
Console console,
JavaSymbolFinder javaSymbolFinder,
String buildFileName) {
this.console = console;
this.javaSymbolFinder = javaSymbolFinder;
this.buildFileName = buildFileName;
}
public static MissingSymbolsHandler create(
ProjectFilesystem projectFilesystem,
ImmutableSet<Description<?>> descriptions,
BuckConfig config,
BuckEventBus buckEventBus,
Console console,
JavacOptions javacOptions,
ImmutableMap<String, String> environment) {
SrcRootsFinder srcRootsFinder = new SrcRootsFinder(projectFilesystem);
ParserConfig parserConfig = new ParserConfig(config);
ProjectBuildFileParserFactory projectBuildFileParserFactory =
new DefaultProjectBuildFileParserFactory(
projectFilesystem,
parserConfig,
descriptions);
JavaSymbolFinder javaSymbolFinder = new JavaSymbolFinder(
projectFilesystem,
srcRootsFinder,
javacOptions,
projectBuildFileParserFactory,
config,
buckEventBus,
console,
environment);
return new MissingSymbolsHandler(
console,
javaSymbolFinder,
parserConfig.getBuildFileName());
}
/**
* Instantiate a MissingSymbolsHandler and wrap it in a listener that calls it on the appropriate
* events. This is done as part of the global listener setup in Main, and it's the entry point
* into most of the dependency autodetection code.
*/
public static BuckEventListener createListener(
ProjectFilesystem projectFilesystem,
ImmutableSet<Description<?>> descriptions,
BuckConfig config,
BuckEventBus buckEventBus,
Console console,
JavacOptions javacOptions,
ImmutableMap<String, String> environment) {
final MissingSymbolsHandler missingSymbolsHandler = create(
projectFilesystem,
descriptions,
config,
buckEventBus,
console,
javacOptions,
environment);
final Multimap<BuildId, MissingSymbolEvent> missingSymbolEvents = HashMultimap.create();
BuckEventListener missingSymbolsListener = new BuckEventListener() {
@Override
public void outputTrace(BuildId buildId) {
// If we put {@link #printNeededDependencies} here, it's output won't be visible in buckd.
// Instead, we listen for BuildEvent.Finished, below.
}
@Subscribe
public void onMissingSymbol(MissingSymbolEvent event) {
missingSymbolEvents.put(event.getBuildId(), event);
}
@Subscribe
public void onBuildFinished(BuildEvent.Finished event) throws InterruptedException {
// Shortcircuit if there aren't any failures.
if (missingSymbolEvents.get(event.getBuildId()).isEmpty()) {
return;
}
missingSymbolsHandler.printNeededDependencies(missingSymbolEvents.get(event.getBuildId()));
missingSymbolEvents.removeAll(event.getBuildId());
}
};
return missingSymbolsListener;
}
/**
* Using missing symbol events from the build and the JavaSymbolFinder class, build a list of
* missing dependencies for each broken target.
*/
public ImmutableSetMultimap<BuildTarget, BuildTarget> getNeededDependencies(
Collection<MissingSymbolEvent> missingSymbolEvents) throws InterruptedException {
ImmutableSetMultimap.Builder<BuildTarget, String> targetsMissingSymbolsBuilder =
ImmutableSetMultimap.builder();
for (MissingSymbolEvent event : missingSymbolEvents) {
if (event.getType() != MissingSymbolEvent.SymbolType.Java) {
throw new UnsupportedOperationException("Only implemented for Java.");
}
targetsMissingSymbolsBuilder.put(event.getTarget(), event.getSymbol());
}
ImmutableSetMultimap<BuildTarget, String> targetsMissingSymbols =
targetsMissingSymbolsBuilder.build();
ImmutableSetMultimap<String, BuildTarget> symbolProviders =
javaSymbolFinder.findTargetsForSymbols(ImmutableSet.copyOf(targetsMissingSymbols.values()));
ImmutableSetMultimap.Builder<BuildTarget, BuildTarget> neededDeps =
ImmutableSetMultimap.builder();
for (BuildTarget target: targetsMissingSymbols.keySet()) {
for (String symbol : targetsMissingSymbols.get(target)) {
// TODO(jacko): Properly handle symbols that are defined in more than one place.
// TODO(jacko): Properly handle target visibility.
neededDeps.putAll(target, ImmutableSortedSet.copyOf(symbolProviders.get(symbol)));
}
}
return neededDeps.build();
}
/**
* Get a list of missing dependencies from {@link #getNeededDependencies} and print it to the
* console in a list-of-Python-strings way that's easy to copy and paste.
*/
private void printNeededDependencies(Collection<MissingSymbolEvent> missingSymbolEvents)
throws InterruptedException {
ImmutableSetMultimap<BuildTarget, BuildTarget> neededDependencies =
getNeededDependencies(missingSymbolEvents);
ImmutableSortedSet.Builder<String> samePackageDeps = ImmutableSortedSet.naturalOrder();
ImmutableSortedSet.Builder<String> otherPackageDeps = ImmutableSortedSet.naturalOrder();
for (BuildTarget target : neededDependencies.keySet()) {
print(formatTarget(target) + " is missing deps:");
Set<BuildTarget> sortedDeps = ImmutableSortedSet.copyOf(neededDependencies.get(target));
for (BuildTarget neededDep : sortedDeps) {
if (neededDep.getBaseName().equals(target.getBaseName())) {
samePackageDeps.add(":" + neededDep.getShortName());
} else {
otherPackageDeps.add(neededDep.toString());
}
}
}
String format = " '%s',";
for (String dep : samePackageDeps.build()) {
print(String.format(format, dep));
}
for (String dep : otherPackageDeps.build()) {
print(String.format(format, dep));
}
}
/**
* Format a target string so that the path to the BUCK file its in is easily copyable.
*/
private String formatTarget(BuildTarget buildTarget) {
return String.format("%s (:%s)",
buildTarget.getBasePath().resolve(buildFileName),
buildTarget.getShortName());
}
private void print(String line) {
console.getStdOut().println(console.getAnsi().asWarningText(line));
}
}