blob: 43280b64a9303dd26efe510de2763de6c508e5d2 [file] [log] [blame]
// 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.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);
}
/**
* 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());
}
}
}