| // 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 static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil.configurationId; |
| |
| import com.google.auto.value.AutoOneOf; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.ActionLookupKey; |
| import com.google.devtools.build.lib.analysis.ConfiguredTargetValue; |
| import com.google.devtools.build.lib.analysis.ExecGroupCollection; |
| import com.google.devtools.build.lib.analysis.InconsistentNullConfigException; |
| import com.google.devtools.build.lib.analysis.PlatformConfiguration; |
| import com.google.devtools.build.lib.analysis.PlatformOptions; |
| import com.google.devtools.build.lib.analysis.TargetAndConfiguration; |
| import com.google.devtools.build.lib.analysis.TransitiveDependencyState; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.BuildOptionsView; |
| import com.google.devtools.build.lib.analysis.config.ConfigConditions; |
| import com.google.devtools.build.lib.analysis.config.ConfigurationTransitionEvent; |
| import com.google.devtools.build.lib.analysis.config.CoreOptions; |
| import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; |
| import com.google.devtools.build.lib.analysis.config.StarlarkTransitionCache; |
| import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement; |
| import com.google.devtools.build.lib.analysis.config.transitions.ComposingTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; |
| 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.platform.PlatformInfo; |
| import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition.TransitionException; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.ConfigurationId; |
| 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.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.ExtendedEventHandler; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.RuleClass; |
| import com.google.devtools.build.lib.packages.RuleTransitionData; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.packages.TargetUtils; |
| import com.google.devtools.build.lib.packages.Type; |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; |
| import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException; |
| import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey; |
| import com.google.devtools.build.lib.skyframe.config.PlatformMappingException; |
| import com.google.devtools.build.lib.skyframe.toolchains.PlatformLookupUtil.InvalidPlatformException; |
| import com.google.devtools.build.lib.skyframe.toolchains.ToolchainContextKey; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import com.google.devtools.build.skyframe.state.StateMachine; |
| import com.google.devtools.common.options.OptionsParsingException; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.function.Consumer; |
| import javax.annotation.Nullable; |
| import net.starlark.java.syntax.Location; |
| |
| /** |
| * Computes the target and configuration for a configured target key. |
| * |
| * <p>If the key has a configuration and the target is configurable, attempts to apply a rule side |
| * transition. If the configuration changes, delegates to a target with the new configuration. If |
| * the target is not configurable, directly delegates to the null configuration. |
| */ |
| public final class TargetAndConfigurationProducer |
| implements StateMachine, |
| StateMachine.ValueOrExceptionSink<InvalidConfigurationException>, |
| Consumer<SkyValue>, |
| TargetProducer.ResultSink { |
| /** Accepts results of this producer. */ |
| public interface ResultSink { |
| void acceptTargetAndConfiguration(TargetAndConfiguration value, ConfiguredTargetKey fullKey); |
| |
| void acceptTargetAndConfigurationDelegatedValue(ConfiguredTargetValue value); |
| |
| void acceptTargetAndConfigurationError(TargetAndConfigurationError error); |
| } |
| |
| /** Tagged union of possible errors. */ |
| @AutoOneOf(TargetAndConfigurationError.Kind.class) |
| public abstract static class TargetAndConfigurationError { |
| /** Tags the error type. */ |
| public enum Kind { |
| CONFIGURED_VALUE_CREATION, |
| NO_SUCH_THING, |
| INCONSISTENT_NULL_CONFIG |
| } |
| |
| public abstract Kind kind(); |
| |
| public abstract ConfiguredValueCreationException configuredValueCreation(); |
| |
| public abstract NoSuchThingException noSuchThing(); |
| |
| public abstract InconsistentNullConfigException inconsistentNullConfig(); |
| |
| private static TargetAndConfigurationError of(ConfiguredValueCreationException e) { |
| return AutoOneOf_TargetAndConfigurationProducer_TargetAndConfigurationError |
| .configuredValueCreation(e); |
| } |
| |
| private static TargetAndConfigurationError of(NoSuchThingException e) { |
| return AutoOneOf_TargetAndConfigurationProducer_TargetAndConfigurationError.noSuchThing(e); |
| } |
| |
| private static TargetAndConfigurationError of(InconsistentNullConfigException e) { |
| return AutoOneOf_TargetAndConfigurationProducer_TargetAndConfigurationError |
| .inconsistentNullConfig(e); |
| } |
| } |
| |
| // -------------------- Input -------------------- |
| private final ConfiguredTargetKey preRuleTransitionKey; |
| @Nullable private final TransitionFactory<RuleTransitionData> trimmingTransitionFactory; |
| private final PatchTransition toolchainTaggedTrimmingTransition; |
| private final StarlarkTransitionCache transitionCache; |
| |
| private final TransitiveDependencyState transitiveState; |
| |
| // -------------------- Output -------------------- |
| private final ResultSink sink; |
| private final ExtendedEventHandler eventHandler; |
| |
| // -------------------- Internal State -------------------- |
| private Target target; |
| |
| public TargetAndConfigurationProducer( |
| ConfiguredTargetKey preRuleTransitionKey, |
| @Nullable TransitionFactory<RuleTransitionData> trimmingTransitionFactory, |
| PatchTransition toolchainTaggedTrimmingTransition, |
| StarlarkTransitionCache transitionCache, |
| TransitiveDependencyState transitiveState, |
| ResultSink sink, |
| ExtendedEventHandler eventHandler) { |
| this.preRuleTransitionKey = preRuleTransitionKey; |
| this.trimmingTransitionFactory = trimmingTransitionFactory; |
| this.toolchainTaggedTrimmingTransition = toolchainTaggedTrimmingTransition; |
| this.transitionCache = transitionCache; |
| this.transitiveState = transitiveState; |
| this.sink = sink; |
| this.eventHandler = eventHandler; |
| } |
| |
| @Override |
| public StateMachine step(Tasks tasks) { |
| return new TargetProducer( |
| preRuleTransitionKey.getLabel(), |
| transitiveState, |
| (TargetProducer.ResultSink) this, |
| /* runAfter= */ this::determineConfiguration); |
| } |
| |
| @Override |
| public void acceptTarget(Target target) { |
| this.target = target; |
| } |
| |
| @Override |
| public void acceptTargetError(NoSuchPackageException e) { |
| eventHandler.handle(Event.error(e.getMessage())); |
| sink.acceptTargetAndConfigurationError(TargetAndConfigurationError.of(e)); |
| } |
| |
| @Override |
| public void acceptTargetError(NoSuchTargetException e, Location location) { |
| eventHandler.handle(Event.error(location, e.getMessage())); |
| sink.acceptTargetAndConfigurationError(TargetAndConfigurationError.of(e)); |
| } |
| |
| private StateMachine determineConfiguration(Tasks tasks) { |
| if (target == null) { |
| return DONE; // A target could not be determined. |
| } |
| |
| BuildConfigurationKey configurationKey = preRuleTransitionKey.getConfigurationKey(); |
| if (configurationKey == null) { |
| if (target.isConfigurable()) { |
| // We somehow ended up in a target that requires a non-null configuration but with a key |
| // that doesn't have a configuration. This is always an error, but we need to bubble this |
| // up to the parent to provide more context. |
| sink.acceptTargetAndConfigurationError( |
| TargetAndConfigurationError.of(new InconsistentNullConfigException())); |
| return DONE; |
| } |
| sink.acceptTargetAndConfiguration( |
| new TargetAndConfiguration(target, /* configuration= */ null), preRuleTransitionKey); |
| return DONE; |
| } |
| |
| if (!target.isConfigurable()) { |
| // If target is not configurable, but requested with a configuration. Delegates to a key with |
| // the null configuration. This is expected to be uncommon. The common case of a |
| // non-configurable target is an input file, but those are usually package local and requested |
| // correctly with the null configuration. |
| delegateTo( |
| tasks, |
| ConfiguredTargetKey.builder() |
| .setLabel(preRuleTransitionKey.getLabel()) |
| .setExecutionPlatformLabel(preRuleTransitionKey.getExecutionPlatformLabel()) |
| .build()); |
| return DONE; |
| } |
| |
| if (!preRuleTransitionKey.shouldApplyRuleTransition()) { |
| lookUpConfigurationValue(tasks); |
| return DONE; |
| } |
| |
| return new RuleTransitionApplier(); |
| } |
| |
| private void delegateTo(Tasks tasks, ActionLookupKey delegate) { |
| tasks.lookUp(delegate, (Consumer<SkyValue>) this); |
| } |
| |
| @Override |
| public void accept(SkyValue value) { |
| sink.acceptTargetAndConfigurationDelegatedValue((ConfiguredTargetValue) value); |
| } |
| |
| private void lookUpConfigurationValue(Tasks tasks) { |
| tasks.lookUp( |
| preRuleTransitionKey.getConfigurationKey(), |
| InvalidConfigurationException.class, |
| (ValueOrExceptionSink<InvalidConfigurationException>) this); |
| } |
| |
| @Override |
| public void acceptValueOrException( |
| @Nullable SkyValue value, @Nullable InvalidConfigurationException error) { |
| if (value != null) { |
| sink.acceptTargetAndConfiguration( |
| new TargetAndConfiguration(target, (BuildConfigurationValue) value), |
| preRuleTransitionKey); |
| return; |
| } |
| emitError( |
| error.getMessage(), TargetUtils.getLocationMaybe(target), error.getDetailedExitCode()); |
| } |
| |
| /** Applies any requested rule transition before producing the final configuration. */ |
| private class RuleTransitionApplier |
| implements StateMachine, |
| TransitionApplier.ResultSink, |
| ConfigConditionsProducer.ResultSink, |
| PlatformInfoProducer.ResultSink { |
| // -------------------- Internal State -------------------- |
| @Nullable private PlatformInfo platformInfo; |
| private ConfigConditions configConditions; |
| private ConfigurationTransition ruleTransition; |
| private BuildConfigurationKey configurationKey; |
| |
| @Override |
| public StateMachine step(Tasks tasks) throws InterruptedException { |
| |
| UnloadedToolchainContextsInputs unloadedToolchainContextsInputs = |
| getUnloadedToolchainContextsInputs( |
| target, preRuleTransitionKey.getExecutionPlatformLabel()); |
| |
| if (unloadedToolchainContextsInputs.targetToolchainContextKey() != null) { |
| PlatformConfiguration platformConfiguration = |
| new PlatformConfiguration(preRuleTransitionKey.getConfigurationKey().getOptions()); |
| tasks.enqueue( |
| new PlatformInfoProducer( |
| platformConfiguration.getTargetPlatform(), |
| (PlatformInfoProducer.ResultSink) this, |
| this::computeConfigConditions)); |
| } else { |
| this.platformInfo = null; |
| computeConfigConditions(tasks); |
| } |
| |
| return DONE; |
| } |
| |
| // TODO: @aranguyen b/297077082 |
| public UnloadedToolchainContextsInputs getUnloadedToolchainContextsInputs( |
| Target target, @Nullable Label parentExecutionPlatformLabel) throws InterruptedException { |
| if (!(target instanceof Rule rule)) { |
| return UnloadedToolchainContextsInputs.empty(); |
| } |
| |
| if (!preRuleTransitionKey |
| .getConfigurationKey() |
| .getOptions() |
| .contains(PlatformOptions.class)) { |
| return UnloadedToolchainContextsInputs.empty(); |
| } |
| PlatformConfiguration platformConfiguration = |
| new PlatformConfiguration(preRuleTransitionKey.getConfigurationKey().getOptions()); |
| var defaultExecConstraintLabels = |
| getExecutionPlatformConstraints(rule, platformConfiguration); |
| var ruleClass = rule.getRuleClassObject(); |
| boolean useAutoExecGroups = |
| rule.isAttrDefined("$use_auto_exec_groups", Type.BOOLEAN) |
| ? (boolean) rule.getAttr("$use_auto_exec_groups") |
| : preRuleTransitionKey |
| .getConfigurationKey() |
| .getOptions() |
| .get(CoreOptions.class) |
| .useAutoExecGroups; |
| |
| var processedExecGroups = |
| ExecGroupCollection.process( |
| ruleClass.getExecGroups(), |
| defaultExecConstraintLabels, |
| ruleClass.getToolchainTypes(), |
| useAutoExecGroups); |
| |
| if (!rule.useToolchainResolution()) { |
| return UnloadedToolchainContextsInputs.create(processedExecGroups, null); |
| } |
| |
| return UnloadedToolchainContextsInputs.create( |
| processedExecGroups, |
| createDefaultToolchainContextKey( |
| computeToolchainConfigurationKey( |
| preRuleTransitionKey.getConfigurationKey().getOptions(), |
| toolchainTaggedTrimmingTransition), |
| defaultExecConstraintLabels, |
| /* debugTarget= */ platformConfiguration.debugToolchainResolution(rule.getLabel()), |
| /* useAutoExecGroups= */ useAutoExecGroups, |
| ruleClass.getToolchainTypes(), |
| parentExecutionPlatformLabel)); |
| } |
| |
| public ToolchainContextKey createDefaultToolchainContextKey( |
| BuildConfigurationKey configurationKey, |
| ImmutableSet<Label> defaultExecConstraintLabels, |
| boolean debugTarget, |
| boolean useAutoExecGroups, |
| ImmutableSet<ToolchainTypeRequirement> toolchainTypes, |
| @Nullable Label parentExecutionPlatformLabel) { |
| ToolchainContextKey.Builder toolchainContextKeyBuilder = |
| ToolchainContextKey.key() |
| .configurationKey(configurationKey) |
| .execConstraintLabels(defaultExecConstraintLabels) |
| .debugTarget(debugTarget); |
| |
| // Add toolchain types only if automatic exec groups are not created for this target. |
| if (!useAutoExecGroups) { |
| toolchainContextKeyBuilder.toolchainTypes(toolchainTypes); |
| } |
| |
| if (parentExecutionPlatformLabel != null) { |
| // Find out what execution platform the parent used, and force that. |
| // This should only be set for direct toolchain dependencies. |
| toolchainContextKeyBuilder.forceExecutionPlatform(parentExecutionPlatformLabel); |
| } |
| return toolchainContextKeyBuilder.build(); |
| } |
| |
| private BuildConfigurationKey computeToolchainConfigurationKey( |
| BuildOptions buildOptions, PatchTransition toolchainTaggedTrimmingTransition) |
| throws InterruptedException { |
| // The toolchain context's options are the parent rule's options with manual trimming |
| // auto-applied. This means toolchains don't inherit feature flags. This helps build |
| // performance: if the toolchain context had the exact same configuration of its parent and |
| // that |
| // included feature flags, all the toolchain's dependencies would apply this transition |
| // individually. That creates a lot more potentially expensive applications of that transition |
| // (especially since manual trimming applies to every configured target in the build). |
| // |
| // In other words: without this modification: |
| // parent rule -> toolchain context -> toolchain |
| // -> toolchain dep 1 # applies manual trimming to remove feature flags |
| // -> toolchain dep 2 # applies manual trimming to remove feature flags |
| // ... |
| // |
| // With this modification: |
| // parent rule -> toolchain context # applies manual trimming to remove feature flags |
| // -> toolchain |
| // -> toolchain dep 1 |
| // -> toolchain dep 2 |
| // ... |
| // |
| // None of this has any effect on rules that don't utilize manual trimming. |
| BuildOptions toolchainOptions = |
| toolchainTaggedTrimmingTransition.patch( |
| new BuildOptionsView( |
| buildOptions, toolchainTaggedTrimmingTransition.requiresOptionFragments()), |
| eventHandler); |
| return BuildConfigurationKey.create(toolchainOptions); |
| } |
| |
| private ImmutableSet<Label> getExecutionPlatformConstraints( |
| Rule rule, @Nullable PlatformConfiguration platformConfiguration) { |
| if (platformConfiguration == null) { |
| return ImmutableSet.of(); // See NoConfigTransition. |
| } |
| NonconfigurableAttributeMapper mapper = NonconfigurableAttributeMapper.of(rule); |
| ImmutableSet.Builder<Label> execConstraintLabels = new ImmutableSet.Builder<>(); |
| |
| execConstraintLabels.addAll(rule.getRuleClassObject().getExecutionPlatformConstraints()); |
| if (rule.getRuleClassObject() |
| .hasAttr(RuleClass.EXEC_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST)) { |
| execConstraintLabels.addAll( |
| mapper.get(RuleClass.EXEC_COMPATIBLE_WITH_ATTR, BuildType.LABEL_LIST)); |
| } |
| |
| execConstraintLabels.addAll( |
| platformConfiguration.getAdditionalExecutionConstraintsFor(rule.getLabel())); |
| |
| return execConstraintLabels.build(); |
| } |
| |
| @CanIgnoreReturnValue |
| public StateMachine computeConfigConditions(Tasks tasks) { |
| // TODO @aranguyen b/297077082 |
| tasks.enqueue( |
| new ConfigConditionsProducer( |
| target, |
| preRuleTransitionKey.getLabel(), |
| preRuleTransitionKey.getConfigurationKey(), |
| platformInfo, |
| transitiveState, |
| (ConfigConditionsProducer.ResultSink) this, |
| this::computeTransition)); |
| return DONE; |
| } |
| |
| @Override |
| public void acceptConfigConditions(ConfigConditions configConditions) { |
| this.configConditions = configConditions; |
| } |
| |
| @Override |
| public void acceptConfigConditionsError(ConfiguredValueCreationException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| public StateMachine computeTransition(Tasks tasks) { |
| if (configConditions == null) { |
| return DONE; |
| } |
| |
| // logic to compute transition with ConfigConditions |
| var transitionData = |
| RuleTransitionData.create( |
| target.getAssociatedRule(), |
| configConditions.asProviders(), |
| preRuleTransitionKey.getConfigurationKey().getOptionsChecksum()); |
| |
| ConfigurationTransition transition = null; |
| |
| TransitionFactory<RuleTransitionData> transitionFactory = |
| target.getAssociatedRule().getRuleClassObject().getTransitionFactory(); |
| if (transitionFactory != null) { |
| transition = transitionFactory.create(transitionData); |
| } |
| |
| if (trimmingTransitionFactory != null) { |
| var trimmingTransition = trimmingTransitionFactory.create(transitionData); |
| if (transition != null) { |
| transition = ComposingTransition.of(transition, trimmingTransition); |
| } else { |
| transition = trimmingTransition; |
| } |
| } |
| |
| if (transition == null) { |
| lookUpConfigurationValue(tasks); |
| return DONE; |
| } |
| |
| this.ruleTransition = transition; |
| |
| return new TransitionApplier( |
| preRuleTransitionKey.getConfigurationKey(), |
| ruleTransition, |
| transitionCache, |
| (TransitionApplier.ResultSink) this, |
| eventHandler, |
| /* runAfter= */ this::processTransitionedKey); |
| } |
| |
| @Override |
| public void acceptTransitionedConfigurations( |
| ImmutableMap<String, BuildConfigurationKey> transitionResult) { |
| checkState(transitionResult.size() == 1, "Expected exactly one result: %s", transitionResult); |
| this.configurationKey = |
| checkNotNull( |
| transitionResult.get(ConfigurationTransition.PATCH_TRANSITION_KEY), |
| "Transition result missing patch transition entry: %s", |
| transitionResult); |
| } |
| |
| @Override |
| public void acceptTransitionError(TransitionException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptTransitionError(OptionsParsingException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptPlatformMappingError(PlatformMappingException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptPlatformFlagsError(InvalidPlatformException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptPlatformInfo(PlatformInfo info) { |
| this.platformInfo = info; |
| } |
| |
| @Override |
| public void acceptPlatformInfoError(InvalidPlatformException error) { |
| emitErrorMessage(error.getMessage()); |
| } |
| |
| private StateMachine processTransitionedKey(Tasks tasks) { |
| if (configurationKey == null) { |
| return DONE; // There was an error. |
| } |
| |
| BuildConfigurationKey parentConfiguration = preRuleTransitionKey.getConfigurationKey(); |
| if (configurationKey.equals(parentConfiguration)) { |
| // This key owns the configuration and the computation completes normally. |
| lookUpConfigurationValue(tasks); |
| return DONE; |
| } |
| |
| eventHandler.post( |
| ConfigurationTransitionEvent.create( |
| parentConfiguration.getOptionsChecksum(), configurationKey.getOptionsChecksum())); |
| |
| return new IdempotencyChecker(); |
| } |
| |
| /** |
| * Checks the transition for idempotency before applying delegation. |
| * |
| * <p>If the transition is non-idempotent, marks {@link |
| * ConfiguredTargetKey#shouldApplyRuleTransition} false in the delegate key. |
| */ |
| private class IdempotencyChecker implements StateMachine, TransitionApplier.ResultSink { |
| /* At first glance, it seems like setting `shouldApplyRuleTransition=false` should be benign |
| * in both cases, but it would be an error in the idempotent case. |
| * |
| * Idempotent Case |
| * |
| * If we were to mark the idempotent case with `shouldApplyRuleTransition=false`, it would |
| * lead to action conflicts. Let `//foo[123]` be a key that rule transitions to `//foo[abc]` |
| * and suppose the outcome is marked `//foo[abc] shouldApplyRuleTransition=false`. |
| * |
| * A different parent might directly request `//foo[abc] shouldApplyRuleTransition=true`. |
| * Since the rule transition is a idempotent, it would result in the same actions as |
| * `//foo[abc] shouldApplyRuleTransition=false` with a different key, causing action |
| * conflicts. |
| * |
| * Non-idempotent Case |
| * |
| * In the example of //foo[abc] shouldApplyRuleTransition=false and //foo[abc] |
| * shouldApplyRuleTransition=true, there should be no action conflicts because the |
| * `shouldApplyRuleTransition=false` is the result of a non-idempotent rule transition and |
| * `shouldApplyRuleTransition=true` will produce a different configuration. */ |
| |
| // -------------------- Internal State -------------------- |
| private BuildConfigurationKey configurationKey2; |
| |
| @Override |
| public StateMachine step(Tasks tasks) { |
| return new TransitionApplier( |
| configurationKey, |
| ruleTransition, |
| transitionCache, |
| (TransitionApplier.ResultSink) this, |
| eventHandler, |
| /* runAfter= */ this::checkIdempotencyAndDelegate); |
| } |
| |
| @Override |
| public void acceptTransitionedConfigurations( |
| ImmutableMap<String, BuildConfigurationKey> transitionResult) { |
| checkState( |
| transitionResult.size() == 1, "Expected exactly one result: %s", transitionResult); |
| this.configurationKey2 = |
| checkNotNull( |
| transitionResult.get(ConfigurationTransition.PATCH_TRANSITION_KEY), |
| "Transition result missing patch transition entry: %s", |
| transitionResult); |
| } |
| |
| @Override |
| public void acceptTransitionError(TransitionException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptTransitionError(OptionsParsingException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptPlatformMappingError(PlatformMappingException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| @Override |
| public void acceptPlatformFlagsError(InvalidPlatformException e) { |
| emitErrorMessage(e.getMessage()); |
| } |
| |
| private StateMachine checkIdempotencyAndDelegate(Tasks tasks) { |
| if (configurationKey2 == null) { |
| return DONE; // There was an error. |
| } |
| |
| ConfiguredTargetKey.Builder keyBuilder = |
| ConfiguredTargetKey.builder() |
| .setLabel(preRuleTransitionKey.getLabel()) |
| .setExecutionPlatformLabel(preRuleTransitionKey.getExecutionPlatformLabel()) |
| .setConfigurationKey(configurationKey); |
| |
| if (!configurationKey.equals(configurationKey2)) { |
| // The transition was not idempotent. Explicitly informs the delegate to avoid applying a |
| // rule transition. |
| keyBuilder.setShouldApplyRuleTransition(false); |
| } |
| delegateTo(tasks, keyBuilder.build()); |
| return DONE; |
| } |
| } |
| |
| private void emitErrorMessage(String message) { |
| emitError(message, TargetUtils.getLocationMaybe(target), /* exitCode= */ null); |
| } |
| } |
| |
| private void emitError( |
| String message, @Nullable Location location, @Nullable DetailedExitCode exitCode) { |
| Cause cause = |
| new AnalysisFailedCause( |
| preRuleTransitionKey.getLabel(), |
| configurationIdMessage(preRuleTransitionKey.getConfigurationKey().getOptionsChecksum()), |
| exitCode != null ? exitCode : createDetailedExitCode(message)); |
| sink.acceptTargetAndConfigurationError( |
| TargetAndConfigurationError.of( |
| new ConfiguredValueCreationException( |
| location, |
| message, |
| target.getLabel(), |
| configurationId(preRuleTransitionKey.getConfigurationKey()), |
| NestedSetBuilder.create(Order.STABLE_ORDER, cause), |
| exitCode != null ? exitCode : createDetailedExitCode(message)))); |
| } |
| |
| public static ConfigurationId configurationIdMessage(@Nullable String optionsCheckSum) { |
| if (optionsCheckSum == null) { |
| return ConfigurationId.newBuilder().setId("none").build(); |
| } |
| return ConfigurationId.newBuilder().setId(optionsCheckSum).build(); |
| } |
| |
| public static DetailedExitCode createDetailedExitCode(String message) { |
| return DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setAnalysis( |
| Analysis.newBuilder().setCode(Analysis.Code.CONFIGURED_VALUE_CREATION_FAILED)) |
| .build()); |
| } |
| |
| // Public for Cquery. |
| // TODO: @aranguyen keep cquery in sync with ConfiguredTargetFunction |
| @Nullable |
| public static ConfigurationTransition computeTransition( |
| Rule rule, @Nullable TransitionFactory<RuleTransitionData> trimmingTransitionFactory) { |
| var transitionData = RuleTransitionData.create(rule, /* configConditions= */ null, ""); |
| |
| ConfigurationTransition transition = null; |
| |
| TransitionFactory<RuleTransitionData> transitionFactory = |
| rule.getRuleClassObject().getTransitionFactory(); |
| if (transitionFactory != null) { |
| transition = transitionFactory.create(transitionData); |
| } |
| boolean isAlias = rule.getAssociatedRule().getName().equals("alias"); |
| if (trimmingTransitionFactory != null && !isAlias) { |
| var trimmingTransition = trimmingTransitionFactory.create(transitionData); |
| if (transition != null) { |
| transition = ComposingTransition.of(transition, trimmingTransition); |
| } else { |
| transition = trimmingTransition; |
| } |
| } |
| |
| return transition; |
| } |
| } |