| // Copyright 2016 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.packages; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.Objects; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Represents a constraint on a set of providers required by a dependency (of a rule or an aspect). |
| * |
| * <p>Currently we support three kinds of constraints: |
| * |
| * <ul> |
| * <li>accept any dependency. |
| * <li>accept no dependency (used for aspects-on-aspects to indicate that an aspect never wants to |
| * see any other aspect applied to a target. |
| * <li>accept a dependency that provides all providers from one of several sets of providers. It |
| * just so happens that in all current usages these sets are either all builtin providers or |
| * all Starlark providers, so this is the only use case this class currently supports. |
| * </ul> |
| */ |
| @Immutable |
| public final class RequiredProviders { |
| /** A constraint: either ANY, NONE, or RESTRICTED */ |
| private final Constraint constraint; |
| /** |
| * Sets of builtin providers. If non-empty, {@link #constraint} is {@link Constraint#RESTRICTED} |
| */ |
| private final ImmutableList<ImmutableSet<Class<? extends TransitiveInfoProvider>>> |
| builtinProviders; |
| /** |
| * Sets of builtin providers. If non-empty, {@link #constraint} is {@link Constraint#RESTRICTED} |
| */ |
| private final ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> starlarkProviders; |
| |
| public String getDescription() { |
| return constraint.getDescription(this); |
| } |
| |
| @Override |
| public String toString() { |
| return getDescription(); |
| } |
| |
| /** |
| * Returns the list of sets of acceptable Starlark providers for a restricted constraint, or an |
| * empty list for an "any" or "none" constraint. |
| * |
| * <p>This method is intended for documentation generation. Do not use it for evaluating whether |
| * provider constraints are satisfied: it does not distinguish between {@code acceptsAny} and |
| * {@code acceptsNone}, and it does not export built-in TransitiveInfoProvider constraints. |
| */ |
| public ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> getStarlarkProviders() { |
| return starlarkProviders; |
| } |
| |
| /** Represents one of the constraints as desctibed in {@link RequiredProviders} */ |
| @VisibleForSerialization |
| enum Constraint { |
| /** Accept any dependency */ |
| ANY { |
| @Override |
| public boolean satisfies( |
| AdvertisedProviderSet advertisedProviderSet, |
| RequiredProviders requiredProviders, |
| Builder missing) { |
| return true; |
| } |
| |
| @Override |
| public boolean satisfies( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider, |
| RequiredProviders requiredProviders, |
| Builder missingProviders) { |
| return true; |
| } |
| |
| @Override |
| Builder copyAsBuilder(RequiredProviders providers) { |
| return acceptAnyBuilder(); |
| } |
| |
| @Override |
| public String getDescription(RequiredProviders providers) { |
| return "no providers required"; |
| } |
| }, |
| /** Accept no dependency */ |
| NONE { |
| @Override |
| public boolean satisfies( |
| AdvertisedProviderSet advertisedProviderSet, |
| RequiredProviders requiredProviders, |
| Builder missing) { |
| return false; |
| } |
| |
| @Override |
| public boolean satisfies( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider, |
| RequiredProviders requiredProviders, |
| Builder missingProviders) { |
| return false; |
| } |
| |
| @Override |
| Builder copyAsBuilder(RequiredProviders providers) { |
| return acceptNoneBuilder(); |
| } |
| |
| @Override |
| public String getDescription(RequiredProviders providers) { |
| return "no providers accepted"; |
| } |
| }, |
| |
| /** Accept a dependency that has all providers from one of the sets. */ |
| RESTRICTED { |
| @Override |
| public boolean satisfies( |
| final AdvertisedProviderSet advertisedProviderSet, |
| RequiredProviders requiredProviders, |
| Builder missing) { |
| if (advertisedProviderSet.canHaveAnyProvider()) { |
| return true; |
| } |
| return satisfies( |
| advertisedProviderSet.getBuiltinProviders()::contains, |
| advertisedProviderSet.getStarlarkProviders()::contains, |
| requiredProviders, |
| missing); |
| } |
| |
| @Override |
| boolean satisfies( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider, |
| RequiredProviders requiredProviders, |
| Builder missingProviders) { |
| for (ImmutableSet<Class<? extends TransitiveInfoProvider>> builtinProviderSet : |
| requiredProviders.builtinProviders) { |
| if (builtinProviderSet.stream().allMatch(hasBuiltinProvider)) { |
| return true; |
| } |
| |
| // Collect missing providers |
| if (missingProviders != null) { |
| missingProviders.addBuiltinSet( |
| builtinProviderSet.stream() |
| .filter(hasBuiltinProvider.negate()) |
| .collect(ImmutableSet.toImmutableSet())); |
| } |
| } |
| |
| for (ImmutableSet<StarlarkProviderIdentifier> starlarkProviderSet : |
| requiredProviders.starlarkProviders) { |
| if (starlarkProviderSet.stream().allMatch(hasStarlarkProvider)) { |
| return true; |
| } |
| // Collect missing providers |
| if (missingProviders != null) { |
| missingProviders.addStarlarkSet( |
| starlarkProviderSet.stream() |
| .filter(hasStarlarkProvider.negate()) |
| .collect(ImmutableSet.toImmutableSet())); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| Builder copyAsBuilder(RequiredProviders providers) { |
| Builder result = acceptAnyBuilder(); |
| for (ImmutableSet<Class<? extends TransitiveInfoProvider>> builtinProviderSet : |
| providers.builtinProviders) { |
| result.addBuiltinSet(builtinProviderSet); |
| } |
| for (ImmutableSet<StarlarkProviderIdentifier> starlarkProviderSet : |
| providers.starlarkProviders) { |
| result.addStarlarkSet(starlarkProviderSet); |
| } |
| return result; |
| } |
| |
| @Override |
| public String getDescription(RequiredProviders providers) { |
| StringBuilder result = new StringBuilder(); |
| describe(result, providers.builtinProviders, Class::getSimpleName); |
| describe(result, providers.starlarkProviders, id -> "'" + id.toString() + "'"); |
| return result.toString(); |
| } |
| }; |
| |
| /** Checks if {@code advertisedProviderSet} satisfies these {@code RequiredProviders} */ |
| protected abstract boolean satisfies( |
| AdvertisedProviderSet advertisedProviderSet, |
| RequiredProviders requiredProviders, |
| Builder missing); |
| |
| /** |
| * Checks if a set of providers encoded by predicates {@code hasBuiltinProvider} and {@code |
| * hasStarlarkProvider} satisfies these {@code RequiredProviders} |
| */ |
| abstract boolean satisfies( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider, |
| RequiredProviders requiredProviders, |
| @Nullable Builder missingProviders); |
| |
| abstract Builder copyAsBuilder(RequiredProviders providers); |
| |
| /** Returns a string describing the providers that can be presented to the user. */ |
| abstract String getDescription(RequiredProviders providers); |
| } |
| |
| /** Checks if {@code advertisedProviderSet} satisfies this {@code RequiredProviders} instance. */ |
| public boolean isSatisfiedBy(AdvertisedProviderSet advertisedProviderSet) { |
| return constraint.satisfies(advertisedProviderSet, this, null); |
| } |
| |
| /** |
| * Checks if a set of providers encoded by predicates {@code hasBuiltinProvider} and {@code |
| * hasStarlarkProvider} satisfies this {@code RequiredProviders} instance. |
| */ |
| public boolean isSatisfiedBy( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider) { |
| return constraint.satisfies(hasBuiltinProvider, hasStarlarkProvider, this, null); |
| } |
| |
| /** |
| * Returns providers that are missing. If none are missing, returns {@code RequiredProviders} that |
| * accept anything. |
| */ |
| public RequiredProviders getMissing( |
| Predicate<Class<? extends TransitiveInfoProvider>> hasBuiltinProvider, |
| Predicate<StarlarkProviderIdentifier> hasStarlarkProvider) { |
| Builder builder = acceptAnyBuilder(); |
| if (constraint.satisfies(hasBuiltinProvider, hasStarlarkProvider, this, builder)) { |
| // Ignore all collected missing providers. |
| return acceptAnyBuilder().build(); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Returns providers that are missing. If none are missing, returns {@code RequiredProviders} that |
| * accept anything. |
| */ |
| public RequiredProviders getMissing(AdvertisedProviderSet set) { |
| Builder builder = acceptAnyBuilder(); |
| if (constraint.satisfies(set, this, builder)) { |
| // Ignore all collected missing providers. |
| return acceptAnyBuilder().build(); |
| } |
| return builder.build(); |
| } |
| |
| /** Returns true if this {@code RequiredProviders} instance accept any set of providers. */ |
| public boolean acceptsAny() { |
| return constraint.equals(Constraint.ANY); |
| } |
| |
| @VisibleForSerialization |
| RequiredProviders( |
| Constraint constraint, |
| ImmutableList<ImmutableSet<Class<? extends TransitiveInfoProvider>>> builtinProviders, |
| ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> starlarkProviders) { |
| this.constraint = constraint; |
| |
| Preconditions.checkState( |
| constraint.equals(Constraint.RESTRICTED) |
| || (builtinProviders.isEmpty() && starlarkProviders.isEmpty())); |
| |
| this.builtinProviders = builtinProviders; |
| this.starlarkProviders = starlarkProviders; |
| } |
| |
| /** Helper method to describe lists of sets of things. */ |
| private static <T> void describe( |
| StringBuilder result, |
| ImmutableList<ImmutableSet<T>> listOfSets, |
| Function<T, String> describeOne) { |
| Joiner joiner = Joiner.on(", "); |
| for (ImmutableSet<T> ids : listOfSets) { |
| if (result.length() > 0) { |
| result.append(" or "); |
| } |
| result.append((ids.size() > 1) ? "[" : ""); |
| joiner.appendTo(result, ids.stream().map(describeOne).iterator()); |
| result.append((ids.size() > 1) ? "]" : ""); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| RequiredProviders that = (RequiredProviders) o; |
| return constraint == that.constraint |
| && Objects.equals(builtinProviders, that.builtinProviders) |
| && Objects.equals(starlarkProviders, that.starlarkProviders); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(constraint, builtinProviders, starlarkProviders); |
| } |
| |
| /** |
| * A builder for {@link RequiredProviders} that accepts any dependency |
| * unless restriction provider sets are added. |
| */ |
| public static Builder acceptAnyBuilder() { |
| return new Builder(false); |
| } |
| |
| /** |
| * Mutates the given {@link AspectDefinition.Builder}, adding in the required providers specified |
| * by this {@link RequiredProviders}. |
| * |
| * <p>We use this indirection for the sake of encapsulating the internal representation of {@link |
| * RequiredProviders}. |
| */ |
| void addToAspectDefinitionBuilder(AspectDefinition.Builder aspectDefinitionBuilder) { |
| aspectDefinitionBuilder.requireProviderSets(builtinProviders); |
| aspectDefinitionBuilder.requireStarlarkProviderSets(starlarkProviders); |
| } |
| |
| /** |
| * A builder for {@link RequiredProviders} that accepts no dependency |
| * unless restriction provider sets are added. |
| */ |
| public static Builder acceptNoneBuilder() { |
| return new Builder(true); |
| } |
| |
| /** Returns a Builder initialized to the same value as this {@code RequiredProvider} */ |
| public Builder copyAsBuilder() { |
| return constraint.copyAsBuilder(this); |
| } |
| |
| /** A builder for {@link RequiredProviders} */ |
| public static class Builder { |
| private final ImmutableList.Builder<ImmutableSet<Class<? extends TransitiveInfoProvider>>> |
| builtinProviders; |
| private final ImmutableList.Builder<ImmutableSet<StarlarkProviderIdentifier>> starlarkProviders; |
| private Constraint constraint; |
| |
| private Builder(boolean acceptNone) { |
| constraint = acceptNone ? Constraint.NONE : Constraint.ANY; |
| builtinProviders = ImmutableList.builder(); |
| starlarkProviders = ImmutableList.builder(); |
| } |
| |
| /** |
| * Add an alternative set of Starlark providers. |
| * |
| * <p>If all of these providers are present in the dependency, the dependency satisfies {@link |
| * RequiredProviders}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addStarlarkSet(ImmutableSet<StarlarkProviderIdentifier> starlarkProviderSet) { |
| constraint = Constraint.RESTRICTED; |
| Preconditions.checkState(!starlarkProviderSet.isEmpty()); |
| this.starlarkProviders.add(starlarkProviderSet); |
| return this; |
| } |
| |
| /** |
| * Add an alternative set of builtin providers. |
| * |
| * <p>If all of these providers are present in the dependency, the dependency satisfies {@link |
| * RequiredProviders}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addBuiltinSet( |
| ImmutableSet<Class<? extends TransitiveInfoProvider>> builtinProviderSet) { |
| constraint = Constraint.RESTRICTED; |
| Preconditions.checkState(!builtinProviderSet.isEmpty()); |
| this.builtinProviders.add(builtinProviderSet); |
| return this; |
| } |
| |
| public RequiredProviders build() { |
| return new RequiredProviders(constraint, builtinProviders.build(), starlarkProviders.build()); |
| } |
| } |
| } |