/* * 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.event.ConsoleEvent; import com.facebook.buck.graph.AbstractBottomUpTraversal; import com.facebook.buck.json.BuildFileParseException; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetException; import com.facebook.buck.parser.BuildTargetParser; import com.facebook.buck.parser.BuildTargetPatternParser; import com.facebook.buck.rules.Cell; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.MoreExceptions; import com.facebook.buck.util.ObjectMappers; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; public class AuditInputCommand extends AbstractCommand { private static final Logger LOG = Logger.get(AuditInputCommand.class); @Option(name = "--json", usage = "Output in JSON format") private boolean generateJsonOutput; public boolean shouldGenerateJsonOutput() { return generateJsonOutput; } @Argument private List<String> arguments = new ArrayList<>(); public List<String> getArguments() { return arguments; } public ImmutableList<String> getArgumentsFormattedAsBuildTargets(BuckConfig buckConfig) { return getCommandLineBuildTargetNormalizer(buckConfig).normalizeAll(getArguments()); } @Override public int runWithoutHelp(final CommandRunnerParams params) throws IOException, InterruptedException { // Create a TargetGraph that is composed of the transitive closure of all of the dependent // TargetNodes for the specified BuildTargets. final ImmutableSet<String> fullyQualifiedBuildTargets = ImmutableSet.copyOf(getArgumentsFormattedAsBuildTargets(params.getBuckConfig())); if (fullyQualifiedBuildTargets.isEmpty()) { params .getBuckEventBus() .post(ConsoleEvent.severe("Please specify at least one build target.")); return 1; } ImmutableSet<BuildTarget> targets = getArgumentsFormattedAsBuildTargets(params.getBuckConfig()) .stream() .map( input -> BuildTargetParser.INSTANCE.parse( input, BuildTargetPatternParser.fullyQualified(), params.getCell().getCellPathResolver())) .collect(MoreCollectors.toImmutableSet()); LOG.debug("Getting input for targets: %s", targets); TargetGraph graph; try (CommandThreadManager pool = new CommandThreadManager("Audit", getConcurrencyLimit(params.getBuckConfig()))) { graph = params .getParser() .buildTargetGraph( params.getBuckEventBus(), params.getCell(), getEnableParserProfiling(), pool.getExecutor(), targets); } catch (BuildFileParseException | BuildTargetException e) { params .getBuckEventBus() .post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e))); return 1; } if (shouldGenerateJsonOutput()) { return printJsonInputs(params, graph); } return printInputs(params, graph); } @Override public boolean isReadOnly() { return true; } @VisibleForTesting int printJsonInputs(final CommandRunnerParams params, TargetGraph graph) throws IOException { final SortedMap<String, ImmutableSortedSet<Path>> targetToInputs = new TreeMap<>(); new AbstractBottomUpTraversal<TargetNode<?, ?>, RuntimeException>(graph) { @Override public void visit(TargetNode<?, ?> node) { Optional<Cell> cellRoot = params.getCell().getCellIfKnown(node.getBuildTarget()); Cell cell = cellRoot.isPresent() ? cellRoot.get() : params.getCell(); LOG.debug("Looking at inputs for %s", node.getBuildTarget().getFullyQualifiedName()); SortedSet<Path> targetInputs = new TreeSet<>(); for (Path input : node.getInputs()) { LOG.debug("Walking input %s", input); try { if (!cell.getFilesystem().exists(input)) { throw new HumanReadableException( "Target %s refers to non-existent input file: %s", node, params.getCell().getRoot().relativize(cell.getRoot().resolve(input))); } targetInputs.addAll(cell.getFilesystem().getFilesUnderPath(input)); } catch (IOException e) { throw new RuntimeException(e); } } targetToInputs.put( node.getBuildTarget().getFullyQualifiedName(), ImmutableSortedSet.copyOf(targetInputs)); } }.traverse(); ObjectMappers.WRITER.writeValue(params.getConsole().getStdOut(), targetToInputs); return 0; } private int printInputs(final CommandRunnerParams params, TargetGraph graph) { // Traverse the TargetGraph and print out all of the inputs used to produce each TargetNode. // Keep track of the inputs that have been displayed to ensure that they are not displayed more // than once. new AbstractBottomUpTraversal<TargetNode<?, ?>, RuntimeException>(graph) { final Set<Path> inputs = Sets.newHashSet(); @Override public void visit(TargetNode<?, ?> node) { Optional<Cell> cellRoot = params.getCell().getCellIfKnown(node.getBuildTarget()); Cell cell = cellRoot.isPresent() ? cellRoot.get() : params.getCell(); for (Path input : node.getInputs()) { LOG.debug("Walking input %s", input); try { if (!cell.getFilesystem().exists(input)) { throw new HumanReadableException( "Target %s refers to non-existent input file: %s", node, params.getCell().getRoot().relativize(cell.getRoot().resolve(input))); } ImmutableSortedSet<Path> nodeContents = ImmutableSortedSet.copyOf(cell.getFilesystem().getFilesUnderPath(input)); for (Path path : nodeContents) { putInput(params.getCell().getRoot().relativize(cell.getRoot().resolve(path))); } } catch (IOException e) { throw new RuntimeException(e); } } } private void putInput(Path input) { boolean isNewInput = inputs.add(input); if (isNewInput) { params.getConsole().getStdOut().println(input); } } }.traverse(); return 0; } @Override public String getShortDescription() { return "provides facilities to audit build targets' input files"; } }