Add --toolchain_resolution_debug option to give more information about
toolchain selection.

Fixes #3431.

Change-Id: Ia38415575b6a121cbb6a028bfc0276691cd11b6d
PiperOrigin-RevId: 163196646
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
index 438d6f2..3376e96 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
@@ -83,6 +83,17 @@
   )
   public List<ToolchainResolutionOverride> toolchainResolutionOverrides;
 
+  @Option(
+    name = "toolchain_resolution_debug",
+    defaultValue = "false",
+    documentationCategory = OptionDocumentationCategory.LOGGING,
+    effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+    help =
+        "Print debug information while finding toolchains for a rule. This might help developers "
+            + "of Bazel or Skylark rules with debugging failures due to missing toolchains."
+  )
+  public boolean toolchainResolutionDebug;
+
   @Override
   public PlatformOptions getHost(boolean fallback) {
     PlatformOptions host = (PlatformOptions) getDefault();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
index 28a6ebe..410578f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunction.java
@@ -17,11 +17,14 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.PlatformConfiguration;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
 import com.google.devtools.build.lib.analysis.platform.DeclaredToolchainInfo;
 import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
 import com.google.devtools.build.lib.skyframe.RegisteredToolchainsFunction.InvalidTargetException;
@@ -74,12 +77,14 @@
     }
 
     // Find the right one.
+    boolean debug = configuration.getOptions().get(PlatformOptions.class).toolchainResolutionDebug;
     DeclaredToolchainInfo toolchain =
         resolveConstraints(
             key.toolchainType(),
             key.execPlatform(),
             key.targetPlatform(),
-            toolchains.registeredToolchains());
+            toolchains.registeredToolchains(),
+            debug ? env.getListener() : null);
 
     if (toolchain == null) {
       throw new ToolchainResolutionFunctionException(
@@ -93,35 +98,75 @@
       Label toolchainType,
       PlatformInfo execPlatform,
       PlatformInfo targetPlatform,
-      ImmutableList<DeclaredToolchainInfo> toolchains) {
+      ImmutableList<DeclaredToolchainInfo> toolchains,
+      @Nullable EventHandler eventHandler) {
+
+    debugMessage(eventHandler, "Looking for toolchain of type %s...", toolchainType);
     for (DeclaredToolchainInfo toolchain : toolchains) {
       // Make sure the type matches.
       if (!toolchain.toolchainType().equals(toolchainType)) {
         continue;
       }
-      if (!checkConstraints(toolchain.execConstraints(), execPlatform)) {
+      debugMessage(eventHandler, "  Considering toolchain %s...", toolchain.toolchainLabel());
+      if (!checkConstraints(eventHandler, toolchain.execConstraints(), "execution", execPlatform)) {
+        debugMessage(
+            eventHandler,
+            "  Rejected toolchain %s, because of execution platform mismatch",
+            toolchain.toolchainLabel());
         continue;
       }
-      if (!checkConstraints(toolchain.targetConstraints(), targetPlatform)) {
+      if (!checkConstraints(
+          eventHandler, toolchain.targetConstraints(), "target", targetPlatform)) {
+        debugMessage(
+            eventHandler,
+            "  Rejected toolchain %s, because of target platform mismatch",
+            toolchain.toolchainLabel());
         continue;
       }
 
+      debugMessage(eventHandler, "  Selected toolchain %s", toolchain.toolchainLabel());
       return toolchain;
     }
 
+    debugMessage(eventHandler, "  No toolchain found");
     return null;
   }
 
   /**
+   * Helper method to print a debugging message, if the given {@link EventHandler} is not {@code
+   * null}.
+   */
+  private static void debugMessage(
+      @Nullable EventHandler eventHandler, String template, Object... args) {
+    if (eventHandler == null) {
+      return;
+    }
+
+    eventHandler.handle(Event.info("ToolchainResolution: " + String.format(template, args)));
+  }
+
+  /**
    * Returns {@code true} iff all constraints set by the toolchain are present in the {@link
    * PlatformInfo}.
    */
   private static boolean checkConstraints(
-      Iterable<ConstraintValueInfo> toolchainConstraints, PlatformInfo platform) {
+      @Nullable EventHandler eventHandler,
+      Iterable<ConstraintValueInfo> toolchainConstraints,
+      String platformType,
+      PlatformInfo platform) {
 
     for (ConstraintValueInfo constraint : toolchainConstraints) {
       ConstraintValueInfo found = platform.getConstraint(constraint.constraint());
       if (!constraint.equals(found)) {
+        debugMessage(
+            eventHandler,
+            "    Toolchain constraint %s has value %s, "
+                + "which does not match value %s from the %s platform %s",
+            constraint.constraint().label(),
+            constraint.label(),
+            found != null ? found.label() : "<missing>",
+            platformType,
+            platform.label());
         return false;
       }
     }
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
index e8b1f63..f0bc14a5 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
@@ -368,11 +368,6 @@
         public boolean apply(Event event) {
           switch (event.getKind()) {
             case INFO:
-              throw new UnsupportedOperationException(
-                  "SkyFunctions should not display INFO messages: "
-                      + event.getLocation()
-                      + ": "
-                      + event.getMessage());
             case PROGRESS:
               return false;
             default:
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
index b98d572..d1b17bb 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ToolchainResolutionFunctionTest.java
@@ -203,7 +203,7 @@
 
     DeclaredToolchainInfo resolvedToolchain =
         ToolchainResolutionFunction.resolveConstraints(
-            toolchainType, execPlatform, targetPlatform, toolchains);
+            toolchainType, execPlatform, targetPlatform, toolchains, null);
     return assertThat(resolvedToolchain);
   }
 
diff --git a/src/test/shell/bazel/toolchain_test.sh b/src/test/shell/bazel/toolchain_test.sh
index 62510c6..6490bf2 100755
--- a/src/test/shell/bazel/toolchain_test.sh
+++ b/src/test/shell/bazel/toolchain_test.sh
@@ -182,6 +182,29 @@
   expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 1"'
 }
 
+function test_toolchain_debug_messages {
+  write_test_toolchain
+  write_test_rule
+  write_toolchains
+
+  mkdir -p demo
+  cat >> demo/BUILD <<EOF
+load('//toolchain:rule.bzl', 'use_toolchain')
+# Use the toolchain.
+use_toolchain(
+    name = 'use',
+    message = 'this is the rule')
+EOF
+
+  bazel build \
+    --toolchain_resolution_debug \
+    //demo:use &> $TEST_log || fail "Build failed"
+  expect_log 'ToolchainResolution: Looking for toolchain of type //toolchain:test_toolchain'
+  expect_log 'ToolchainResolution:   Selected toolchain //:toolchain_impl_1'
+  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 1"'
+}
+
+
 function test_toolchain_use_in_aspect {
   write_test_toolchain
   write_test_aspect