| // 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.InconsistentNullConfigException; |
| import com.google.devtools.build.lib.analysis.TransitiveDependencyState; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| 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. |
| */ |
| public final class ConfiguredTargetAndDataProducer |
| implements StateMachine, |
| Consumer<SkyValue>, |
| StateMachine.ValueOrException3Sink< |
| ConfiguredValueCreationException, |
| NoSuchThingException, |
| InconsistentNullConfigException> { |
| /** Interface for accepting values produced by this class. */ |
| public interface ResultSink { |
| void acceptConfiguredTargetAndData(ConfiguredTargetAndData value, int index); |
| |
| void acceptConfiguredTargetAndDataError(ConfiguredValueCreationException error); |
| |
| void acceptConfiguredTargetAndDataError(NoSuchThingException error); |
| |
| void acceptConfiguredTargetAndDataError(InconsistentNullConfigException error); |
| } |
| |
| // -------------------- Input -------------------- |
| private final ConfiguredTargetKey key; |
| private final ImmutableList<String> transitionKeys; |
| 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, |
| ImmutableList<String> transitionKeys, |
| TransitiveDependencyState transitiveState, |
| ResultSink sink, |
| int outputIndex) { |
| this.key = key; |
| this.transitionKeys = transitionKeys; |
| this.transitiveState = transitiveState; |
| this.sink = sink; |
| this.outputIndex = outputIndex; |
| } |
| |
| @Override |
| public StateMachine step(Tasks tasks) { |
| tasks.lookUp( |
| key, |
| ConfiguredValueCreationException.class, |
| NoSuchThingException.class, |
| InconsistentNullConfigException.class, |
| (ValueOrException3Sink< |
| ConfiguredValueCreationException, |
| NoSuchThingException, |
| InconsistentNullConfigException>) |
| this); |
| return this::fetchConfigurationAndPackage; |
| } |
| |
| @Override |
| public void acceptValueOrException3( |
| @Nullable SkyValue value, |
| @Nullable ConfiguredValueCreationException error, |
| @Nullable NoSuchThingException missingTargetError, |
| @Nullable InconsistentNullConfigException visibilityError) { |
| if (value != null) { |
| var configuredTargetValue = (ConfiguredTargetValue) value; |
| this.configuredTarget = configuredTargetValue.getConfiguredTarget(); |
| if (transitiveState.storeTransitivePackages()) { |
| transitiveState.updateTransitivePackages( |
| ConfiguredTargetKey.fromConfiguredTarget(configuredTarget), |
| configuredTargetValue.getTransitivePackages()); |
| } |
| return; |
| } |
| if (error != null) { |
| transitiveState.addTransitiveCauses(error.getRootCauses()); |
| sink.acceptConfiguredTargetAndDataError(error); |
| return; |
| } |
| if (missingTargetError != null) { |
| sink.acceptConfiguredTargetAndDataError(missingTargetError); |
| return; |
| } |
| if (visibilityError != null) { |
| sink.acceptConfiguredTargetAndDataError(visibilityError); |
| return; |
| } |
| throw new IllegalArgumentException("both value and error were null"); |
| } |
| |
| private StateMachine fetchConfigurationAndPackage(Tasks tasks) throws InterruptedException { |
| 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) { |
| 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, transitionKeys), |
| outputIndex); |
| return DONE; |
| } |
| } |