// 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.MoreObjects; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.packages.AdvertisedProviderSet; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.StringCanonicalizer; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.annotation.Nullable; /** * A <i>transitive</i> target reference that, when built in skyframe, loads the entire transitive * closure of a target. Retains the first error message found during the transitive traversal, the * kind of target, and a set of names of providers if the target is a {@link Rule}. * * <p>Interns values for error-free traversal nodes that correspond to built-in rules. */ @Immutable @ThreadSafe public abstract class TransitiveTraversalValue implements SkyValue { // A quick-lookup cache that allows us to get the value for a given target kind, assuming no error // messages for the target. The number of built-in target kinds is limited, so memory bloat is not // a concern. private static final ConcurrentMap<String, TransitiveTraversalValue> VALUES_BY_TARGET_KIND = new ConcurrentHashMap<>(); /** * A strong interner of TransitiveTargetValue objects. Because we only wish to intern values for * built-in non-skylark targets, we need an interner with an additional method to return the * canonical representative if it is present without interning our sample. This is only mutated in * {@link #forTarget}, and read in {@link #forTarget} and {@link #create}. */ private static final InternerWithPresenceCheck<TransitiveTraversalValue> VALUE_INTERNER = new InternerWithPresenceCheck<>(); private final String kind; protected TransitiveTraversalValue(String kind) { this.kind = Preconditions.checkNotNull(kind); } static TransitiveTraversalValue unsuccessfulTransitiveTraversal( String firstErrorMessage, Target target) { return new TransitiveTraversalValueWithError( Preconditions.checkNotNull(firstErrorMessage), target.getTargetKind()); } static TransitiveTraversalValue forTarget(Target target, @Nullable String firstErrorMessage) { if (firstErrorMessage == null) { if (target instanceof Rule && ((Rule) target).getRuleClassObject().isSkylark()) { Rule rule = (Rule) target; // Do not intern values for skylark rules. return TransitiveTraversalValue.create( rule.getRuleClassObject().getAdvertisedProviders(), rule.getTargetKind(), firstErrorMessage); } else { TransitiveTraversalValue value = VALUES_BY_TARGET_KIND.get(target.getTargetKind()); if (value != null) { return value; } AdvertisedProviderSet providers = target instanceof Rule ? ((Rule) target).getRuleClassObject().getAdvertisedProviders() : AdvertisedProviderSet.EMPTY; value = new TransitiveTraversalValueWithoutError(providers, target.getTargetKind()); // May already be there from another target or a concurrent put. value = VALUE_INTERNER.intern(value); // May already be there from a concurrent put. VALUES_BY_TARGET_KIND.putIfAbsent(target.getTargetKind(), value); return value; } } else { return new TransitiveTraversalValueWithError(firstErrorMessage, target.getTargetKind()); } } public static TransitiveTraversalValue create( AdvertisedProviderSet providers, String kind, @Nullable String firstErrorMessage) { TransitiveTraversalValue value = firstErrorMessage == null ? new TransitiveTraversalValueWithoutError(providers, kind) : new TransitiveTraversalValueWithError(firstErrorMessage, kind); if (firstErrorMessage == null) { TransitiveTraversalValue oldValue = VALUE_INTERNER.getCanonical(value); return oldValue == null ? value : oldValue; } return value; } /** Returns if the associated target can have any provider. True for "alias" rules. */ public abstract boolean canHaveAnyProvider(); /** * Returns the set of provider names from the target, if the target is a {@link Rule}. Otherwise * returns the empty set. */ public abstract AdvertisedProviderSet getProviders(); /** Returns the target kind. */ public String getKind() { return kind; } /** * Returns the first error message, if any, from loading the target and its transitive * dependencies. */ @Nullable public abstract String getFirstErrorMessage(); @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof TransitiveTraversalValue)) { return false; } TransitiveTraversalValue that = (TransitiveTraversalValue) o; return Objects.equals(this.getFirstErrorMessage(), that.getFirstErrorMessage()) && Objects.equals(this.getKind(), that.getKind()) && this.getProviders().equals(that.getProviders()); } @Override public int hashCode() { return Objects.hash(getFirstErrorMessage(), getKind(), getProviders()); } @ThreadSafe public static SkyKey key(Label label) { Preconditions.checkArgument(!label.getPackageIdentifier().getRepository().isDefault()); return label; } /** A transitive target reference without error. */ public static final class TransitiveTraversalValueWithoutError extends TransitiveTraversalValue { private final AdvertisedProviderSet advertisedProviders; private TransitiveTraversalValueWithoutError( AdvertisedProviderSet providers, @Nullable String kind) { super(kind); this.advertisedProviders = Preconditions.checkNotNull(providers); } @Override public boolean canHaveAnyProvider() { return advertisedProviders.canHaveAnyProvider(); } @Override public AdvertisedProviderSet getProviders() { return advertisedProviders; } @Override @Nullable public String getFirstErrorMessage() { return null; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("kind", getKind()) .add("providers", advertisedProviders) .toString(); } } /** A transitive target reference with error. */ public static final class TransitiveTraversalValueWithError extends TransitiveTraversalValue { private final String firstErrorMessage; private TransitiveTraversalValueWithError(String firstErrorMessage, String kind) { super(kind); this.firstErrorMessage = StringCanonicalizer.intern(Preconditions.checkNotNull(firstErrorMessage)); } @Override public boolean canHaveAnyProvider() { return AdvertisedProviderSet.EMPTY.canHaveAnyProvider(); } @Override public AdvertisedProviderSet getProviders() { return AdvertisedProviderSet.EMPTY; } @Override @Nullable public String getFirstErrorMessage() { return firstErrorMessage; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("error", firstErrorMessage) .add("kind", getKind()) .toString(); } } }