Provide clearer messaging when a build fails because a rule's
supported environments get refined away by incompatible
select paths.

This is a fix of TODO:   
https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java#L597

Old message:
  ERROR: /workspace/foo/BUILD:3:1: in cc_binary rule //foo:main: all environments have been refined out of the following groups: //buildenv:environment_group

New message:
  ERROR: /workspace/foo/BUILD:3:1: in cc_binary rule //foo:main: the current command-line flags disqualify all supported environments because of incompatible select() paths:
 environment: //buildenv:gce removed by: //util/lib:some_dep_with_select (/workspace/util/lib/BUILD:12:1)

--
MOS_MIGRATED_REVID=125788804
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 17617e7..eb9822f 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
@@ -24,10 +24,12 @@
 import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection;
 import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironments;
 import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider;
 import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
@@ -180,9 +182,12 @@
         ConstraintSemantics.getSupportedEnvironments(ruleContext);
     if (supportedEnvironments != null) {
       EnvironmentCollection.Builder refinedEnvironments = new EnvironmentCollection.Builder();
-      ConstraintSemantics.checkConstraints(ruleContext, supportedEnvironments, refinedEnvironments);
+      Map<Label, Target> removedEnvironmentCulprits = new LinkedHashMap<>();
+      ConstraintSemantics.checkConstraints(ruleContext, supportedEnvironments, refinedEnvironments,
+          removedEnvironmentCulprits);
       add(SupportedEnvironmentsProvider.class,
-          new SupportedEnvironments(supportedEnvironments, refinedEnvironments.build()));
+          new SupportedEnvironments(supportedEnvironments, refinedEnvironments.build(),
+              removedEnvironmentCulprits));
     }
   }
 
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 398056c..cab7c42 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
@@ -511,9 +511,13 @@
    *     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.
    */
   public static void checkConstraints(RuleContext ruleContext,
-      EnvironmentCollection staticEnvironments, EnvironmentCollection.Builder refinedEnvironments) {
+      EnvironmentCollection staticEnvironments, EnvironmentCollection.Builder refinedEnvironments,
+      Map<Label, Target> removedEnvironmentCulprits) {
     Set<EnvironmentWithGroup> refinedEnvironmentsSoFar = new LinkedHashSet<>();
     // Start with the full set of static environments:
     refinedEnvironmentsSoFar.addAll(staticEnvironments.getGroupedEnvironments());
@@ -565,11 +569,13 @@
         }
         refinedEnvironmentsSoFar.remove(envToPrune);
         groupsWithEnvironmentsRemoved.add(envToPrune.group());
+        removedEnvironmentCulprits.put(envToPrune.environment(),
+            findOriginalRefiner(ruleContext, depEnvironments, envToPrune));
       }
     }
 
     checkRefinedEnvironmentConstraints(ruleContext, groupsWithEnvironmentsRemoved,
-        refinedEnvironmentsSoFar, refinedEnvironments);
+        refinedEnvironmentsSoFar, refinedEnvironments, removedEnvironmentCulprits);
   }
 
   /**
@@ -582,9 +588,11 @@
    * <p>Violations of this expectation trigger rule analysis errors.
    */
   private static void checkRefinedEnvironmentConstraints(
-      RuleContext ruleContext, Set<EnvironmentGroup> groupsWithEnvironmentsRemoved,
+      RuleContext ruleContext,
+      Set<EnvironmentGroup> groupsWithEnvironmentsRemoved,
       Set<EnvironmentWithGroup> refinedEnvironmentsSoFar,
-      EnvironmentCollection.Builder refinedEnvironments) {
+      EnvironmentCollection.Builder refinedEnvironments,
+      Map<Label, Target> removedEnvironmentCulprits) {
     Set<EnvironmentGroup> refinedGroups = new LinkedHashSet<>();
     for (EnvironmentWithGroup envWithGroup : refinedEnvironmentsSoFar) {
       refinedEnvironments.put(envWithGroup.group(), envWithGroup.environment());
@@ -594,17 +602,54 @@
         ? ImmutableSet.<EnvironmentGroup>of()
         : Sets.difference(groupsWithEnvironmentsRemoved, refinedGroups);
     if (!newlyEmptyGroups.isEmpty()) {
-      // TODO(bazel-team): specify exactly which deps violated expectations.
-      Set<Label> groupsAsLabels = new LinkedHashSet<>();
-      for (EnvironmentGroup group : newlyEmptyGroups) {
-        groupsAsLabels.add(group.getLabel());
-      }
-      ruleContext.ruleError("all environments have been refined out of the following groups: "
-          + Joiner.on(", ").join(groupsAsLabels));
+      ruleContext.ruleError(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<EnvironmentGroup> newlyEmptyGroups,
+      Map<Label, Target> removedEnvironmentCulprits) {
+    StringBuilder message = new StringBuilder("the current command-line flags disqualify "
+        + "all supported environments because of incompatible select() paths:");
+    for (EnvironmentGroup group : newlyEmptyGroups) {
+      if (newlyEmptyGroups.size() > 1) {
+        message.append("\n\nenvironment group: " + group.getLabel() + ":");
+      }
+      for (Label prunedEnvironment : group.getEnvironments()) {
+        Target culprit = removedEnvironmentCulprits.get(prunedEnvironment);
+        if (culprit != null) { // Only environments this rule declared support for have culprits.
+          message.append("\n environment: " + prunedEnvironment
+              + " removed by: " + culprit.getLabel() + " (" + culprit.getLocation() + ")");
+        }
+      }
+    }
+    return message.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 Target findOriginalRefiner(RuleContext ruleContext,
+      SupportedEnvironmentsProvider dep, EnvironmentWithGroup envToPrune) {
+    Target depCulprit = dep.getRemovedEnvironmentCulprit(envToPrune.environment());
+    // If the dep has no record of this environment being refined, that means the current rule
+    // is the culprit.
+    return depCulprit == null ? ruleContext.getTarget() : depCulprit;
+  }
+
+  /**
    * Finds the given environment in the given set and returns the default environments for its
    * group.
    */
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 52e10c8..dfdc7e4 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
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.analysis.constraints;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FileProvider;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
@@ -23,6 +24,7 @@
 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;
+import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
 
 /**
@@ -47,7 +49,8 @@
 
     EnvironmentCollection env = new EnvironmentCollection.Builder().put(group, label).build();
     return new RuleConfiguredTargetBuilder(ruleContext)
-        .addProvider(SupportedEnvironmentsProvider.class, new SupportedEnvironments(env, env))
+        .addProvider(SupportedEnvironmentsProvider.class,
+            new SupportedEnvironments(env, env, ImmutableMap.<Label, Target>of()))
         .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
         .add(FileProvider.class, FileProvider.EMPTY)
         .add(FilesToRunProvider.class, FilesToRunProvider.EMPTY)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
index 681bea0..f1fcae1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
@@ -14,17 +14,25 @@
 
 package com.google.devtools.build.lib.analysis.constraints;
 
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Target;
+
+import java.util.Map;
+
 /**
  * Standard {@link SupportedEnvironmentsProvider} implementation.
  */
 public class SupportedEnvironments implements SupportedEnvironmentsProvider {
   private final EnvironmentCollection staticEnvironments;
   private final EnvironmentCollection refinedEnvironments;
+  private final ImmutableMap<Label, Target> removedEnvironmentCulprits;
 
   public SupportedEnvironments(EnvironmentCollection staticEnvironments,
-      EnvironmentCollection refinedEnvironments) {
+      EnvironmentCollection refinedEnvironments, Map<Label, Target> removedEnvironmentCulprits) {
     this.staticEnvironments = staticEnvironments;
     this.refinedEnvironments = refinedEnvironments;
+    this.removedEnvironmentCulprits = ImmutableMap.copyOf(removedEnvironmentCulprits);
   }
 
   @Override
@@ -36,4 +44,9 @@
   public EnvironmentCollection getRefinedEnvironments() {
     return refinedEnvironments;
   }
+
+  @Override
+  public Target getRemovedEnvironmentCulprit(Label environment) {
+    return removedEnvironmentCulprits.get(environment);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
index 3337a44..81909fc 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
@@ -15,6 +15,8 @@
 package com.google.devtools.build.lib.analysis.constraints;
 
 import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Target;
 
 /**
  * A provider that advertises which environments the associated target is compatible with
@@ -39,4 +41,20 @@
    * {@link ConstraintSemantics} for details.
    */
   EnvironmentCollection getRefinedEnvironments();
+
+  /**
+   * If the given environment was refined away from this target's set of supported environments,
+   * returns the dependency that originally removed the environment.
+   *
+   * <p>For example, if the current rule is restricted_to [E] and depends on D1, D1 is
+   * restricted_to [E] and depends on D2, and D2 is restricted_to [E, F] and has a select()
+   * with one path following an E-restricted dep and the other path following an F-restricted dep,
+   * then when the build chooses the F path the current rule has [E] refined to [] and D2 is the
+   * culprit.
+   *
+   * <p>If the given environment was not refined away for this rule, returns null.
+   *
+   * <p>See {@link ConstraintSemantics} class documentation for more details on refinement.
+   */
+  Target getRemovedEnvironmentCulprit(Label environment);
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java
index 721a796..4d4b382 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/constraints/ConstraintsTest.java
@@ -210,7 +210,7 @@
    * environment set is empty.
    */
   @Test
-  public void testDefaultSupportedEnvironments() throws Exception {
+  public void defaultSupportedEnvironments() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     String ruleDef = getDependencyRule();
     assertThat(supportedEnvironments("dep", ruleDef)).isEmpty();
@@ -220,7 +220,7 @@
    * "Constraining" a rule's environments explicitly sets them.
    */
   @Test
-  public void testConstrainedSupportedEnvironments() throws Exception {
+  public void constrainedSupportedEnvironments() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     String ruleDef = getDependencyRule(constrainedTo("//buildenv/foo:c"));
@@ -232,7 +232,7 @@
    * Specifying compatibility adds the specified environments to the defaults.
    */
   @Test
-  public void testCompatibleSupportedEnvironments() throws Exception {
+  public void compatibleSupportedEnvironments() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     String ruleDef = getDependencyRule(compatibleWith("//buildenv/foo:c"));
@@ -244,7 +244,7 @@
    * A rule can't support *no* environments.
    */
   @Test
-  public void testSupportedEnvironmentsConstrainedtoNothing() throws Exception {
+  public void supportedEnvironmentsConstrainedtoNothing() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     reporter.removeHandler(failFastHandler);
     String ruleDef = getDependencyRule(constrainedTo());
@@ -256,7 +256,7 @@
    * Restrict the environments within one group, declare compatibility for another.
    */
   @Test
-  public void testSupportedEnvironmentsInMultipleGroups() throws Exception {
+  public void supportedEnvironmentsInMultipleGroups() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     new EnvironmentGroupMaker("buildenv/bar").setEnvironments("c", "d").setDefaults("c").make();
     String ruleDef = getDependencyRule(
@@ -270,7 +270,7 @@
    * The same label can't appear in both a constraint and a compatibility declaration.
    */
   @Test
-  public void testSameEnvironmentCompatibleAndRestricted() throws Exception {
+  public void sameEnvironmentCompatibleAndRestricted() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     reporter.removeHandler(failFastHandler);
     String ruleDef = getDependencyRule(
@@ -283,7 +283,7 @@
    * Two labels from the same group can't appear in different attributes.
    */
   @Test
-  public void testSameGroupCompatibleAndRestricted() throws Exception {
+  public void sameGroupCompatibleAndRestricted() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     reporter.removeHandler(failFastHandler);
     String ruleDef = getDependencyRule(
@@ -297,7 +297,7 @@
    * Tests that rule class defaults change a rule's default set of environments.
    */
   @Test
-  public void testSupportedEnvironmentsRuleClassDefaults() throws Exception {
+  public void supportedEnvironmentsRuleClassDefaults() throws Exception {
     writeRuleClassDefaultEnvironments();
     String ruleDef = "rule_class_default(name = 'a')";
     Set<Label> expectedEnvironments = asLabelSet("//buildenv/rule_class_compat:a",
@@ -309,7 +309,7 @@
    * Tests that explicit declarations override rule class defaults.
    */
   @Test
-  public void testExplicitAttributesOverrideRuleClassDefaults() throws Exception {
+  public void explicitAttributesOverrideRuleClassDefaults() throws Exception {
     writeRuleClassDefaultEnvironments();
     String ruleDef = "rule_class_default("
         + "    name = 'a',"
@@ -326,7 +326,7 @@
    * in rule class defaults but not in explicit rule attributes.
    */
   @Test
-  public void testKnownEnvironmentsIncludesThoseFromRuleClassDefaults() throws Exception {
+  public void knownEnvironmentsIncludesThoseFromRuleClassDefaults() throws Exception {
     writeRuleClassDefaultEnvironments();
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     String ruleDef = "rule_class_default("
@@ -344,7 +344,7 @@
    * compatibility rule class defaults.
    */
   @Test
-  public void testSameEnvironmentRuleClassCompatibleAndRestricted() throws Exception {
+  public void sameEnvironmentRuleClassCompatibleAndRestricted() throws Exception {
     writeRuleClassDefaultEnvironments();
     reporter.removeHandler(failFastHandler);
     String ruleDef = "bad_rule_class_default(name = 'a')";
@@ -357,7 +357,7 @@
    * Tests that a dependency is valid if both rules implicitly inherit all default environments.
    */
   @Test
-  public void testAllDefaults() throws Exception {
+  public void allDefaults() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         getDependencyRule(),
@@ -370,7 +370,7 @@
    * Tests that a dependency is valid when both rules explicitly declare the same constraints.
    */
   @Test
-  public void testSameConstraintsDeclaredExplicitly() throws Exception {
+  public void sameConstraintsDeclaredExplicitly() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         getDependencyRule(constrainedTo("//buildenv/foo:b")),
@@ -384,7 +384,7 @@
    * their constraints and the depender supports a subset of the dependency's environments
    */
   @Test
-  public void testValidConstraintsDeclaredExplicitly() throws Exception {
+  public void validConstraintsDeclaredExplicitly() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         getDependencyRule(constrainedTo("//buildenv/foo:a", "//buildenv/foo:b")),
@@ -398,7 +398,7 @@
    * their constraints and the depender supports an environment the dependency doesn't.
    */
   @Test
-  public void testInvalidConstraintsDeclaredExplicitly() throws Exception {
+  public void invalidConstraintsDeclaredExplicitly() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         getDependencyRule(constrainedTo("//buildenv/foo:b")),
@@ -414,7 +414,7 @@
    * defaults.
    */
   @Test
-  public void testSameCompatibilityConstraints() throws Exception {
+  public void sameCompatibilityConstraints() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -429,7 +429,7 @@
    * the depender only adds environments also added by the dependency.
    */
   @Test
-  public void testValidCompatibilityConstraints() throws Exception {
+  public void validCompatibilityConstraints() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -444,7 +444,7 @@
    * the depender adds environments not added by the dependency.
    */
   @Test
-  public void testInvalidCompatibilityConstraints() throws Exception {
+  public void invalidCompatibilityConstraints() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -460,7 +460,7 @@
    * Tests the error message when the dependency is missing multiple expected environments.
    */
   @Test
-  public void testMultipleMissingEnvironments() throws Exception {
+  public void multipleMissingEnvironments() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -476,7 +476,7 @@
    * Tests a valid dependency including environments from different groups.
    */
   @Test
-  public void testValidMultigroupConstraints() throws Exception {
+  public void validMultigroupConstraints() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     new EnvironmentGroupMaker("buildenv/bar").setEnvironments("d", "e", "f").setDefaults("d")
@@ -493,7 +493,7 @@
    * Tests an invalid dependency including environments from different groups.
    */
   @Test
-  public void testInvalidMultigroupConstraints() throws Exception {
+  public void invalidMultigroupConstraints() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     new EnvironmentGroupMaker("buildenv/bar").setEnvironments("d", "e", "f").setDefaults("d")
@@ -513,7 +513,7 @@
    * group, but implicitly supports it because that environment is a default.
    */
   @Test
-  public void testValidConstraintsUnknownEnvironmentToDependency() throws Exception {
+  public void validConstraintsUnknownEnvironmentToDependency() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a", "b")
         .make();
     scratch.file("hello/BUILD",
@@ -528,7 +528,7 @@
    * environment's group and doesn't support it because it isn't a default.
    */
   @Test
-  public void testInvalidConstraintsUnknownEnvironmentToDependency() throws Exception {
+  public void invalidConstraintsUnknownEnvironmentToDependency() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a", "b")
         .make();
     scratch.file("hello/BUILD",
@@ -546,7 +546,7 @@
    * are accounted for in the dependency.
    */
   @Test
-  public void testValidConstraintsUnknownEnvironmentToDependender() throws Exception {
+  public void validConstraintsUnknownEnvironmentToDependender() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -562,7 +562,7 @@
    * isn't accounted for in the dependency.
    */
   @Test
-  public void testInvalidConstraintsUnknownEnvironmentToDependender() throws Exception {
+  public void invalidConstraintsUnknownEnvironmentToDependender() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
     scratch.file("hello/BUILD",
@@ -578,7 +578,7 @@
    * Tests the case where one dependency is valid and another one isn't.
    */
   @Test
-  public void testOneDependencyIsInvalid() throws Exception {
+  public void oneDependencyIsInvalid() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         getRuleDef("sh_library", "bad_dep", constrainedTo("//buildenv/foo:b")),
@@ -593,7 +593,7 @@
   }
 
   @Test
-  public void testConstraintEnforcementDisabled() throws Exception {
+  public void constraintEnforcementDisabled() throws Exception {
     useConfiguration("--experimental_enforce_constraints=0");
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a")
         .make();
@@ -609,7 +609,7 @@
    * be invalid.
    */
   @Test
-  public void testCompatibilityPackageDefaults() throws Exception {
+  public void compatibilityPackageDefaults() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         "package(default_compatible_with = ['//buildenv/foo:b'])",
@@ -623,7 +623,7 @@
    * Tests that a rule's compatibility declaration overrides its package defaults compatibility.
    */
   @Test
-  public void testPackageDefaultsCompatibilityOverride() throws Exception {
+  public void packageDefaultsCompatibilityOverride() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     // We intentionally create an invalid dependency structure vs. a valid one. If we tested on
     // a valid one, this test wouldn't be able to distinguish between rule declarations overriding
@@ -643,7 +643,7 @@
    * be invalid.
    */
   @Test
-  public void testRestrictionPackageDefaults() throws Exception {
+  public void restrictionPackageDefaults() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a", "b")
         .make();
     scratch.file("hello/BUILD",
@@ -658,7 +658,7 @@
    * Tests that a rule's restriction declaration overrides its package defaults restriction.
    */
   @Test
-  public void testPackageDefaultsRestrictionOverride() throws Exception {
+  public void packageDefaultsRestrictionOverride() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     // We intentionally create an invalid dependency structure vs. a valid one. If we tested on
     // a valid one, this test wouldn't be able to distinguish between rule declarations overriding
@@ -680,7 +680,7 @@
    * before being supplied to the rule. See comments in DependencyResolver for more discussion.
    */
   @Test
-  public void testPackageDefaultsDirectlyFillRuleAttributes() throws Exception {
+  public void packageDefaultsDirectlyFillRuleAttributes() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     scratch.file("hello/BUILD",
         "package(default_restricted_to = ['//buildenv/foo:b'])",
@@ -692,7 +692,7 @@
   }
 
   @Test
-  public void testHostDependenciesAreNotChecked() throws Exception {
+  public void hostDependenciesAreNotChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         "sh_binary(name = 'host_tool',",
@@ -710,7 +710,7 @@
   }
 
   @Test
-  public void testHostDependenciesNotCheckedNoDistinctHostConfiguration() throws Exception {
+  public void hostDependenciesNotCheckedNoDistinctHostConfiguration() throws Exception {
     useConfiguration("--nodistinct_host_configuration");
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
@@ -729,7 +729,7 @@
   }
 
   @Test
-  public void testImplicitAndLateBoundDependenciesAreNotChecked() throws Exception {
+  public void implicitAndLateBoundDependenciesAreNotChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         "rule_with_implicit_and_latebound_deps(",
@@ -744,7 +744,7 @@
   }
 
   @Test
-  public void testImplicitDepsWithWhiteListedAttributeAreChecked() throws Exception {
+  public void implicitDepsWithWhiteListedAttributeAreChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make();
     scratch.file("hello/BUILD",
         "rule_with_enforced_implicit_deps(",
@@ -757,7 +757,7 @@
   }
 
   @Test
-  public void testOutputFilesAreChecked() throws Exception {
+  public void outputFilesAreChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     scratch.file("hello/BUILD",
         "genrule(name = 'gen', srcs = [], outs = ['shlib.sh'], cmd = '')",
@@ -773,7 +773,7 @@
   }
 
   @Test
-  public void testConfigSettingRulesAreNotChecked() throws Exception {
+  public void configSettingRulesAreNotChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     scratch.file("hello/BUILD",
         "sh_library(",
@@ -788,7 +788,7 @@
   }
 
   @Test
-  public void testFulfills() throws Exception {
+  public void fulfills() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setFulfills("a", "b")
@@ -802,7 +802,7 @@
   }
 
   @Test
-  public void testFulfillsIsNotSymmetric() throws Exception {
+  public void fulfillsIsNotSymmetric() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setFulfills("a", "b")
@@ -818,7 +818,7 @@
   }
 
   @Test
-  public void testFulfillsIsTransitive() throws Exception {
+  public void fulfillsIsTransitive() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b", "c")
         .setFulfills("a", "b")
@@ -833,7 +833,7 @@
   }
 
   @Test
-  public void testDefaultEnvironmentDirectlyFulfills() throws Exception {
+  public void defaultEnvironmentDirectlyFulfills() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setFulfills("a", "b")
@@ -847,7 +847,7 @@
   }
 
   @Test
-  public void testDefaultEnvironmentIndirectlyFulfills() throws Exception {
+  public void defaultEnvironmentIndirectlyFulfills() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b", "c")
         .setFulfills("a", "b")
@@ -862,7 +862,7 @@
   }
 
   @Test
-  public void testEnvironmentFulfillsExpectedDefault() throws Exception {
+  public void environmentFulfillsExpectedDefault() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setFulfills("a", "b")
@@ -876,7 +876,7 @@
   }
 
   @Test
-  public void testConstraintExemptRulesDontHaveConstraintAttributes() throws Exception {
+  public void constraintExemptRulesDontHaveConstraintAttributes() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setDefaults("a")
@@ -893,7 +893,7 @@
   }
 
   @Test
-  public void testBuildingEnvironmentGroupDirectlyDoesntCrash() throws Exception {
+  public void buildingEnvironmentGroupDirectlyDoesntCrash() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo")
         .setEnvironments("a", "b")
         .setDefaults("a")
@@ -914,7 +914,7 @@
   }
 
   @Test
-  public void testSelectableDepsCanMissEnvironments() throws Exception {
+  public void selectableDepsCanMissEnvironments() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -931,7 +931,7 @@
   }
 
   @Test
-  public void testStaticCheckingOnSelectsTemporarilyDisabled() throws Exception {
+  public void staticCheckingOnSelectsTemporarilyDisabled() throws Exception {
     // TODO(bazel-team): update this test once static checking on selects is implemented. When
     // that happens, the union of all deps in the select must support the environments in the
     // depending rule. So the logic here is constraint-invalid because //buildenv/foo:c isn't
@@ -952,7 +952,7 @@
   }
 
   @Test
-  public void testDepInBothSelectAndUnconditionalListIsAlwaysChecked() throws Exception {
+  public void depInBothSelectAndUnconditionalListIsAlwaysChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -973,7 +973,7 @@
   }
 
   @Test
-  public void testUnconditionalSelectsAlwaysChecked() throws Exception {
+  public void unconditionalSelectsAlwaysChecked() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -991,7 +991,7 @@
   }
 
   @Test
-  public void testRefinedEnvironmentCheckingValidCaseDirect() throws Exception {
+  public void refinedEnvironmentCheckingValidCaseDirect() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -1009,7 +1009,7 @@
   }
 
   @Test
-  public void testRefinedEnvironmentCheckingBadCaseDirect() throws Exception {
+  public void refinedEnvironmentCheckingBadCaseDirect() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -1025,12 +1025,13 @@
     reporter.removeHandler(failFastHandler);
     // Invalid because "--define mode=a" refines :lib to "compatible_with = []" (empty).
     assertNull(getConfiguredTarget("//hello:lib"));
-    assertContainsEvent("//hello:lib: all environments have been refined out of the following"
-        + " groups: //buildenv/foo:foo");
+    assertContainsEvent("//hello:lib: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + " environment: //buildenv/foo:b removed by: //hello:lib (/workspace/hello/BUILD:1:1)");
   }
 
   @Test
-  public void testRefinedEnvironmentCheckingValidCaseTransitive() throws Exception {
+  public void refinedEnvironmentCheckingValidCaseTransitive() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -1053,7 +1054,7 @@
   }
 
   @Test
-  public void testRefinedEnvironmentCheckingBadCaseTransitive() throws Exception {
+  public void refinedEnvironmentCheckingBadCaseTransitive() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -1074,12 +1075,48 @@
     reporter.removeHandler(failFastHandler);
     // Invalid because "--define mode=a" refines :lib to "compatible_with = ['//buildenv/foo:a']".
     assertNull(getConfiguredTarget("//hello:depender"));
-    assertContainsEvent("//hello:depender: all environments have been refined out of the following"
-        + " groups: //buildenv/foo:foo");
+    assertContainsEvent("//hello:depender: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + " environment: //buildenv/foo:b removed by: //hello:lib (/workspace/hello/BUILD:1:1)");
   }
 
   @Test
-  public void testEnvironmentRefiningAccountsForImplicitDefaults() throws Exception {
+  public void refinedEnvironmentCheckingBadCaseChooseLowestLevelCulprit() throws Exception {
+    new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
+    writeDepsForSelectTests();
+    scratch.file("hello/BUILD",
+        "cc_library(",
+        "    name = 'lib2',",  // Even though both lib1 and lib2 refine away b, lib2 is the culprit.
+        "    srcs = [],",
+        "    deps = select({",
+        "        '//config:a': ['//deps:dep_a'],",
+        "        '//config:b': ['//deps:dep_b'],",
+        "    }),",
+        "    compatible_with = ['//buildenv/foo:a', '//buildenv/foo:b'])",
+        "cc_library(",
+        "    name = 'lib1',",
+        "    srcs = [],",
+        "    deps = select({",
+        "        '//config:a': [':lib2'],",
+        "        '//config:b': ['//deps:dep_b'],",
+        "    }),",
+        "    compatible_with = ['//buildenv/foo:a', '//buildenv/foo:b'])",
+        "cc_library(",
+        "    name = 'depender',",
+        "    srcs = [],",
+        "    deps = [':lib1'],",
+        "    compatible_with = ['//buildenv/foo:b'])");
+    useConfiguration("--define", "mode=a");
+    reporter.removeHandler(failFastHandler);
+    // Invalid because "--define mode=a" refines :lib to "compatible_with = ['//buildenv/foo:a']".
+    assertNull(getConfiguredTarget("//hello:depender"));
+    assertContainsEvent("//hello:depender: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + " environment: //buildenv/foo:b removed by: //hello:lib2 (/workspace/hello/BUILD:1:1)");
+  }
+
+  @Test
+  public void environmentRefiningAccountsForImplicitDefaults() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("b").make();
     writeDepsForSelectTests();
     scratch.file("hello/BUILD",
@@ -1095,12 +1132,13 @@
     // Invalid because :lib has an implicit default of ['//buildenv/foo:b'] and "--define mode=a"
     // refines it to "compatible_with = []" (empty).
     assertNull(getConfiguredTarget("//hello:lib"));
-    assertContainsEvent("//hello:lib: all environments have been refined out of the following"
-        + " groups: //buildenv/foo:foo");
+    assertContainsEvent("//hello:lib: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + " environment: //buildenv/foo:b removed by: //hello:lib (/workspace/hello/BUILD:1:1)");
   }
 
   @Test
-  public void testEnvironmentRefiningChecksAllEnvironmentGroups() throws Exception {
+  public void environmentRefiningChecksAllEnvironmentGroups() throws Exception {
     new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
     new EnvironmentGroupMaker("buildenv/bar").setEnvironments("c", "d").setDefaults().make();
     scratch.file("deps/BUILD",
@@ -1126,8 +1164,45 @@
         // Invalid because while the //buildenv/foo refinement successfully refines :lib to
         // ['//buildenv/foo:a'], the bar refinement refines it to [].
         assertNull(getConfiguredTarget("//hello:lib"));
-        assertContainsEvent("//hello:lib: all environments have been refined out of the following"
-            + " groups: //buildenv/bar:bar");
+    assertContainsEvent("//hello:lib: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + " environment: //buildenv/bar:c removed by: //hello:lib (/workspace/hello/BUILD:1:1)");
+  }
+
+  /**
+   * When multiple environment groups get cleared out by refinement, batch the missing environments
+   * by group membership.
+   */
+  @Test
+  public void refinedEnvironmentCheckingPartitionsErrorsbyEnvironmentGroup() throws Exception {
+    new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults().make();
+    new EnvironmentGroupMaker("buildenv/bar").setEnvironments("c", "d").setDefaults().make();
+    scratch.file("hello/BUILD",
+        "cc_library(",
+        "    name = 'all_groups_gone',",
+        "    srcs = [],",
+        "    restricted_to = ['//buildenv/foo:b', '//buildenv/bar:d'])",
+        "cc_library(",
+        "    name = 'all_groups_there',",
+        "    srcs = [],",
+        "    restricted_to = ['//buildenv/foo:a', '//buildenv/bar:c'])",
+        "cc_library(",
+        "    name = 'lib',",
+        "    srcs = [],",
+        "    deps = select({",
+        "        '//config:a': [':all_groups_gone'],",
+        "        '//config:b': [':all_groups_there'],",
+        "    }),",
+        "    compatible_with = ['//buildenv/foo:a', '//buildenv/bar:c'])");
+    useConfiguration("--define", "mode=a");
+    reporter.removeHandler(failFastHandler);
+    assertNull(getConfiguredTarget("//hello:lib"));
+    assertContainsEvent("//hello:lib: the current command-line flags disqualify all supported "
+        + "environments because of incompatible select() paths:\n"
+        + "\nenvironment group: //buildenv/foo:foo:\n"
+        + " environment: //buildenv/foo:a removed by: //hello:lib (/workspace/hello/BUILD:9:1)\n"
+        + "\nenvironment group: //buildenv/bar:bar:\n"
+        + " environment: //buildenv/bar:c removed by: //hello:lib (/workspace/hello/BUILD:9:1)");
   }
 }