blob: 2fc63c516375a05e784d886e5fc60243f961eb66 [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.cli;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.parser.PartialGraph;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.DependencyGraph;
import com.facebook.buck.util.Ansi;
import com.facebook.buck.util.BuckConstant;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
/**
* Outputs targets that own a specified list of files.
*/
public class AuditOwnerCommand extends AbstractCommandRunner<AuditOwnerOptions> {
private static final String FILE_INDENT = " ";
public AuditOwnerCommand(CommandRunnerParams params) {
super(params);
}
@VisibleForTesting
static final class OwnersReport {
final ImmutableSetMultimap<BuildRule, Path> owners;
final ImmutableSet<Path> inputsWithNoOwners;
final ImmutableSet<String> nonExistentInputs;
final ImmutableSet<String> nonFileInputs;
public OwnersReport(SetMultimap<BuildRule, Path> owners,
Set<Path> inputsWithNoOwners,
Set<String> nonExistentInputs,
Set<String> nonFileInputs) {
this.owners = ImmutableSetMultimap.copyOf(owners);
this.inputsWithNoOwners = ImmutableSet.copyOf(inputsWithNoOwners);
this.nonExistentInputs = ImmutableSet.copyOf(nonExistentInputs);
this.nonFileInputs = ImmutableSet.copyOf(nonFileInputs);
}
}
@Override
AuditOwnerOptions createOptions(BuckConfig buckConfig) {
return new AuditOwnerOptions(buckConfig);
}
@Override
int runCommandWithOptionsInternal(AuditOwnerOptions options) throws IOException {
// Build full graph.
PartialGraph graph;
try {
graph = PartialGraph.createFullGraph(
getProjectFilesystem(),
options.getDefaultIncludes(),
getParser(),
getBuckEventBus());
} catch (BuildTargetException | BuildFileParseException e) {
console.printBuildFailureWithoutStacktrace(e);
return 1;
}
OwnersReport report = generateOwnersReport(graph.getDependencyGraph(), options);
printReport(options, report);
return 0;
}
@VisibleForTesting
OwnersReport generateOwnersReport(DependencyGraph graph, AuditOwnerOptions options) {
// Process arguments assuming they are all relative file paths.
Set<Path> inputs = Sets.newHashSet();
Set<String> nonExistentInputs = Sets.newHashSet();
Set<String> nonFileInputs = Sets.newHashSet();
ProjectFilesystem projectFilesystem = getProjectFilesystem();
for (String filePath : options.getArguments()) {
File file = projectFilesystem.getFileForRelativePath(filePath);
if (!file.exists()) {
nonExistentInputs.add(filePath);
} else if (!file.isFile()) {
nonFileInputs.add(filePath);
} else {
inputs.add(Paths.get(filePath));
}
}
// Try to find owners for each valid and existing file.
Set<Path> inputsWithNoOwners = Sets.newHashSet(inputs);
SetMultimap<BuildRule, Path> owners = TreeMultimap.create();
for (BuildRule rule : graph.getNodes()) {
for (Path ruleInput : rule.getInputs()) {
if (inputs.contains(ruleInput)) {
inputsWithNoOwners.remove(ruleInput);
owners.put(rule, ruleInput);
}
}
}
// Try to guess owners for nonexistent files.
if (options.isGuessForDeletedEnabled()) {
guessOwnersForNonExistentFiles(graph, owners, nonExistentInputs);
}
return new OwnersReport(owners, inputsWithNoOwners, nonExistentInputs, nonFileInputs);
}
/**
* Guess target owners for deleted/missing files by finding first
* BUCK file and assuming that all targets in this file used
* missing file as input.
*/
private void guessOwnersForNonExistentFiles(DependencyGraph graph,
SetMultimap<BuildRule, Path> owners, Set<String> nonExistentFiles) {
ProjectFilesystem projectFilesystem = getProjectFilesystem();
for (String nonExistentFile : nonExistentFiles) {
File file = projectFilesystem.getFileForRelativePath(nonExistentFile);
File buck = findBuckFileFor(file);
for (BuildRule rule : graph.getNodes()) {
if (rule.getType() == BuildRuleType.PROJECT_CONFIG) {
continue;
}
try {
File ruleBuck = rule.getBuildTarget().getBuildFile(projectFilesystem);
if (buck.getCanonicalFile().equals(ruleBuck.getCanonicalFile())) {
owners.put(rule, Paths.get(nonExistentFile));
}
} catch (IOException | BuildTargetException e) {
throw Throwables.propagate(e);
}
}
}
}
private File findBuckFileFor(File file) {
File dir = file;
if (!dir.isDirectory()) {
dir = dir.getParentFile();
}
File projectRoot = getProjectFilesystem().getProjectRoot();
while (dir != null && !dir.equals(projectRoot)) {
File buck = new File(dir, BuckConstant.BUILD_RULES_FILE_NAME);
if (buck.exists()) {
return buck;
}
dir = dir.getParentFile();
}
throw new RuntimeException("Failed to find BUCK file for " + file.getPath());
}
private void printReport(AuditOwnerOptions options, OwnersReport report) {
if (options.isFullReportEnabled()) {
printFullReport(report);
} else {
printOwnersOnlyReport(report);
}
}
/**
* Print only targets which were identified as owners.
*/
private void printOwnersOnlyReport(OwnersReport report) {
Set<BuildRule> sortedRules = report.owners.keySet();
for (BuildRule rule : sortedRules) {
console.getStdOut().println(rule.getFullyQualifiedName());
}
}
/**
* Print detailed report on all owners.
*/
private void printFullReport(OwnersReport report) {
PrintStream out = console.getStdOut();
Ansi ansi = console.getAnsi();
if (report.owners.isEmpty()) {
out.println(ansi.asErrorText("No owners found"));
} else {
out.println(ansi.asSuccessText("Owners:"));
for (BuildRule rule : report.owners.keySet()) {
out.println(rule.getFullyQualifiedName());
Set<Path> files = report.owners.get(rule);
for (Path input : files) {
out.println(FILE_INDENT + input);
}
}
}
if (!report.inputsWithNoOwners.isEmpty()) {
out.println();
out.println(ansi.asErrorText("Files without owners:"));
for (Path input : report.inputsWithNoOwners) {
out.println(FILE_INDENT + input);
}
}
if (!report.nonExistentInputs.isEmpty()) {
out.println();
out.println(ansi.asErrorText("Non existent files:"));
for (String input : report.nonExistentInputs) {
out.println(FILE_INDENT + input);
}
}
if (!report.nonFileInputs.isEmpty()) {
out.println();
out.println(ansi.asErrorText("Non-file inputs:"));
for (String input : report.nonFileInputs) {
out.println(FILE_INDENT + input);
}
}
}
@Override
String getUsageIntro() {
return "prints targets that own specified files";
}
}