| // 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 com.google.common.base.Preconditions; |
| import com.google.common.collect.Interner; |
| import com.google.devtools.build.lib.actions.ActionLookupValue.ActionLookupKey; |
| import com.google.devtools.build.lib.analysis.AliasProvider; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.skyframe.SkyFunctionName; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A (Label, Configuration key) pair. Note that this pair may be used to look up the generating |
| * action of an artifact. |
| */ |
| @AutoCodec |
| public class ConfiguredTargetKey extends ActionLookupKey { |
| private final Label label; |
| @Nullable private final BuildConfigurationValue.Key configurationKey; |
| |
| private transient int hashCode; |
| |
| private ConfiguredTargetKey(Label label, @Nullable BuildConfigurationValue.Key configurationKey) { |
| this.label = Preconditions.checkNotNull(label); |
| this.configurationKey = configurationKey; |
| } |
| |
| private static Label getLabel(ConfiguredTarget configuredTarget) { |
| AliasProvider aliasProvider = configuredTarget.getProvider(AliasProvider.class); |
| return aliasProvider != null |
| ? aliasProvider.getAliasChain().get(0) |
| : configuredTarget.getLabel(); |
| } |
| |
| public static ConfiguredTargetKey of( |
| ConfiguredTarget configuredTarget, BuildConfiguration buildConfiguration) { |
| return of(getLabel(configuredTarget), buildConfiguration); |
| } |
| |
| public static ConfiguredTargetKey of( |
| ConfiguredTarget configuredTarget, |
| BuildConfigurationValue.Key configurationKey, |
| boolean isHostConfiguration) { |
| return of(getLabel(configuredTarget), configurationKey, isHostConfiguration); |
| } |
| |
| public static ConfiguredTargetKey inTargetConfig(ConfiguredTarget configuredTarget) { |
| return of( |
| getLabel(configuredTarget), |
| configuredTarget.getConfigurationKey(), |
| /*isHostConfiguration=*/ false); |
| } |
| |
| /** |
| * Caches so that the number of ConfiguredTargetKey instances is {@code O(configured targets)} and |
| * not {@code O(edges between configured targets)}. |
| */ |
| private static final Interner<ConfiguredTargetKey> interner = BlazeInterners.newWeakInterner(); |
| private static final Interner<HostConfiguredTargetKey> hostInterner = |
| BlazeInterners.newWeakInterner(); |
| |
| public static ConfiguredTargetKey of(Label label, @Nullable BuildConfiguration configuration) { |
| KeyAndHost keyAndHost = keyFromConfiguration(configuration); |
| return of(label, keyAndHost.key, keyAndHost.isHost); |
| } |
| |
| @AutoCodec.Instantiator |
| public static ConfiguredTargetKey of( |
| Label label, |
| @Nullable BuildConfigurationValue.Key configurationKey, |
| boolean isHostConfiguration) { |
| if (isHostConfiguration) { |
| return hostInterner.intern(new HostConfiguredTargetKey(label, configurationKey)); |
| } else { |
| return interner.intern(new ConfiguredTargetKey(label, configurationKey)); |
| } |
| } |
| |
| static KeyAndHost keyFromConfiguration(@Nullable BuildConfiguration configuration) { |
| return configuration == null |
| ? KeyAndHost.NULL_INSTANCE |
| : new KeyAndHost( |
| BuildConfigurationValue.key(configuration), configuration.isHostConfiguration()); |
| } |
| |
| @Override |
| public Label getLabel() { |
| return label; |
| } |
| |
| @Override |
| public SkyFunctionName functionName() { |
| return SkyFunctions.CONFIGURED_TARGET; |
| } |
| |
| @Nullable |
| BuildConfigurationValue.Key getConfigurationKey() { |
| return configurationKey; |
| } |
| |
| @Override |
| public int hashCode() { |
| // We use the hash code caching strategy employed by java.lang.String. There are three subtle |
| // things going on here: |
| // |
| // (1) We use a value of 0 to indicate that the hash code hasn't been computed and cached yet. |
| // Yes, this means that if the hash code is really 0 then we will "recompute" it each time. But |
| // this isn't a problem in practice since a hash code of 0 should be rare. |
| // |
| // (2) Since we have no synchronization, multiple threads can race here thinking there are the |
| // first one to compute and cache the hash code. |
| // |
| // (3) Moreover, since 'hashCode' is non-volatile, the cached hash code value written from one |
| // thread may not be visible by another. |
| // |
| // All three of these issues are benign from a correctness perspective; in the end we have no |
| // overhead from synchronization, at the cost of potentially computing the hash code more than |
| // once. |
| int h = hashCode; |
| if (h == 0) { |
| h = computeHashCode(); |
| hashCode = h; |
| } |
| return h; |
| } |
| |
| private int computeHashCode() { |
| int configVal = configurationKey == null ? 79 : configurationKey.hashCode(); |
| return 31 * label.hashCode() + configVal + (isHostConfiguration() ? 41 : 0); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (!(obj instanceof ConfiguredTargetKey)) { |
| return false; |
| } |
| ConfiguredTargetKey other = (ConfiguredTargetKey) obj; |
| return this.isHostConfiguration() == other.isHostConfiguration() |
| && Objects.equals(label, other.label) |
| && Objects.equals(configurationKey, other.configurationKey); |
| } |
| |
| public boolean isHostConfiguration() { |
| return false; |
| } |
| |
| public String prettyPrint() { |
| if (label == null) { |
| return "null"; |
| } |
| return isHostConfiguration() ? (label + " (host)") : label.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s %s %s", label, configurationKey, isHostConfiguration()); |
| } |
| |
| static class HostConfiguredTargetKey extends ConfiguredTargetKey { |
| private HostConfiguredTargetKey( |
| Label label, @Nullable BuildConfigurationValue.Key configurationKey) { |
| super(label, configurationKey); |
| } |
| |
| @Override |
| public boolean isHostConfiguration() { |
| return true; |
| } |
| } |
| |
| /** |
| * Simple wrapper class for turning a {@link BuildConfiguration} into a {@link |
| * BuildConfigurationValue.Key} and boolean isHost. |
| */ |
| public static class KeyAndHost { |
| private static final KeyAndHost NULL_INSTANCE = new KeyAndHost(null, false); |
| |
| @Nullable public final BuildConfigurationValue.Key key; |
| final boolean isHost; |
| |
| private KeyAndHost(@Nullable BuildConfigurationValue.Key key, boolean isHost) { |
| this.key = key; |
| this.isHost = isHost; |
| } |
| } |
| } |