| // 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)); |
| } |
| } |
| } |