Split AspectKey subclasses out of AspectValue.

PiperOrigin-RevId: 306879050
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
new file mode 100644
index 0000000..77ee261
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
@@ -0,0 +1,487 @@
+// Copyright 2020 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.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Interner;
+import com.google.devtools.build.lib.actions.ActionLookupValue.ActionLookupKey;
+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.packages.AspectClass;
+import com.google.devtools.build.lib.packages.AspectDescriptor;
+import com.google.devtools.build.lib.packages.AspectParameters;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey.KeyAndHost;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import javax.annotation.Nullable;
+
+/** A base class for keys that have AspectValue as a Sky value. */
+public abstract class AspectValueKey extends ActionLookupKey {
+
+  private static final Interner<AspectKey> aspectKeyInterner = BlazeInterners.newWeakInterner();
+  private static final Interner<SkylarkAspectLoadingKey> skylarkAspectKeyInterner =
+      BlazeInterners.newWeakInterner();
+
+  public abstract String getDescription();
+
+  @Override
+  public abstract Label getLabel();
+
+  // Methods to create aspect keys.
+
+  public static AspectKey createAspectKey(
+      Label label,
+      BuildConfiguration baseConfiguration,
+      ImmutableList<AspectKey> baseKeys,
+      AspectDescriptor aspectDescriptor,
+      BuildConfiguration aspectConfiguration) {
+    KeyAndHost aspectKeyAndHost = ConfiguredTargetKey.keyFromConfiguration(aspectConfiguration);
+    return AspectKey.createAspectKey(
+        label,
+        ConfiguredTargetKey.of(label, baseConfiguration),
+        baseKeys,
+        aspectDescriptor,
+        aspectKeyAndHost.key,
+        aspectKeyAndHost.isHost);
+  }
+
+  public static AspectKey createAspectKey(
+      Label label,
+      BuildConfiguration baseConfiguration,
+      AspectDescriptor aspectDescriptor,
+      BuildConfiguration aspectConfiguration) {
+    KeyAndHost aspectKeyAndHost = ConfiguredTargetKey.keyFromConfiguration(aspectConfiguration);
+    return AspectKey.createAspectKey(
+        label,
+        ConfiguredTargetKey.of(label, baseConfiguration),
+        ImmutableList.of(),
+        aspectDescriptor,
+        aspectKeyAndHost.key,
+        aspectKeyAndHost.isHost);
+  }
+
+  public static SkylarkAspectLoadingKey createSkylarkAspectKey(
+      Label targetLabel,
+      BuildConfiguration aspectConfiguration,
+      BuildConfiguration targetConfiguration,
+      Label skylarkFileLabel,
+      String skylarkExportName) {
+    KeyAndHost keyAndHost = ConfiguredTargetKey.keyFromConfiguration(aspectConfiguration);
+    SkylarkAspectLoadingKey key =
+        keyAndHost.isHost
+            ? new HostSkylarkAspectLoadingKey(
+                targetLabel,
+                keyAndHost.key,
+                ConfiguredTargetKey.of(targetLabel, targetConfiguration),
+                skylarkFileLabel,
+                skylarkExportName)
+            : new SkylarkAspectLoadingKey(
+                targetLabel,
+                keyAndHost.key,
+                ConfiguredTargetKey.of(targetLabel, targetConfiguration),
+                skylarkFileLabel,
+                skylarkExportName);
+
+    return skylarkAspectKeyInterner.intern(key);
+  }
+
+  // Specific subtypes of aspect keys.
+
+  /** A base class for a key representing an aspect applied to a particular target. */
+  @AutoCodec
+  public static class AspectKey extends AspectValueKey {
+    private final Label label;
+    private final ImmutableList<AspectKey> baseKeys;
+    private final BuildConfigurationValue.Key aspectConfigurationKey;
+    private final ConfiguredTargetKey baseConfiguredTargetKey;
+    private final AspectDescriptor aspectDescriptor;
+    private int hashCode;
+
+    private AspectKey(
+        Label label,
+        BuildConfigurationValue.Key aspectConfigurationKey,
+        ConfiguredTargetKey baseConfiguredTargetKey,
+        ImmutableList<AspectKey> baseKeys,
+        AspectDescriptor aspectDescriptor) {
+      this.baseKeys = baseKeys;
+      this.label = label;
+      this.aspectConfigurationKey = aspectConfigurationKey;
+      this.baseConfiguredTargetKey = baseConfiguredTargetKey;
+      this.aspectDescriptor = aspectDescriptor;
+    }
+
+    @AutoCodec.VisibleForSerialization
+    @AutoCodec.Instantiator
+    static AspectKey createAspectKey(
+        Label label,
+        ConfiguredTargetKey baseConfiguredTargetKey,
+        ImmutableList<AspectKey> baseKeys,
+        AspectDescriptor aspectDescriptor,
+        BuildConfigurationValue.Key aspectConfigurationKey,
+        boolean aspectConfigurationIsHost) {
+      return aspectKeyInterner.intern(
+          aspectConfigurationIsHost
+              ? new HostAspectKey(
+                  label,
+                  aspectConfigurationKey,
+                  baseConfiguredTargetKey,
+                  baseKeys,
+                  aspectDescriptor)
+              : new AspectKey(
+                  label,
+                  aspectConfigurationKey,
+                  baseConfiguredTargetKey,
+                  baseKeys,
+                  aspectDescriptor));
+    }
+
+    @Override
+    public SkyFunctionName functionName() {
+      return SkyFunctions.ASPECT;
+    }
+
+
+    @Override
+    public Label getLabel() {
+      return label;
+    }
+
+    public AspectClass getAspectClass() {
+      return aspectDescriptor.getAspectClass();
+    }
+
+    @Nullable
+    public AspectParameters getParameters() {
+      return aspectDescriptor.getParameters();
+    }
+
+    public AspectDescriptor getAspectDescriptor() {
+      return aspectDescriptor;
+    }
+
+    @Nullable
+    ImmutableList<AspectKey> getBaseKeys() {
+      return baseKeys;
+    }
+
+    @Override
+    public String getDescription() {
+      if (baseKeys.isEmpty()) {
+        return String.format("%s of %s",
+            aspectDescriptor.getAspectClass().getName(), getLabel());
+      } else {
+        return String.format("%s on top of %s",
+            aspectDescriptor.getAspectClass().getName(), baseKeys.toString());
+      }
+    }
+
+    // Note that this does not factor into equality/hash-code computations because its value is
+    // already encoded in the aspectConfigurationKey, albeit in an opaque way.
+    protected boolean aspectConfigurationIsHost() {
+      return false;
+    }
+
+    /**
+     * Returns the key of the configured target of the aspect; that is, the configuration in which
+     * the aspect will be evaluated.
+     *
+     * <p>In trimmed configuration mode, the aspect may require more fragments than the target on
+     * which it is being evaluated; in addition to configuration fragments required by the target
+     * and its dependencies, an aspect has configuration fragment requirements of its own, as well
+     * as dependencies of its own with their own configuration fragment requirements.
+     *
+     * <p>The aspect configuration contains all of these fragments, and is used to create the
+     * aspect's RuleContext and to retrieve the dependencies. Note that dependencies will have their
+     * configurations trimmed from this one as normal.
+     *
+     * <p>Because of these properties, this configuration is always a superset of the base target's
+     * configuration. In untrimmed configuration mode, this configuration will be equivalent to the
+     * base target's configuration.
+     */
+    BuildConfigurationValue.Key getAspectConfigurationKey() {
+      return aspectConfigurationKey;
+    }
+
+    /** Returns the key for the base configured target for this aspect. */
+    ConfiguredTargetKey getBaseConfiguredTargetKey() {
+      return baseConfiguredTargetKey;
+    }
+
+    @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() {
+      return Objects.hashCode(
+          label, baseKeys, aspectConfigurationKey, baseConfiguredTargetKey, aspectDescriptor);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+
+      if (!(other instanceof AspectKey)) {
+        return false;
+      }
+
+      AspectKey that = (AspectKey) other;
+      return Objects.equal(label, that.label)
+          && Objects.equal(baseKeys, that.baseKeys)
+          && Objects.equal(aspectConfigurationKey, that.aspectConfigurationKey)
+          && Objects.equal(baseConfiguredTargetKey, that.baseConfiguredTargetKey)
+          && Objects.equal(aspectDescriptor, that.aspectDescriptor);
+    }
+
+    public String prettyPrint() {
+      if (label == null) {
+        return "null";
+      }
+
+      String baseKeysString =
+          baseKeys.isEmpty()
+          ? ""
+          : String.format(" (over %s)", baseKeys.toString());
+      return String.format(
+          "%s with aspect %s%s%s",
+          label.toString(),
+          aspectDescriptor.getAspectClass().getName(),
+          (aspectConfigurationKey != null && aspectConfigurationIsHost()) ? "(host) " : "",
+          baseKeysString);
+    }
+
+    @Override
+    public String toString() {
+      return (baseKeys == null ? label : baseKeys.toString())
+          + "#"
+          + aspectDescriptor
+          + " "
+          + aspectConfigurationKey
+          + " "
+          + baseConfiguredTargetKey
+          + " "
+          + aspectDescriptor.getParameters()
+          + (aspectConfigurationIsHost() ? " (host)" : "");
+    }
+
+    AspectKey withLabel(Label label) {
+      ImmutableList.Builder<AspectKey> newBaseKeys = ImmutableList.builder();
+      for (AspectKey baseKey : baseKeys) {
+        newBaseKeys.add(baseKey.withLabel(label));
+      }
+
+      return createAspectKey(
+          label,
+          ConfiguredTargetKey.of(
+              label,
+              baseConfiguredTargetKey.getConfigurationKey(),
+              baseConfiguredTargetKey.isHostConfiguration()),
+          newBaseKeys.build(),
+          aspectDescriptor,
+          aspectConfigurationKey,
+          aspectConfigurationIsHost());
+    }
+  }
+
+  /** An {@link AspectKey} for an aspect in the host configuration. */
+  static class HostAspectKey extends AspectKey {
+    private HostAspectKey(
+        Label label,
+        BuildConfigurationValue.Key aspectConfigurationKey,
+        ConfiguredTargetKey baseConfiguredTargetKey,
+        ImmutableList<AspectKey> baseKeys,
+        AspectDescriptor aspectDescriptor) {
+      super(label, aspectConfigurationKey, baseConfiguredTargetKey, baseKeys, aspectDescriptor);
+    }
+
+    @Override
+    protected boolean aspectConfigurationIsHost() {
+      return true;
+    }
+  }
+
+  /** The key for a Starlark aspect. */
+  public static class SkylarkAspectLoadingKey extends AspectValueKey {
+
+    private final Label targetLabel;
+    private final BuildConfigurationValue.Key aspectConfigurationKey;
+    private final ConfiguredTargetKey baseConfiguredTargetKey;
+    private final Label skylarkFileLabel;
+    private final String skylarkValueName;
+    private int hashCode;
+
+    private SkylarkAspectLoadingKey(
+        Label targetLabel,
+        BuildConfigurationValue.Key aspectConfigurationKey,
+        ConfiguredTargetKey baseConfiguredTargetKey,
+        Label skylarkFileLabel,
+        String skylarkFunctionName) {
+      this.targetLabel = targetLabel;
+      this.aspectConfigurationKey = aspectConfigurationKey;
+      this.baseConfiguredTargetKey = baseConfiguredTargetKey;
+      this.skylarkFileLabel = skylarkFileLabel;
+      this.skylarkValueName = skylarkFunctionName;
+    }
+
+    @Override
+    public SkyFunctionName functionName() {
+      return SkyFunctions.LOAD_STARLARK_ASPECT;
+    }
+
+    String getSkylarkValueName() {
+      return skylarkValueName;
+    }
+
+    Label getSkylarkFileLabel() {
+      return skylarkFileLabel;
+    }
+
+    protected boolean isAspectConfigurationHost() {
+      return false;
+    }
+
+    @Override
+    public Label getLabel() {
+      return targetLabel;
+    }
+
+    @Override
+    public String getDescription() {
+      // Starlark aspects are referred to on command line with <file>%<value ame>
+      return String.format("%s%%%s of %s", skylarkFileLabel, skylarkValueName, targetLabel);
+    }
+
+    @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() {
+      return Objects.hashCode(
+          targetLabel,
+          aspectConfigurationKey,
+          baseConfiguredTargetKey,
+          skylarkFileLabel,
+          skylarkValueName);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o == this) {
+        return true;
+      }
+
+      if (!(o instanceof SkylarkAspectLoadingKey)) {
+        return false;
+      }
+      SkylarkAspectLoadingKey that = (SkylarkAspectLoadingKey) o;
+      return Objects.equal(targetLabel, that.targetLabel)
+          && Objects.equal(aspectConfigurationKey, that.aspectConfigurationKey)
+          && Objects.equal(baseConfiguredTargetKey, that.baseConfiguredTargetKey)
+          && Objects.equal(skylarkFileLabel, that.skylarkFileLabel)
+          && Objects.equal(skylarkValueName, that.skylarkValueName);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("targetLabel", targetLabel)
+          .add("aspectConfigurationKey", aspectConfigurationKey)
+          .add("baseConfiguredTargetKey", baseConfiguredTargetKey)
+          .add("skylarkFileLabel", skylarkFileLabel)
+          .add("skylarkValueName", skylarkValueName)
+          .toString();
+    }
+
+    AspectKey toAspectKey(AspectClass aspectClass) {
+      return AspectKey.createAspectKey(
+          targetLabel,
+          baseConfiguredTargetKey,
+          ImmutableList.of(),
+          new AspectDescriptor(aspectClass, AspectParameters.EMPTY),
+          aspectConfigurationKey,
+          isAspectConfigurationHost());
+    }
+  }
+
+  /** A {@link SkylarkAspectLoadingKey} for an aspect in the host configuration. */
+  private static class HostSkylarkAspectLoadingKey extends SkylarkAspectLoadingKey {
+
+    private HostSkylarkAspectLoadingKey(
+        Label targetLabel,
+        BuildConfigurationValue.Key aspectConfigurationKey,
+        ConfiguredTargetKey baseConfiguredTargetKey,
+        Label skylarkFileLabel,
+        String skylarkFunctionName) {
+      super(
+          targetLabel,
+          aspectConfigurationKey,
+          baseConfiguredTargetKey,
+          skylarkFileLabel,
+          skylarkFunctionName);
+    }
+
+    @Override
+    protected boolean isAspectConfigurationHost() {
+      return true;
+    }
+  }
+}