blob: c67d4f7d8c938c71f652f87bcebdfbcc4188821f [file] [log] [blame]
// 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
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.actions.ActionLookupKeyOrProxy;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
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 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. To elaborate, in
* order of highest to lowest potential for concern:
*
* <p>1. The {@link BuildConfigurationKey} must be post-transition and thus ready for immediate use
* in dependency resolution and analysis. In practice, this means that if the rule has an
* incoming-edge transition (cfg in {@link RuleClass}) or there are global trimming transitions,
* THOSE TRANSITIONS MUST ALREADY BE DONE before creating the key. Failure to do so will lead to
* build graphs with ConfiguredTarget that have seemingly impossible {@link BuildConfigurationValue}
* (due to the skipped transitions).
*
* <p>2. 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>The {@link ConfiguredTargetKey} is not a {@link SkyKey} and must be cast to one using {@link
* ActionLookupKeyOrProxy#toKey}.
*
* <p>TODO(blaze-configurability-team): Consider just using BuildOptions over a
* BuildConfigurationKey.
*/
public abstract class ConfiguredTargetKey implements ActionLookupKeyOrProxy {
/**
* 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<SkyKey> interner = SkyKey.newInterner();
@Nullable private final BuildConfigurationKey configurationKey;
private final transient int hashCode;
private ConfiguredTargetKey(@Nullable BuildConfigurationKey configurationKey, int hashCode) {
this.configurationKey = configurationKey;
this.hashCode = hashCode;
}
public Builder toBuilder() {
return builder()
.setConfigurationKey(configurationKey)
.setLabel(getLabel())
.setExecutionPlatformLabel(getExecutionPlatformLabel());
}
@Nullable
@Override
public final BuildConfigurationKey getConfigurationKey() {
return configurationKey;
}
public abstract Label getExecutionPlatformLabel();
@Override
public final int hashCode() {
return hashCode;
}
public boolean isProxy() {
return false;
}
private static int computeHashCode(
Label label,
@Nullable BuildConfigurationKey configurationKey,
@Nullable Label executionPlatformLabel) {
return hashObjects(label, configurationKey, executionPlatformLabel);
}
@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());
}
public String prettyPrint() {
if (getLabel() == null) {
return "null";
}
return String.format("%s (%s)", getLabel(), formatConfigurationKey(configurationKey));
}
private static ConfiguredTargetKey intern(ConfiguredTargetKey key) {
return (ConfiguredTargetKey) interner.intern((SkyKey) key);
}
@Override
public 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();
}
private static final class RealConfiguredTargetKey extends ConfiguredTargetKey
implements ActionLookupKey {
private final Label label;
private RealConfiguredTargetKey(
Label label, @Nullable BuildConfigurationKey configurationKey, int hashCode) {
super(configurationKey, hashCode);
this.label = label;
}
static ConfiguredTargetKey create(
Label label, @Nullable BuildConfigurationKey configurationKey) {
int hashCode = computeHashCode(label, configurationKey, /* executionPlatformLabel= */ null);
return intern(new RealConfiguredTargetKey(label, configurationKey, 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 Label getExecutionPlatformLabel() {
return null;
}
}
private static final class ToolchainDependencyConfiguredTargetKey extends ConfiguredTargetKey
implements ActionLookupKey {
private final Label label;
private final Label executionPlatformLabel;
private ToolchainDependencyConfiguredTargetKey(
Label label,
@Nullable BuildConfigurationKey configurationKey,
int hashCode,
Label executionPlatformLabel) {
super(configurationKey, hashCode);
this.label = label;
this.executionPlatformLabel = checkNotNull(executionPlatformLabel);
}
private static ConfiguredTargetKey create(
Label label,
@Nullable BuildConfigurationKey configurationKey,
Label executionPlatformLabel) {
int hashCode = computeHashCode(label, configurationKey, executionPlatformLabel);
return intern(
new ToolchainDependencyConfiguredTargetKey(
label, configurationKey, hashCode, executionPlatformLabel));
}
@Override
public SkyFunctionName functionName() {
return SkyFunctions.CONFIGURED_TARGET;
}
@Override
public Label getLabel() {
return label;
}
@Override
public Label getExecutionPlatformLabel() {
return executionPlatformLabel;
}
@Override
public SkyKeyInterner<?> getSkyKeyInterner() {
return interner;
}
}
// This class implements SkyKey only so that it can share the interner. It should never be used as
// a SkyKey.
private static final class ProxyConfiguredTargetKey extends ConfiguredTargetKey
implements SkyKey {
private final ConfiguredTargetKey delegate;
private static ConfiguredTargetKey create(
ConfiguredTargetKey delegate, @Nullable BuildConfigurationKey configurationKey) {
int hashCode =
computeHashCode(
delegate.getLabel(), configurationKey, delegate.getExecutionPlatformLabel());
return intern(new ProxyConfiguredTargetKey(delegate, configurationKey, hashCode));
}
private ProxyConfiguredTargetKey(
ConfiguredTargetKey delegate,
@Nullable BuildConfigurationKey configurationKey,
int hashCode) {
super(configurationKey, hashCode);
checkArgument(
!delegate.isProxy(), "Proxy keys must not be nested: %s %s", delegate, configurationKey);
this.delegate = delegate;
}
@Override
public SkyFunctionName functionName() {
// ProxyConfiguredTargetKey is never used directly by Skyframe. It must always be cast using
// toKey.
throw new UnsupportedOperationException();
}
@Override
public Label getLabel() {
return delegate.getLabel();
}
@Override
@Nullable
public Label getExecutionPlatformLabel() {
return delegate.getExecutionPlatformLabel();
}
@Override
public ActionLookupKey toKey() {
return (ActionLookupKey) delegate;
}
@Override
public boolean isProxy() {
return true;
}
@Override
public Builder toBuilder() {
return new Builder().setDelegate(delegate).setConfigurationKey(getConfigurationKey());
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("delegate", delegate)
.add("config", getConfigurationKey())
.toString();
}
@Override
public String prettyPrint() {
return super.prettyPrint()
+ " virtual("
+ formatConfigurationKey(getConfigurationKey())
+ ")";
}
@Override
public SkyKeyInterner<?> getSkyKeyInterner() {
return interner;
}
}
/** Returns a new {@link Builder} to create instances of {@link ConfiguredTargetKey}. */
public static Builder builder() {
return new Builder();
}
/** Returns a new {@link ConfiguredTargetKey}. */
public static ConfiguredTargetKey fromConfiguredTarget(ConfiguredTarget configuredTarget) {
return builder()
.setLabel(configuredTarget.getOriginalLabel())
.setConfigurationKey(configuredTarget.getConfigurationKey())
.build();
}
/** A helper class to create instances of {@link ConfiguredTargetKey}. */
public static final class Builder {
private Label label = null;
private BuildConfigurationKey configurationKey = null;
private Label executionPlatformLabel = null;
private ConfiguredTargetKey delegate;
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;
}
/**
* If set, creates a {@link ProxyConfiguredTargetKey}.
*
* <p>It's invalid to set a label or execution platform label if this is set. Those will be
* defined by the corresponding values of {@code delegate}.
*/
@CanIgnoreReturnValue
public Builder setDelegate(ConfiguredTargetKey delegate) {
this.delegate = delegate;
return this;
}
/** Builds a new {@link ConfiguredTargetKey} based on the supplied data. */
public ConfiguredTargetKey build() {
if (this.delegate != null) {
checkArgument(label == null);
checkArgument(executionPlatformLabel == null);
return ProxyConfiguredTargetKey.create(delegate, configurationKey);
}
if (this.executionPlatformLabel != null) {
return ToolchainDependencyConfiguredTargetKey.create(
label, configurationKey, executionPlatformLabel);
}
return RealConfiguredTargetKey.create(label, configurationKey);
}
}
private static String formatConfigurationKey(@Nullable BuildConfigurationKey key) {
if (key == null) {
return "null";
}
return key.getOptions().checksum();
}
/**
* Codec for all {@link ConfiguredTargetKey} subtypes.
*
* <p>By design, {@link ProxyConfiguredTargetKey} serializes as a key without delegation. Upon
* deserialization, if the key is locally delegated, it becomes delegating again due to interning.
* If not, it deserializes to the appropriate non-delegating key.
*/
@Keep
private static class ConfiguredTargetKeyCodec implements ObjectCodec<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);
}
@Override
public ConfiguredTargetKey deserialize(DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
return builder()
.setLabel(context.deserialize(codedIn))
.setConfigurationKey(context.deserialize(codedIn))
.setExecutionPlatformLabel(context.deserialize(codedIn))
.build();
}
}
}