Repository: bazelbuild/bazel
Issue: --enable_bzlmod breaks toolchain resolution for toolchains registered in WORKSPACE
Labels: type: bug
P2
team-ExternalDeps
area-Bzlmod

### Description of the bug:


Setting the `--enable_bzlmod` flag (even with an empty `MODULE.bazel` file) breaks toolchain resolution for toolchains registered in the WORKSPACE file.

### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.


https://github.com/gregmagolan/repro-enable_bzlmod_breaks_workspace

### Which operating system are you running Bazel on?


MacOS

### What is the output of `bazel info release`?


```
release 6.0.0
```

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.



### What's the output of `git remote get-url origin; git rev-parse master; git rev-parse HEAD` ?

https://github.com/gregmagolan/repro-enable_bzlmod_breaks_workspace


### Have you found anything relevant by searching the web?

Searched through Bazel issues and didn't see any duplicates.

### Any other information, logs, or outputs that you want to share?


Issue comments:
This feels to be Working As Intended, since modules specifically have a different way to register toolchains, but I'll leave it for @Wyverald to clarify.
I passed this by @Wyverald on Bazel Slack and he asked to create an issue and cc him. Was just trying to find his GitHub handle :)

If WORKSPACE files can't register toolchains when bzlmod is enabled then it will be hard to use rulesets that have bzlmod support alongside rulesets that don't (while they all get up to speed) if they both register toolchains.
Thank you for contributing to the Bazel repository! This issue has been marked as stale since it has not had any activity in the last 30 days. It will be closed in the next 7 days unless any other activity occurs or one of the following labels is added: "not stale", "awaiting-bazeler".
Not stale. 
Ah, I think I figured out what's wrong here. It's that damn `platforms` repo again...

When you don't enable Bzlmod, `@platforms` for everyone is the `@@platforms` repo defined in a WORKSPACE prefix/suffix (don't remember which). All good.

When you enable Bzlmod, `@platforms` is still the `@@platforms` repo defined in a WORKSPACE prefix/suffix for everything that is *not* in the Bzlmod system (i.e. all the WORKSPACE repos including `@@rules_nodejs`). However, for things in the Bzlmod system (i.e. only built-in modules like `bazel_tools` and `local_config_platform` because your MODULE.bazel file is empty), `@platforms` actually refers to `@@platforms~0.0.5`. This is important because when we search for an appropriate execution platform (most likely your host machine), `@local_config_platform//:host` actually uses the platforms defined in `@@platforms~0.0.5`, but `rules_nodejs` defines toolchains for platforms defined in `@@platforms`.

The immediate workaround would be to add a single line in your MODULE.bazel file (`bazel_dep(name="platforms", version="0.0.5")`). I'll have to ponder how we could resolve this situation better, if it's even possible.
Closing this as things are technically working as intended here. Hopefully the existence of this issue will serve as documentation for whoever's encountering this problem in the future.
> Closing this as things are technically working as intended here. Hopefully the existence of this issue will serve as documentation for whoever's encountering this problem in the future.

Uhm, the module system not resolving a dependency, and the user having to manually depend on a specific version of a deep dependency is "working as intended"?

In my view this completely invalidates bzlmod.

It might be working "as designed", but definitely not "as intended".
Agreed, letting the user add a dependency to make it work isn't really a solution to me.
To anyone that don't want to read the whole thing: 
Add to `MODULE.bazel`:
```
bazel_dep(name="platforms", version="0.0.5")
```
if anyone has an idea how to fix this, please leave a suggestion.
Maybe making `platforms` v0.0.5 the default on Bazel 7 would be a solution ?

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
index 50a24292c869f5..5ae1c263366aba 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
@@ -39,7 +39,14 @@ public abstract class ModuleKey {
           "bazel_tools",
           RepositoryName.BAZEL_TOOLS,
           "local_config_platform",
-          RepositoryName.createUnvalidated("local_config_platform"));
+          RepositoryName.createUnvalidated("local_config_platform"),
+          // Ensures that references to "@platforms" in WORKSPACE files resolve to the repository of
+          // the "platforms" module. Without this, constraints on toolchains registered in WORKSPACE
+          // would reference the "platforms" repository defined in the WORKSPACE suffix, whereas
+          // the host constraints generated by local_config_platform would reference the "platforms"
+          // module repository, resulting in a toolchain resolution mismatch.
+          "platforms",
+          RepositoryName.createUnvalidated("platforms"));
 
   public static final ModuleKey ROOT = create("", Version.EMPTY);
 


Test patch:
diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py
index 941823f11fb680..250006526da668 100644
--- a/src/test/py/bazel/bzlmod/bazel_module_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_module_test.py
@@ -557,6 +557,84 @@ def testNativeModuleNameAndVersion(self):
     self.assertIn('@@bar~override reporting in: bar@2.0', stderr)
     self.assertIn('@@quux reporting in: None@None', stderr)
 
+  def testWorkspaceToolchainRegistrationWithPlatformsConstraint(self):
+    """Regression test for https://github.com/bazelbuild/bazel/issues/17289."""
+    self.ScratchFile('MODULE.bazel')
+    self.ScratchFile(
+        'WORKSPACE', ['register_toolchains("//:my_toolchain_toolchain")']
+    )
+    os.remove(self.Path('WORKSPACE.bzlmod'))
+
+    self.ScratchFile(
+        'BUILD.bazel',
+        [
+            'load(":defs.bzl", "get_host_os", "my_consumer", "my_toolchain")',
+            'toolchain_type(name = "my_toolchain_type")',
+            'my_toolchain(',
+            '    name = "my_toolchain",',
+            '    my_value = "Hello, Bzlmod!",',
+            ')',
+            'toolchain(',
+            '    name = "my_toolchain_toolchain",',
+            '    toolchain = ":my_toolchain",',
+            '    toolchain_type = ":my_toolchain_type",',
+            '    target_compatible_with = [',
+            '        "@platforms//os:" + get_host_os(),',
+            '    ],',
+            ')',
+            'my_consumer(',
+            '    name = "my_consumer",',
+            ')',
+        ],
+    )
+
+    self.ScratchFile(
+        'defs.bzl',
+        [
+            (
+                'load("@local_config_platform//:constraints.bzl",'
+                ' "HOST_CONSTRAINTS")'
+            ),
+            'def _my_toolchain_impl(ctx):',
+            '    return [',
+            '        platform_common.ToolchainInfo(',
+            '            my_value = ctx.attr.my_value,',
+            '        ),',
+            '    ]',
+            'my_toolchain = rule(',
+            '    implementation = _my_toolchain_impl,',
+            '    attrs = {',
+            '        "my_value": attr.string(),',
+            '    },',
+            ')',
+            'def _my_consumer(ctx):',
+            '    my_toolchain_info = ctx.toolchains["//:my_toolchain_type"]',
+            '    out = ctx.actions.declare_file(ctx.attr.name)',
+            (
+                '    ctx.actions.write(out, "my_value ='
+                ' {}".format(my_toolchain_info.my_value))'
+            ),
+            '    return [DefaultInfo(files = depset([out]))]',
+            'my_consumer = rule(',
+            '    implementation = _my_consumer,',
+            '    attrs = {},',
+            '    toolchains = ["//:my_toolchain_type"],',
+            ')',
+            'def get_host_os():',
+            '    for constraint in HOST_CONSTRAINTS:',
+            '        if constraint.startswith("@platforms//os:"):',
+            '            return constraint.removeprefix("@platforms//os:")',
+        ],
+    )
+
+    self.RunBazel([
+        'build',
+        '//:my_consumer',
+        '--toolchain_resolution_debug=//:my_toolchain_type',
+    ])
+    with open(self.Path('bazel-bin/my_consumer'), 'r') as f:
+      self.assertEqual(f.read().strip(), 'my_value = Hello, Bzlmod!')
+
 
 if __name__ == '__main__':
   unittest.main()