| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // 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.google.devtools.build.lib.runtime.commands; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; |
| import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType; |
| import com.google.devtools.build.lib.actions.ActionGraph; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.CommandLineExpansionException; |
| import com.google.devtools.build.lib.actions.extra.DetailedExtraActionInfo; |
| import com.google.devtools.build.lib.actions.extra.ExtraActionSummary; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FileProvider; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.PrintActionVisitor; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; |
| import com.google.devtools.build.lib.buildtool.BuildRequest; |
| import com.google.devtools.build.lib.buildtool.BuildResult; |
| import com.google.devtools.build.lib.buildtool.BuildTool; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.pkgcache.LoadingOptions; |
| import com.google.devtools.build.lib.runtime.BlazeCommand; |
| import com.google.devtools.build.lib.runtime.BlazeCommandResult; |
| import com.google.devtools.build.lib.runtime.BlazeRuntime; |
| import com.google.devtools.build.lib.runtime.Command; |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; |
| import com.google.devtools.build.lib.runtime.KeepGoingOption; |
| import com.google.devtools.build.lib.util.ExitCode; |
| import com.google.devtools.build.lib.util.io.OutErr; |
| import com.google.devtools.common.options.Option; |
| import com.google.devtools.common.options.OptionDocumentationCategory; |
| import com.google.devtools.common.options.OptionEffectTag; |
| import com.google.devtools.common.options.OptionsBase; |
| import com.google.devtools.common.options.OptionsParsingResult; |
| import com.google.protobuf.TextFormat; |
| import java.util.ArrayList; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Implements 'blaze print_action' by finding the Configured target[s] for the file[s] listed. |
| * |
| */ |
| @Command(name = "print_action", |
| builds = true, |
| inherits = {BuildCommand.class}, |
| options = {PrintActionCommand.PrintActionOptions.class}, |
| help = "resource:print_action.txt", |
| shortDescription = "Prints the command line args for compiling a file.", |
| completion = "label", |
| allowResidue = true, |
| canRunInOutputDirectory = true) |
| public final class PrintActionCommand implements BlazeCommand { |
| |
| /** |
| * Options for print_action, used to parse command-line arguments. |
| */ |
| public static class PrintActionOptions extends OptionsBase { |
| @Option( |
| name = "print_action_mnemonics", |
| allowMultiple = true, |
| defaultValue = "", |
| documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, |
| effectTags = {OptionEffectTag.UNKNOWN}, |
| help = |
| "Lists which mnemonics to filter print_action data by, no filtering takes place " |
| + "when left empty." |
| ) |
| public List<String> printActionMnemonics = new ArrayList<>(); |
| } |
| |
| @Override |
| public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) { |
| LoadingOptions loadingOptions = |
| options.getOptions(LoadingOptions.class); |
| |
| PrintActionOptions printActionOptions = options.getOptions(PrintActionOptions.class); |
| PrintActionRunner runner = new PrintActionRunner(loadingOptions.compileOneDependency, options, |
| env.getReporter().getOutErr(), |
| options.getResidue(), Sets.newHashSet(printActionOptions.printActionMnemonics)); |
| return BlazeCommandResult.exitCode(runner.printActionsForTargets(env)); |
| } |
| |
| /** |
| * Contains all the logic to get extra_action information for print actions. |
| * Maintains requires state to perform required analyses. |
| */ |
| private class PrintActionRunner { |
| private final boolean compileOneDependency; |
| private final OptionsParsingResult options; |
| private final OutErr outErr; |
| private final List<String> requestedTargets; |
| private final boolean keepGoing; |
| private final ExtraActionSummary.Builder summaryBuilder; |
| private final Predicate<ActionAnalysisMetadata> actionMnemonicMatcher; |
| |
| public PrintActionRunner( |
| boolean compileOneDependency, |
| OptionsParsingResult options, |
| OutErr outErr, |
| List<String> requestedTargets, |
| final Set<String> printActionMnemonics) { |
| this.compileOneDependency = compileOneDependency; |
| this.options = options; |
| this.outErr = outErr; |
| this.requestedTargets = requestedTargets; |
| keepGoing = options.getOptions(KeepGoingOption.class).keepGoing; |
| summaryBuilder = ExtraActionSummary.newBuilder(); |
| actionMnemonicMatcher = new Predicate<ActionAnalysisMetadata>() { |
| @Override |
| public boolean apply(ActionAnalysisMetadata action) { |
| return printActionMnemonics.isEmpty() |
| || printActionMnemonics.contains(action.getMnemonic()); |
| } |
| }; |
| } |
| |
| private ExitCode printActionsForTargets(CommandEnvironment env) { |
| BuildResult result = gatherActionsForTargets(env, requestedTargets); |
| if (result == null) { |
| return ExitCode.PARSING_FAILURE; |
| } |
| if (hasFatalBuildFailure(result)) { |
| env.getReporter().handle(Event.error("Build failed when printing actions")); |
| return result.getExitCondition(); |
| } |
| String action = TextFormat.printToString(summaryBuilder); |
| if (!action.isEmpty()) { |
| outErr.printOut(action); |
| return result.getExitCondition(); |
| } else { |
| env.getReporter().handle(Event.error("no actions to print were found")); |
| return ExitCode.PARSING_FAILURE; |
| } |
| } |
| |
| private BuildResult gatherActionsForTargets(CommandEnvironment env, List<String> targets) { |
| BlazeRuntime runtime = env.getRuntime(); |
| String commandName = PrintActionCommand.this.getClass().getAnnotation(Command.class).name(); |
| BuildRequest request = BuildRequest.create(commandName, options, |
| runtime.getStartupOptionsProvider(), |
| targets, outErr, env.getCommandId(), env.getCommandStartTime()); |
| BuildResult result = new BuildTool(env).processRequest(request, null); |
| if (hasFatalBuildFailure(result)) { |
| return result; |
| } |
| |
| ActionGraph actionGraph = env.getSkyframeExecutor().getActionGraph(env.getReporter()); |
| |
| for (ConfiguredTarget configuredTarget : result.getActualTargets()) { |
| NestedSet<Artifact> filesToCompile = NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| OutputGroupInfo outputGroupInfo = OutputGroupInfo.get(configuredTarget); |
| if (outputGroupInfo != null) { |
| filesToCompile = |
| outputGroupInfo.getOutputGroup(OutputGroupInfo.FILES_TO_COMPILE); |
| } |
| if (!filesToCompile.isEmpty()) { |
| try { |
| if (compileOneDependency) { |
| gatherActionsForFiles( |
| configuredTarget, |
| env, |
| actionGraph, |
| env.getSkyframeExecutor().getActionKeyContext(), |
| targets); |
| } else { |
| Target target = null; |
| try { |
| target = |
| env.getPackageManager() |
| .getTarget(env.getReporter(), configuredTarget.getLabel()); |
| } catch (NoSuchTargetException | NoSuchPackageException | InterruptedException e) { |
| env.getReporter().handle(Event.error("Failed to find target to gather actions.")); |
| return null; |
| } |
| gatherActionsForTarget( |
| configuredTarget, |
| target, |
| actionGraph, |
| env.getSkyframeExecutor().getActionKeyContext()); |
| } |
| } catch (CommandLineExpansionException e) { |
| env.getReporter().handle(Event.error(null, "Error expanding command line: " + e)); |
| return null; |
| } |
| } else { |
| env.getReporter().handle(Event.error( |
| null, configuredTarget + " is not a supported target kind")); |
| return null; |
| } |
| } |
| return result; |
| } |
| |
| private BuildResult gatherActionsForFiles( |
| ConfiguredTarget configuredTarget, |
| CommandEnvironment env, |
| ActionGraph actionGraph, |
| ActionKeyContext actionKeyContext, |
| List<String> files) |
| throws CommandLineExpansionException { |
| Set<String> filesDesired = new LinkedHashSet<>(files); |
| ActionFilter filter = new DefaultActionFilter(filesDesired, actionMnemonicMatcher); |
| |
| gatherActionsForFile(configuredTarget, filter, env, actionGraph, actionKeyContext); |
| return null; |
| } |
| |
| private void gatherActionsForTarget( |
| ConfiguredTarget configuredTarget, |
| Target target, |
| ActionGraph actionGraph, |
| ActionKeyContext actionKeyContext) |
| throws CommandLineExpansionException { |
| if (!(target instanceof Rule)) { |
| return; |
| } |
| |
| PrintActionVisitor visitor = new PrintActionVisitor(actionGraph, configuredTarget, |
| actionMnemonicMatcher); |
| |
| // TODO(jvg): do we want to support ruleConfiguredTarget.getOutputArtifacts()? |
| // We do for extra actions, but as we're past the action graph building phase, |
| // we cannot call it without risking to trigger creation of OutputArtifacts post |
| // graph building phase (not allowed). Right now we do not need them for our scenarios. |
| visitor.visitWhiteNodes( |
| configuredTarget.getProvider(FileProvider.class).getFilesToBuild().toList()); |
| |
| Iterable<ActionAnalysisMetadata> actions = visitor.getActions(); |
| for (ActionAnalysisMetadata action : actions) { |
| if (action instanceof Action) { |
| DetailedExtraActionInfo.Builder detail = DetailedExtraActionInfo.newBuilder(); |
| detail.setAction(((Action) action).getExtraActionInfo(actionKeyContext)); |
| summaryBuilder.addAction(detail); |
| } |
| } |
| } |
| |
| /** |
| * Looks for files to compile in the given configured target and outputs the corresponding |
| * extra_action if the filter evaluates to {@code true}. |
| */ |
| private void gatherActionsForFile( |
| ConfiguredTarget configuredTarget, |
| ActionFilter filter, |
| CommandEnvironment env, |
| ActionGraph actionGraph, |
| ActionKeyContext actionKeyContext) |
| throws CommandLineExpansionException { |
| NestedSet<Artifact> artifacts = OutputGroupInfo.get(configuredTarget) |
| .getOutputGroup(OutputGroupInfo.FILES_TO_COMPILE); |
| |
| if (artifacts.isEmpty()) { |
| return; |
| } |
| |
| for (Artifact artifact : artifacts.toList()) { |
| ActionAnalysisMetadata action = actionGraph.getGeneratingAction(artifact); |
| if (filter.shouldOutput(action, configuredTarget, env)) { |
| if (action instanceof Action) { |
| DetailedExtraActionInfo.Builder detail = DetailedExtraActionInfo.newBuilder(); |
| detail.setAction(((Action) action).getExtraActionInfo(actionKeyContext)); |
| summaryBuilder.addAction(detail); |
| } |
| } |
| } |
| } |
| |
| private boolean hasFatalBuildFailure(BuildResult result) { |
| return result.getActualTargets() == null || (!result.getSuccess() && !keepGoing); |
| } |
| } |
| |
| /** Filter for extra actions. */ |
| private interface ActionFilter { |
| /** Returns true if the given action is not null and should be printed. */ |
| boolean shouldOutput( |
| ActionAnalysisMetadata action, ConfiguredTarget configuredTarget, CommandEnvironment env); |
| } |
| |
| /** |
| * C++ headers are not plain vanilla action inputs: they do not show up in Action.getInputs(), |
| * since the actual set of header files is the one discovered during include scanning. |
| * |
| * <p>However, since there is a scheduling dependency on the header files, we can use the system |
| * to implement said scheduling dependency to figure them out. Thus, we go a-fishing in the action |
| * graph reaching through scheduling dependency middlemen: one of these exists for each {@code |
| * CcCompilationContext} in the transitive closure of the rule. |
| */ |
| private static void expandRecursiveHelper( |
| ActionGraph actionGraph, |
| Iterable<Artifact> artifacts, |
| Set<Artifact> visited, |
| Set<Artifact> result) { |
| for (Artifact artifact : artifacts) { |
| if (!visited.add(artifact)) { |
| continue; |
| } |
| if (!artifact.isMiddlemanArtifact()) { |
| result.add(artifact); |
| continue; |
| } |
| |
| ActionAnalysisMetadata middlemanAction = actionGraph.getGeneratingAction(artifact); |
| if (middlemanAction.getActionType() != MiddlemanType.SCHEDULING_DEPENDENCY_MIDDLEMAN) { |
| continue; |
| } |
| |
| expandRecursiveHelper(actionGraph, middlemanAction.getInputs().toList(), visited, result); |
| } |
| } |
| |
| private static void expandRecursive(ActionGraph actionGraph, Iterable<Artifact> artifacts, |
| Set<Artifact> result) { |
| expandRecursiveHelper(actionGraph, artifacts, Sets.<Artifact>newHashSet(), result); |
| } |
| |
| /** |
| * A stateful filter that keeps track of which files have already been covered. This makes it such |
| * that blaze only prints out one action protobuf per file. This is important for headers. In |
| * addition, this also handles C++ header files, which are not considered to be action inputs by |
| * blaze (due to include scanning). |
| * |
| * <p> |
| * As caveats, this only works for files that are given as proper relative paths, rather than |
| * using target syntax, and only if the current working directory is the client root. |
| */ |
| private static class DefaultActionFilter implements ActionFilter { |
| private final Set<String> filesDesired; |
| private final Predicate<ActionAnalysisMetadata> actionMnemonicMatcher; |
| |
| private DefaultActionFilter(Set<String> filesDesired, |
| Predicate<ActionAnalysisMetadata> actionMnemonicMatcher) { |
| this.filesDesired = filesDesired; |
| this.actionMnemonicMatcher = actionMnemonicMatcher; |
| } |
| |
| @Override |
| public boolean shouldOutput( |
| ActionAnalysisMetadata action, ConfiguredTarget configuredTarget, CommandEnvironment env) { |
| if (action == null) { |
| return false; |
| } |
| // Check all the inputs for the configured target against the file we want argv for. |
| Set<Artifact> expandedArtifacts = Sets.newHashSet(); |
| expandRecursive( |
| env.getSkyframeExecutor().getActionGraph(env.getReporter()), |
| action.getInputs().toList(), |
| expandedArtifacts); |
| for (Artifact input : expandedArtifacts) { |
| if (filesDesired.remove(input.getRootRelativePath().getSafePathString())) { |
| return actionMnemonicMatcher.apply(action); |
| } |
| } |
| |
| // C++ header files show up in the dependency on the Target, but not the ConfiguredTarget, so |
| // we also check the target's header files there. |
| RuleConfiguredTarget ruleConfiguredTarget = (RuleConfiguredTarget) configuredTarget; |
| Rule rule; |
| try { |
| rule = |
| (Rule) |
| env.getPackageManager().getTarget(env.getReporter(), configuredTarget.getLabel()); |
| } catch (NoSuchTargetException | NoSuchPackageException | InterruptedException e) { |
| env.getReporter().handle(Event.error("Failed to find target to determine output.")); |
| return false; |
| } |
| if (!rule.isAttrDefined("hdrs", BuildType.LABEL_LIST)) { |
| return false; |
| } |
| List<Label> hdrs = |
| ConfiguredAttributeMapper.of(rule, ruleConfiguredTarget.getConfigConditions()) |
| .get("hdrs", BuildType.LABEL_LIST); |
| if (hdrs != null) { |
| for (Label hdrLabel : hdrs) { |
| if (filesDesired.remove(hdrLabel.toPathFragment().getPathString())) { |
| return actionMnemonicMatcher.apply(action); |
| } |
| } |
| } |
| return false; // no match |
| } |
| } |
| } |