blob: ee0192584fa50023384e8753bbc3ab506594de36 [file] [log] [blame]
// Copyright 2023 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.producers;
import static com.google.common.base.Preconditions.checkState;
import static com.google.devtools.build.lib.analysis.AspectResolutionHelpers.computeAspectCollection;
import static com.google.devtools.build.lib.analysis.producers.AttributeConfiguration.Kind.VISIBILITY;
import static com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData.SPLIT_DEP_ORDERING;
import static java.util.Arrays.copyOfRange;
import static java.util.Arrays.sort;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.AspectCollection;
import com.google.devtools.build.lib.analysis.DuplicateException;
import com.google.devtools.build.lib.analysis.InconsistentAspectOrderException;
import com.google.devtools.build.lib.analysis.InconsistentNullConfigException;
import com.google.devtools.build.lib.analysis.InvalidVisibilityDependencyException;
import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException;
import com.google.devtools.build.lib.analysis.configuredtargets.PackageGroupConfiguredTarget;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.skyframe.AspectCreationException;
import com.google.devtools.build.lib.skyframe.BaseTargetPrerequisitesSupplier;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey;
import com.google.devtools.build.skyframe.state.StateMachine;
import java.util.HashSet;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Computes requested prerequisite(s), applying any requested aspects.
*
* <p>A dependency is specified by a {@link Label} and an execution platform {@link Label} if it is
* a toolchain.
*
* <p>Its configuration is determined by an {@link AttributeConfiguration}, which may be split and
* result in multiple outputs.
*
* <p>Computes any specified aspects, applying the appropriate filtering, and merges them into the
* resulting values.
*/
final class PrerequisitesProducer
implements StateMachine,
ConfiguredTargetAndDataProducer.ResultSink,
ConfiguredAspectProducer.ResultSink {
interface ResultSink {
void acceptPrerequisitesValue(ConfiguredTargetAndData[] prerequisites);
void acceptPrerequisitesError(NoSuchThingException error);
void acceptPrerequisitesError(InvalidVisibilityDependencyException error);
void acceptPrerequisitesCreationError(ConfiguredValueCreationException error);
void acceptPrerequisitesAspectError(DependencyEvaluationException error);
void acceptPrerequisitesAspectError(AspectCreationException error);
}
// -------------------- Input --------------------
private final PrerequisiteParameters parameters;
private final Label label;
@Nullable // Non-null for toolchain prerequisites.
private final Label executionPlatformLabel;
private final AttributeConfiguration configuration;
private final ImmutableList<Aspect> propagatingAspects;
private final boolean useBaseTargetPrerequisitesSupplier;
// -------------------- Output --------------------
private final ResultSink sink;
// -------------------- Internal State --------------------
private ConfiguredTargetAndData[] configuredTargets;
private boolean hasError;
PrerequisitesProducer(
PrerequisiteParameters parameters,
Label label,
@Nullable Label executionPlatformLabel,
AttributeConfiguration configuration,
ImmutableList<Aspect> propagatingAspects,
ResultSink sink,
boolean useBaseTargetPrerequisitesSupplier) {
this.parameters = parameters;
this.label = label;
this.executionPlatformLabel = executionPlatformLabel;
this.configuration = configuration;
this.propagatingAspects = propagatingAspects;
this.sink = sink;
this.useBaseTargetPrerequisitesSupplier = useBaseTargetPrerequisitesSupplier;
// size > 0 guaranteed by contract of SplitTransition.
int size = configuration.count();
this.configuredTargets = new ConfiguredTargetAndData[size];
}
@Override
public StateMachine step(Tasks tasks) {
BaseTargetPrerequisitesSupplier baseTargetPrerequisitesSupplier =
useBaseTargetPrerequisitesSupplier ? parameters.baseTargetPrerequisitesSupplier() : null;
switch (configuration.kind()) {
case VISIBILITY:
tasks.enqueue(
new ConfiguredTargetAndDataProducer(
getPrerequisiteKey(/* configurationKey= */ null),
/* transitionKeys= */ ImmutableList.of(),
parameters.transitiveState(),
(ConfiguredTargetAndDataProducer.ResultSink) this,
/* outputIndex= */ 0,
baseTargetPrerequisitesSupplier));
break;
case NULL_TRANSITION_KEYS:
tasks.enqueue(
new ConfiguredTargetAndDataProducer(
getPrerequisiteKey(/* configurationKey= */ null),
configuration.nullTransitionKeys(),
parameters.transitiveState(),
(ConfiguredTargetAndDataProducer.ResultSink) this,
/* outputIndex= */ 0,
baseTargetPrerequisitesSupplier));
break;
case UNARY:
tasks.enqueue(
new ConfiguredTargetAndDataProducer(
getPrerequisiteKey(configuration.unary()),
/* transitionKeys= */ ImmutableList.of(),
parameters.transitiveState(),
(ConfiguredTargetAndDataProducer.ResultSink) this,
/* outputIndex= */ 0,
baseTargetPrerequisitesSupplier));
break;
case SPLIT:
int index = 0;
for (Map.Entry<String, BuildConfigurationKey> entry : configuration.split().entrySet()) {
tasks.enqueue(
new ConfiguredTargetAndDataProducer(
getPrerequisiteKey(entry.getValue()),
ImmutableList.of(entry.getKey()),
parameters.transitiveState(),
(ConfiguredTargetAndDataProducer.ResultSink) this,
index,
baseTargetPrerequisitesSupplier));
++index;
}
break;
}
return this::computeConfiguredAspects;
}
@Override
public void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index) {
configuredTargets[index] = value;
}
@Override
public void acceptConfiguredTargetAndDataError(NoSuchThingException error) {
hasError = true;
sink.acceptPrerequisitesError(error);
}
@Override
public void acceptConfiguredTargetAndDataError(InconsistentNullConfigException error) {
hasError = true;
if (configuration.kind() == VISIBILITY) {
// The target was configurable, but used as a visibility dependency. This is invalid because
// only `PackageGroup`s are accepted as visibility dependencies and those are not
// configurable. Propagates the exception with more precise information.
sink.acceptPrerequisitesError(new InvalidVisibilityDependencyException(label));
return;
}
// `configuration.kind()` was `NULL_TRANSITION_KEYS`. This is only used when the target is in
// the same package as the parent and not configurable so this should never happen.
throw new IllegalStateException(error);
}
@Override
public void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error) {
hasError = true;
sink.acceptPrerequisitesCreationError(error);
}
private StateMachine computeConfiguredAspects(Tasks tasks) {
if (hasError) {
return DONE;
}
if (configuration.kind() == VISIBILITY) {
// Verifies that the dependency is a `package_group`. The value is always at index 0 because
// the `VISIBILITY` configuration is always unary.
if (!(configuredTargets[0].getConfiguredTarget() instanceof PackageGroupConfiguredTarget)) {
sink.acceptPrerequisitesError(new InvalidVisibilityDependencyException(label));
return DONE;
}
}
cleanupValues();
AspectCollection aspects;
try {
// All configured targets in the set have the same underlying target so using an arbitrary one
// for aspect filtering is safe.
aspects = computeAspectCollection(configuredTargets[0], propagatingAspects);
} catch (InconsistentAspectOrderException e) {
sink.acceptPrerequisitesAspectError(new DependencyEvaluationException(e));
return DONE;
}
if (aspects.isEmpty()) { // Short circuits if there are no aspects.
sink.acceptPrerequisitesValue(configuredTargets);
return DONE;
}
for (int i = 0; i < configuredTargets.length; ++i) {
ConfiguredTargetAndData target = configuredTargets[i];
configuredTargets[i] = null;
tasks.enqueue(
new ConfiguredAspectProducer(
aspects,
target,
(ConfiguredAspectProducer.ResultSink) this,
i,
parameters.transitiveState()));
}
return this::emitMergedTargets;
}
@Override
public void acceptConfiguredAspectMergedTarget(
int outputIndex, ConfiguredTargetAndData mergedTarget) {
configuredTargets[outputIndex] = mergedTarget;
}
@Override
public void acceptConfiguredAspectError(DuplicateException error) {
hasError = true;
sink.acceptPrerequisitesAspectError(
new DependencyEvaluationException(
new ConfiguredValueCreationException(
parameters.location(),
error.getMessage(),
parameters.label(),
parameters.eventId(),
/* rootCauses= */ null,
/* detailedExitCode= */ null),
/* depReportedOwnError= */ false));
}
@Override
public void acceptConfiguredAspectError(AspectCreationException error) {
hasError = true;
sink.acceptPrerequisitesAspectError(error);
}
private StateMachine emitMergedTargets(Tasks tasks) {
if (!hasError) {
sink.acceptPrerequisitesValue(configuredTargets);
}
return DONE;
}
private ConfiguredTargetKey getPrerequisiteKey(@Nullable BuildConfigurationKey configurationKey) {
var key = ConfiguredTargetKey.builder().setLabel(label).setConfigurationKey(configurationKey);
if (executionPlatformLabel != null) {
key.setExecutionPlatformLabel(executionPlatformLabel);
}
return key.build();
}
private void cleanupValues() {
if (configuredTargets.length == 1) {
return;
}
// Otherwise, there was a split transition.
if (configuredTargets[0].getConfiguration() == null) {
// The resulting configurations are null. Aggregates the transition keys.
var keys = new ImmutableList.Builder<String>();
keys.addAll(configuredTargets[0].getTransitionKeys());
for (int i = 1; i < configuredTargets.length; ++i) {
checkState(
configuredTargets[i].getConfiguration() == null,
"inconsistent split transition result from %s to %s",
parameters.label(),
label);
keys.addAll(configuredTargets[i].getTransitionKeys());
}
configuredTargets =
new ConfiguredTargetAndData[] {configuredTargets[0].copyWithTransitionKeys(keys.build())};
return;
}
// Deduplicates entries that have identical configurations and thus identical values, keeping
// only the first entry with the configuration.
var seenConfigurations = new HashSet<BuildConfigurationKey>();
int firstIndex = 0;
for (int i = 0; i < configuredTargets.length; ++i) {
if (!seenConfigurations.add(configuredTargets[i].getConfigurationKey())) {
// The target at `i` was a duplicate of a previous target. Deletes it by:
// 1. overwriting it with the first target; and
// 2. removing the slot previously associated with the first target.
configuredTargets[i] = configuredTargets[firstIndex++];
}
}
if (firstIndex > 0) {
configuredTargets = copyOfRange(configuredTargets, firstIndex, configuredTargets.length);
}
sort(configuredTargets, SPLIT_DEP_ORDERING);
}
}