blob: cbe7eb522cbd7b8a97094727627a7be8ef049594 [file] [log] [blame]
// 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.analysis;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.test.TestProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSet.Node;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.profiler.AutoProfiler;
import com.google.devtools.build.lib.profiler.GoogleAutoProfilerUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A small static class containing utility methods for handling the inclusion of extra top-level
* artifacts into the build.
*/
public final class TopLevelArtifactHelper {
/** Set of {@link Artifact}s in an output group. */
@Immutable
public static final class ArtifactsInOutputGroup {
private final boolean important;
private final boolean incomplete;
private final NestedSet<Artifact> artifacts;
private ArtifactsInOutputGroup(
boolean important, boolean incomplete, NestedSet<Artifact> artifacts) {
this.important = important;
this.incomplete = incomplete;
this.artifacts = checkNotNull(artifacts);
}
public NestedSet<Artifact> getArtifacts() {
return artifacts;
}
/** Returns {@code true} if the user should know about this output group. */
public boolean areImportant() {
return important;
}
public boolean isIncomplete() {
return incomplete;
}
}
/**
* The set of artifacts to build.
*
* <p>There are two kinds: the ones that the user cares about (e.g. files to build) and the ones
* they don't (e.g. baseline coverage artifacts). The latter type doesn't get reported on various
* outputs, e.g. on the console output listing the output artifacts of targets on the command
* line.
*/
@Immutable
public static final class ArtifactsToBuild {
private final ImmutableMap<String, ArtifactsInOutputGroup> artifacts;
private final boolean allOutputGroupsImportant;
private ArtifactsToBuild(
ImmutableMap<String, ArtifactsInOutputGroup> artifacts, boolean allOutputGroupsImportant) {
this.artifacts = checkNotNull(artifacts);
this.allOutputGroupsImportant = allOutputGroupsImportant;
}
/** Returns the artifacts that the user should know about. */
public NestedSet<Artifact> getImportantArtifacts() {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
for (ArtifactsInOutputGroup artifactsInOutputGroup : artifacts.values()) {
if (artifactsInOutputGroup.areImportant()) {
builder.addTransitive(artifactsInOutputGroup.getArtifacts());
}
}
return builder.build();
}
/** Returns the actual set of artifacts that need to be built. */
public NestedSet<Artifact> getAllArtifacts() {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
for (ArtifactsInOutputGroup artifactsInOutputGroup : artifacts.values()) {
builder.addTransitive(artifactsInOutputGroup.getArtifacts());
}
return builder.build();
}
/**
* Returns the set of all {@link Artifact}s grouped by their corresponding output group.
*
* <p>If an {@link Artifact} belongs to two or more output groups, it appears once in each
* output group.
*/
public ImmutableMap<String, ArtifactsInOutputGroup> getAllArtifactsByOutputGroup() {
return artifacts;
}
/**
* Returns if all of the output groups returned by {@link #getAllArtifactsByOutputGroup()} are
* "important" - implying that all artifacts will be reported in BEP events.
*/
public boolean areAllOutputGroupsImportant() {
return allOutputGroupsImportant;
}
}
private TopLevelArtifactHelper() {
// Prevent instantiation.
}
private static final Duration MIN_LOGGING = Duration.ofMillis(10);
/**
* Returns the set of all top-level output artifacts.
*
* <p>In contrast with {@link AnalysisResult#getArtifactsToBuild}, which only returns artifacts to
* request from the build tool, this method returns <em>all</em> artifacts produced by top-level
* targets (including tests) and aspects.
*/
public static ImmutableSet<Artifact> findAllTopLevelArtifacts(AnalysisResult analysisResult) {
try (AutoProfiler ignored =
GoogleAutoProfilerUtils.logged("finding top level artifacts", MIN_LOGGING)) {
ImmutableSet.Builder<Artifact> artifacts = ImmutableSet.builder();
artifacts.addAll(analysisResult.getArtifactsToBuild());
TopLevelArtifactContext ctx = analysisResult.getTopLevelContext();
Set<NestedSet.Node> visited = new HashSet<>();
for (ProviderCollection provider :
Iterables.concat(
analysisResult.getTargetsToBuild(), analysisResult.getAspectsMap().values())) {
for (ArtifactsInOutputGroup group :
getAllArtifactsToBuild(provider, ctx).getAllArtifactsByOutputGroup().values()) {
memoizedAddAll(group.getArtifacts(), artifacts, visited);
}
}
if (analysisResult.getTargetsToTest() != null) {
for (ConfiguredTarget testTarget : analysisResult.getTargetsToTest()) {
artifacts.addAll(TestProvider.getTestStatusArtifacts(testTarget));
}
}
return artifacts.build();
}
}
private static void memoizedAddAll(
NestedSet<Artifact> current,
ImmutableSet.Builder<Artifact> artifacts,
Set<NestedSet.Node> visited) {
if (!visited.add(current.toNode())) {
return;
}
artifacts.addAll(current.getLeaves());
for (NestedSet<Artifact> child : current.getNonLeaves()) {
memoizedAddAll(child, artifacts, visited);
}
}
/**
* Returns all artifacts to build if this target is requested as a top-level target. The resulting
* set includes the temps and either the files to compile, if {@code context.compileOnly() ==
* true}, or the files to run.
*
* <p>Calls to this method should generally return quickly; however, the runfiles computation can
* be lazy, in which case it can be expensive on the first call. Subsequent calls may or may not
* return the same {@code Iterable} instance.
*/
public static ArtifactsToBuild getAllArtifactsToBuild(
ProviderCollection target, TopLevelArtifactContext context) {
return getAllArtifactsToBuild(
OutputGroupInfo.get(target), target.getProvider(FileProvider.class), context);
}
static ArtifactsToBuild getAllArtifactsToBuild(
@Nullable OutputGroupInfo outputGroupInfo,
@Nullable FileProvider fileProvider,
TopLevelArtifactContext context) {
ImmutableMap.Builder<String, ArtifactsInOutputGroup> allOutputGroups =
ImmutableMap.builderWithExpectedSize(context.outputGroups().size());
boolean allOutputGroupsImportant = true;
for (String outputGroup : context.outputGroups()) {
NestedSetBuilder<Artifact> results = NestedSetBuilder.stableOrder();
if (outputGroup.equals(OutputGroupInfo.DEFAULT) && fileProvider != null) {
results.addTransitive(fileProvider.getFilesToBuild());
}
if (outputGroupInfo != null) {
results.addTransitive(outputGroupInfo.getOutputGroup(outputGroup));
}
// Ignore output groups that have no artifacts.
if (results.isEmpty()) {
continue;
}
boolean isImportantGroup =
!outputGroup.startsWith(OutputGroupInfo.HIDDEN_OUTPUT_GROUP_PREFIX);
allOutputGroupsImportant &= isImportantGroup;
ArtifactsInOutputGroup artifacts =
new ArtifactsInOutputGroup(isImportantGroup, /*incomplete=*/ false, results.build());
allOutputGroups.put(outputGroup, artifacts);
}
return new ArtifactsToBuild(
allOutputGroups.buildOrThrow(), /*allOutputGroupsImportant=*/ allOutputGroupsImportant);
}
/**
* Returns false if the build outputs provided by the target should never be shown to users.
*
* <p>Always returns false for hidden rules and source file targets.
*/
public static boolean shouldConsiderForDisplay(ConfiguredTarget configuredTarget) {
// TODO(bazel-team): this is quite ugly. Add a marker provider for this check.
if (configuredTarget instanceof InputFileConfiguredTarget) {
// Suppress display of source files (because we do no work to build them).
return false;
}
if (configuredTarget instanceof RuleConfiguredTarget) {
RuleConfiguredTarget ruleCt = (RuleConfiguredTarget) configuredTarget;
if (ruleCt.getRuleClassString().contains("$")) {
// Suppress display of hidden rules
return false;
}
}
return true;
}
/**
* Returns true if the given artifact should be shown to users as a build output.
*
* <p>Always returns false for middleman and source artifacts.
*/
public static boolean shouldDisplay(Artifact artifact) {
return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact();
}
/**
* Recursive procedure filtering a target/aspect's declared {@code
* NestedSet<ArtifactsInOutputGroup>} and {@code NestedSet<Artifact>} to only include {@link
* Artifact Artifacts} that were produced by successful actions.
*/
public static class SuccessfulArtifactFilter {
private final Set<Node> artifactSetCanBeSkipped = new HashSet<>();
private final HashMap<Node, NestedSet<Artifact>> artifactSetToFilteredSet = new HashMap<>();
private final ImmutableSet<Artifact> builtArtifacts;
public SuccessfulArtifactFilter(ImmutableSet<Artifact> builtArtifacts) {
this.builtArtifacts = builtArtifacts;
}
/**
* Filters the declared output groups to only include artifacts that were actually built.
*
* <p>If no filtering is performed then the input NestedSet is returned directly.
*/
public ImmutableMap<String, ArtifactsInOutputGroup> filterArtifactsInOutputGroup(
ImmutableMap<String, ArtifactsInOutputGroup> outputGroups) {
boolean leavesDirty = false;
ImmutableMap.Builder<String, ArtifactsInOutputGroup> resultBuilder = ImmutableMap.builder();
for (Map.Entry<String, ArtifactsInOutputGroup> entry : outputGroups.entrySet()) {
ArtifactsInOutputGroup artifactsInOutputGroup = entry.getValue();
ArtifactsInOutputGroup filteredArtifactsInOutputGroup;
NestedSet<Artifact> filteredArtifacts =
filterArtifactNestedSetToBuiltArtifacts(artifactsInOutputGroup.getArtifacts());
if (filteredArtifacts == null) {
filteredArtifactsInOutputGroup = artifactsInOutputGroup;
} else {
filteredArtifactsInOutputGroup =
new ArtifactsInOutputGroup(
artifactsInOutputGroup.areImportant(), /*incomplete=*/ true, filteredArtifacts);
leavesDirty = true;
}
if (!filteredArtifactsInOutputGroup.getArtifacts().isEmpty()) {
resultBuilder.put(entry.getKey(), filteredArtifactsInOutputGroup);
}
}
if (!leavesDirty) {
return outputGroups;
}
return resultBuilder.buildOrThrow();
}
/**
* Recursively filters the declared artifacts to only include artifacts that were actually
* built.
*
* <p>Returns {@code null} if no artifacts are filtered out of the input.
*/
@Nullable
private NestedSet<Artifact> filterArtifactNestedSetToBuiltArtifacts(
NestedSet<Artifact> declaredArtifacts) {
Node declaredArtifactsNode = declaredArtifacts.toNode();
if (artifactSetCanBeSkipped.contains(declaredArtifactsNode)) {
return null;
}
NestedSet<Artifact> memoizedFilteredSet = artifactSetToFilteredSet.get(declaredArtifactsNode);
if (memoizedFilteredSet != null) {
return memoizedFilteredSet;
}
// Scan the Artifact leaves for any artifact not present in builtArtifacts. If an un-built
// artifact is found, exit the loop early, and construct the list of filteredArtifacts later.
// This avoids unnecessary allocation in the case where all artifacts are built.
boolean leavesDirty = false;
ImmutableList<Artifact> leaves = declaredArtifacts.getLeaves();
for (Artifact a : leaves) {
if (!builtArtifacts.contains(a)) {
leavesDirty = true;
break;
}
}
// Unconditionally populate filteredNonLeaves by filtering each NestedSet<Artifact> non-leaf
// successor, and set nonLeavesDirty if anything is filtered out. The filteredNonLeaves list
// will only be used if leavesDirty is true or nonLeavesDirty is true.
boolean nonLeavesDirty = false;
ImmutableList<NestedSet<Artifact>> nonLeaves = declaredArtifacts.getNonLeaves();
List<NestedSet<Artifact>> filteredNonLeaves = new ArrayList<>(nonLeaves.size());
for (NestedSet<Artifact> nonLeaf : nonLeaves) {
NestedSet<Artifact> filteredNonLeaf = filterArtifactNestedSetToBuiltArtifacts(nonLeaf);
// Null indicates no filtering happened and the input may be used as-is.
if (filteredNonLeaf != null) {
nonLeavesDirty = true;
} else {
filteredNonLeaf = nonLeaf;
}
if (!filteredNonLeaf.isEmpty()) {
filteredNonLeaves.add(filteredNonLeaf);
}
}
if (!leavesDirty && !nonLeavesDirty) {
artifactSetCanBeSkipped.add(declaredArtifactsNode);
// Returning null indicates no filtering happened and the input may be used as-is.
return null;
}
NestedSetBuilder<Artifact> newSetBuilder =
new NestedSetBuilder<>(declaredArtifacts.getOrder());
for (Artifact a : leaves) {
if (builtArtifacts.contains(a)) {
newSetBuilder.add(a);
}
}
for (NestedSet<Artifact> filteredNonLeaf : filteredNonLeaves) {
newSetBuilder.addTransitive(filteredNonLeaf);
}
NestedSet<Artifact> result = newSetBuilder.build();
artifactSetToFilteredSet.put(declaredArtifactsNode, result);
return result;
}
}
}