blob: 89286aac0d10c64af277077553f02d74a4b75f27 [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.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
import com.google.devtools.build.lib.analysis.InvalidVisibilityDependencyException;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.Package;
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.PackageValue;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.state.StateMachine;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
* Determines {@link ConfiguredTargetAndData} from {@link ConfiguredTargetKey}.
*
* <p>The resulting package and configuration are based on the resulting {@link ConfiguredTarget}
* and may be different from what is in the key, for example, if there is an alias.
*/
final class ConfiguredTargetAndDataProducer
implements StateMachine,
Consumer<SkyValue>,
StateMachine.ValueOrException2Sink<
ConfiguredValueCreationException, InvalidVisibilityDependencyException> {
/** Interface for accepting values produced by this class. */
interface ResultSink {
void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index);
void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error);
void acceptConfiguredTargetAndDataError(InvalidVisibilityDependencyException error);
}
// -------------------- Input --------------------
private final ConfiguredTargetKey key;
@Nullable // Null if no transition key is needed (patch transition or no-op split transition).
private final String transitionKey;
private final TransitiveDependencyState transitiveState;
// -------------------- Output --------------------
private final ResultSink sink;
private final int outputIndex;
// -------------------- Internal State --------------------
private ConfiguredTarget configuredTarget;
@Nullable // Null if the configured target key's configuration key is null.
private BuildConfigurationValue configurationValue;
private Package pkg;
public ConfiguredTargetAndDataProducer(
ConfiguredTargetKey key,
@Nullable String transitionKey,
TransitiveDependencyState transitiveState,
ResultSink sink,
int outputIndex) {
this.key = key;
this.transitionKey = transitionKey;
this.transitiveState = transitiveState;
this.sink = sink;
this.outputIndex = outputIndex;
}
@Override
public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
tasks.lookUp(
key.toKey(),
ConfiguredValueCreationException.class,
InvalidVisibilityDependencyException.class,
(ValueOrException2Sink<
ConfiguredValueCreationException, InvalidVisibilityDependencyException>)
this);
return this::fetchConfigurationAndPackage;
}
@Override
public void acceptValueOrException2(
@Nullable SkyValue value,
@Nullable ConfiguredValueCreationException error,
@Nullable InvalidVisibilityDependencyException visibilityError) {
if (value != null) {
var configuredTargetValue = (ConfiguredTargetValue) value;
this.configuredTarget = configuredTargetValue.getConfiguredTarget();
transitiveState.updateTransitivePackages(configuredTargetValue);
return;
}
if (error != null) {
transitiveState.addTransitiveCauses(error.getRootCauses());
sink.acceptConfiguredTargetAndDataError(error);
return;
}
if (visibilityError != null) {
sink.acceptConfiguredTargetAndDataError(visibilityError);
return;
}
throw new IllegalArgumentException("both value and error were null");
}
private StateMachine fetchConfigurationAndPackage(Tasks tasks, ExtendedEventHandler listener) {
if (configuredTarget == null) {
return DONE; // There was a previous error.
}
var configurationKey = configuredTarget.getConfigurationKey();
if (configurationKey != null) {
tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
}
// An alternative to this is to optimistically fetch the package using the label of the
// configured target key. However, the actual package may differ when this is an
// AliasConfiguredTarget and would need to be refetched.
// TODO(shahan): This lookup should be skipped when the ConfiguredTarget is fetched remotely.
var packageId = configuredTarget.getLabel().getPackageIdentifier();
this.pkg = transitiveState.getDependencyPackage(packageId);
if (pkg == null) {
// In incremental builds, it is possible that the package won't be present in the cache. For
// example, suppose that a configured target A has two children B and C. If B is dirty, it
// causes A's re-evaluation, which causes this fetch to be performed for C. However, C has not
// been evaluated this build.
tasks.lookUp(packageId, (Consumer<SkyValue>) this);
}
return this::constructResult;
}
@Override
public void accept(SkyValue value) {
if (value instanceof BuildConfigurationValue) {
this.configurationValue = (BuildConfigurationValue) value;
return;
}
if (value instanceof PackageValue) {
this.pkg = ((PackageValue) value).getPackage();
return;
}
throw new IllegalArgumentException("unexpected value: " + value);
}
private StateMachine constructResult(Tasks tasks, ExtendedEventHandler listener) {
Target target;
try {
target = pkg.getTarget(configuredTarget.getLabel().getName());
} catch (NoSuchTargetException e) {
// The package was fetched based on the label of the configured target. Since the configured
// target exists, it must have existed in the package when it was created.
throw new IllegalStateException("Target already verified for " + configuredTarget, e);
}
sink.acceptConfiguredTargetAndData(
new ConfiguredTargetAndData(
configuredTarget,
target,
configurationValue,
transitionKey == null ? ImmutableList.of() : ImmutableList.of(transitionKey)),
outputIndex);
return DONE;
}
}