| // Copyright 2014 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.buildtool; |
| |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.AspectCollection; |
| import com.google.devtools.build.lib.analysis.ConfiguredAspect; |
| 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.ProviderCollection; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.exec.ExecutionOptions; |
| import com.google.devtools.build.lib.runtime.BlazeRuntime; |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; |
| import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; |
| import com.google.devtools.build.lib.util.io.OutErr; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Objects; |
| |
| /** |
| * Handles --show_result and --experimental_show_artifacts. |
| */ |
| class BuildResultPrinter { |
| private final CommandEnvironment env; |
| |
| BuildResultPrinter(CommandEnvironment env) { |
| this.env = env; |
| } |
| |
| /** |
| * Shows the result of the build. Information includes the list of up-to-date and failed targets |
| * and list of output artifacts for successful targets |
| * |
| * <p>This corresponds to the --show_result flag. |
| */ |
| void showBuildResult( |
| BuildRequest request, |
| BuildResult result, |
| Collection<ConfiguredTarget> configuredTargets, |
| Collection<ConfiguredTarget> configuredTargetsToSkip, |
| ImmutableMap<AspectKey, ConfiguredAspect> aspects) { |
| // NOTE: be careful what you print! We don't want to create a consistency |
| // problem where the summary message and the exit code disagree. The logic |
| // here is already complex. |
| boolean ok = |
| outputTargets(request, result, configuredTargets, configuredTargetsToSkip, aspects); |
| if (!ok && !request.getOptions(ExecutionOptions.class).verboseFailures) { |
| request |
| .getOutErr() |
| .printErr("Use --verbose_failures to see the command lines of failed build steps.\n"); |
| } |
| } |
| |
| /** |
| * Outputs the targets, omitting values with {@code (nothing to build)} when it allows staying |
| * under the --show_result limit. |
| * |
| * <p>This method exits early if there are too many results. |
| * |
| * @return {@code true} if no errors were detected among the results inspected, this can be a |
| * false positive on early exit. |
| */ |
| private boolean outputTargets( |
| BuildRequest request, |
| BuildResult result, |
| Collection<ConfiguredTarget> configuredTargets, |
| Collection<ConfiguredTarget> configuredTargetsToSkip, |
| ImmutableMap<AspectKey, ConfiguredAspect> aspects) { |
| BlazeRuntime runtime = env.getRuntime(); |
| String productName = runtime.getProductName(); |
| PathPrettyPrinter prettyPrinter = |
| new PathPrettyPrinter( |
| request.getBuildOptions().getSymlinkPrefix(productName), |
| result.getConvenienceSymlinks()); |
| OutErr outErr = request.getOutErr(); |
| |
| // Filter and split aspects to display. |
| ImmutableSet<String> aspectsToIgnore = |
| ImmutableSet.copyOf(request.getBuildOptions().hideAspectResults); |
| PartitionedAspectKeys partitionedAspectKeys = |
| partitionAspectKeys( |
| request.useValidationAspect(), |
| aspects.keySet().stream() |
| .filter(k -> !aspectsToIgnore.contains(k.getAspectClass().getName())) |
| .collect(toImmutableSet())); |
| |
| Collection<ConfiguredTarget> targetsToPrint = filterTargetsToPrint(configuredTargets); |
| TopLevelArtifactContext context = request.getTopLevelArtifactContext(); |
| |
| // `essentialBudget` tracks the number of non-empty results that can be printed. |
| int essentialBudget = request.getBuildOptions().maxResultTargets; |
| |
| // Splits the targets we care about into three buckets. Targets are only considered successful |
| // if they and their validation aspects succeeded. |
| var skipped = new ArrayList<ConfiguredTarget>(); |
| var succeeded = new ArrayList<ConfiguredTarget>(); |
| var artifactsToPrintPerTarget = new ArrayList<ArrayList<Artifact>>(); |
| var failed = new ArrayList<ConfiguredTarget>(); |
| essentialBudget = |
| splitConfiguredTargetsByResultReturnRemaining( |
| targetsToPrint, |
| result, |
| context, |
| configuredTargetsToSkip, |
| partitionedAspectKeys.validationAspects, |
| skipped, |
| succeeded, |
| artifactsToPrintPerTarget, |
| failed, |
| essentialBudget); |
| if (essentialBudget < 0) { |
| return failed.isEmpty(); |
| } |
| |
| // Splits the aspects we care about into two buckets. |
| var successfulAspects = new ArrayList<AspectKey>(); |
| var failedAspects = new ArrayList<AspectKey>(); |
| var artifactsToPrintPerAspect = new ArrayList<ArrayList<Artifact>>(successfulAspects.size()); |
| essentialBudget = |
| splitAspectsByResultReturnRemaining( |
| partitionedAspectKeys.aspectsToPrint, |
| aspects, |
| context, |
| result.getSuccessfulAspects(), |
| successfulAspects, |
| artifactsToPrintPerAspect, |
| failedAspects, |
| essentialBudget); |
| if (essentialBudget < 0) { |
| return failed.isEmpty() && failedAspects.isEmpty(); |
| } |
| |
| // Omits "nothing to build" values if it enables staying under --show_result. |
| boolean omitNothingToBuild = |
| (targetsToPrint.size() + partitionedAspectKeys.aspectsToPrint.size()) |
| > request.getBuildOptions().maxResultTargets; |
| |
| outputConfiguredTargets( |
| outErr, |
| prettyPrinter, |
| succeeded, |
| artifactsToPrintPerTarget, |
| failed, |
| skipped, |
| omitNothingToBuild); |
| outputAspects( |
| outErr, |
| prettyPrinter, |
| successfulAspects, |
| artifactsToPrintPerAspect, |
| failedAspects, |
| omitNothingToBuild); |
| |
| return failed.isEmpty() && failedAspects.isEmpty(); |
| } |
| |
| private static int splitConfiguredTargetsByResultReturnRemaining( |
| Collection<ConfiguredTarget> configuredTargets, |
| BuildResult result, |
| TopLevelArtifactContext context, |
| Collection<ConfiguredTarget> configuredTargetsToSkip, |
| ImmutableList<AspectKey> validationAspects, |
| ArrayList<ConfiguredTarget> skipped, |
| ArrayList<ConfiguredTarget> succeeded, |
| ArrayList<ArrayList<Artifact>> artifactsToPrintPerTarget, |
| ArrayList<ConfiguredTarget> failed, |
| int essentialBudget) { |
| ImmutableSet<ConfiguredTargetKey> validationFailures = |
| validationAspects.stream() |
| .filter(k -> !result.getSuccessfulAspects().contains(k)) |
| .map(AspectKey::getBaseConfiguredTargetKey) |
| .collect(toImmutableSet()); |
| Collection<ConfiguredTarget> successfulTargets = result.getSuccessfulTargets(); |
| for (ConfiguredTarget target : configuredTargets) { |
| if (configuredTargetsToSkip.contains(target)) { |
| skipped.add(target); |
| if (--essentialBudget < 0) { |
| return essentialBudget; |
| } |
| } else if (successfulTargets.contains(target) |
| && !validationFailures.contains(ConfiguredTargetKey.fromConfiguredTarget(target))) { |
| succeeded.add(target); |
| ArrayList<Artifact> artifactsToPrint = getArtifactsToPrint(target, context); |
| artifactsToPrintPerTarget.add(artifactsToPrint); |
| if (!artifactsToPrint.isEmpty()) { |
| if (--essentialBudget < 0) { |
| return essentialBudget; |
| } |
| } |
| } else { |
| failed.add(target); |
| if (--essentialBudget < 0) { |
| return essentialBudget; |
| } |
| } |
| } |
| return essentialBudget; |
| } |
| |
| private static ArrayList<Artifact> getArtifactsToPrint( |
| ProviderCollection target, TopLevelArtifactContext context) { |
| var artifacts = new ArrayList<Artifact>(); |
| // For up-to-date targets report generated artifacts, but only if they have associated action |
| // and not runfiles trees. |
| for (Artifact artifact : |
| TopLevelArtifactHelper.getAllArtifactsToBuild(target, context) |
| .getImportantArtifacts() |
| .toList()) { |
| if (TopLevelArtifactHelper.shouldDisplay(artifact)) { |
| artifacts.add(artifact); |
| } |
| } |
| return artifacts; |
| } |
| |
| private static int splitAspectsByResultReturnRemaining( |
| Collection<AspectKey> aspectsToPrint, |
| ImmutableMap<AspectKey, ConfiguredAspect> aspects, |
| TopLevelArtifactContext context, |
| ImmutableSet<AspectKey> successfulAspects, |
| ArrayList<AspectKey> succeeded, |
| ArrayList<ArrayList<Artifact>> artifactsToPrintPerAspect, |
| ArrayList<AspectKey> failed, |
| int essentialBudget) { |
| for (AspectKey aspect : aspectsToPrint) { |
| if (successfulAspects.contains(aspect)) { |
| succeeded.add(aspect); |
| ArrayList<Artifact> artifactsToPrint = getArtifactsToPrint(aspects.get(aspect), context); |
| artifactsToPrintPerAspect.add(artifactsToPrint); |
| if (!artifactsToPrint.isEmpty()) { |
| if (--essentialBudget < 0) { |
| return essentialBudget; |
| } |
| } |
| } else { |
| failed.add(aspect); |
| if (--essentialBudget < 0) { |
| return essentialBudget; |
| } |
| } |
| } |
| return essentialBudget; |
| } |
| |
| private static void outputConfiguredTargets( |
| OutErr outErr, |
| PathPrettyPrinter prettyPrinter, |
| ArrayList<ConfiguredTarget> succeeded, |
| ArrayList<ArrayList<Artifact>> artifactsToPrintPerTarget, |
| ArrayList<ConfiguredTarget> failed, |
| ArrayList<ConfiguredTarget> skipped, |
| boolean omitNothingToBuild) { |
| for (ConfiguredTarget target : skipped) { |
| outErr.printErr("Target " + target.getLabel() + " was skipped\n"); |
| } |
| for (int i = 0; i < succeeded.size(); ++i) { |
| ConfiguredTarget target = succeeded.get(i); |
| Label label = target.getLabel(); |
| ArrayList<Artifact> artifacts = artifactsToPrintPerTarget.get(i); |
| if (artifacts.isEmpty()) { |
| if (!omitNothingToBuild) { |
| outErr.printErr("Target " + label + " up-to-date (nothing to build)\n"); |
| } |
| continue; |
| } |
| outErr.printErr("Target " + label + " up-to-date:\n"); |
| for (Artifact artifact : artifacts) { |
| outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, artifact)); |
| } |
| } |
| for (ConfiguredTarget target : failed) { |
| outErr.printErr("Target " + target.getLabel() + " failed to build\n"); |
| |
| // For failed compilation, it is still useful to examine temp artifacts, (ie, preprocessed and |
| // assembler files). |
| OutputGroupInfo topLevelProvider = OutputGroupInfo.get(target); |
| if (topLevelProvider != null) { |
| for (Artifact temp : topLevelProvider.getOutputGroup(OutputGroupInfo.TEMP_FILES).toList()) { |
| if (temp.getPath().exists()) { |
| outErr.printErrLn( |
| " See temp at " + prettyPrinter.getPrettyPath(temp.getPath().asFragment())); |
| } |
| } |
| } |
| } |
| } |
| |
| private static void outputAspects( |
| OutErr outErr, |
| PathPrettyPrinter prettyPrinter, |
| ArrayList<AspectKey> succeeded, |
| ArrayList<ArrayList<Artifact>> artifactsToPrintPerAspect, |
| ArrayList<AspectKey> failed, |
| boolean omitNothingToBuild) { |
| for (int i = 0; i < succeeded.size(); ++i) { |
| AspectKey aspect = succeeded.get(i); |
| Label label = aspect.getLabel(); |
| String aspectName = aspect.getAspectClass().getName(); |
| ArrayList<Artifact> artifacts = artifactsToPrintPerAspect.get(i); |
| if (artifacts.isEmpty()) { |
| if (!omitNothingToBuild) { |
| outErr.printErr( |
| "Aspect " + aspectName + " of " + label + " up-to-date (nothing to build)\n"); |
| } |
| continue; |
| } |
| outErr.printErr("Aspect " + aspectName + " of " + label + " up-to-date:\n"); |
| for (Artifact artifact : artifacts) { |
| outErr.printErrLn(formatArtifactForShowResults(prettyPrinter, artifact)); |
| } |
| } |
| for (AspectKey aspect : failed) { |
| Label label = aspect.getLabel(); |
| String aspectName = aspect.getAspectClass().getName(); |
| outErr.printErr("Aspect " + aspectName + " of " + label + " failed to build\n"); |
| } |
| } |
| |
| private static String formatArtifactForShowResults( |
| PathPrettyPrinter prettyPrinter, Artifact artifact) { |
| return " " + prettyPrinter.getPrettyPath(artifact.getPath().asFragment()); |
| } |
| |
| /** |
| * Returns a list of configured targets that should participate in printing. |
| * |
| * <p>Hidden rules and other inserted targets are ignored. |
| */ |
| private Collection<ConfiguredTarget> filterTargetsToPrint( |
| Collection<ConfiguredTarget> configuredTargets) { |
| ImmutableList.Builder<ConfiguredTarget> result = ImmutableList.builder(); |
| for (ConfiguredTarget configuredTarget : configuredTargets) { |
| if (!TopLevelArtifactHelper.shouldConsiderForDisplay(configuredTarget)) { |
| continue; |
| } |
| if (configuredTarget instanceof OutputFileConfiguredTarget) { |
| // Suppress display of generated files (because they appear underneath |
| // their generating rule), EXCEPT those ones which are not part of the |
| // filesToBuild of their generating rule (e.g. .par, _deploy.jar |
| // files), OR when a user explicitly requests an output file but not |
| // its rule. |
| TransitiveInfoCollection generatingRule = |
| ((OutputFileConfiguredTarget) configuredTarget).getGeneratingRule(); |
| if (generatingRule |
| .getProvider(FileProvider.class) |
| .getFilesToBuild() |
| .toSet() |
| .containsAll( |
| configuredTarget.getProvider(FileProvider.class).getFilesToBuild().toList()) |
| && configuredTargets.contains(generatingRule)) { |
| continue; |
| } |
| } |
| |
| result.add(configuredTarget); |
| } |
| return result.build(); |
| } |
| |
| /** Splits aspects based on whether they are validation aspects. */ |
| private static PartitionedAspectKeys partitionAspectKeys( |
| boolean useValidationAspects, ImmutableSet<AspectKey> keys) { |
| if (!useValidationAspects) { |
| return new PartitionedAspectKeys(keys, ImmutableList.of()); |
| } |
| |
| var aspectsToPrintBuilder = ImmutableSet.<AspectKey>builder(); |
| var validationAspectsBuilder = ImmutableList.<AspectKey>builder(); |
| for (AspectKey key : keys) { |
| if (Objects.equals(key.getAspectClass().getName(), AspectCollection.VALIDATION_ASPECT_NAME)) { |
| validationAspectsBuilder.add(key); |
| } else { |
| aspectsToPrintBuilder.add(key); |
| } |
| } |
| return new PartitionedAspectKeys( |
| aspectsToPrintBuilder.build(), validationAspectsBuilder.build()); |
| } |
| |
| private static class PartitionedAspectKeys { |
| private final ImmutableSet<AspectKey> aspectsToPrint; |
| |
| private final ImmutableList<AspectKey> validationAspects; |
| |
| private PartitionedAspectKeys( |
| ImmutableSet<AspectKey> aspectsToPrint, ImmutableList<AspectKey> validationAspects) { |
| this.aspectsToPrint = aspectsToPrint; |
| this.validationAspects = validationAspects; |
| } |
| } |
| } |