| // 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 static java.util.Objects.requireNonNull; | |
| 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.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.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 java.util.function.Supplier; | |
| 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 {@code 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 Supplier<StarlarkTransitionCache> starlarkTransitionCacheSupplier; | |
| private final ConfiguredRuleClassProvider 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( | |
| Supplier<StarlarkTransitionCache> starlarkTransitionCacheSupplier, | |
| ConfiguredRuleClassProvider ruleClassProvider, | |
| boolean storeTransitivePackages, | |
| PrerequisitePackageFunction prerequisitePackages) { | |
| this.starlarkTransitionCacheSupplier = requireNonNull(starlarkTransitionCacheSupplier); | |
| this.ruleClassProvider = requireNonNull(ruleClassProvider); | |
| this.storeTransitivePackages = storeTransitivePackages; | |
| this.prerequisitePackages = requireNonNull(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.getRuleDefinitionEnvironmentLabel(), | |
| ruleTarget.getRuleClass(), | |
| 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}. | |
| */ | |
| private void computeConfiguration( | |
| Environment env, State state, ConfiguredTargetKey baseConfiguredTargetKey, Target target) | |
| throws InterruptedException { | |
| if (state.myProducer == null) { | |
| state.myProducer = | |
| new Driver( | |
| new TransitionedBaseConfigurationProducer( | |
| baseConfiguredTargetKey, | |
| ruleClassProvider.getTrimmingTransitionFactory(), | |
| ruleClassProvider.getToolchainTaggedTrimmingTransition(), | |
| starlarkTransitionCacheSupplier.get(), | |
| 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); | |
| 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); | |
| } | |
| static final class TopLevelStarlarkAspectFunctionException extends SkyFunctionException { | |
| TopLevelStarlarkAspectFunctionException(ActionConflictException cause) { | |
| super(cause, Transience.PERSISTENT); | |
| } | |
| 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, this, 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)); | |
| } | |
| } | |
| } |