Create an interface for ConstraintSemantics.
This breaks the cycle between ConstraintSemantics and RuleContext.
PiperOrigin-RevId: 306664492
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
index 61abde8..f8d1330 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -38,6 +38,7 @@
import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics;
+import com.google.devtools.build.lib.analysis.constraints.RuleContextConstraintSemantics;
import com.google.devtools.build.lib.analysis.skylark.SkylarkModules;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -144,7 +145,8 @@
private Set<String> reservedActionMnemonics = new TreeSet<>();
private BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider =
(BuildOptions options) -> ActionEnvironment.EMPTY;
- private ConstraintSemantics constraintSemantics = new ConstraintSemantics();
+ private ConstraintSemantics<RuleContext> constraintSemantics =
+ new RuleContextConstraintSemantics();
private ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy =
ThirdPartyLicenseExistencePolicy.USER_CONTROLLABLE;
@@ -272,7 +274,7 @@
* don't depend on rules that aren't compatible with the same environments. Defaults to
* {@ConstraintSemantics}. See {@ConstraintSemantics} for more details.
*/
- public Builder setConstraintSemantics(ConstraintSemantics constraintSemantics) {
+ public Builder setConstraintSemantics(ConstraintSemantics<RuleContext> constraintSemantics) {
this.constraintSemantics = constraintSemantics;
return this;
}
@@ -545,7 +547,7 @@
private final ImmutableMap<String, Class<?>> configurationFragmentMap;
- private final ConstraintSemantics constraintSemantics;
+ private final ConstraintSemantics<RuleContext> constraintSemantics;
private final ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy;
@@ -571,7 +573,7 @@
ImmutableList<SymlinkDefinition> symlinkDefinitions,
ImmutableSet<String> reservedActionMnemonics,
BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider,
- ConstraintSemantics constraintSemantics,
+ ConstraintSemantics<RuleContext> constraintSemantics,
ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy) {
this.preludeLabel = preludeLabel;
this.runfilesPrefix = runfilesPrefix;
@@ -838,7 +840,7 @@
return symlinkDefinitions;
}
- public ConstraintSemantics getConstraintSemantics() {
+ public ConstraintSemantics<RuleContext> getConstraintSemantics() {
return constraintSemantics;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index 472f363..9de9feb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -380,7 +380,7 @@
if (!ruleContext.getRule().getRuleClassObject().supportsConstraintChecking()) {
return;
}
- ConstraintSemantics constraintSemantics = ruleContext.getConstraintSemantics();
+ ConstraintSemantics<RuleContext> constraintSemantics = ruleContext.getConstraintSemantics();
EnvironmentCollection supportedEnvironments =
constraintSemantics.getSupportedEnvironments(ruleContext);
if (supportedEnvironments != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index f46baf5..b7d35bf 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -199,7 +199,7 @@
private final ImmutableList<Class<? extends Fragment>> universalFragments;
private final RuleErrorConsumer reporter;
@Nullable private final ToolchainCollection<ResolvedToolchainContext> toolchainContexts;
- private final ConstraintSemantics constraintSemantics;
+ private final ConstraintSemantics<RuleContext> constraintSemantics;
private final ImmutableSet<String> requiredConfigFragments;
private ActionOwner actionOwner;
@@ -219,7 +219,7 @@
ActionLookupValue.ActionLookupKey actionLookupKey,
ImmutableMap<String, Attribute> aspectAttributes,
@Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
- ConstraintSemantics constraintSemantics,
+ ConstraintSemantics<RuleContext> constraintSemantics,
ImmutableSet<String> requiredConfigFragments) {
super(
builder.env,
@@ -1246,7 +1246,7 @@
.hasConstraintValue(constraintValue);
}
- public ConstraintSemantics getConstraintSemantics() {
+ public ConstraintSemantics<RuleContext> getConstraintSemantics() {
return constraintSemantics;
}
@@ -1623,7 +1623,7 @@
private ImmutableMap<String, Attribute> aspectAttributes;
private ImmutableList<Aspect> aspects;
private ToolchainCollection<ResolvedToolchainContext> toolchainContexts;
- private ConstraintSemantics constraintSemantics;
+ private ConstraintSemantics<RuleContext> constraintSemantics;
private ImmutableSet<String> requiredConfigFragments = ImmutableSet.of();
@VisibleForTesting
@@ -1753,7 +1753,7 @@
return this;
}
- public Builder setConstraintSemantics(ConstraintSemantics constraintSemantics) {
+ public Builder setConstraintSemantics(ConstraintSemantics<RuleContext> constraintSemantics) {
this.constraintSemantics = constraintSemantics;
return this;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
index d6f835a..bd7be99 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
@@ -1,4 +1,4 @@
-// Copyright 2015 The Bazel Authors. All rights reserved.
+// 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.
@@ -11,45 +11,14 @@
// 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.analysis.constraints;
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Verify;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import com.google.devtools.build.lib.analysis.ConfiguredTarget;
-import com.google.devtools.build.lib.analysis.LabelAndLocation;
-import com.google.devtools.build.lib.analysis.RuleContext;
-import com.google.devtools.build.lib.analysis.TransitionMode;
-import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
-import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget;
-import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup;
import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider.RemovedEnvironmentCulprit;
import com.google.devtools.build.lib.cmdline.Label;
-import com.google.devtools.build.lib.packages.Attribute;
-import com.google.devtools.build.lib.packages.AttributeMap;
-import com.google.devtools.build.lib.packages.BuildType;
-import com.google.devtools.build.lib.packages.DependencyFilter;
import com.google.devtools.build.lib.packages.EnvironmentGroup;
-import com.google.devtools.build.lib.packages.EnvironmentLabels;
-import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
-import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.Target;
-import com.google.devtools.build.lib.packages.Type;
-import com.google.devtools.build.lib.packages.Type.LabelClass;
-import com.google.devtools.build.lib.packages.Type.LabelVisitor;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.StringJoiner;
import javax.annotation.Nullable;
/**
@@ -57,54 +26,54 @@
*
* <p>This is how the system works:
*
- * <p>All build rules can declare which "static environments" they can be built for, where a
- * "static environment" is a label instance of an {@link EnvironmentRule} rule declared in a
- * BUILD file. There are various ways to do this:
+ * <p>All build rules can declare which "static environments" they can be built for, where a "static
+ * environment" is a label instance of an {@link EnvironmentRule} rule declared in a BUILD file.
+ * There are various ways to do this:
*
* <ul>
- * <li>Through a "restricted to" attribute setting
- * ({@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR}). This is the most direct form of
- * specification - it declares the exact set of environments the rule supports (for its group -
- * see precise details below).
- * <li>Through a "compatible with" attribute setting
- * ({@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR}. This declares <b>additional</b>
- * environments a rule supports in addition to "standard" environments that are supported by
- * default (see below).
+ * <li>Through a "restricted to" attribute setting ({@link
+ * com.google.devtools.build.lib.packages.RuleClass#RESTRICTED_ENVIRONMENT_ATTR}). This is the
+ * most direct form of specification - it declares the exact set of environments the rule
+ * supports (for its group - see precise details below).
+ * <li>Through a "compatible with" attribute setting ({@link
+ * com.google.devtools.build.lib.packages.RuleClass#COMPATIBLE_ENVIRONMENT_ATTR}. This
+ * declares <b>additional</b> environments a rule supports in addition to "standard"
+ * environments that are supported by default (see below).
* <li>Through "default" specifications in {@link EnvironmentGroup} rules. Every environment
- * belongs to a group of thematically related peers (e.g. "target architectures", "JDK versions",
- * or "mobile devices"). An environment group's definition includes which of these
- * environments should be supported "by default" if not otherwise specified by one of the above
- * mechanisms. In particular, a rule with no environment-related attributes automatically
- * inherits all defaults.
- * <li>Through a rule class default ({@link RuleClass.Builder#restrictedTo} and
- * {@link RuleClass.Builder#compatibleWith}). This overrides global defaults for all instances
- * of the given rule class. This can be used, for example, to make all *_test rules "testable"
- * without each instance having to explicitly declare this capability.
+ * belongs to a group of thematically related peers (e.g. "target architectures", "JDK
+ * versions", or "mobile devices"). An environment group's definition includes which of these
+ * environments should be supported "by default" if not otherwise specified by one of the
+ * above mechanisms. In particular, a rule with no environment-related attributes
+ * automatically inherits all defaults.
+ * <li>Through a rule class default ({@link
+ * com.google.devtools.build.lib.packages.RuleClass.Builder#restrictedTo} and {@link
+ * com.google.devtools.build.lib.packages.RuleClass.Builder#compatibleWith}). This overrides
+ * global defaults for all instances of the given rule class. This can be used, for example,
+ * to make all *_test rules "testable" without each instance having to explicitly declare this
+ * capability.
* </ul>
*
- * <p>Groups exist to model the idea that some environments are related while others have nothing
- * to do with each other. Say, for example, we want to say a rule works for PowerPC platforms but
- * not x86. We can do so by setting its "restricted to" attribute to
- * {@code ['//sample/path:powerpc']}. Because both PowerPC and x86 are in the same
- * "target architectures" group, this setting removes x86 from the set of supported environments.
- * But since JDK support belongs to its own group ("JDK versions") it says nothing about which JDK
- * the rule supports.
+ * <p>Groups exist to model the idea that some environments are related while others have nothing to
+ * do with each other. Say, for example, we want to say a rule works for PowerPC platforms but not
+ * x86. We can do so by setting its "restricted to" attribute to {@code ['//sample/path:powerpc']}.
+ * Because both PowerPC and x86 are in the same "target architectures" group, this setting removes
+ * x86 from the set of supported environments. But since JDK support belongs to its own group ("JDK
+ * versions") it says nothing about which JDK the rule supports.
*
- * <p>More precisely, if a rule has a "restricted to" value of [A, B, C], this removes support
- * for all default environments D such that group(D) is in [group(A), group(B), group(C)] AND
- * D is not in [A, B, C] (in other words, D isn't explicitly opted back in). The rule's full
- * set of supported environments thus becomes [A, B, C] + all defaults that belong to unrelated
- * groups.
+ * <p>More precisely, if a rule has a "restricted to" value of [A, B, C], this removes support for
+ * all default environments D such that group(D) is in [group(A), group(B), group(C)] AND D is not
+ * in [A, B, C] (in other words, D isn't explicitly opted back in). The rule's full set of supported
+ * environments thus becomes [A, B, C] + all defaults that belong to unrelated groups.
*
- * <p>If the rule has a "compatible with" value of [E, F, G], these are unconditionally
- * added to its set of supported environments (in addition to the results from above).
+ * <p>If the rule has a "compatible with" value of [E, F, G], these are unconditionally added to its
+ * set of supported environments (in addition to the results from above).
*
* <p>An environment may not appear in both a rule's "restricted to" and "compatible with" values.
- * If two environments belong to the same group, they must either both be in "restricted to",
- * both be in "compatible with", or not explicitly specified.
+ * If two environments belong to the same group, they must either both be in "restricted to", both
+ * be in "compatible with", or not explicitly specified.
*
- * <p>Given all the above, constraint enforcement is this: rule A can depend on rule B if, for
- * every static environment A supports, B also supports that environment.
+ * <p>Given all the above, constraint enforcement is this: rule A can depend on rule B if, for every
+ * static environment A supports, B also supports that environment.
*
* <p>Configurable attributes introduce the additional concept of "refined environments". Given:
*
@@ -124,247 +93,40 @@
* restricted_to = [":B"])
* </pre>
*
- * "lib"'s static environments are what are declared via restricted_to: {@code [":A", ":B"]}.
- * But normal constraint checking doesn't work well here: neither "depA" or "depB" supports both
- * environments, so each is technically invalid. But the two of them together <i>do</i> support
- * both environments. So constraint checking with selects checks that "lib"'s environments
- * are supported by the <i>union</i> of its selectable dependencies, then <i>refines</i> its
- * environments to whichever deps get chosen. In other words:
+ * "lib"'s static environments are what are declared via restricted_to: {@code [":A", ":B"]}. But
+ * normal constraint checking doesn't work well here: neither "depA" or "depB" supports both
+ * environments, so each is technically invalid. But the two of them together <i>do</i> support both
+ * environments. So constraint checking with selects checks that "lib"'s environments are supported
+ * by the <i>union</i> of its selectable dependencies, then <i>refines</i> its environments to
+ * whichever deps get chosen. In other words:
*
* <ol>
* <li>The above example is considered constraint-valid.
* <li>When building with "config_a", "lib"'s refined environment set is {@code [":A"]}.
* <li>When building with "config_b", "lib"'s refined environment set is {@code [":B"]}.
- * <li>Any rule depending on "lib" has its environments refined by the intersection with "lib".
- * So if "depender" has {@code restricted_to = [":A", ":B"]} and {@code deps = [":lib"]},
- * then when building with "config_a", "depender"'s refined environment set is {@code [":A"]}.
+ * <li>Any rule depending on "lib" has its environments refined by the intersection with "lib". So
+ * if "depender" has {@code restricted_to = [":A", ":B"]} and {@code deps = [":lib"]}, then
+ * when building with "config_a", "depender"'s refined environment set is {@code [":A"]}.
* <li>For each environment group, every rule's refined environment set must be non-empty. This
* ensures the "chosen" dep in a select matches all rules up the dependency chain. So if
* "depender" had {@code restricted_to = [":B"]}, it wouldn't be allowed in a "config_a"
* build.
* </ol>
+ *
* </code>.
+ *
+ * @param <T> The type of object to check for constraints.
*/
-public class ConstraintSemantics {
- public ConstraintSemantics() {
- }
+public interface ConstraintSemantics<T> {
/**
- * Logs an error message that the current rule violates constraints.
- */
- public void ruleError(RuleContext ruleContext, String message) {
- ruleContext.ruleError(message);
- }
-
- /**
- * Logs an error message that an attribute on the current rule doesn't properly declare
- * constraints.
- */
- public void attributeError(RuleContext ruleContext, String attribute, String message) {
- ruleContext.attributeError(attribute, message);
- }
-
- /**
- * Provides a set of default environments for a given environment group.
- */
- private interface DefaultsProvider {
- Collection<Label> getDefaults(EnvironmentLabels group);
- }
-
- /**
- * Provides a group's defaults as specified in the environment group's BUILD declaration.
- */
- private static class GroupDefaultsProvider implements DefaultsProvider {
- @Override
- public Collection<Label> getDefaults(EnvironmentLabels group) {
- return group.getDefaults();
- }
- }
-
- /**
- * Provides a group's defaults, factoring in rule class defaults as specified by
- * {@link com.google.devtools.build.lib.packages.RuleClass.Builder#compatibleWith}
- * and {@link com.google.devtools.build.lib.packages.RuleClass.Builder#restrictedTo}.
- */
- private static class RuleClassDefaultsProvider implements DefaultsProvider {
- private final EnvironmentCollection ruleClassDefaults;
- private final GroupDefaultsProvider groupDefaults;
-
- RuleClassDefaultsProvider(EnvironmentCollection ruleClassDefaults) {
- this.ruleClassDefaults = ruleClassDefaults;
- this.groupDefaults = new GroupDefaultsProvider();
- }
-
- @Override
- public Collection<Label> getDefaults(EnvironmentLabels group) {
- if (ruleClassDefaults.getGroups().contains(group)) {
- return ruleClassDefaults.getEnvironments(group);
- } else {
- // If there are no rule class defaults for this group, just inherit global defaults.
- return groupDefaults.getDefaults(group);
- }
- }
- }
-
- /**
- * Collects the set of supported environments for a given rule by merging its
- * restriction-style and compatibility-style environment declarations as specified by
- * the given attributes. Only includes environments from "known" groups, i.e. the groups
- * owning the environments explicitly referenced from these attributes.
- */
- private class EnvironmentCollector {
- private final RuleContext ruleContext;
- private final String restrictionAttr;
- private final String compatibilityAttr;
- private final DefaultsProvider defaultsProvider;
-
- private final EnvironmentCollection restrictionEnvironments;
- private final EnvironmentCollection compatibilityEnvironments;
- private final EnvironmentCollection supportedEnvironments;
-
- /**
- * Constructs a new collector on the given attributes.
- *
- * @param ruleContext analysis context for the rule
- * @param restrictionAttr the name of the attribute that declares "restricted to"-style
- * environments. If the rule doesn't have this attribute, this is considered an
- * empty declaration.
- * @param compatibilityAttr the name of the attribute that declares "compatible with"-style
- * environments. If the rule doesn't have this attribute, this is considered an
- * empty declaration.
- * @param defaultsProvider provider for the default environments within a group if not
- * otherwise overridden by the above attributes
- */
- EnvironmentCollector(RuleContext ruleContext, String restrictionAttr, String compatibilityAttr,
- DefaultsProvider defaultsProvider) {
- this.ruleContext = ruleContext;
- this.restrictionAttr = restrictionAttr;
- this.compatibilityAttr = compatibilityAttr;
- this.defaultsProvider = defaultsProvider;
-
- EnvironmentCollection.Builder environmentsBuilder = new EnvironmentCollection.Builder();
- restrictionEnvironments = collectRestrictionEnvironments(environmentsBuilder);
- compatibilityEnvironments = collectCompatibilityEnvironments(environmentsBuilder);
- supportedEnvironments = environmentsBuilder.build();
- }
-
- /**
- * Returns the set of environments supported by this rule, as determined by the
- * restriction-style attribute, compatibility-style attribute, and group defaults
- * provider instantiated with this class.
- */
- EnvironmentCollection getEnvironments() {
- return supportedEnvironments;
- }
-
- /**
- * Validity-checks that no group has its environment referenced in both the "compatible with"
- * and restricted to" attributes. Returns true if all is good, returns false and reports
- * appropriate errors if there are any problems.
- */
- boolean validateEnvironmentSpecifications() {
- ImmutableCollection<EnvironmentLabels> restrictionGroups =
- restrictionEnvironments.getGroups();
- boolean hasErrors = false;
-
- for (EnvironmentLabels group : compatibilityEnvironments.getGroups()) {
- if (restrictionGroups.contains(group)) {
- // To avoid error-spamming the user, when we find a conflict we only report one example
- // environment from each attribute for that group.
- Label compatibilityEnv =
- compatibilityEnvironments.getEnvironments(group).iterator().next();
- Label restrictionEnv = restrictionEnvironments.getEnvironments(group).iterator().next();
-
- if (compatibilityEnv.equals(restrictionEnv)) {
- attributeError(ruleContext, compatibilityAttr, compatibilityEnv
- + " cannot appear both here and in " + restrictionAttr);
- } else {
- attributeError(ruleContext, compatibilityAttr, compatibilityEnv + " and "
- + restrictionEnv + " belong to the same environment group. They should be declared "
- + "together either here or in " + restrictionAttr);
- }
- hasErrors = true;
- }
- }
-
- return !hasErrors;
- }
-
- /**
- * Adds environments specified in the "restricted to" attribute to the set of supported
- * environments and returns the environments added.
- */
- private EnvironmentCollection collectRestrictionEnvironments(
- EnvironmentCollection.Builder supportedEnvironments) {
- return collectEnvironments(restrictionAttr, supportedEnvironments);
- }
-
- /**
- * Adds environments specified in the "compatible with" attribute to the set of supported
- * environments, along with all defaults from the groups they belong to. Returns these
- * environments, not including the defaults.
- */
- private EnvironmentCollection collectCompatibilityEnvironments(
- EnvironmentCollection.Builder supportedEnvironments) {
- EnvironmentCollection compatibilityEnvironments =
- collectEnvironments(compatibilityAttr, supportedEnvironments);
- for (EnvironmentLabels group : compatibilityEnvironments.getGroups()) {
- supportedEnvironments.putAll(group, defaultsProvider.getDefaults(group));
- }
- return compatibilityEnvironments;
- }
-
- /**
- * Adds environments specified by the given attribute to the set of supported environments
- * and returns the environments added.
- *
- * <p>If this rule doesn't have the given attributes, returns an empty set.
- */
- private EnvironmentCollection collectEnvironments(String attrName,
- EnvironmentCollection.Builder supportedEnvironments) {
- if (!ruleContext.getRule().isAttrDefined(attrName, BuildType.LABEL_LIST)) {
- return EnvironmentCollection.EMPTY;
- }
- EnvironmentCollection.Builder environments = new EnvironmentCollection.Builder();
- for (TransitiveInfoCollection envTarget :
- ruleContext.getPrerequisites(attrName, TransitionMode.DONT_CHECK)) {
- EnvironmentWithGroup envInfo = resolveEnvironment(envTarget);
- environments.put(envInfo.group(), envInfo.environment());
- supportedEnvironments.put(envInfo.group(), envInfo.environment());
- }
- return environments.build();
- }
-
- /**
- * Returns the environment and its group. An {@link Environment} rule only "supports" one
- * environment: itself. Extract that from its more generic provider interface and sanity
- * check that that's in fact what we see.
- */
- private EnvironmentWithGroup resolveEnvironment(TransitiveInfoCollection envRule) {
- SupportedEnvironmentsProvider prereq =
- Preconditions.checkNotNull(envRule.getProvider(SupportedEnvironmentsProvider.class));
- return Iterables.getOnlyElement(prereq.getStaticEnvironments().getGroupedEnvironments());
- }
- }
-
- /**
- * Exception indicating errors finding/parsing environments or their containing groups.
- */
- public static class EnvironmentLookupException extends Exception {
- private EnvironmentLookupException(String message) {
- super(message);
- }
- }
-
- /**
- * Returns the environment group that owns the given environment. Both must belong to
- * the same package.
+ * Returns the environment group that owns the given environment. Both must belong to the same
+ * package.
*
* @throws EnvironmentLookupException if the input is not an {@link EnvironmentRule} or no
* matching group is found
*/
- public static EnvironmentGroup getEnvironmentGroup(Target envTarget)
- throws EnvironmentLookupException {
+ static EnvironmentGroup getEnvironmentGroup(Target envTarget) throws EnvironmentLookupException {
if (!(envTarget instanceof Rule)
|| !((Rule) envTarget).getRuleClass().equals(ConstraintConstants.ENVIRONMENT_RULE)) {
throw new EnvironmentLookupException(
@@ -380,139 +142,22 @@
}
/**
- * Returns the set of environments this rule supports, applying the logic described in
- * {@link ConstraintSemantics}.
+ * Returns the set of environments this rule supports.
*
* <p>Note this set is <b>not complete</b> - it doesn't include environments from groups we don't
- * "know about". Environments and groups can be declared in any package. If the rule includes
- * no references to that package, then it simply doesn't know anything about them. But the
- * constraint semantics say the rule should support the defaults for that group. We encode this
- * implicitly: given the returned set, for any group that's not in the set the rule is also
- * considered to support that group's defaults.
+ * "know about". Environments and groups can be declared in any package. If the rule includes no
+ * references to that package, then it simply doesn't know anything about them. But the constraint
+ * semantics say the rule should support the defaults for that group. We encode this implicitly:
+ * given the returned set, for any group that's not in the set the rule is also considered to
+ * support that group's defaults.
*
- * @param ruleContext analysis context for the rule. A rule error is triggered here if
- * invalid constraint settings are discovered.
- * @return the environments this rule supports, not counting defaults "unknown" to this rule
- * as described above. Returns null if any errors are encountered.
+ * @param context analysis context for the rule. A rule error is triggered here if invalid
+ * constraint settings are discovered.
+ * @return the environments this rule supports, not counting defaults "unknown" to this rule as
+ * described above. Returns null if any errors are encountered.
*/
@Nullable
- public EnvironmentCollection getSupportedEnvironments(RuleContext ruleContext) {
- if (!validateAttributes(ruleContext)) {
- return null;
- }
-
- // This rule's rule class defaults (or null if the rule class has no defaults).
- EnvironmentCollector ruleClassCollector = maybeGetRuleClassDefaults(ruleContext);
- // Default environments for this rule. If the rule has rule class defaults, this is
- // those defaults. Otherwise it's the global defaults specified by environment_group
- // declarations.
- DefaultsProvider ruleDefaults;
-
- if (ruleClassCollector != null) {
- if (!ruleClassCollector.validateEnvironmentSpecifications()) {
- return null;
- }
- ruleDefaults = new RuleClassDefaultsProvider(ruleClassCollector.getEnvironments());
- } else {
- ruleDefaults = new GroupDefaultsProvider();
- }
-
- EnvironmentCollector ruleCollector = new EnvironmentCollector(ruleContext,
- RuleClass.RESTRICTED_ENVIRONMENT_ATTR, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, ruleDefaults);
- if (!ruleCollector.validateEnvironmentSpecifications()) {
- return null;
- }
-
- EnvironmentCollection supportedEnvironments = ruleCollector.getEnvironments();
- if (ruleClassCollector != null) {
- // If we have rule class defaults from groups that aren't referenced from the rule itself,
- // we need to add them in too to override the global defaults.
- supportedEnvironments =
- addUnknownGroupsToCollection(supportedEnvironments, ruleClassCollector.getEnvironments());
- }
- return supportedEnvironments;
- }
-
- /**
- * Returns the rule class defaults specified for this rule, or null if there are
- * no such defaults.
- */
- @Nullable
- private EnvironmentCollector maybeGetRuleClassDefaults(RuleContext ruleContext) {
- Rule rule = ruleContext.getRule();
- String restrictionAttr = RuleClass.DEFAULT_RESTRICTED_ENVIRONMENT_ATTR;
- String compatibilityAttr = RuleClass.DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR;
-
- if (rule.isAttrDefined(restrictionAttr, BuildType.LABEL_LIST)
- || rule.isAttrDefined(compatibilityAttr, BuildType.LABEL_LIST)) {
- return new EnvironmentCollector(ruleContext, restrictionAttr, compatibilityAttr,
- new GroupDefaultsProvider());
- } else {
- return null;
- }
- }
-
- /**
- * Adds environments to an {@link EnvironmentCollection} from groups that aren't already
- * a part of that collection.
- *
- * @param environments the collection to add to
- * @param toAdd the collection to add. All environments in this collection in groups
- * that aren't represented in {@code environments} are added to {@code environments}.
- * @return the expanded collection.
- */
- private static EnvironmentCollection addUnknownGroupsToCollection(
- EnvironmentCollection environments, EnvironmentCollection toAdd) {
- EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder();
- builder.putAll(environments);
- for (EnvironmentLabels candidateGroup : toAdd.getGroups()) {
- if (!environments.getGroups().contains(candidateGroup)) {
- builder.putAll(candidateGroup, toAdd.getEnvironments(candidateGroup));
- }
- }
- return builder.build();
- }
-
- /**
- * Validity-checks this rule's constraint-related attributes. Returns true if all is good,
- * returns false and reports appropriate errors if there are any problems.
- */
- private boolean validateAttributes(RuleContext ruleContext) {
- AttributeMap attributes = ruleContext.attributes();
-
- // Report an error if "restricted to" is explicitly set to nothing. Even if this made
- // conceptual sense, we don't know which groups we should apply that to.
- String restrictionAttr = RuleClass.RESTRICTED_ENVIRONMENT_ATTR;
- List<? extends TransitiveInfoCollection> restrictionEnvironments =
- ruleContext.getPrerequisites(restrictionAttr, TransitionMode.DONT_CHECK);
- if (restrictionEnvironments.isEmpty()
- && attributes.isAttributeValueExplicitlySpecified(restrictionAttr)) {
- attributeError(ruleContext, restrictionAttr, "attribute cannot be empty");
- return false;
- }
-
- return true;
- }
-
- /**
- * Helper container for checkConstraints: stores both a set of deps that need to be
- * constraint-checked and the subset of those deps that only appear inside selects.
- */
- private static class DepsToCheck {
- private final Set<TransitiveInfoCollection> allDeps;
- private final Set<TransitiveInfoCollection> selectOnlyDeps;
- DepsToCheck(Set<TransitiveInfoCollection> depsToCheck,
- Set<TransitiveInfoCollection> selectOnlyDeps) {
- this.allDeps = depsToCheck;
- this.selectOnlyDeps = selectOnlyDeps;
- }
- Set<TransitiveInfoCollection> allDeps() {
- return allDeps;
- }
- boolean isSelectOnly(TransitiveInfoCollection dep) {
- return selectOnlyDeps.contains(dep);
- }
- }
+ EnvironmentCollection getSupportedEnvironments(T context);
/**
* Performs constraint checking on the given rule's dependencies and reports any errors. This
@@ -527,7 +172,7 @@
* <li>Refined environment checking: no environment groups can be "emptied" due to refinement
* </ul>
*
- * @param ruleContext the rule to analyze
+ * @param context the rule to analyze
* @param staticEnvironments the rule's supported environments, as defined by the return value of
* {@link #getSupportedEnvironments}. In particular, for any environment group that's not in
* this collection, the rule is assumed to support the defaults for that group.
@@ -536,406 +181,16 @@
* pruning away environments through refinement. If multiple dependencies qualify (e.g. two
* direct deps under the current rule), one is arbitrarily chosen.
*/
- public void checkConstraints(
- RuleContext ruleContext,
+ void checkConstraints(
+ T context,
EnvironmentCollection staticEnvironments,
EnvironmentCollection.Builder refinedEnvironments,
- Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
- Set<EnvironmentWithGroup> refinedEnvironmentsSoFar = new LinkedHashSet<>();
- // Start with the full set of static environments:
- refinedEnvironmentsSoFar.addAll(staticEnvironments.getGroupedEnvironments());
- Set<EnvironmentLabels> groupsWithEnvironmentsRemoved = new LinkedHashSet<>();
- // Maps the label results of getUnsupportedEnvironments() to EnvironmentWithGroups. We can't
- // have that method just return EnvironmentWithGroups because it also collects group defaults,
- // which we only have labels for.
- Map<Label, EnvironmentWithGroup> labelsToEnvironments = new HashMap<>();
- for (EnvironmentWithGroup envWithGroup : staticEnvironments.getGroupedEnvironments()) {
- labelsToEnvironments.put(envWithGroup.environment(), envWithGroup);
- }
+ Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits);
- DepsToCheck depsToCheck = getConstraintCheckedDependencies(ruleContext);
-
- for (TransitiveInfoCollection dep : depsToCheck.allDeps()) {
- if (!depsToCheck.isSelectOnly(dep)) {
- // TODO(bazel-team): support static constraint checking for selects. A selectable constraint
- // is valid if the union of all deps in the select includes all of this rule's static
- // environments. Determining that requires following the select paths that don't get chosen,
- // which means we won't have ConfiguredTargets for those deps and need to find another
- // way to get their environments.
- checkStaticConstraints(ruleContext, staticEnvironments, dep);
- }
- refineEnvironmentsForDep(ruleContext, staticEnvironments, dep, labelsToEnvironments,
- refinedEnvironmentsSoFar, groupsWithEnvironmentsRemoved, removedEnvironmentCulprits);
- }
-
- checkRefinedConstraints(ruleContext, groupsWithEnvironmentsRemoved,
- refinedEnvironmentsSoFar, refinedEnvironments, removedEnvironmentCulprits);
- }
-
- /**
- * Performs static constraint checking against the given dep.
- *
- * @param ruleContext the rule being analyzed
- * @param staticEnvironments the static environments of the rule being analyzed
- * @param dep the dep to check
- */
- private void checkStaticConstraints(RuleContext ruleContext,
- EnvironmentCollection staticEnvironments, TransitiveInfoCollection dep) {
- SupportedEnvironmentsProvider depEnvironments =
- dep.getProvider(SupportedEnvironmentsProvider.class);
- Collection<Label> unsupportedEnvironments =
- getUnsupportedEnvironments(depEnvironments.getStaticEnvironments(), staticEnvironments);
- if (!unsupportedEnvironments.isEmpty()) {
- ruleError(ruleContext,
- "dependency " + dep.getLabel() + " doesn't support expected environment"
- + (unsupportedEnvironments.size() == 1 ? "" : "s")
- + ": " + Joiner.on(", ").join(unsupportedEnvironments));
- }
- }
-
- /**
- * Helper method for {@link #checkConstraints}: refines a rule's environments with the given dep.
- *
- * <p>A rule's <b>complete</b> refined set applies this process to every dep.
- */
- private static void refineEnvironmentsForDep(
- RuleContext ruleContext,
- EnvironmentCollection staticEnvironments,
- TransitiveInfoCollection dep,
- Map<Label, EnvironmentWithGroup> labelsToEnvironments,
- Set<EnvironmentWithGroup> refinedEnvironmentsSoFar,
- Set<EnvironmentLabels> groupsWithEnvironmentsRemoved,
- Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
-
- SupportedEnvironmentsProvider depEnvironments =
- dep.getProvider(SupportedEnvironmentsProvider.class);
-
- // Stores the environments that are pruned from the refined set because of this dep. Even
- // though they're removed, some subset of the environments they fulfill may belong in the
- // refined set. For example, if environment "both" fulfills "a" and "b" and "lib" statically
- // sets restricted_to = ["both"] and "dep" sets restricted_to = ["a"], then lib's refined set
- // excludes "both". But rather than be emptied out it can be reduced to "a".
- Set<Label> prunedEnvironmentsFromThisDep = new LinkedHashSet<>();
-
- // Refine this rule's environments by intersecting with the dep's refined environments:
- for (Label refinedEnvironmentToPrune : getUnsupportedEnvironments(
- depEnvironments.getRefinedEnvironments(), staticEnvironments)) {
- EnvironmentWithGroup envToPrune = labelsToEnvironments.get(refinedEnvironmentToPrune);
- if (envToPrune == null) {
- // If we have no record of this environment, that means the current rule implicitly uses
- // the defaults for this group. So explicitly opt that group's defaults into the refined
- // set before trying to remove specific items.
- for (EnvironmentWithGroup defaultEnv :
- getDefaults(refinedEnvironmentToPrune, depEnvironments.getRefinedEnvironments())) {
- refinedEnvironmentsSoFar.add(defaultEnv);
- labelsToEnvironments.put(defaultEnv.environment(), defaultEnv);
- }
- envToPrune = Verify.verifyNotNull(labelsToEnvironments.get(refinedEnvironmentToPrune));
- }
- refinedEnvironmentsSoFar.remove(envToPrune);
- groupsWithEnvironmentsRemoved.add(envToPrune.group());
- removedEnvironmentCulprits.put(envToPrune.environment(),
- findOriginalRefiner(ruleContext, dep.getLabel(), depEnvironments, envToPrune));
- prunedEnvironmentsFromThisDep.add(envToPrune.environment());
- }
-
- // Add in any dep environment that one of the environments we removed fulfills. In other
- // words, the removed environment is no good, but some subset of it may be.
- for (EnvironmentWithGroup depEnv :
- depEnvironments.getRefinedEnvironments().getGroupedEnvironments()) {
- for (Label fulfiller : depEnv.group().getFulfillers(depEnv.environment()).toList()) {
- if (prunedEnvironmentsFromThisDep.contains(fulfiller)) {
- refinedEnvironmentsSoFar.add(depEnv);
- }
- }
- }
- }
-
- /**
- * Helper method for {@link #checkConstraints}: performs refined environment constraint checking.
- *
- * <p>Refined environment expectations: no environment group should be emptied out due to
- * refining. This reflects the idea that some of the static declared environments get pruned out
- * by the build configuration, but <i>all</i> environments shouldn't be pruned out.
- *
- * <p>Violations of this expectation trigger rule analysis errors.
- */
- private void checkRefinedConstraints(
- RuleContext ruleContext,
- Set<EnvironmentLabels> groupsWithEnvironmentsRemoved,
- Set<EnvironmentWithGroup> refinedEnvironmentsSoFar,
- EnvironmentCollection.Builder refinedEnvironments,
- Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
- Set<EnvironmentLabels> refinedGroups = new LinkedHashSet<>();
- for (EnvironmentWithGroup envWithGroup : refinedEnvironmentsSoFar) {
- refinedEnvironments.put(envWithGroup.group(), envWithGroup.environment());
- refinedGroups.add(envWithGroup.group());
- }
- Set<EnvironmentLabels> newlyEmptyGroups =
- groupsWithEnvironmentsRemoved.isEmpty()
- ? ImmutableSet.of()
- : Sets.difference(groupsWithEnvironmentsRemoved, refinedGroups);
- if (!newlyEmptyGroups.isEmpty()) {
- ruleError(ruleContext, getOverRefinementError(newlyEmptyGroups, removedEnvironmentCulprits));
- }
- }
-
- /**
- * Constructs an error message for when all environments have been pruned out of one or more
- * environment groups due to refining.
- */
- private static String getOverRefinementError(
- Set<EnvironmentLabels> newlyEmptyGroups,
- Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
- StringJoiner message = new StringJoiner("\n")
- .add("the current command line flags disqualify all supported environments because of "
- + "incompatible select() paths:");
- for (EnvironmentLabels group : newlyEmptyGroups) {
- if (newlyEmptyGroups.size() > 1) {
- message
- .add(" ")
- .add("environment group: " + group.getLabel() + ":");
- }
- for (Label prunedEnvironment : group.getEnvironments()) {
- RemovedEnvironmentCulprit culprit = removedEnvironmentCulprits.get(prunedEnvironment);
- // Only environments this rule statically declared support for have culprits.
- if (culprit != null) {
- message
- .add(" ")
- .add(getMissingEnvironmentCulpritMessage(prunedEnvironment, culprit));
- }
- }
- }
- return message.toString();
- }
-
- static String getMissingEnvironmentCulpritMessage(Label environment,
- RemovedEnvironmentCulprit reason) {
- LabelAndLocation culprit = reason.culprit();
- return new StringJoiner("\n")
- .add(" environment: " + environment)
- .add(" removed by: " + culprit.getLabel() + " (" + culprit.getLocation() + ")")
- .add(" which has a select() that chooses dep: " + reason.selectedDepForCulprit())
- .add(" which lacks: " + environment)
- .toString();
- }
-
- /**
- * Given an environment that should be refined out of the current rule because of the given dep,
- * returns the original dep that caused the removal.
- *
- * <p>For example, say we have R -> D1 -> D2 and all rules support environment E. If the
- * refinement happens because D2 has
- * <pre>
- * deps = select({":foo": ["restricted_to_E"], ":bar": ["restricted_to_F"]}} # Choose F.
- * </pre>
- *
- * <p>then D2 is the original refiner (even though D1 and R inherit the same pruning).
- */
- private static RemovedEnvironmentCulprit findOriginalRefiner(RuleContext ruleContext, Label dep,
- SupportedEnvironmentsProvider depEnvironments, EnvironmentWithGroup envToPrune) {
- RemovedEnvironmentCulprit depCulprit =
- depEnvironments.getRemovedEnvironmentCulprit(envToPrune.environment());
- if (depCulprit != null) {
- return depCulprit;
- }
- // If the dep has no record of this environment being refined, that means the current rule
- // is the culprit.
- return RemovedEnvironmentCulprit.create(
- LabelAndLocation.of(ruleContext.getTarget()),
- // While it'd be nice to know the dep's location too, it isn't strictly necessary.
- // Especially since we already have the parent's location. So it's easy enough to find the
- // dep. And we want to respect the efficiency concerns described in LabelAndLocation.
- //
- // Alternatively, we could prepare error strings directly in SupportedEnvironmentsProvider,
- // which should remove the need for LabelAndLocation for any target.
- dep);
- }
-
- /**
- * Finds the given environment in the given set and returns the default environments for its
- * group.
- */
- private static Collection<EnvironmentWithGroup> getDefaults(Label env,
- EnvironmentCollection allEnvironments) {
- EnvironmentLabels group = null;
- for (EnvironmentLabels candidateGroup : allEnvironments.getGroups()) {
- if (candidateGroup.getDefaults().contains(env)) {
- group = candidateGroup;
- break;
- }
- }
- Verify.verifyNotNull(group);
- ImmutableSet.Builder<EnvironmentWithGroup> builder = ImmutableSet.builder();
- for (Label defaultEnv : group.getDefaults()) {
- builder.add(EnvironmentWithGroup.create(defaultEnv, group));
- }
- return builder.build();
- }
-
- /**
- * Given a collection of environments and a collection of expected environments, returns the
- * missing environments that would cause constraint expectations to be violated. Includes the
- * effects of environment group defaults.
- */
- static Collection<Label> getUnsupportedEnvironments(
- EnvironmentCollection actualEnvironments, EnvironmentCollection expectedEnvironments) {
- Set<Label> missingEnvironments = new LinkedHashSet<>();
- Collection<Label> actualEnvironmentLabels = actualEnvironments.getEnvironments();
-
- // Check if each explicitly expected environment is satisfied.
- for (EnvironmentWithGroup expectedEnv : expectedEnvironments.getGroupedEnvironments()) {
- EnvironmentLabels group = expectedEnv.group();
- Label environment = expectedEnv.environment();
- boolean isSatisfied = false;
- if (actualEnvironments.getGroups().contains(group)) {
- // If the actual environments include members from the expected environment's group, we
- // need to either find the environment itself or another one that transitively fulfills it.
- if (actualEnvironmentLabels.contains(environment)
- || intersect(actualEnvironmentLabels, group.getFulfillers(environment).toList())) {
- isSatisfied = true;
- }
- } else {
- // If the actual environments don't reference the expected environment's group at all,
- // the group's defaults are implicitly included. So we need to check those defaults for
- // either the expected environment or another environment that transitively fulfills it.
- if (group.isDefault(environment)
- || intersect(group.getFulfillers(environment).toList(), group.getDefaults())) {
- isSatisfied = true;
- }
- }
- if (!isSatisfied) {
- missingEnvironments.add(environment);
- }
- }
-
- // For any environment group not referenced by the expected environments, its defaults are
- // implicitly expected. We can ignore this if the actual environments also don't reference the
- // group (since in that case the same defaults apply), otherwise we have to check.
- for (EnvironmentLabels group : actualEnvironments.getGroups()) {
- if (!expectedEnvironments.getGroups().contains(group)) {
- for (Label expectedDefault : group.getDefaults()) {
- if (!actualEnvironmentLabels.contains(expectedDefault)
- && !intersect(
- actualEnvironmentLabels, group.getFulfillers(expectedDefault).toList())) {
- missingEnvironments.add(expectedDefault);
- }
- }
- }
- }
-
- return missingEnvironments;
- }
-
- private static boolean intersect(Iterable<Label> labels1, Iterable<Label> labels2) {
- return !Sets.intersection(Sets.newHashSet(labels1), Sets.newHashSet(labels2)).isEmpty();
- }
-
- /**
- * Returns all dependencies that should be constraint-checked against the current rule,
- * including both "uncoditional" deps (outside selects) and deps that only appear in selects.
- */
- private static DepsToCheck getConstraintCheckedDependencies(RuleContext ruleContext) {
- Set<TransitiveInfoCollection> depsToCheck = new LinkedHashSet<>();
- Set<TransitiveInfoCollection> selectOnlyDeps = new LinkedHashSet<>();
- Set<TransitiveInfoCollection> depsOutsideSelects = new LinkedHashSet<>();
-
- AttributeMap attributes = ruleContext.attributes();
- for (String attr : attributes.getAttributeNames()) {
- Attribute attrDef = attributes.getAttributeDefinition(attr);
- if (attrDef.getType().getLabelClass() != LabelClass.DEPENDENCY
- || attrDef.skipConstraintsOverride()) {
- continue;
- }
- if (!attrDef.checkConstraintsOverride()) {
- // Use the same implicit deps check that query uses. This facilitates running queries to
- // determine exactly which rules need to be constraint-annotated for depot migrations.
- if (!DependencyFilter.NO_IMPLICIT_DEPS.apply(ruleContext.getRule(), attrDef)
- // We can't identify host deps by calling BuildConfiguration.isHostConfiguration()
- // because --nodistinct_host_configuration subverts that call.
- || attrDef.getTransitionFactory().isHost()) {
- continue;
- }
- }
-
- Set<Label> selectOnlyDepsForThisAttribute =
- getDepsOnlyInSelects(ruleContext, attr, attributes.getAttributeType(attr));
- for (TransitiveInfoCollection dep :
- ruleContext.getPrerequisites(attr, TransitionMode.DONT_CHECK)) {
- // Output files inherit the environment spec of their generating rule.
- if (dep instanceof OutputFileConfiguredTarget) {
- // Note this reassignment means constraint violation errors reference the generating
- // rule, not the file. This makes the source of the environmental mismatch more clear.
- dep = ((OutputFileConfiguredTarget) dep).getGeneratingRule();
- }
- // Input files don't support environments. We may subsequently opt them into constraint
- // checking, but for now just pass them by.
- if (dep.getProvider(SupportedEnvironmentsProvider.class) != null) {
- depsToCheck.add(dep);
- // For normal configured targets the target's label is the same label appearing in the
- // select(). But for AliasConfiguredTargets the label in the select() refers to the alias,
- // while dep.getLabel() refers to the target the alias points to. So add this quick check
- // to make sure we're comparing the same labels.
- Label depLabelInSelect =
- (dep instanceof ConfiguredTarget)
- ? ((ConfiguredTarget) dep).getOriginalLabel()
- : dep.getLabel();
- if (!selectOnlyDepsForThisAttribute.contains(depLabelInSelect)) {
- depsOutsideSelects.add(dep);
- }
- }
- }
- }
-
- for (TransitiveInfoCollection dep : depsToCheck) {
- if (!depsOutsideSelects.contains(dep)) {
- selectOnlyDeps.add(dep);
- }
- }
-
- return new DepsToCheck(depsToCheck, selectOnlyDeps);
- }
-
- /**
- * Returns the deps for this attribute that only appear in selects.
- *
- * <p>For example:
- * <pre>
- * deps = [":a"] + select({"//foo:cond": [":b"]}) + select({"//conditions:default": [":c"]})
- * </pre>
- *
- * returns {@code [":b"]}. Even though {@code [":c"]} also appears in a select, that's a
- * degenerate case with only one always-chosen condition. So that's considered the same as
- * an unconditional dep.
- *
- * <p>Note that just because a dep only appears in selects for this attribute doesn't mean it
- * won't appear unconditionally in another attribute.
- */
- private static Set<Label> getDepsOnlyInSelects(RuleContext ruleContext, String attr,
- Type<?> attrType) {
- Rule rule = ruleContext.getRule();
- if (!rule.isConfigurableAttribute(attr) || !BuildType.isLabelType(attrType)) {
- return ImmutableSet.of();
- }
- Set<Label> unconditionalDeps = new LinkedHashSet<>();
- Set<Label> selectableDeps = new LinkedHashSet<>();
- BuildType.SelectorList<?> selectList = (BuildType.SelectorList<?>)
- RawAttributeMapper.of(rule).getRawAttributeValue(rule, attr);
- for (BuildType.Selector<?> select : selectList.getSelectors()) {
- addSelectValuesToSet(select, select.isUnconditional() ? unconditionalDeps : selectableDeps);
- }
- return Sets.difference(selectableDeps, unconditionalDeps);
- }
-
- /**
- * Adds all label values from the given select to the given set. Automatically handles different
- * value types (e.g. labels vs. label lists).
- */
- private static void addSelectValuesToSet(BuildType.Selector<?> select, final Set<Label> set) {
- Type<?> type = select.getOriginalType();
- LabelVisitor<?> visitor = (label, dummy) -> set.add(label);
- for (Object value : select.getEntries().values()) {
- type.visitLabels(visitor, value, /*context=*/ null);
+ /** Exception indicating errors finding/parsing environments or their containing groups. */
+ class EnvironmentLookupException extends Exception {
+ private EnvironmentLookupException(String message) {
+ super(message);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
index c0e8493..e9c34df 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
@@ -25,7 +25,6 @@
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.EnvironmentGroup;
-import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
/**
* Implementation for the environment rule.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/RuleContextConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/RuleContextConstraintSemantics.java
new file mode 100644
index 0000000..92cfb83
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/RuleContextConstraintSemantics.java
@@ -0,0 +1,816 @@
+// Copyright 2015 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.analysis.constraints;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.LabelAndLocation;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitionMode;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup;
+import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider.RemovedEnvironmentCulprit;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.DependencyFilter;
+import com.google.devtools.build.lib.packages.EnvironmentLabels;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.packages.Type.LabelClass;
+import com.google.devtools.build.lib.packages.Type.LabelVisitor;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import javax.annotation.Nullable;
+
+/** Implementation of {@link ConstraintSemantics} using {@link RuleContext} to check constraints. */
+public class RuleContextConstraintSemantics implements ConstraintSemantics<RuleContext> {
+ public RuleContextConstraintSemantics() {}
+
+ /**
+ * Logs an error message that the current rule violates constraints.
+ */
+ public void ruleError(RuleContext ruleContext, String message) {
+ ruleContext.ruleError(message);
+ }
+
+ /**
+ * Logs an error message that an attribute on the current rule doesn't properly declare
+ * constraints.
+ */
+ public void attributeError(RuleContext ruleContext, String attribute, String message) {
+ ruleContext.attributeError(attribute, message);
+ }
+
+ /**
+ * Provides a set of default environments for a given environment group.
+ */
+ private interface DefaultsProvider {
+ Collection<Label> getDefaults(EnvironmentLabels group);
+ }
+
+ /**
+ * Provides a group's defaults as specified in the environment group's BUILD declaration.
+ */
+ private static class GroupDefaultsProvider implements DefaultsProvider {
+ @Override
+ public Collection<Label> getDefaults(EnvironmentLabels group) {
+ return group.getDefaults();
+ }
+ }
+
+ /**
+ * Provides a group's defaults, factoring in rule class defaults as specified by
+ * {@link com.google.devtools.build.lib.packages.RuleClass.Builder#compatibleWith}
+ * and {@link com.google.devtools.build.lib.packages.RuleClass.Builder#restrictedTo}.
+ */
+ private static class RuleClassDefaultsProvider implements DefaultsProvider {
+ private final EnvironmentCollection ruleClassDefaults;
+ private final GroupDefaultsProvider groupDefaults;
+
+ RuleClassDefaultsProvider(EnvironmentCollection ruleClassDefaults) {
+ this.ruleClassDefaults = ruleClassDefaults;
+ this.groupDefaults = new GroupDefaultsProvider();
+ }
+
+ @Override
+ public Collection<Label> getDefaults(EnvironmentLabels group) {
+ if (ruleClassDefaults.getGroups().contains(group)) {
+ return ruleClassDefaults.getEnvironments(group);
+ } else {
+ // If there are no rule class defaults for this group, just inherit global defaults.
+ return groupDefaults.getDefaults(group);
+ }
+ }
+ }
+
+ /**
+ * Collects the set of supported environments for a given rule by merging its
+ * restriction-style and compatibility-style environment declarations as specified by
+ * the given attributes. Only includes environments from "known" groups, i.e. the groups
+ * owning the environments explicitly referenced from these attributes.
+ */
+ private class EnvironmentCollector {
+ private final RuleContext ruleContext;
+ private final String restrictionAttr;
+ private final String compatibilityAttr;
+ private final DefaultsProvider defaultsProvider;
+
+ private final EnvironmentCollection restrictionEnvironments;
+ private final EnvironmentCollection compatibilityEnvironments;
+ private final EnvironmentCollection supportedEnvironments;
+
+ /**
+ * Constructs a new collector on the given attributes.
+ *
+ * @param ruleContext analysis context for the rule
+ * @param restrictionAttr the name of the attribute that declares "restricted to"-style
+ * environments. If the rule doesn't have this attribute, this is considered an
+ * empty declaration.
+ * @param compatibilityAttr the name of the attribute that declares "compatible with"-style
+ * environments. If the rule doesn't have this attribute, this is considered an
+ * empty declaration.
+ * @param defaultsProvider provider for the default environments within a group if not
+ * otherwise overridden by the above attributes
+ */
+ EnvironmentCollector(RuleContext ruleContext, String restrictionAttr, String compatibilityAttr,
+ DefaultsProvider defaultsProvider) {
+ this.ruleContext = ruleContext;
+ this.restrictionAttr = restrictionAttr;
+ this.compatibilityAttr = compatibilityAttr;
+ this.defaultsProvider = defaultsProvider;
+
+ EnvironmentCollection.Builder environmentsBuilder = new EnvironmentCollection.Builder();
+ restrictionEnvironments = collectRestrictionEnvironments(environmentsBuilder);
+ compatibilityEnvironments = collectCompatibilityEnvironments(environmentsBuilder);
+ supportedEnvironments = environmentsBuilder.build();
+ }
+
+ /**
+ * Returns the set of environments supported by this rule, as determined by the
+ * restriction-style attribute, compatibility-style attribute, and group defaults
+ * provider instantiated with this class.
+ */
+ EnvironmentCollection getEnvironments() {
+ return supportedEnvironments;
+ }
+
+ /**
+ * Validity-checks that no group has its environment referenced in both the "compatible with"
+ * and restricted to" attributes. Returns true if all is good, returns false and reports
+ * appropriate errors if there are any problems.
+ */
+ boolean validateEnvironmentSpecifications() {
+ ImmutableCollection<EnvironmentLabels> restrictionGroups =
+ restrictionEnvironments.getGroups();
+ boolean hasErrors = false;
+
+ for (EnvironmentLabels group : compatibilityEnvironments.getGroups()) {
+ if (restrictionGroups.contains(group)) {
+ // To avoid error-spamming the user, when we find a conflict we only report one example
+ // environment from each attribute for that group.
+ Label compatibilityEnv =
+ compatibilityEnvironments.getEnvironments(group).iterator().next();
+ Label restrictionEnv = restrictionEnvironments.getEnvironments(group).iterator().next();
+
+ if (compatibilityEnv.equals(restrictionEnv)) {
+ attributeError(ruleContext, compatibilityAttr, compatibilityEnv
+ + " cannot appear both here and in " + restrictionAttr);
+ } else {
+ attributeError(ruleContext, compatibilityAttr, compatibilityEnv + " and "
+ + restrictionEnv + " belong to the same environment group. They should be declared "
+ + "together either here or in " + restrictionAttr);
+ }
+ hasErrors = true;
+ }
+ }
+
+ return !hasErrors;
+ }
+
+ /**
+ * Adds environments specified in the "restricted to" attribute to the set of supported
+ * environments and returns the environments added.
+ */
+ private EnvironmentCollection collectRestrictionEnvironments(
+ EnvironmentCollection.Builder supportedEnvironments) {
+ return collectEnvironments(restrictionAttr, supportedEnvironments);
+ }
+
+ /**
+ * Adds environments specified in the "compatible with" attribute to the set of supported
+ * environments, along with all defaults from the groups they belong to. Returns these
+ * environments, not including the defaults.
+ */
+ private EnvironmentCollection collectCompatibilityEnvironments(
+ EnvironmentCollection.Builder supportedEnvironments) {
+ EnvironmentCollection compatibilityEnvironments =
+ collectEnvironments(compatibilityAttr, supportedEnvironments);
+ for (EnvironmentLabels group : compatibilityEnvironments.getGroups()) {
+ supportedEnvironments.putAll(group, defaultsProvider.getDefaults(group));
+ }
+ return compatibilityEnvironments;
+ }
+
+ /**
+ * Adds environments specified by the given attribute to the set of supported environments
+ * and returns the environments added.
+ *
+ * <p>If this rule doesn't have the given attributes, returns an empty set.
+ */
+ private EnvironmentCollection collectEnvironments(String attrName,
+ EnvironmentCollection.Builder supportedEnvironments) {
+ if (!ruleContext.getRule().isAttrDefined(attrName, BuildType.LABEL_LIST)) {
+ return EnvironmentCollection.EMPTY;
+ }
+ EnvironmentCollection.Builder environments = new EnvironmentCollection.Builder();
+ for (TransitiveInfoCollection envTarget :
+ ruleContext.getPrerequisites(attrName, TransitionMode.DONT_CHECK)) {
+ EnvironmentWithGroup envInfo = resolveEnvironment(envTarget);
+ environments.put(envInfo.group(), envInfo.environment());
+ supportedEnvironments.put(envInfo.group(), envInfo.environment());
+ }
+ return environments.build();
+ }
+
+ /**
+ * Returns the environment and its group. An {@link Environment} rule only "supports" one
+ * environment: itself. Extract that from its more generic provider interface and sanity
+ * check that that's in fact what we see.
+ */
+ private EnvironmentWithGroup resolveEnvironment(TransitiveInfoCollection envRule) {
+ SupportedEnvironmentsProvider prereq =
+ Preconditions.checkNotNull(envRule.getProvider(SupportedEnvironmentsProvider.class));
+ return Iterables.getOnlyElement(prereq.getStaticEnvironments().getGroupedEnvironments());
+ }
+ }
+
+ /**
+ * Returns the set of environments this rule supports, applying the logic described in {@link
+ * RuleContextConstraintSemantics}.
+ *
+ * <p>Note this set is <b>not complete</b> - it doesn't include environments from groups we don't
+ * "know about". Environments and groups can be declared in any package. If the rule includes no
+ * references to that package, then it simply doesn't know anything about them. But the constraint
+ * semantics say the rule should support the defaults for that group. We encode this implicitly:
+ * given the returned set, for any group that's not in the set the rule is also considered to
+ * support that group's defaults.
+ *
+ * @param ruleContext analysis context for the rule. A rule error is triggered here if invalid
+ * constraint settings are discovered.
+ * @return the environments this rule supports, not counting defaults "unknown" to this rule as
+ * described above. Returns null if any errors are encountered.
+ */
+ @Override
+ @Nullable
+ public EnvironmentCollection getSupportedEnvironments(RuleContext ruleContext) {
+ if (!validateAttributes(ruleContext)) {
+ return null;
+ }
+
+ // This rule's rule class defaults (or null if the rule class has no defaults).
+ EnvironmentCollector ruleClassCollector = maybeGetRuleClassDefaults(ruleContext);
+ // Default environments for this rule. If the rule has rule class defaults, this is
+ // those defaults. Otherwise it's the global defaults specified by environment_group
+ // declarations.
+ DefaultsProvider ruleDefaults;
+
+ if (ruleClassCollector != null) {
+ if (!ruleClassCollector.validateEnvironmentSpecifications()) {
+ return null;
+ }
+ ruleDefaults = new RuleClassDefaultsProvider(ruleClassCollector.getEnvironments());
+ } else {
+ ruleDefaults = new GroupDefaultsProvider();
+ }
+
+ EnvironmentCollector ruleCollector = new EnvironmentCollector(ruleContext,
+ RuleClass.RESTRICTED_ENVIRONMENT_ATTR, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, ruleDefaults);
+ if (!ruleCollector.validateEnvironmentSpecifications()) {
+ return null;
+ }
+
+ EnvironmentCollection supportedEnvironments = ruleCollector.getEnvironments();
+ if (ruleClassCollector != null) {
+ // If we have rule class defaults from groups that aren't referenced from the rule itself,
+ // we need to add them in too to override the global defaults.
+ supportedEnvironments =
+ addUnknownGroupsToCollection(supportedEnvironments, ruleClassCollector.getEnvironments());
+ }
+ return supportedEnvironments;
+ }
+
+ /**
+ * Returns the rule class defaults specified for this rule, or null if there are
+ * no such defaults.
+ */
+ @Nullable
+ private EnvironmentCollector maybeGetRuleClassDefaults(RuleContext ruleContext) {
+ Rule rule = ruleContext.getRule();
+ String restrictionAttr = RuleClass.DEFAULT_RESTRICTED_ENVIRONMENT_ATTR;
+ String compatibilityAttr = RuleClass.DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR;
+
+ if (rule.isAttrDefined(restrictionAttr, BuildType.LABEL_LIST)
+ || rule.isAttrDefined(compatibilityAttr, BuildType.LABEL_LIST)) {
+ return new EnvironmentCollector(ruleContext, restrictionAttr, compatibilityAttr,
+ new GroupDefaultsProvider());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds environments to an {@link EnvironmentCollection} from groups that aren't already
+ * a part of that collection.
+ *
+ * @param environments the collection to add to
+ * @param toAdd the collection to add. All environments in this collection in groups
+ * that aren't represented in {@code environments} are added to {@code environments}.
+ * @return the expanded collection.
+ */
+ private static EnvironmentCollection addUnknownGroupsToCollection(
+ EnvironmentCollection environments, EnvironmentCollection toAdd) {
+ EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder();
+ builder.putAll(environments);
+ for (EnvironmentLabels candidateGroup : toAdd.getGroups()) {
+ if (!environments.getGroups().contains(candidateGroup)) {
+ builder.putAll(candidateGroup, toAdd.getEnvironments(candidateGroup));
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Validity-checks this rule's constraint-related attributes. Returns true if all is good,
+ * returns false and reports appropriate errors if there are any problems.
+ */
+ private boolean validateAttributes(RuleContext ruleContext) {
+ AttributeMap attributes = ruleContext.attributes();
+
+ // Report an error if "restricted to" is explicitly set to nothing. Even if this made
+ // conceptual sense, we don't know which groups we should apply that to.
+ String restrictionAttr = RuleClass.RESTRICTED_ENVIRONMENT_ATTR;
+ List<? extends TransitiveInfoCollection> restrictionEnvironments =
+ ruleContext.getPrerequisites(restrictionAttr, TransitionMode.DONT_CHECK);
+ if (restrictionEnvironments.isEmpty()
+ && attributes.isAttributeValueExplicitlySpecified(restrictionAttr)) {
+ attributeError(ruleContext, restrictionAttr, "attribute cannot be empty");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper container for checkConstraints: stores both a set of deps that need to be
+ * constraint-checked and the subset of those deps that only appear inside selects.
+ */
+ private static class DepsToCheck {
+ private final Set<TransitiveInfoCollection> allDeps;
+ private final Set<TransitiveInfoCollection> selectOnlyDeps;
+ DepsToCheck(Set<TransitiveInfoCollection> depsToCheck,
+ Set<TransitiveInfoCollection> selectOnlyDeps) {
+ this.allDeps = depsToCheck;
+ this.selectOnlyDeps = selectOnlyDeps;
+ }
+ Set<TransitiveInfoCollection> allDeps() {
+ return allDeps;
+ }
+ boolean isSelectOnly(TransitiveInfoCollection dep) {
+ return selectOnlyDeps.contains(dep);
+ }
+ }
+
+ /**
+ * Performs constraint checking on the given rule's dependencies and reports any errors. This
+ * includes:
+ *
+ * <ul>
+ * <li>Static environment checking: if this rule supports environment E, all deps outside
+ * selects must also support E
+ * <li>Refined environment computation: this rule's refined environments are its static
+ * environments intersected with the refined environments of all dependencies (including
+ * chosen deps in selects)
+ * <li>Refined environment checking: no environment groups can be "emptied" due to refinement
+ * </ul>
+ *
+ * @param ruleContext the rule to analyze
+ * @param staticEnvironments the rule's supported environments, as defined by the return value of
+ * {@link #getSupportedEnvironments}. In particular, for any environment group that's not in
+ * this collection, the rule is assumed to support the defaults for that group.
+ * @param refinedEnvironments a builder for populating this rule's refined environments
+ * @param removedEnvironmentCulprits a builder for populating the core dependencies that trigger
+ * pruning away environments through refinement. If multiple dependencies qualify (e.g. two
+ * direct deps under the current rule), one is arbitrarily chosen.
+ */
+ @Override
+ public void checkConstraints(
+ RuleContext ruleContext,
+ EnvironmentCollection staticEnvironments,
+ EnvironmentCollection.Builder refinedEnvironments,
+ Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
+ Set<EnvironmentWithGroup> refinedEnvironmentsSoFar = new LinkedHashSet<>();
+ // Start with the full set of static environments:
+ refinedEnvironmentsSoFar.addAll(staticEnvironments.getGroupedEnvironments());
+ Set<EnvironmentLabels> groupsWithEnvironmentsRemoved = new LinkedHashSet<>();
+ // Maps the label results of getUnsupportedEnvironments() to EnvironmentWithGroups. We can't
+ // have that method just return EnvironmentWithGroups because it also collects group defaults,
+ // which we only have labels for.
+ Map<Label, EnvironmentWithGroup> labelsToEnvironments = new HashMap<>();
+ for (EnvironmentWithGroup envWithGroup : staticEnvironments.getGroupedEnvironments()) {
+ labelsToEnvironments.put(envWithGroup.environment(), envWithGroup);
+ }
+
+ DepsToCheck depsToCheck = getConstraintCheckedDependencies(ruleContext);
+
+ for (TransitiveInfoCollection dep : depsToCheck.allDeps()) {
+ if (!depsToCheck.isSelectOnly(dep)) {
+ // TODO(bazel-team): support static constraint checking for selects. A selectable constraint
+ // is valid if the union of all deps in the select includes all of this rule's static
+ // environments. Determining that requires following the select paths that don't get chosen,
+ // which means we won't have ConfiguredTargets for those deps and need to find another
+ // way to get their environments.
+ checkStaticConstraints(ruleContext, staticEnvironments, dep);
+ }
+ refineEnvironmentsForDep(ruleContext, staticEnvironments, dep, labelsToEnvironments,
+ refinedEnvironmentsSoFar, groupsWithEnvironmentsRemoved, removedEnvironmentCulprits);
+ }
+
+ checkRefinedConstraints(ruleContext, groupsWithEnvironmentsRemoved,
+ refinedEnvironmentsSoFar, refinedEnvironments, removedEnvironmentCulprits);
+ }
+
+ /**
+ * Performs static constraint checking against the given dep.
+ *
+ * @param ruleContext the rule being analyzed
+ * @param staticEnvironments the static environments of the rule being analyzed
+ * @param dep the dep to check
+ */
+ private void checkStaticConstraints(RuleContext ruleContext,
+ EnvironmentCollection staticEnvironments, TransitiveInfoCollection dep) {
+ SupportedEnvironmentsProvider depEnvironments =
+ dep.getProvider(SupportedEnvironmentsProvider.class);
+ Collection<Label> unsupportedEnvironments =
+ getUnsupportedEnvironments(depEnvironments.getStaticEnvironments(), staticEnvironments);
+ if (!unsupportedEnvironments.isEmpty()) {
+ ruleError(ruleContext,
+ "dependency " + dep.getLabel() + " doesn't support expected environment"
+ + (unsupportedEnvironments.size() == 1 ? "" : "s")
+ + ": " + Joiner.on(", ").join(unsupportedEnvironments));
+ }
+ }
+
+ /**
+ * Helper method for {@link #checkConstraints}: refines a rule's environments with the given dep.
+ *
+ * <p>A rule's <b>complete</b> refined set applies this process to every dep.
+ */
+ private static void refineEnvironmentsForDep(
+ RuleContext ruleContext,
+ EnvironmentCollection staticEnvironments,
+ TransitiveInfoCollection dep,
+ Map<Label, EnvironmentWithGroup> labelsToEnvironments,
+ Set<EnvironmentWithGroup> refinedEnvironmentsSoFar,
+ Set<EnvironmentLabels> groupsWithEnvironmentsRemoved,
+ Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
+
+ SupportedEnvironmentsProvider depEnvironments =
+ dep.getProvider(SupportedEnvironmentsProvider.class);
+
+ // Stores the environments that are pruned from the refined set because of this dep. Even
+ // though they're removed, some subset of the environments they fulfill may belong in the
+ // refined set. For example, if environment "both" fulfills "a" and "b" and "lib" statically
+ // sets restricted_to = ["both"] and "dep" sets restricted_to = ["a"], then lib's refined set
+ // excludes "both". But rather than be emptied out it can be reduced to "a".
+ Set<Label> prunedEnvironmentsFromThisDep = new LinkedHashSet<>();
+
+ // Refine this rule's environments by intersecting with the dep's refined environments:
+ for (Label refinedEnvironmentToPrune : getUnsupportedEnvironments(
+ depEnvironments.getRefinedEnvironments(), staticEnvironments)) {
+ EnvironmentWithGroup envToPrune = labelsToEnvironments.get(refinedEnvironmentToPrune);
+ if (envToPrune == null) {
+ // If we have no record of this environment, that means the current rule implicitly uses
+ // the defaults for this group. So explicitly opt that group's defaults into the refined
+ // set before trying to remove specific items.
+ for (EnvironmentWithGroup defaultEnv :
+ getDefaults(refinedEnvironmentToPrune, depEnvironments.getRefinedEnvironments())) {
+ refinedEnvironmentsSoFar.add(defaultEnv);
+ labelsToEnvironments.put(defaultEnv.environment(), defaultEnv);
+ }
+ envToPrune = Verify.verifyNotNull(labelsToEnvironments.get(refinedEnvironmentToPrune));
+ }
+ refinedEnvironmentsSoFar.remove(envToPrune);
+ groupsWithEnvironmentsRemoved.add(envToPrune.group());
+ removedEnvironmentCulprits.put(envToPrune.environment(),
+ findOriginalRefiner(ruleContext, dep.getLabel(), depEnvironments, envToPrune));
+ prunedEnvironmentsFromThisDep.add(envToPrune.environment());
+ }
+
+ // Add in any dep environment that one of the environments we removed fulfills. In other
+ // words, the removed environment is no good, but some subset of it may be.
+ for (EnvironmentWithGroup depEnv :
+ depEnvironments.getRefinedEnvironments().getGroupedEnvironments()) {
+ for (Label fulfiller : depEnv.group().getFulfillers(depEnv.environment()).toList()) {
+ if (prunedEnvironmentsFromThisDep.contains(fulfiller)) {
+ refinedEnvironmentsSoFar.add(depEnv);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method for {@link #checkConstraints}: performs refined environment constraint checking.
+ *
+ * <p>Refined environment expectations: no environment group should be emptied out due to
+ * refining. This reflects the idea that some of the static declared environments get pruned out
+ * by the build configuration, but <i>all</i> environments shouldn't be pruned out.
+ *
+ * <p>Violations of this expectation trigger rule analysis errors.
+ */
+ private void checkRefinedConstraints(
+ RuleContext ruleContext,
+ Set<EnvironmentLabels> groupsWithEnvironmentsRemoved,
+ Set<EnvironmentWithGroup> refinedEnvironmentsSoFar,
+ EnvironmentCollection.Builder refinedEnvironments,
+ Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
+ Set<EnvironmentLabels> refinedGroups = new LinkedHashSet<>();
+ for (EnvironmentWithGroup envWithGroup : refinedEnvironmentsSoFar) {
+ refinedEnvironments.put(envWithGroup.group(), envWithGroup.environment());
+ refinedGroups.add(envWithGroup.group());
+ }
+ Set<EnvironmentLabels> newlyEmptyGroups =
+ groupsWithEnvironmentsRemoved.isEmpty()
+ ? ImmutableSet.of()
+ : Sets.difference(groupsWithEnvironmentsRemoved, refinedGroups);
+ if (!newlyEmptyGroups.isEmpty()) {
+ ruleError(ruleContext, getOverRefinementError(newlyEmptyGroups, removedEnvironmentCulprits));
+ }
+ }
+
+ /**
+ * Constructs an error message for when all environments have been pruned out of one or more
+ * environment groups due to refining.
+ */
+ private static String getOverRefinementError(
+ Set<EnvironmentLabels> newlyEmptyGroups,
+ Map<Label, RemovedEnvironmentCulprit> removedEnvironmentCulprits) {
+ StringJoiner message = new StringJoiner("\n")
+ .add("the current command line flags disqualify all supported environments because of "
+ + "incompatible select() paths:");
+ for (EnvironmentLabels group : newlyEmptyGroups) {
+ if (newlyEmptyGroups.size() > 1) {
+ message
+ .add(" ")
+ .add("environment group: " + group.getLabel() + ":");
+ }
+ for (Label prunedEnvironment : group.getEnvironments()) {
+ RemovedEnvironmentCulprit culprit = removedEnvironmentCulprits.get(prunedEnvironment);
+ // Only environments this rule statically declared support for have culprits.
+ if (culprit != null) {
+ message
+ .add(" ")
+ .add(getMissingEnvironmentCulpritMessage(prunedEnvironment, culprit));
+ }
+ }
+ }
+ return message.toString();
+ }
+
+ static String getMissingEnvironmentCulpritMessage(Label environment,
+ RemovedEnvironmentCulprit reason) {
+ LabelAndLocation culprit = reason.culprit();
+ return new StringJoiner("\n")
+ .add(" environment: " + environment)
+ .add(" removed by: " + culprit.getLabel() + " (" + culprit.getLocation() + ")")
+ .add(" which has a select() that chooses dep: " + reason.selectedDepForCulprit())
+ .add(" which lacks: " + environment)
+ .toString();
+ }
+
+ /**
+ * Given an environment that should be refined out of the current rule because of the given dep,
+ * returns the original dep that caused the removal.
+ *
+ * <p>For example, say we have R -> D1 -> D2 and all rules support environment E. If the
+ * refinement happens because D2 has
+ * <pre>
+ * deps = select({":foo": ["restricted_to_E"], ":bar": ["restricted_to_F"]}} # Choose F.
+ * </pre>
+ *
+ * <p>then D2 is the original refiner (even though D1 and R inherit the same pruning).
+ */
+ private static RemovedEnvironmentCulprit findOriginalRefiner(RuleContext ruleContext, Label dep,
+ SupportedEnvironmentsProvider depEnvironments, EnvironmentWithGroup envToPrune) {
+ RemovedEnvironmentCulprit depCulprit =
+ depEnvironments.getRemovedEnvironmentCulprit(envToPrune.environment());
+ if (depCulprit != null) {
+ return depCulprit;
+ }
+ // If the dep has no record of this environment being refined, that means the current rule
+ // is the culprit.
+ return RemovedEnvironmentCulprit.create(
+ LabelAndLocation.of(ruleContext.getTarget()),
+ // While it'd be nice to know the dep's location too, it isn't strictly necessary.
+ // Especially since we already have the parent's location. So it's easy enough to find the
+ // dep. And we want to respect the efficiency concerns described in LabelAndLocation.
+ //
+ // Alternatively, we could prepare error strings directly in SupportedEnvironmentsProvider,
+ // which should remove the need for LabelAndLocation for any target.
+ dep);
+ }
+
+ /**
+ * Finds the given environment in the given set and returns the default environments for its
+ * group.
+ */
+ private static Collection<EnvironmentWithGroup> getDefaults(Label env,
+ EnvironmentCollection allEnvironments) {
+ EnvironmentLabels group = null;
+ for (EnvironmentLabels candidateGroup : allEnvironments.getGroups()) {
+ if (candidateGroup.getDefaults().contains(env)) {
+ group = candidateGroup;
+ break;
+ }
+ }
+ Verify.verifyNotNull(group);
+ ImmutableSet.Builder<EnvironmentWithGroup> builder = ImmutableSet.builder();
+ for (Label defaultEnv : group.getDefaults()) {
+ builder.add(EnvironmentWithGroup.create(defaultEnv, group));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Given a collection of environments and a collection of expected environments, returns the
+ * missing environments that would cause constraint expectations to be violated. Includes the
+ * effects of environment group defaults.
+ */
+ static Collection<Label> getUnsupportedEnvironments(
+ EnvironmentCollection actualEnvironments, EnvironmentCollection expectedEnvironments) {
+ Set<Label> missingEnvironments = new LinkedHashSet<>();
+ Collection<Label> actualEnvironmentLabels = actualEnvironments.getEnvironments();
+
+ // Check if each explicitly expected environment is satisfied.
+ for (EnvironmentWithGroup expectedEnv : expectedEnvironments.getGroupedEnvironments()) {
+ EnvironmentLabels group = expectedEnv.group();
+ Label environment = expectedEnv.environment();
+ boolean isSatisfied = false;
+ if (actualEnvironments.getGroups().contains(group)) {
+ // If the actual environments include members from the expected environment's group, we
+ // need to either find the environment itself or another one that transitively fulfills it.
+ if (actualEnvironmentLabels.contains(environment)
+ || intersect(actualEnvironmentLabels, group.getFulfillers(environment).toList())) {
+ isSatisfied = true;
+ }
+ } else {
+ // If the actual environments don't reference the expected environment's group at all,
+ // the group's defaults are implicitly included. So we need to check those defaults for
+ // either the expected environment or another environment that transitively fulfills it.
+ if (group.isDefault(environment)
+ || intersect(group.getFulfillers(environment).toList(), group.getDefaults())) {
+ isSatisfied = true;
+ }
+ }
+ if (!isSatisfied) {
+ missingEnvironments.add(environment);
+ }
+ }
+
+ // For any environment group not referenced by the expected environments, its defaults are
+ // implicitly expected. We can ignore this if the actual environments also don't reference the
+ // group (since in that case the same defaults apply), otherwise we have to check.
+ for (EnvironmentLabels group : actualEnvironments.getGroups()) {
+ if (!expectedEnvironments.getGroups().contains(group)) {
+ for (Label expectedDefault : group.getDefaults()) {
+ if (!actualEnvironmentLabels.contains(expectedDefault)
+ && !intersect(
+ actualEnvironmentLabels, group.getFulfillers(expectedDefault).toList())) {
+ missingEnvironments.add(expectedDefault);
+ }
+ }
+ }
+ }
+
+ return missingEnvironments;
+ }
+
+ private static boolean intersect(Iterable<Label> labels1, Iterable<Label> labels2) {
+ return !Sets.intersection(Sets.newHashSet(labels1), Sets.newHashSet(labels2)).isEmpty();
+ }
+
+ /**
+ * Returns all dependencies that should be constraint-checked against the current rule,
+ * including both "uncoditional" deps (outside selects) and deps that only appear in selects.
+ */
+ private static DepsToCheck getConstraintCheckedDependencies(RuleContext ruleContext) {
+ Set<TransitiveInfoCollection> depsToCheck = new LinkedHashSet<>();
+ Set<TransitiveInfoCollection> selectOnlyDeps = new LinkedHashSet<>();
+ Set<TransitiveInfoCollection> depsOutsideSelects = new LinkedHashSet<>();
+
+ AttributeMap attributes = ruleContext.attributes();
+ for (String attr : attributes.getAttributeNames()) {
+ Attribute attrDef = attributes.getAttributeDefinition(attr);
+ if (attrDef.getType().getLabelClass() != LabelClass.DEPENDENCY
+ || attrDef.skipConstraintsOverride()) {
+ continue;
+ }
+ if (!attrDef.checkConstraintsOverride()) {
+ // Use the same implicit deps check that query uses. This facilitates running queries to
+ // determine exactly which rules need to be constraint-annotated for depot migrations.
+ if (!DependencyFilter.NO_IMPLICIT_DEPS.apply(ruleContext.getRule(), attrDef)
+ // We can't identify host deps by calling BuildConfiguration.isHostConfiguration()
+ // because --nodistinct_host_configuration subverts that call.
+ || attrDef.getTransitionFactory().isHost()) {
+ continue;
+ }
+ }
+
+ Set<Label> selectOnlyDepsForThisAttribute =
+ getDepsOnlyInSelects(ruleContext, attr, attributes.getAttributeType(attr));
+ for (TransitiveInfoCollection dep :
+ ruleContext.getPrerequisites(attr, TransitionMode.DONT_CHECK)) {
+ // Output files inherit the environment spec of their generating rule.
+ if (dep instanceof OutputFileConfiguredTarget) {
+ // Note this reassignment means constraint violation errors reference the generating
+ // rule, not the file. This makes the source of the environmental mismatch more clear.
+ dep = ((OutputFileConfiguredTarget) dep).getGeneratingRule();
+ }
+ // Input files don't support environments. We may subsequently opt them into constraint
+ // checking, but for now just pass them by.
+ if (dep.getProvider(SupportedEnvironmentsProvider.class) != null) {
+ depsToCheck.add(dep);
+ // For normal configured targets the target's label is the same label appearing in the
+ // select(). But for AliasConfiguredTargets the label in the select() refers to the alias,
+ // while dep.getLabel() refers to the target the alias points to. So add this quick check
+ // to make sure we're comparing the same labels.
+ Label depLabelInSelect =
+ (dep instanceof ConfiguredTarget)
+ ? ((ConfiguredTarget) dep).getOriginalLabel()
+ : dep.getLabel();
+ if (!selectOnlyDepsForThisAttribute.contains(depLabelInSelect)) {
+ depsOutsideSelects.add(dep);
+ }
+ }
+ }
+ }
+
+ for (TransitiveInfoCollection dep : depsToCheck) {
+ if (!depsOutsideSelects.contains(dep)) {
+ selectOnlyDeps.add(dep);
+ }
+ }
+
+ return new DepsToCheck(depsToCheck, selectOnlyDeps);
+ }
+
+ /**
+ * Returns the deps for this attribute that only appear in selects.
+ *
+ * <p>For example:
+ * <pre>
+ * deps = [":a"] + select({"//foo:cond": [":b"]}) + select({"//conditions:default": [":c"]})
+ * </pre>
+ *
+ * returns {@code [":b"]}. Even though {@code [":c"]} also appears in a select, that's a
+ * degenerate case with only one always-chosen condition. So that's considered the same as
+ * an unconditional dep.
+ *
+ * <p>Note that just because a dep only appears in selects for this attribute doesn't mean it
+ * won't appear unconditionally in another attribute.
+ */
+ private static Set<Label> getDepsOnlyInSelects(RuleContext ruleContext, String attr,
+ Type<?> attrType) {
+ Rule rule = ruleContext.getRule();
+ if (!rule.isConfigurableAttribute(attr) || !BuildType.isLabelType(attrType)) {
+ return ImmutableSet.of();
+ }
+ Set<Label> unconditionalDeps = new LinkedHashSet<>();
+ Set<Label> selectableDeps = new LinkedHashSet<>();
+ BuildType.SelectorList<?> selectList = (BuildType.SelectorList<?>)
+ RawAttributeMapper.of(rule).getRawAttributeValue(rule, attr);
+ for (BuildType.Selector<?> select : selectList.getSelectors()) {
+ addSelectValuesToSet(select, select.isUnconditional() ? unconditionalDeps : selectableDeps);
+ }
+ return Sets.difference(selectableDeps, unconditionalDeps);
+ }
+
+ /**
+ * Adds all label values from the given select to the given set. Automatically handles different
+ * value types (e.g. labels vs. label lists).
+ */
+ private static void addSelectValuesToSet(BuildType.Selector<?> select, final Set<Label> set) {
+ Type<?> type = select.getOriginalType();
+ LabelVisitor<?> visitor = (label, dummy) -> set.add(label);
+ for (Object value : select.getEntries().values()) {
+ type.visitLabels(visitor, value, /*context=*/ null);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java
index 11fec91..bbeffe7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java
@@ -226,15 +226,17 @@
}
SupportedEnvironmentsProvider provider =
Verify.verifyNotNull(asProvider.getProvider(SupportedEnvironmentsProvider.class));
- return ConstraintSemantics
- .getUnsupportedEnvironments(provider.getRefinedEnvironments(), expectedEnvironments)
+ return RuleContextConstraintSemantics.getUnsupportedEnvironments(
+ provider.getRefinedEnvironments(), expectedEnvironments)
.stream()
// We apply this filter because the target might also not support default environments in
// other environment groups. We don't care about those. We only care about the environments
// explicitly referenced.
.filter(Predicates.in(expectedEnvironmentLabels))
- .map(environment ->
- new MissingEnvironment(environment, provider.getRemovedEnvironmentCulprit(environment)))
+ .map(
+ environment ->
+ new MissingEnvironment(
+ environment, provider.getRemovedEnvironmentCulprit(environment)))
.collect(Collectors.toSet());
}
@@ -271,7 +273,7 @@
msg.add(" "); // Pretty-format for clarity.
}
msg.add(
- ConstraintSemantics.getMissingEnvironmentCulpritMessage(
+ RuleContextConstraintSemantics.getMissingEnvironmentCulpritMessage(
missingEnvironment.environment, missingEnvironment.culprit));
lastEntryWasMultiline = true;
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index f234aeb..c8171c5 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -759,8 +759,8 @@
*
* <p>Most attributes are enforced by default, so in the common case this call is unnecessary.
*
- * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies}
- * for enforcement policy details.
+ * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for
+ * enforcement policy details.
*/
public Builder<TYPE> checkConstraints() {
Verify.verify(!propertyFlags.contains(PropertyFlag.SKIP_CONSTRAINTS_OVERRIDE),
@@ -772,8 +772,8 @@
* Skips constraint checking on this attribute even if default enforcement policy would check
* it. If default policy skips the attribute, this is a no-op.
*
- * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies}
- * for enforcement policy details.
+ * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for
+ * enforcement policy details.
*/
public Builder<TYPE> dontCheckConstraints() {
Verify.verify(!propertyFlags.contains(PropertyFlag.CHECK_CONSTRAINTS_OVERRIDE),
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/constraints/AbstractConstraintsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/constraints/AbstractConstraintsTest.java
index b09c573..da1f5e4 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/constraints/AbstractConstraintsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/constraints/AbstractConstraintsTest.java
@@ -19,7 +19,6 @@
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
-
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -150,7 +149,9 @@
*/
protected Collection<Label> supportedEnvironments(String ruleName, String ruleDef)
throws Exception {
- return (new ConstraintSemantics()).getSupportedEnvironments(
- getRuleContext(scratchConfiguredTarget("hello", ruleName, ruleDef))).getEnvironments();
+ return (new RuleContextConstraintSemantics())
+ .getSupportedEnvironments(
+ getRuleContext(scratchConfiguredTarget("hello", ruleName, ruleDef)))
+ .getEnvironments();
}
}