|  | // Copyright 2014 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.common.base.Preconditions.checkNotNull; | 
|  | import static com.google.devtools.build.lib.unsafe.UnsafeProvider.getFieldOffset; | 
|  | import static com.google.devtools.build.lib.util.HashCodes.hashObjects; | 
|  |  | 
|  | import com.google.common.base.MoreObjects; | 
|  | import com.google.devtools.build.lib.actions.ActionLookupKey; | 
|  | import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; | 
|  | import com.google.devtools.build.lib.cmdline.Label; | 
|  | import com.google.devtools.build.lib.query2.common.CqueryNode; | 
|  | import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.AsyncDeserializationContext; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.DeferredObjectCodec; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; | 
|  | import com.google.devtools.build.lib.skyframe.serialization.SerializationException; | 
|  | import com.google.devtools.build.skyframe.SkyFunctionName; | 
|  | import com.google.devtools.build.skyframe.SkyKey; | 
|  | import com.google.errorprone.annotations.CanIgnoreReturnValue; | 
|  | import com.google.errorprone.annotations.Keep; | 
|  | import com.google.protobuf.CodedInputStream; | 
|  | import com.google.protobuf.CodedOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.util.Objects; | 
|  | import java.util.function.Supplier; | 
|  | import javax.annotation.Nullable; | 
|  |  | 
|  | /** | 
|  | * In simple form, a ({@link Label}, {@link BuildConfigurationValue}) pair used to trigger immediate | 
|  | * dependency resolution and the rule analysis. | 
|  | * | 
|  | * <p>In practice, a ({@link Label} and post-transition {@link BuildConfigurationKey}) pair plus a | 
|  | * possible execution platform override {@link Label} with special constraints described as follows. | 
|  | * | 
|  | * <p>A build should not request keys with equal ({@link Label}, {@link BuildConfigurationValue}) | 
|  | * pairs but different execution platform override {@link Label} if the invoked rule will register | 
|  | * actions. (This is potentially OK if all outputs of all registered actions incorporate the | 
|  | * execution platform in their name unless the build also requests keys without an override that | 
|  | * happen to resolve to the same execution platform.) In practice, this issue has not been seen in | 
|  | * any 'real' builds; however, pathologically failure could lead to multiple (potentially different) | 
|  | * ConfiguredTarget that have the same ({@link Label}, {@link BuildConfigurationValue}) pair. | 
|  | * | 
|  | * <p>Note that this key may be used to look up the generating action of an artifact. | 
|  | * | 
|  | * <p>TODO(blaze-configurability-team): Consider just using BuildOptions over a | 
|  | * BuildConfigurationKey. | 
|  | */ | 
|  | public class ConfiguredTargetKey implements ActionLookupKey { | 
|  | /** | 
|  | * Cache so that the number of ConfiguredTargetKey instances is {@code O(configured targets)} and | 
|  | * not {@code O(edges between configured targets)}. | 
|  | */ | 
|  | private static final SkyKey.SkyKeyInterner<ConfiguredTargetKey> interner = SkyKey.newInterner(); | 
|  |  | 
|  | private final Label label; | 
|  | @Nullable private final BuildConfigurationKey configurationKey; | 
|  | private final int hashCode; | 
|  |  | 
|  | private ConfiguredTargetKey( | 
|  | Label label, @Nullable BuildConfigurationKey configurationKey, int hashCode) { | 
|  | this.label = label; | 
|  | this.configurationKey = configurationKey; | 
|  | this.hashCode = hashCode; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final SkyFunctionName functionName() { | 
|  | return SkyFunctions.CONFIGURED_TARGET; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public SkyKeyInterner<?> getSkyKeyInterner() { | 
|  | return interner; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Label getLabel() { | 
|  | return label; | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | @Override | 
|  | public final BuildConfigurationKey getConfigurationKey() { | 
|  | return configurationKey; | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | public Label getExecutionPlatformLabel() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * True if the target's rule transition should be applied. | 
|  | * | 
|  | * <p>True by default but set false when a non-idempotent rule transition is detected. It prevents | 
|  | * over-application of such transitions. | 
|  | */ | 
|  | public boolean shouldApplyRuleTransition() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | public final String prettyPrint() { | 
|  | if (getLabel() == null) { | 
|  | return "null"; | 
|  | } | 
|  | return String.format("%s (%s)", getLabel(), formatConfigurationKey(configurationKey)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final int hashCode() { | 
|  | return hashCode; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final boolean equals(Object obj) { | 
|  | if (this == obj) { | 
|  | return true; | 
|  | } | 
|  | if (!(obj instanceof ConfiguredTargetKey)) { | 
|  | return false; | 
|  | } | 
|  | ConfiguredTargetKey other = (ConfiguredTargetKey) obj; | 
|  | return hashCode == other.hashCode | 
|  | && getLabel().equals(other.getLabel()) | 
|  | && Objects.equals(configurationKey, other.configurationKey) | 
|  | && Objects.equals(getExecutionPlatformLabel(), other.getExecutionPlatformLabel()) | 
|  | && shouldApplyRuleTransition() == other.shouldApplyRuleTransition(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final String toString() { | 
|  | // TODO(b/162809183): consider reverting to less verbose toString when bug is resolved. | 
|  | MoreObjects.ToStringHelper helper = | 
|  | MoreObjects.toStringHelper(this).add("label", getLabel()).add("config", configurationKey); | 
|  | if (getExecutionPlatformLabel() != null) { | 
|  | helper.add("executionPlatformLabel", getExecutionPlatformLabel()); | 
|  | } | 
|  | return helper.toString(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Key indicating that no rule transition should be applied to the configuration. | 
|  | * | 
|  | * <p>NOTE: although it's true that no rule transition is applied when there is a null | 
|  | * configuration, this key type is used to handle a special edge case described below. It should | 
|  | * only be used with a non-null configuration. | 
|  | * | 
|  | * <p>When a non-noop rule transition occurs, it creates a new <i>delegation</i> {@link | 
|  | * ConfiguredTargetKey} with the resulting configuration. This is so if different starting | 
|  | * configurations result in the same configuration after transition, they converge on the same | 
|  | * key-value entry in Skyframe. | 
|  | * | 
|  | * <p>This can be problematic when transitions are not idempotent because evaluation of the | 
|  | * <i>delegate</i> repeats the transition, resulting in a another <i>delegate</i>. In cases of | 
|  | * non-convergent transitions, this may lead to infinite expansion. | 
|  | * | 
|  | * <p>To ensure that transitions are effectively only applied once, prior to delegation, the | 
|  | * {@link ConfiguredTargetFunction} applies the transition a second time to check it for | 
|  | * idempotency. It sets {@link ConfiguredTargetKey#shouldApplyRuleTransition} false when it is not | 
|  | * idempotent. | 
|  | */ | 
|  | private static final class ConfiguredTargetKeyWithFinalConfiguration extends ConfiguredTargetKey { | 
|  | // This is implemented using subtypes instead of adding a boolean field to `ConfiguredTargetKey` | 
|  | // to reduce memory cost. | 
|  |  | 
|  | private ConfiguredTargetKeyWithFinalConfiguration( | 
|  | Label label, BuildConfigurationKey configurationKey, int hashCode) { | 
|  | super(label, checkNotNull(configurationKey), hashCode); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean shouldApplyRuleTransition() { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class ToolchainDependencyConfiguredTargetKey extends ConfiguredTargetKey { | 
|  | private final Label executionPlatformLabel; | 
|  |  | 
|  | private ToolchainDependencyConfiguredTargetKey( | 
|  | Label label, | 
|  | @Nullable BuildConfigurationKey configurationKey, | 
|  | int hashCode, | 
|  | Label executionPlatformLabel) { | 
|  | super(label, configurationKey, hashCode); | 
|  | this.executionPlatformLabel = checkNotNull(executionPlatformLabel); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Label getExecutionPlatformLabel() { | 
|  | return executionPlatformLabel; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final class ToolchainDependencyConfiguredTargetKeyWithFinalConfiguration | 
|  | extends ToolchainDependencyConfiguredTargetKey { | 
|  | private ToolchainDependencyConfiguredTargetKeyWithFinalConfiguration( | 
|  | Label label, | 
|  | BuildConfigurationKey configurationKey, | 
|  | int hashCode, | 
|  | Label executionPlatformLabel) { | 
|  | super(label, checkNotNull(configurationKey), hashCode, executionPlatformLabel); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean shouldApplyRuleTransition() { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | public Builder toBuilder() { | 
|  | return builder() | 
|  | .setConfigurationKey(configurationKey) | 
|  | .setLabel(getLabel()) | 
|  | .setExecutionPlatformLabel(getExecutionPlatformLabel()) | 
|  | .setShouldApplyRuleTransition(shouldApplyRuleTransition()); | 
|  | } | 
|  |  | 
|  | /** Returns a new {@link Builder} to create instances of {@link ConfiguredTargetKey}. */ | 
|  | public static Builder builder() { | 
|  | return new Builder(); | 
|  | } | 
|  |  | 
|  | /** Returns the {@link ConfiguredTargetKey} that owns {@code configuredTarget}. */ | 
|  | public static ConfiguredTargetKey fromConfiguredTarget(CqueryNode configuredTarget) { | 
|  | // If configuredTarget is a MergedConfiguredTarget unwraps it first. MergedConfiguredTarget is | 
|  | // ephemeral and does not have a directly corresponding entry in Skyframe. | 
|  | // | 
|  | // The cast exists because the key passes through parts of analysis that work on both aspects | 
|  | // and configured targets. This process discards the key's specific type information. | 
|  | return (ConfiguredTargetKey) configuredTarget.unwrapIfMerged().getLookupKey(); | 
|  | } | 
|  |  | 
|  | /** A helper class to create instances of {@link ConfiguredTargetKey}. */ | 
|  | public static final class Builder implements Supplier<ConfiguredTargetKey> { | 
|  | private Label label = null; | 
|  | private BuildConfigurationKey configurationKey = null; | 
|  | private Label executionPlatformLabel = null; | 
|  | private boolean shouldApplyRuleTransition = true; | 
|  |  | 
|  | private Builder() {} | 
|  |  | 
|  | /** Sets the label for the target. */ | 
|  | @CanIgnoreReturnValue | 
|  | public Builder setLabel(Label label) { | 
|  | this.label = label; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Sets the {@link BuildConfigurationValue} for the configured target. */ | 
|  | @CanIgnoreReturnValue | 
|  | public Builder setConfiguration(@Nullable BuildConfigurationValue buildConfiguration) { | 
|  | return setConfigurationKey(buildConfiguration == null ? null : buildConfiguration.getKey()); | 
|  | } | 
|  |  | 
|  | /** Sets the configuration key for the configured target. */ | 
|  | @CanIgnoreReturnValue | 
|  | public Builder setConfigurationKey(@Nullable BuildConfigurationKey configurationKey) { | 
|  | this.configurationKey = configurationKey; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the execution platform {@link Label} this configured target should use for toolchain | 
|  | * resolution. When present, this overrides the normally determined execution platform. | 
|  | */ | 
|  | @CanIgnoreReturnValue | 
|  | public Builder setExecutionPlatformLabel(@Nullable Label executionPlatformLabel) { | 
|  | this.executionPlatformLabel = executionPlatformLabel; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @CanIgnoreReturnValue | 
|  | public Builder setShouldApplyRuleTransition(boolean shouldApplyRuleTransition) { | 
|  | this.shouldApplyRuleTransition = shouldApplyRuleTransition; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Builds a new {@link ConfiguredTargetKey} based on the supplied data. */ | 
|  | public ConfiguredTargetKey build() { | 
|  | int hashCode = | 
|  | computeHashCode( | 
|  | label, configurationKey, executionPlatformLabel, shouldApplyRuleTransition); | 
|  | ConfiguredTargetKey newKey; | 
|  | if (executionPlatformLabel == null) { | 
|  | newKey = | 
|  | shouldApplyRuleTransition | 
|  | ? new ConfiguredTargetKey(label, configurationKey, hashCode) | 
|  | : new ConfiguredTargetKeyWithFinalConfiguration(label, configurationKey, hashCode); | 
|  | } else { | 
|  | newKey = | 
|  | shouldApplyRuleTransition | 
|  | ? new ToolchainDependencyConfiguredTargetKey( | 
|  | label, configurationKey, hashCode, executionPlatformLabel) | 
|  | : new ToolchainDependencyConfiguredTargetKeyWithFinalConfiguration( | 
|  | label, configurationKey, hashCode, executionPlatformLabel); | 
|  | } | 
|  | return interner.intern(newKey); | 
|  | } | 
|  |  | 
|  | /** Implements the {@link Supplier} used for deserialization. */ | 
|  | @Override | 
|  | public ConfiguredTargetKey get() { | 
|  | return build(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static int computeHashCode( | 
|  | Label label, | 
|  | @Nullable BuildConfigurationKey configurationKey, | 
|  | @Nullable Label executionPlatformLabel, | 
|  | boolean shouldApplyRuleTransition) { | 
|  | int hashCode = hashObjects(label, configurationKey, executionPlatformLabel); | 
|  | if (!shouldApplyRuleTransition) { | 
|  | hashCode = ~hashCode; | 
|  | } | 
|  | return hashCode; | 
|  | } | 
|  |  | 
|  | private static String formatConfigurationKey(@Nullable BuildConfigurationKey key) { | 
|  | if (key == null) { | 
|  | return "null"; | 
|  | } | 
|  | return key.getOptions().checksum(); | 
|  | } | 
|  |  | 
|  | /** Codec for all {@link ConfiguredTargetKey} subtypes. */ | 
|  | @Keep | 
|  | private static class ConfiguredTargetKeyCodec extends DeferredObjectCodec<ConfiguredTargetKey> { | 
|  | @Override | 
|  | public Class<ConfiguredTargetKey> getEncodedClass() { | 
|  | return ConfiguredTargetKey.class; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void serialize( | 
|  | SerializationContext context, ConfiguredTargetKey key, CodedOutputStream codedOut) | 
|  | throws SerializationException, IOException { | 
|  | context.serialize(key.getLabel(), codedOut); | 
|  | context.serialize(key.getConfigurationKey(), codedOut); | 
|  | context.serialize(key.getExecutionPlatformLabel(), codedOut); | 
|  | codedOut.writeBoolNoTag(key.shouldApplyRuleTransition()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Supplier<ConfiguredTargetKey> deserializeDeferred( | 
|  | AsyncDeserializationContext context, CodedInputStream codedIn) | 
|  | throws SerializationException, IOException { | 
|  | Builder builder = builder(); | 
|  | context.deserializeFully(codedIn, builder, LABEL_OFFSET); | 
|  | context.deserializeFully(codedIn, builder, CONFIGURATION_KEY_OFFSET); | 
|  | context.deserializeFully(codedIn, builder, EXECUTION_PLATFORM_LABEL_OFFSET); | 
|  | return builder.setShouldApplyRuleTransition(codedIn.readBool()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Below are Builder offsets, used for deserialization. | 
|  |  | 
|  | private static final long LABEL_OFFSET; | 
|  | private static final long CONFIGURATION_KEY_OFFSET; | 
|  | private static final long EXECUTION_PLATFORM_LABEL_OFFSET; | 
|  |  | 
|  | static { | 
|  | try { | 
|  | LABEL_OFFSET = getFieldOffset(Builder.class, "label"); | 
|  | CONFIGURATION_KEY_OFFSET = getFieldOffset(Builder.class, "configurationKey"); | 
|  | EXECUTION_PLATFORM_LABEL_OFFSET = getFieldOffset(Builder.class, "executionPlatformLabel"); | 
|  | } catch (NoSuchFieldException e) { | 
|  | throw new ExceptionInInitializerError(e); | 
|  | } | 
|  | } | 
|  | } |