Java tutorial
/* * 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.BuildFileParseException; import com.facebook.buck.json.ProjectBuildFileParser; import com.facebook.buck.model.BuildFileTree; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetException; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.parser.Parser; import com.facebook.buck.parser.PartialGraph; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleType; import com.facebook.buck.rules.Buildable; import com.facebook.buck.rules.DependencyGraph; import com.facebook.buck.rules.ProjectConfigDescription; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MorePaths; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; 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.StringWriter; import java.nio.file.Path; import java.util.Collections; import java.util.EnumSet; 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(CommandRunnerParams params) { super(params); } @Override TargetsCommandOptions createOptions(BuckConfig buckConfig) { return new TargetsCommandOptions(buckConfig); } @Override int runCommandWithOptionsInternal(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(getBuildRuleTypes().getBuildRuleType(name)); } catch (IllegalArgumentException e) { console.printBuildFailure("Invalid build rule type: " + name); return 1; } } // Find the build targets that match the specified options. ImmutableSet<BuildTarget> matchingBuildTargets; try { matchingBuildTargets = ImmutableSet .copyOf(getBuildTargets(options.getArgumentsFormattedAsBuildTargets())); } catch (NoSuchBuildTargetException e) { console.printBuildFailureWithoutStacktrace(e); return 1; } // Parse the entire dependency graph, or (if targets are specified), // only the specified targets and their dependencies.. PartialGraph graph; try { if (matchingBuildTargets.isEmpty()) { graph = PartialGraph.createFullGraph(getProjectFilesystem(), options.getDefaultIncludes(), getParser(), getBuckEventBus()); } else { graph = PartialGraph.createPartialGraphIncludingRoots(matchingBuildTargets, options.getDefaultIncludes(), getParser(), getBuckEventBus()); } } catch (BuildTargetException | BuildFileParseException e) { console.printBuildFailureWithoutStacktrace(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()) { try { printJsonForTargets(matchingBuildRules, options.getDefaultIncludes()); } catch (BuildFileParseException e) { console.printBuildFailureWithoutStacktrace(e); return 1; } } else { printTargetsList(matchingBuildRules, options.isShowOutput(), options.isShowRuleKey()); } return 0; } @VisibleForTesting void printTargetsList(SortedMap<String, BuildRule> matchingBuildRules, boolean showOutput, boolean showRuleKey) throws IOException { for (Map.Entry<String, BuildRule> target : matchingBuildRules.entrySet()) { String output = target.getKey(); BuildRule buildRule = target.getValue(); if (showRuleKey) { output += " " + buildRule.getRuleKey(); } if (showOutput) { Buildable buildable = buildRule.getBuildable(); if (buildable != null) { Path outputPath = buildable.getPathToOutputFile(); if (outputPath != null) { output += " " + outputPath; } } } getStdOut().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 BuildFileParseException, IOException { ImmutableList<String> includesCopy = ImmutableList.copyOf(defaultIncludes); printJsonForTargetsInternal(buildIndex, includesCopy); } private void printJsonForTargetsInternal(SortedMap<String, BuildRule> buildIndex, ImmutableList<String> defaultIncludes) throws BuildFileParseException, IOException { // Print the JSON representation of the build rule for the specified target(s). getStdOut().println("["); ObjectMapper mapper = new ObjectMapper(); Iterator<BuildRule> valueIterator = buildIndex.values().iterator(); while (valueIterator.hasNext()) { BuildRule buildRule = valueIterator.next(); BuildTarget buildTarget = buildRule.getBuildTarget(); List<Map<String, Object>> rules; try { File buildFile = buildTarget.getBuildFile(getProjectFilesystem()); rules = getParser().parseBuildFile(buildFile, defaultIncludes, EnumSet.noneOf(ProjectBuildFileParser.Option.class)); } catch (BuildTargetException e) { console.printErrorText("unable to find rule for target " + buildTarget.getFullyQualifiedName()); continue; } // 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.printErrorText("unable to find rule for target " + buildTarget.getFullyQualifiedName()); continue; } Path outputPath; Buildable buildable = buildRule.getBuildable(); if (buildable != null) { outputPath = buildable.getPathToOutputFile(); } else if (ProjectConfigDescription.TYPE.equals(buildRule.getType())) { // We know that project_config() rules are special. outputPath = null; } else { throw new RuntimeException("No Buildable for " + buildRule.getFullyQualifiedName()); } if (outputPath != null) { targetRule.put("buck.output_file", outputPath.toString()); } // 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(); try { mapper.writerWithDefaultPrettyPrinter().writeValue(stringWriter, sortedTargetRule); } catch (IOException e) { // Shouldn't be possible while writing to a StringWriter... throw Throwables.propagate(e); } String output = stringWriter.getBuffer().toString(); if (valueIterator.hasNext()) { output += ","; } getStdOut().println(output); } getStdOut().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) { getStdOut().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; Parser parser = getParser(); try { ruleObjects = parser.parseBuildFile(buildTarget.getBuildFile(getProjectFilesystem()), options.getDefaultIncludes(), EnumSet.noneOf(ProjectBuildFileParser.Option.class)); } catch (BuildTargetException | BuildFileParseException e) { // TODO(devjasta): this doesn't smell right! return null; } // 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<Path> referencedInputs; private Set<String> basePathOfTargets; private Set<BuildRule> dependentTargets; private Set<BuildTarget> matchingBuildRules; /** * @param referencedPaths All of these paths must be relative to the project root. */ public TargetsCommandPredicate(PartialGraph partialGraph, ImmutableSet<BuildRuleType> buildRuleTypes, ImmutableSet<String> referencedPaths, ImmutableSet<BuildTarget> matchingBuildRules) { this.graph = partialGraph.getDependencyGraph(); this.buildRuleTypes = Preconditions.checkNotNull(buildRuleTypes); this.matchingBuildRules = Preconditions.checkNotNull(matchingBuildRules); Preconditions.checkNotNull(referencedPaths); if (!referencedPaths.isEmpty()) { this.referencedInputs = MorePaths.asPaths(referencedPaths); BuildFileTree tree = new BuildFileTree(partialGraph.getTargets()); basePathOfTargets = Sets.newHashSet(); dependentTargets = Sets.newHashSet(); for (Path input : referencedInputs) { basePathOfTargets.add(tree.getBasePathOfAncestorTarget(input.toString())); } } 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 (Path 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()))); } } }