blob: 6c90dbe0a85b4e599ea30b80707439d5422e0839 [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 com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.InconsistentNullConfigException;
import com.google.devtools.build.lib.analysis.TransitiveDependencyState;
import com.google.devtools.build.lib.analysis.config.ConfigConditions;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.Target;
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.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.DetailedExitCode.DetailedExitCodeComparator;
import com.google.devtools.build.skyframe.state.StateMachine;
import java.util.List;
import javax.annotation.Nullable;
/** Computes the targets that key the configurable attributes used by this rule. */
final class ConfigConditionsProducer
implements StateMachine, ConfiguredTargetAndDataProducer.ResultSink {
interface ResultSink {
void acceptConfigConditions(ConfigConditions configConditions);
void acceptConfigConditionsError(ConfiguredValueCreationException error);
}
// -------------------- Input --------------------
private final Label targetLabel;
private final Target target;
private final BuildConfigurationKey buildConfigurationKey;
@Nullable private final PlatformInfo targetPlatformInfo;
private final TransitiveDependencyState transitiveState;
// -------------------- Output --------------------
private final ResultSink sink;
// -------------------- Sequencing --------------------
private final StateMachine runAfter;
// -------------------- Internal State --------------------
@Nullable // Null if there are no config labels.
private final List<Label> configLabels;
@Nullable // Null if there are no config labels.
private final ConfiguredTargetAndData[] prerequisites;
@Nullable // Null if there are no dependency errors.
private DetailedExitCode mostImportantExitCode;
ConfigConditionsProducer(
Target target,
Label targetLabel,
BuildConfigurationKey buildConfigurationKey,
@Nullable PlatformInfo targetPlatformInfo,
TransitiveDependencyState transitiveState,
ResultSink sink,
StateMachine runAfter) {
this.targetLabel = targetLabel;
this.target = target;
this.buildConfigurationKey = buildConfigurationKey;
this.targetPlatformInfo = targetPlatformInfo;
this.transitiveState = transitiveState;
this.sink = sink;
this.runAfter = runAfter;
this.configLabels = computeConfigLabels(target);
this.prerequisites =
configLabels == null ? null : new ConfiguredTargetAndData[configLabels.size()];
}
@Override
public StateMachine step(Tasks tasks) {
if (configLabels == null) {
sink.acceptConfigConditions(ConfigConditions.EMPTY);
return runAfter;
}
// Collect the actual deps without a configuration transition (since by definition config
// conditions evaluate over the current target's configuration). If the dependency is
// (erroneously) something that needs the null configuration, its analysis will be
// short-circuited. That error will be reported later.
for (int i = 0; i < configLabels.size(); ++i) {
tasks.enqueue(
new ConfiguredTargetAndDataProducer(
ConfiguredTargetKey.builder()
.setLabel(configLabels.get(i))
.setConfigurationKey(buildConfigurationKey)
.build(),
/* transitionKeys= */ ImmutableList.of(),
transitiveState,
(ConfiguredTargetAndDataProducer.ResultSink) this,
i,
/* baseTargetPrerequisitesSupplier= */ null));
}
return this::constructConfigConditions;
}
@Override
public void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index) {
prerequisites[index] = value;
}
@Override
public void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error) {
emitErrorIfMostImportant(error.getDetailedExitCode());
}
@Override
public void acceptConfiguredTargetAndDataError(NoSuchThingException error) {
emitErrorIfMostImportant(error.getDetailedExitCode());
}
@Override
public void acceptConfiguredTargetAndDataError(InconsistentNullConfigException error) {
// A config label was evaluated with a null configuration. This should never happen as
// ConfigConditions are only present if the parent is a Rule, then always evaluated with the
// parent configuration.
throw new IllegalArgumentException(
"ConfigCondition dependency should never be evaluated with a null configuration.", error);
}
private StateMachine constructConfigConditions(Tasks tasks) {
if (mostImportantExitCode != null) {
return runAfter; // There was a previous error.
}
var asConfiguredTargets = new ImmutableMap.Builder<Label, ConfiguredTargetAndData>();
var asConfigConditions = new ImmutableMap.Builder<Label, ConfigMatchingProvider>();
for (int i = 0; i < configLabels.size(); ++i) {
var label = configLabels.get(i);
var prerequisite = prerequisites[i];
asConfiguredTargets.put(label, prerequisite);
try {
asConfigConditions.put(
label, ConfigConditions.fromConfiguredTarget(prerequisite, targetPlatformInfo));
} catch (ConfigConditions.InvalidConditionException e) {
String message =
String.format(
"%s is not a valid select() condition for %s.\n",
prerequisite.getTargetLabel(), targetLabel)
+ String.format(
"To inspect the select(), run: bazel query --output=build %s.\n", targetLabel)
+ "For more help, see https://bazel.build/reference/be/functions#select.\n\n";
sink.acceptConfigConditionsError(new ConfiguredValueCreationException(target, message));
return runAfter;
}
}
sink.acceptConfigConditions(
ConfigConditions.create(
asConfiguredTargets.buildOrThrow(), asConfigConditions.buildOrThrow()));
return runAfter;
}
/**
* Computes the config labels belonging to the given target.
*
* @return null if there were no config labels, implying a {@link ConfigConditions#EMPTY} result.
*/
@Nullable
private static List<Label> computeConfigLabels(Target target) {
Rule rule = target.getAssociatedRule();
if (rule == null) {
return null;
}
var attrs = RawAttributeMapper.of(rule);
if (!attrs.has(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE)) {
return null;
}
// Collects the labels of the configured targets we need to resolve.
List<Label> configLabels =
attrs.get(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE, BuildType.LABEL_LIST);
if (configLabels.isEmpty()) {
return null;
}
return configLabels;
}
private void emitErrorIfMostImportant(@Nullable DetailedExitCode newExitCode) {
mostImportantExitCode =
DetailedExitCodeComparator.chooseMoreImportantWithFirstIfTie(
newExitCode, mostImportantExitCode);
if (newExitCode.equals(mostImportantExitCode)) {
sink.acceptConfigConditionsError(
// The precise error is reported by the dependency that failed to load.
// TODO(gregce): beautify this error: https://github.com/bazelbuild/bazel/issues/11984.
new ConfiguredValueCreationException(
target, "errors encountered resolving select() keys for " + targetLabel));
}
}
}