blob: 33c8e1433ed08d7c80ef10effcb93d2331cddc3c [file] [log] [blame]
// Copyright 2015 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.skyframe;
import static com.google.devtools.build.lib.analysis.producers.TargetAndConfigurationProducer.configurationIdMessage;
import static com.google.devtools.build.lib.analysis.producers.TargetAndConfigurationProducer.createDetailedExitCode;
import static com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil.configurationId;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionConflictException;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.AspectCollection;
import com.google.devtools.build.lib.analysis.AspectResolutionHelpers;
import com.google.devtools.build.lib.analysis.AspectValue;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
import com.google.devtools.build.lib.analysis.InconsistentAspectOrderException;
import com.google.devtools.build.lib.analysis.TransitiveDependencyState;
import com.google.devtools.build.lib.analysis.config.StarlarkTransitionCache;
import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.producers.RuleTransitionApplier;
import com.google.devtools.build.lib.analysis.producers.RuleTransitionApplier.IdempotencyState;
import com.google.devtools.build.lib.analysis.producers.TargetAndConfigurationData;
import com.google.devtools.build.lib.causes.AnalysisFailedCause;
import com.google.devtools.build.lib.causes.Cause;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.RuleTransitionData;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code;
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.TopLevelAspectsKey;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetEvaluationExceptions.DependencyException;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetEvaluationExceptions.ReportedException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider;
import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunction.Environment.SkyKeyComputeState;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
import com.google.devtools.build.skyframe.state.Driver;
import com.google.devtools.build.skyframe.state.StateMachine;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.syntax.Location;
/**
* SkyFunction to run the aspects path obtained from top-level aspects on the list of top-level
* targets.
*
* <p>Used for loading top-level aspects, filtering them based on their required providers, and
* computing the relationship between top-level aspects.
*
* <p>At top level, in {@link com.google.devtools.build.lib.analysis.BuildView}, we cannot invoke
* two SkyFunctions one after another, so BuildView calls this function to do the work.
*/
final class ToplevelStarlarkAspectFunction implements SkyFunction {
private final BuildViewProvider buildViewProvider;
private final RuleClassProvider ruleClassProvider;
private final boolean storeTransitivePackages;
// Do not use this field for package retrieval of the base configured target since it will cause
// incrementality errors because an essential dependency edge would not be registered.
private final PrerequisitePackageFunction prerequisitePackages;
ToplevelStarlarkAspectFunction(
BuildViewProvider buildViewProvider,
RuleClassProvider ruleClassProvider,
boolean storeTransitivePackages,
PrerequisitePackageFunction prerequisitePackages) {
this.buildViewProvider = buildViewProvider;
this.ruleClassProvider = ruleClassProvider;
this.storeTransitivePackages = storeTransitivePackages;
this.prerequisitePackages = prerequisitePackages;
}
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws InterruptedException,
TopLevelStarlarkAspectFunctionException,
DependencyException,
ReportedException {
TopLevelAspectsKey topLevelAspectsKey = (TopLevelAspectsKey) skyKey.argument();
LoadAspectsKey loadAspectsKey =
LoadAspectsKey.create(
topLevelAspectsKey.getTopLevelAspectsClasses(),
topLevelAspectsKey.getTopLevelAspectsParameters());
PackageIdentifier packageIdentifier =
topLevelAspectsKey.getBaseConfiguredTargetKey().getLabel().getPackageIdentifier();
SkyframeLookupResult initialLookupResult =
env.getValuesAndExceptions(ImmutableList.of(loadAspectsKey, packageIdentifier));
var loadAspectsValue = (LoadAspectsValue) initialLookupResult.get(loadAspectsKey);
if (loadAspectsValue == null) {
return null; // aspects are not ready
}
var packageValue = (PackageValue) initialLookupResult.get(packageIdentifier);
if (packageValue == null) {
return null; // package is not ready
}
Target target =
getTarget(packageValue, topLevelAspectsKey.getBaseConfiguredTargetKey().getLabel());
State state = env.getState(() -> new State(storeTransitivePackages, prerequisitePackages));
// Configuration of top level target could change during the analysis phase with rule
// transitions. In order not to wait for the complete configuration of the assigned target,
// {@link RuleTransitionApplier} is used to apply potentially requested rule transitions
// upfront. Configuration can be `null` if the target is not configurable, in which case the
// Skyframe restart is needed.
ConfiguredTargetKey baseConfiguredTargetKey =
getConfiguredTargetKey(state, topLevelAspectsKey.getBaseConfiguredTargetKey(), target, env);
if (baseConfiguredTargetKey == null) {
return null;
}
ImmutableList<AspectKey> aspectsKeys =
createAspectsKeys(
state, target, loadAspectsValue.getAspects(), baseConfiguredTargetKey, env);
if (aspectsKeys == null) {
return null; // alias target needs to be resolved
}
SkyframeLookupResult result = env.getValuesAndExceptions(aspectsKeys);
if (env.valuesMissing()) {
return null; // some aspects keys are not evaluated
}
ImmutableMap.Builder<AspectKey, AspectValue> valuesMap =
ImmutableMap.builderWithExpectedSize(aspectsKeys.size());
for (AspectKey aspectKey : aspectsKeys) {
try {
AspectValue value =
(AspectValue) result.getOrThrow(aspectKey, ActionConflictException.class);
if (value == null) {
return null;
}
valuesMap.put(aspectKey, value);
} catch (ActionConflictException e) {
// Required in case of skymeld: the AspectKey isn't accessible from the BuildDriverKey.
throw new TopLevelStarlarkAspectFunctionException(
ActionConflictException.withAspectKeyInfo(e, aspectKey));
}
}
return new TopLevelAspectsValue(valuesMap.buildOrThrow());
}
private static Target getTarget(PackageValue packageValue, Label targetLabel)
throws DependencyException {
Package pkg = packageValue.getPackage();
try {
return pkg.getTarget(targetLabel.getName());
} catch (NoSuchTargetException e) {
throw new DependencyException(e);
}
}
@Nullable
private static ImmutableList<AspectKey> createAspectsKeys(
State state,
Target target,
ImmutableList<Aspect> aspects,
ConfiguredTargetKey baseConfiguredTargetKey,
Environment env)
throws InterruptedException, DependencyException, TopLevelStarlarkAspectFunctionException {
if (state.aspectKeys != null) {
return state.aspectKeys;
}
// In case the target is an alias, we need to resolve its actual target.
if (AliasProvider.mayBeAlias(target)) {
var aliasConfiguredValue = (ConfiguredTargetValue) env.getValue(baseConfiguredTargetKey);
if (env.valuesMissing()) {
return null;
}
Label actualLabel = aliasConfiguredValue.getConfiguredTarget().getActual().getLabel();
var packageValue = (PackageValue) env.getValue(actualLabel.getPackageIdentifier());
if (env.valuesMissing()) {
return null;
}
target = getTarget(packageValue, actualLabel);
}
AspectCollection aspectCollection;
try {
// TODO(bazel-team): Filter aspects more based on rule type. For example, aspect key should
// not be created for a file target if the aspect does not apply to files or their generating
// rules. Currently, some tests depend on such keys being created, so they need to be modified
// first.
if (target.isRule()) {
Rule ruleTarget = (Rule) target;
aspectCollection =
AspectResolutionHelpers.computeAspectCollection(
aspects,
ruleTarget.getAdvertisedProviders(),
ruleTarget.getLabel(),
ruleTarget.getRuleClassObject(),
ruleTarget.getOnlyTagsAttribute(),
ruleTarget.getLocation(),
env.getListener());
} else {
aspectCollection =
AspectResolutionHelpers.computeAspectCollectionNoAspectsFiltering(
aspects, target.getLabel(), target.getLocation());
}
} catch (InconsistentAspectOrderException e) {
// This is very unlikely, because AspectCollection should have deduplicated top level aspects.
env.getListener().handle(Event.error(e.getMessage()));
throw new TopLevelStarlarkAspectFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
} catch (EvalException e) {
env.getListener().handle(Event.error(e.getMessageWithStack()));
throw new TopLevelStarlarkAspectFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
state.aspectKeys = aspectCollection.createAspectKeys(baseConfiguredTargetKey);
return state.aspectKeys;
}
/**
* Returns {@code `baseConfiguredTargetKey`} if the configuration didn't change with potential
* transitions ({@link IdempotencyState#IDENTITY}). Otherwise, returns a new {@link
* ConfiguredTargetKey} with the new configuration ({@code `buildConfigurationKey`}).
*/
@Nullable
private ConfiguredTargetKey createConfiguredTargetKey(
BuildConfigurationKey buildConfigurationKey,
ConfiguredTargetKey baseConfiguredTargetKey,
IdempotencyState idempotencyState) {
if (idempotencyState == IdempotencyState.IDENTITY) {
return baseConfiguredTargetKey;
}
ConfiguredTargetKey.Builder keyBuilder =
ConfiguredTargetKey.builder()
.setLabel(baseConfiguredTargetKey.getLabel())
.setConfigurationKey(buildConfigurationKey);
if (idempotencyState == IdempotencyState.NON_IDEMPOTENT) {
// The transition was not idempotent. Explicitly informs the delegate to avoid applying a
// rule transition.
keyBuilder.setShouldApplyRuleTransition(false);
}
return keyBuilder.build();
}
/**
* Computes configuration of the target by driving the state machine of {@link
* RuleTransitionApplier}.
*/
public void computeConfiguration(
Environment env,
State state,
ConfiguredTargetKey baseConfiguredTargetKey,
Target target,
ConfiguredRuleClassProvider ruleClassProvider,
BuildViewProvider buildViewProvider)
throws InterruptedException {
if (state.myProducer == null) {
state.myProducer =
new Driver(
new TransitionedBaseConfigurationProducer(
baseConfiguredTargetKey,
ruleClassProvider.getTrimmingTransitionFactory(),
ruleClassProvider.getToolchainTaggedTrimmingTransition(),
buildViewProvider.getSkyframeBuildView().getStarlarkTransitionCache(),
target,
state));
}
if (state.myProducer.drive(env)) {
state.myProducer = null;
}
}
// Computes {@link BuildConfigurationKey} by driving the state machine of {@link
// RuleTransitionApplier} and returns the new {@link ConfiguredTargetKey} with the obtained build
// configuration. In case configuration key is still not ready, returns `null` since Skyframe
// restart is needed.
@Nullable
private ConfiguredTargetKey getConfiguredTargetKey(
State state, ConfiguredTargetKey baseConfiguredTargetKey, Target target, Environment env)
throws InterruptedException, ReportedException {
if (!target.isConfigurable()) {
return baseConfiguredTargetKey.toBuilder().setConfigurationKey(null).build();
}
computeConfiguration(
env,
state,
baseConfiguredTargetKey,
target,
(ConfiguredRuleClassProvider) ruleClassProvider,
buildViewProvider);
if (state.hasError()) {
ConfiguredValueCreationException exception =
state.createException(baseConfiguredTargetKey, target);
if (!exception.getMessage().isEmpty()) {
// Report the error to the user.
env.getListener().handle(Event.error(exception.getLocation(), exception.getMessage()));
}
throw new ReportedException(exception);
}
if (state.configurationKey == null) {
// Skyframe restart is needed since configuration is still not ready.
return null;
}
return createConfiguredTargetKey(
state.configurationKey, baseConfiguredTargetKey, state.idempotencyState);
}
private static class TopLevelStarlarkAspectFunctionException extends SkyFunctionException {
protected TopLevelStarlarkAspectFunctionException(ActionConflictException cause) {
super(cause, Transience.PERSISTENT);
}
protected TopLevelStarlarkAspectFunctionException(
TopLevelAspectsDetailsBuildFailedException cause) {
super(cause, Transience.PERSISTENT);
}
}
/**
* {@link StateMachine} which drives {@link RuleTransitionApplier} to apply potentially requested
* rule transitions and accepts the configuration key in {@link State}.
*/
private static class TransitionedBaseConfigurationProducer
implements StateMachine, TargetAndConfigurationData {
ConfiguredTargetKey preRuleTransitionKey;
TransitionFactory<RuleTransitionData> trimmingTransitionFactory;
PatchTransition toolchainTaggedTrimmingTransition;
StarlarkTransitionCache transitionCache;
Target target;
State state;
TransitionedBaseConfigurationProducer(
ConfiguredTargetKey preRuleTransitionKey,
TransitionFactory<RuleTransitionData> trimmingTransitionFactory,
PatchTransition toolchainTaggedTrimmingTransition,
StarlarkTransitionCache transitionCache,
Target target,
State state) {
this.preRuleTransitionKey = preRuleTransitionKey;
this.trimmingTransitionFactory = trimmingTransitionFactory;
this.toolchainTaggedTrimmingTransition = toolchainTaggedTrimmingTransition;
this.transitionCache = transitionCache;
this.target = target;
this.state = state;
}
@Override
public StateMachine step(Tasks tasks) {
return new RuleTransitionApplier(
target,
(TargetAndConfigurationData) this,
(RuleTransitionApplier.ResultSink) state,
state.storedEvents,
/* runAfter= */ DONE);
}
@Override
public ConfiguredTargetKey getPreRuleTransitionKey() {
return preRuleTransitionKey;
}
@Override
public TransitionFactory<RuleTransitionData> getTrimmingTransitionFactory() {
return trimmingTransitionFactory;
}
@Override
public PatchTransition getToolchainTaggedTrimmingTransition() {
return toolchainTaggedTrimmingTransition;
}
@Override
public StarlarkTransitionCache getTransitionCache() {
return transitionCache;
}
@Override
public TransitiveDependencyState getTransitiveState() {
return state.transitiveState;
}
}
/**
* State which drives a {@link TransitionedBaseConfigurationProducer} and accepts the
* configuration when complete.
*/
public static class State implements SkyKeyComputeState, RuleTransitionApplier.ResultSink {
@Nullable // Non-null while in-flight.
private Driver myProducer;
private final TransitiveDependencyState transitiveState;
private final StoredEventHandler storedEvents;
// --------------- Configuration fields ------------------
private BuildConfigurationKey configurationKey;
private IdempotencyState idempotencyState;
// --------------- Aspect fields ------------------
@Nullable private ImmutableList<AspectKey> aspectKeys;
// --------------- Error handling fields ------------------
@Nullable private String message = null;
@Nullable private Location location = null;
@Nullable private DetailedExitCode exitCode = null;
State(boolean storeTransitivePackages, PrerequisitePackageFunction prerequisitePackages) {
this.transitiveState =
new TransitiveDependencyState(storeTransitivePackages, prerequisitePackages);
this.storedEvents = new StoredEventHandler();
}
/**
* Implementation of {@link RuleTransitionApplier.ResultSink}, where accepting the configuration
* and idempotency state is needed to compute {@link ConfiguredTargetKey}.
*/
@Override
public void acceptConfiguration(
BuildConfigurationKey configurationKey, IdempotencyState idempotencyState) {
this.configurationKey = configurationKey;
this.idempotencyState = idempotencyState;
}
/**
* Implementation of {@link RuleTransitionApplier.ResultSink}, where accepting the error message
* is needed to throw {@link ReportedException}.
*/
@Override
public void acceptErrorMessage(
String message, @Nullable Location location, @Nullable DetailedExitCode exitCode) {
this.message = message;
this.location = location;
this.exitCode = exitCode;
}
public boolean hasError() {
return this.message != null || this.location != null || this.exitCode != null;
}
/**
* Handles an exception thrown during the rule transition application in {@link
* RuleTransitionApplier}
*/
public ConfiguredValueCreationException createException(
ConfiguredTargetKey baseConfiguredTargetKey, Target target) {
Cause cause =
new AnalysisFailedCause(
baseConfiguredTargetKey.getLabel(),
configurationIdMessage(
baseConfiguredTargetKey.getConfigurationKey().getOptionsChecksum()),
exitCode != null ? exitCode : createDetailedExitCode(message));
return new ConfiguredValueCreationException(
location,
message,
target.getLabel(),
configurationId(baseConfiguredTargetKey.getConfigurationKey()),
NestedSetBuilder.create(Order.STABLE_ORDER, cause),
exitCode != null ? exitCode : createDetailedExitCode(message));
}
}
}