Repository: bazelbuild/bazel
Issue: Allow dev_dependency register_toolchains calls
Labels: type: feature request
team-ExternalDeps
untriaged

### Description of the feature request:


Allow `register_toolchains(dev_dependency=True)` so that MODULE files can register toolchains only necessary for development and testing.

The specific case I'm dealing with is documentation generation: generating docs for my code is a dev-time only activity, but it uses tools that use toolchains.

Unfortunately, any `register_toolchains()` call in my module will propagate to consumers. This puts me in a catch-22: if I add register_toolchains(), my users are broken (because it'll try to register toolchains that were never setup). If I don't add it, my docgen tools can't run.

This idea is easily expanded: One might have various tools only necessary for development purposes (lints, test runners, debuggers, type checkers, etc), and it makes sense those tools would rely on some language toolchain. But those toolchains aren't necessary for any users, nor should they depend on them in any way.

cc
@fmeum as he mentioned he proposed this idea in the past
@Wyverald pretty sure he owns bzlmod

### What underlying problem are you trying to solve with this feature?


Making it possible to have dev-time only dependencies that rely on toolchains.

### Which operating system are you running Bazel on?


linux

### What is the output of `bazel info release`?


release 6.1.2

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.


_No response_

### What's the output of `git remote get-url origin; git rev-parse master; git rev-parse HEAD` ?


```text
git@github.com:rickeylev/rules_testing.git
79fd58291d29e22fe92542ca8c35f4af9ee8021a
79fd58291d29e22fe92542ca8c35f4af9ee8021a
```


### Have you found anything relevant by searching the web?

nothing found

### Any other information, logs, or outputs that you want to share?

_No response_

Issue comments:
I'd also like to note this is rather hard to work around.

Fabian suggested creating an extension that creates a "middleman" repo. The extension detects if its the root module, and, if so, populates the middleman with references to the real toolchains. If not, then it puts a fake toolchain in so downstream users inheriting the register_toolchains() call don't break. Implementing this, however, is...well I haven't really figured out a good way, yet. You can't alias `:all`; alias() doesn't work with toolchains; symlinking the underlying build file introduces more complications with repo visibility. Maybe I'm missing something obvious, but it's turning into a headache.

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
index c2800b99f70759..a3d92a8026a583 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
@@ -347,13 +347,27 @@ public void bazelDep(
               + " selected. Should be absolute target patterns (ie. beginning with either"
               + " <code>@</code> or <code>//</code>). See <a href=\"${link toolchains}\">toolchain"
               + " resolution</a> for more information.",
+      parameters = {
+        @Param(
+            name = "dev_dependency",
+            doc =
+                "If true, the execution platforms will not be registered if the current module is"
+                    + " not the root module or `--ignore_dev_dependency` is enabled.",
+            named = true,
+            positional = false,
+            defaultValue = "False"),
+      },
       extraPositionals =
           @Param(
               name = "platform_labels",
               allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
               doc = "The labels of the platforms to register."))
-  public void registerExecutionPlatforms(Sequence<?> platformLabels) throws EvalException {
+  public void registerExecutionPlatforms(boolean devDependency, Sequence<?> platformLabels)
+      throws EvalException {
     hadNonModuleCall = true;
+    if (ignoreDevDeps && devDependency) {
+      return;
+    }
     module.addExecutionPlatformsToRegister(
         checkAllAbsolutePatterns(platformLabels, "register_execution_platforms"));
   }
@@ -365,13 +379,27 @@ public void registerExecutionPlatforms(Sequence<?> platformLabels) throws EvalEx
               + " Should be absolute target patterns (ie. beginning with either <code>@</code> or"
               + " <code>//</code>). See <a href=\"${link toolchains}\">toolchain resolution</a> for"
               + " more information.",
+      parameters = {
+        @Param(
+            name = "dev_dependency",
+            doc =
+                "If true, the toolchains will not be registered if the current module is not the"
+                    + " root module or `--ignore_dev_dependency` is enabled.",
+            named = true,
+            positional = false,
+            defaultValue = "False"),
+      },
       extraPositionals =
           @Param(
               name = "toolchain_labels",
               allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
               doc = "The labels of the toolchains to register."))
-  public void registerToolchains(Sequence<?> toolchainLabels) throws EvalException {
+  public void registerToolchains(boolean devDependency, Sequence<?> toolchainLabels)
+      throws EvalException {
     hadNonModuleCall = true;
+    if (ignoreDevDeps && devDependency) {
+      return;
+    }
     module.addToolchainsToRegister(
         checkAllAbsolutePatterns(toolchainLabels, "register_toolchains"));
   }


Test patch:
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
index f44837d61141d6..cda1434744fe9c 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
@@ -342,6 +342,7 @@ public void testRegisteredExecutionPlatforms_bzlmod() throws Exception {
     scratch.overwriteFile(
         "MODULE.bazel",
         "register_execution_platforms('//:plat')",
+        "register_execution_platforms('//:dev_plat',dev_dependency=True)",
         "bazel_dep(name='bbb',version='1.0')",
         "bazel_dep(name='ccc',version='1.1')");
     registry
@@ -349,11 +350,13 @@ public void testRegisteredExecutionPlatforms_bzlmod() throws Exception {
             createModuleKey("bbb", "1.0"),
             "module(name='bbb',version='1.0')",
             "register_execution_platforms('//:plat')",
+            "register_execution_platforms('//:dev_plat',dev_dependency=True)",
             "bazel_dep(name='ddd',version='1.0')")
         .addModule(
             createModuleKey("ccc", "1.1"),
             "module(name='ccc',version='1.1')",
             "register_execution_platforms('//:plat')",
+            "register_execution_platforms('//:dev_plat',dev_dependency=True)",
             "bazel_dep(name='ddd',version='1.1')")
         // ddd@1.0 is not selected
         .addModule(
@@ -372,7 +375,8 @@ public void testRegisteredExecutionPlatforms_bzlmod() throws Exception {
           moduleRoot.getRelative(repo).getRelative("BUILD").getPathString(),
           "platform(name='plat')");
     }
-    scratch.overwriteFile("BUILD", "platform(name='plat');platform(name='wsplat')");
+    scratch.overwriteFile(
+        "BUILD", "platform(name='plat')", "platform(name='dev_plat')", "platform(name='wsplat')");
     rewriteWorkspace("register_execution_platforms('//:wsplat')");
 
     SkyKey executionPlatformsKey = RegisteredExecutionPlatformsValue.key(targetConfigKey);
@@ -388,6 +392,7 @@ public void testRegisteredExecutionPlatforms_bzlmod() throws Exception {
     assertExecutionPlatformLabels(result.get(executionPlatformsKey))
         .containsExactly(
             Label.parseCanonical("//:plat"),
+            Label.parseCanonical("//:dev_plat"),
             Label.parseCanonical("@@bbb~1.0//:plat"),
             Label.parseCanonical("@@ccc~1.1//:plat"),
             Label.parseCanonical("@@eee~1.0//:plat"),
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
index 5adfeb0b2f4660..2374e2c62126cb 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
@@ -307,6 +307,7 @@ public void testRegisteredToolchains_bzlmod() throws Exception {
     scratch.overwriteFile(
         "MODULE.bazel",
         "register_toolchains('//:tool')",
+        "register_toolchains('//:dev_tool',dev_dependency=True)",
         "bazel_dep(name='bbb',version='1.0')",
         "bazel_dep(name='ccc',version='1.1')",
         "bazel_dep(name='toolchain_def',version='1.0')");
@@ -315,12 +316,14 @@ public void testRegisteredToolchains_bzlmod() throws Exception {
             createModuleKey("bbb", "1.0"),
             "module(name='bbb',version='1.0')",
             "register_toolchains('//:tool')",
+            "register_toolchains('//:dev_tool',dev_dependency=True)",
             "bazel_dep(name='ddd',version='1.0')",
             "bazel_dep(name='toolchain_def',version='1.0')")
         .addModule(
             createModuleKey("ccc", "1.1"),
             "module(name='ccc',version='1.1')",
             "register_toolchains('//:tool')",
+            "register_toolchains('//:dev_tool',dev_dependency=True)",
             "bazel_dep(name='ddd',version='1.1')",
             "bazel_dep(name='toolchain_def',version='1.0')")
         // ddd@1.0 is not selected
@@ -328,11 +331,13 @@ public void testRegisteredToolchains_bzlmod() throws Exception {
             createModuleKey("ddd", "1.0"),
             "module(name='ddd',version='1.0')",
             "register_toolchains('//:tool')",
+            "register_toolchains('//:dev_tool',dev_dependency=True)",
             "bazel_dep(name='toolchain_def',version='1.0')")
         .addModule(
             createModuleKey("ddd", "1.1"),
             "module(name='ddd',version='1.1')",
             "register_toolchains('@eee//:tool', '//:tool')",
+            "register_toolchains('@eee//:dev_tool',dev_dependency=True)",
             "bazel_dep(name='eee',version='1.0')",
             "bazel_dep(name='toolchain_def',version='1.0')")
         .addModule(
@@ -369,11 +374,13 @@ public void testRegisteredToolchains_bzlmod() throws Exception {
       scratch.file(
           moduleRoot.getRelative(repo).getRelative("BUILD").getPathString(),
           "load('@toolchain_def//:toolchain_def.bzl', 'declare_toolchain')",
-          "declare_toolchain(name='tool')");
+          "declare_toolchain(name='tool')",
+          "declare_toolchain(name='dev_tool')");
     }
     scratch.overwriteFile(
         "BUILD",
         "load('@toolchain_def//:toolchain_def.bzl', 'declare_toolchain')",
+        "declare_toolchain(name='dev_tool')",
         "declare_toolchain(name='tool')",
         "declare_toolchain(name='wstool')");
     rewriteWorkspace("register_toolchains('//:wstool')");
@@ -391,6 +398,7 @@ public void testRegisteredToolchains_bzlmod() throws Exception {
     assertToolchainLabels(result.get(toolchainsKey))
         .containsAtLeast(
             Label.parseCanonical("//:tool_impl"),
+            Label.parseCanonical("//:dev_tool_impl"),
             Label.parseCanonical("@@bbb~1.0//:tool_impl"),
             Label.parseCanonical("@@ccc~1.1//:tool_impl"),
             Label.parseCanonical("@@eee~1.0//:tool_impl"),