blob: 1c7846462ce6894729b4cf5b8dfe861e1dc1a85d [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.graph.AbstractBottomUpTraversal;
import com.facebook.buck.json.BuildFileToJsonParser;
import com.facebook.buck.model.BuildFileTree;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.parser.Parser;
import com.facebook.buck.parser.PartialGraph;
import com.facebook.buck.rules.ArtifactCache;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.DependencyGraph;
import com.facebook.buck.rules.InputRule;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.ProjectFilesystem;
import com.fasterxml.jackson.databind.ObjectMapper;
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.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import javax.annotation.Nullable;
public class TargetsCommand extends AbstractCommandRunner<TargetsCommandOptions> {
public TargetsCommand(ArtifactCache artifactCache) {
super(artifactCache);
}
@VisibleForTesting
TargetsCommand(PrintStream stdOut,
PrintStream stdErr,
Console console,
ProjectFilesystem projectFilesystem,
ArtifactCache artifactCache) {
super(stdOut, stdErr, console, projectFilesystem, artifactCache);
}
@Override
TargetsCommandOptions createOptions(BuckConfig buckConfig) {
return new TargetsCommandOptions(buckConfig);
}
@Override
int runCommandWithOptions(TargetsCommandOptions options) throws IOException {
// Exit early if --resolvealias is passed in: no need to parse any build files.
if (options.isResolveAlias()) {
return doResolveAlias(options);
}
// Verify the --type argument.
ImmutableSet<String> types = options.getTypes();
ImmutableSet.Builder<BuildRuleType> buildRuleTypesBuilder = ImmutableSet.builder();
for (String name : types) {
try {
buildRuleTypesBuilder.add(BuildRuleType.valueOf(name.toUpperCase()));
} catch (IllegalArgumentException e) {
console.printFailure("Invalid build rule type: " + name);
return 1;
}
}
// Find the build targets that match the specified options.
Parser parser = createParser();
ImmutableSet<BuildTarget> matchingBuildTargets;
try {
matchingBuildTargets = ImmutableSet.copyOf(
getBuildTargets(parser, options.getArgumentsFormattedAsBuildTargets()));
} catch (NoSuchBuildTargetException e) {
console.printFailureWithoutStacktrace(e);
return 1;
}
// Parse the entire dependency graph.
PartialGraph graph;
try {
graph = PartialGraph.createFullGraph(getProjectFilesystem().getProjectRoot(),
getArtifactCache(),
options.getDefaultIncludes());
} catch (NoSuchBuildTargetException e) {
console.printFailureWithoutStacktrace(e);
return 1;
}
SortedMap<String, BuildRule> matchingBuildRules = getMatchingBuildRules(
graph.getDependencyGraph(),
new TargetsCommandPredicate(
graph,
buildRuleTypesBuilder.build(),
options.getReferencedFiles(getProjectFilesystem().getProjectRoot()),
matchingBuildTargets));
// Print out matching targets in alphabetical order.
if (options.getPrintJson()) {
printJsonForTargets(matchingBuildRules, options.getDefaultIncludes());
} else {
printTargetsList(matchingBuildRules, options.isShowOutput());
}
return 0;
}
@VisibleForTesting
void printTargetsList(SortedMap<String, BuildRule> matchingBuildRules,
boolean showOutput) {
for (Map.Entry<String, BuildRule> target : matchingBuildRules.entrySet()) {
String output = target.getKey();
if (showOutput) {
File outputFile = target.getValue().getOutput();
if (outputFile != null) {
output += " " + outputFile.getPath();
}
}
stdOut.println(output);
}
}
@VisibleForTesting
SortedMap<String, BuildRule> getMatchingBuildRules(
final DependencyGraph graph,
final TargetsCommandPredicate predicate) {
// Traverse the DependencyGraph and select all of the rules that accepted by Predicate.
AbstractBottomUpTraversal<BuildRule, SortedMap<String, BuildRule>> traversal =
new AbstractBottomUpTraversal<BuildRule, SortedMap<String, BuildRule>>(graph) {
final SortedMap<String, BuildRule> matchingBuildRules = Maps.newTreeMap();
@Override
public void visit(BuildRule rule) {
if (predicate.apply(rule)) {
matchingBuildRules.put(rule.getFullyQualifiedName(), rule);
}
}
@Override
public SortedMap<String, BuildRule> getResult() {
return matchingBuildRules;
}
};
traversal.traverse();
return traversal.getResult();
}
@Override
String getUsageIntro() {
return "prints the list of buildable targets";
}
@VisibleForTesting
void printJsonForTargets(SortedMap<String, BuildRule> buildIndex,
Iterable<String> defaultIncludes) throws IOException {
// Print the JSON representation of the build rule for the specified target(s).
stdOut.println("[");
ObjectMapper mapper = new ObjectMapper();
Iterator<String> keySetIterator = buildIndex.keySet().iterator();
while (keySetIterator.hasNext()) {
String key = keySetIterator.next();
BuildRule buildRule = buildIndex.get(key);
BuildTarget buildTarget = buildRule.getBuildTarget();
File buildFile = buildTarget.getBuildFile();
List<Map<String, Object>> rules = BuildFileToJsonParser.getAllRules(
getProjectFilesystem().getProjectRoot().getAbsolutePath(),
Optional.of(buildFile.getPath()),
defaultIncludes);
// Find the build rule information that corresponds to this build buildTarget.
Map<String, Object> targetRule = null;
for (Map<String, Object> rule : rules) {
String name = (String)rule.get("name");
if (name.equals(buildTarget.getShortName())) {
targetRule = rule;
break;
}
}
if (targetRule == null) {
console.printFailure(
"unable to find rule for target " + buildTarget.getFullyQualifiedName());
continue;
}
File outputFile = buildRule.getOutput();
if (outputFile != null) {
targetRule.put("buck.output_file", outputFile.getPath());
}
// Sort the rule items, both so we have a stable order for unit tests and
// to improve readability of the output.
SortedMap<String, Object> sortedTargetRule = Maps.newTreeMap();
sortedTargetRule.putAll(targetRule);
// Print the build rule information as JSON.
StringWriter stringWriter = new StringWriter();
mapper.writerWithDefaultPrettyPrinter().writeValue(stringWriter, sortedTargetRule);
String output = stringWriter.getBuffer().toString();
if (keySetIterator.hasNext()) {
output += ",";
}
stdOut.println(output);
}
stdOut.println("]");
}
/**
* Assumes each argument passed to this command is an alias defined in .buckconfig,
* or a fully qualified (non-alias) target to be verified by checking the build files.
* Prints the build target that each alias maps to on its own line to standard out.
*/
private int doResolveAlias(TargetsCommandOptions options) throws IOException {
List<String> resolvedAliases = Lists.newArrayList();
for (String alias : options.getArguments()) {
String buildTarget;
if (alias.startsWith("//")) {
buildTarget = validateBuildTargetForFullyQualifiedTarget(alias, options);
if (buildTarget == null) {
throw new HumanReadableException("%s is not a valid target.", alias);
}
} else {
buildTarget = options.getBuildTargetForAlias(alias);
if (buildTarget == null) {
throw new HumanReadableException("%s is not an alias.", alias);
}
}
resolvedAliases.add(buildTarget);
}
for (String resolvedAlias : resolvedAliases) {
stdOut.println(resolvedAlias);
}
return 0;
}
/**
* Verify that the given target is a valid full-qualified (non-alias) target.
*/
@Nullable
@VisibleForTesting
String validateBuildTargetForFullyQualifiedTarget(
String target, TargetsCommandOptions options) throws IOException {
BuildTarget buildTarget;
try {
buildTarget = options.getBuildTargetForFullyQualifiedTarget(target);
} catch (NoSuchBuildTargetException e) {
return null;
}
// Get all valid targets in our target directory by reading the build file.
List<Map<String, Object>> ruleObjects = BuildFileToJsonParser.getAllRules(
getProjectFilesystem().getProjectRoot().getAbsolutePath(),
Optional.of(buildTarget.getBuildFile().toString()),
options.getDefaultIncludes());
// Check that the given target is a valid target.
for (Map<String,Object> rule : ruleObjects) {
String name = (String)rule.get("name");
if (name.equals(buildTarget.getShortName())) {
return buildTarget.getFullyQualifiedName();
}
}
return null;
}
static class TargetsCommandPredicate implements Predicate<BuildRule> {
private DependencyGraph graph;
private ImmutableSet<BuildRuleType> buildRuleTypes;
private ImmutableSet<InputRule> referencedInputs;
private Set<String> basePathOfTargets;
private Set<BuildRule> dependentTargets;
private Set<BuildTarget> matchingBuildRules;
public TargetsCommandPredicate(
PartialGraph partialGraph,
ImmutableSet<BuildRuleType> buildRuleTypes,
ImmutableSet<String> referencedFiles,
ImmutableSet<BuildTarget> matchingBuildRules) {
this.graph = partialGraph.getDependencyGraph();
this.buildRuleTypes = Preconditions.checkNotNull(buildRuleTypes);
this.matchingBuildRules = Preconditions.checkNotNull(matchingBuildRules);
Preconditions.checkNotNull(referencedFiles);
if (!referencedFiles.isEmpty()) {
this.referencedInputs = InputRule.inputPathsAsInputRules(
ImmutableSortedSet.copyOf(referencedFiles));
BuildFileTree tree = new BuildFileTree(partialGraph.getTargets());
basePathOfTargets = Sets.newHashSet();
dependentTargets = Sets.newHashSet();
for (InputRule input : referencedInputs) {
basePathOfTargets.add(tree.getBasePathOfAncestorTarget(
input.getBuildTarget().getBasePath()));
}
} else {
basePathOfTargets = ImmutableSet.of();
dependentTargets = ImmutableSet.of();
}
}
@Override
public boolean apply(BuildRule rule) {
boolean isDependent = true;
if (referencedInputs != null) {
// Indirectly depend on some referenced file.
isDependent = !Collections.disjoint(graph.getOutgoingNodesFor(rule), dependentTargets);
// Any referenced file, only those with the nearest BuildTarget can
// directly depend on that file.
if (!isDependent && basePathOfTargets.contains(rule.getBuildTarget().getBasePath())) {
for (InputRule input : rule.getInputs()) {
if (referencedInputs.contains(input)) {
isDependent = true;
break;
}
}
}
if (isDependent) {
// Save the rule only when exists referenced file
// and this rule depend on at least one referenced file.
dependentTargets.add(rule);
}
}
if (!matchingBuildRules.isEmpty() &&
!matchingBuildRules.contains(rule.getBuildTarget())) {
return false;
}
return (isDependent && (buildRuleTypes.isEmpty() || buildRuleTypes.contains(rule.getType())));
}
}
}