Add aliases attribute to rust tag class. (#2975)

The aliases tag attribute can be used to rename the toolchain
repositories created by the rust module
extension. Additionally, the rust module extension has been marked
reproducible, meaning we will no longer store toolchain repos in the
lock file.
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 8fb800f..64bbd53 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -635,6 +635,12 @@
     working_directory: test/bzlmod_repo_mapping/module_a
     test_targets:
       - "//..."
+  aliased_toolchains:
+    name: aliased toolchains test
+    platform: ubuntu2004
+    working_directory: test/aliased_toolchains
+    build_targets:
+      - "@rust_toolchains//:all"
   android_examples_ubuntu2004:
     name: Android Examples
     platform: ubuntu2004
diff --git a/.bazelignore b/.bazelignore
index 8aaeb6a..4d6f747 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -2,6 +2,7 @@
 docs
 examples
 crate_universe/private/bootstrap
+test/aliased_toolchains
 test/bzlmod_repo_mapping
 test/cc_common_link
 test/no_std
diff --git a/docs/src/flatten.md b/docs/src/flatten.md
index 5eb86e2..c0da9ea 100644
--- a/docs/src/flatten.md
+++ b/docs/src/flatten.md
@@ -1841,7 +1841,7 @@
 rust_register_toolchains(<a href="#rust_register_toolchains-dev_components">dev_components</a>, <a href="#rust_register_toolchains-edition">edition</a>, <a href="#rust_register_toolchains-allocator_library">allocator_library</a>, <a href="#rust_register_toolchains-global_allocator_library">global_allocator_library</a>,
                          <a href="#rust_register_toolchains-register_toolchains">register_toolchains</a>, <a href="#rust_register_toolchains-rustfmt_version">rustfmt_version</a>, <a href="#rust_register_toolchains-rust_analyzer_version">rust_analyzer_version</a>, <a href="#rust_register_toolchains-sha256s">sha256s</a>,
                          <a href="#rust_register_toolchains-extra_target_triples">extra_target_triples</a>, <a href="#rust_register_toolchains-extra_rustc_flags">extra_rustc_flags</a>, <a href="#rust_register_toolchains-extra_exec_rustc_flags">extra_exec_rustc_flags</a>, <a href="#rust_register_toolchains-urls">urls</a>,
-                         <a href="#rust_register_toolchains-versions">versions</a>)
+                         <a href="#rust_register_toolchains-versions">versions</a>, <a href="#rust_register_toolchains-aliases">aliases</a>)
 </pre>
 
 Emits a default set of toolchains for Linux, MacOS, and Freebsd
@@ -1878,7 +1878,8 @@
 | <a id="rust_register_toolchains-extra_rustc_flags"></a>extra_rustc_flags |  Dictionary of target triples to list of extra flags to pass to rustc in non-exec configuration.   |  `None` |
 | <a id="rust_register_toolchains-extra_exec_rustc_flags"></a>extra_exec_rustc_flags |  Extra flags to pass to rustc in exec configuration.   |  `None` |
 | <a id="rust_register_toolchains-urls"></a>urls |  A list of mirror urls containing the tools from the Rust-lang static file server. These must contain the '{}' used to substitute the tool being fetched (using .format).   |  `["https://static.rust-lang.org/dist/{}.tar.xz"]` |
-| <a id="rust_register_toolchains-versions"></a>versions |  A list of toolchain versions to download. This paramter only accepts one versions per channel. E.g. `["1.65.0", "nightly/2022-11-02", "beta/2020-12-30"]`.   |  `["1.82.0", "nightly/2024-10-17"]` |
+| <a id="rust_register_toolchains-versions"></a>versions |  A list of toolchain versions to download. This parameter only accepts one versions per channel. E.g. `["1.65.0", "nightly/2022-11-02", "beta/2020-12-30"]`.   |  `["1.82.0", "nightly/2024-10-17"]` |
+| <a id="rust_register_toolchains-aliases"></a>aliases |  A mapping of "full" repository name to another name to use instead.   |  `{}` |
 
 
 <a id="rust_repositories"></a>
@@ -1908,7 +1909,7 @@
                     <a href="#rust_repository_set-global_allocator_library">global_allocator_library</a>, <a href="#rust_repository_set-extra_target_triples">extra_target_triples</a>, <a href="#rust_repository_set-rustfmt_version">rustfmt_version</a>, <a href="#rust_repository_set-edition">edition</a>,
                     <a href="#rust_repository_set-dev_components">dev_components</a>, <a href="#rust_repository_set-extra_rustc_flags">extra_rustc_flags</a>, <a href="#rust_repository_set-extra_exec_rustc_flags">extra_exec_rustc_flags</a>, <a href="#rust_repository_set-opt_level">opt_level</a>, <a href="#rust_repository_set-sha256s">sha256s</a>,
                     <a href="#rust_repository_set-urls">urls</a>, <a href="#rust_repository_set-auth">auth</a>, <a href="#rust_repository_set-netrc">netrc</a>, <a href="#rust_repository_set-auth_patterns">auth_patterns</a>, <a href="#rust_repository_set-register_toolchain">register_toolchain</a>, <a href="#rust_repository_set-exec_compatible_with">exec_compatible_with</a>,
-                    <a href="#rust_repository_set-default_target_compatible_with">default_target_compatible_with</a>)
+                    <a href="#rust_repository_set-default_target_compatible_with">default_target_compatible_with</a>, <a href="#rust_repository_set-aliases">aliases</a>)
 </pre>
 
 Assembles a remote repository for the given toolchain params, produces a proxy repository     to contain the toolchain declaration, and registers the toolchains.
@@ -1939,6 +1940,7 @@
 | <a id="rust_repository_set-register_toolchain"></a>register_toolchain |  If True, the generated `rust_toolchain` target will become a registered toolchain.   |  `True` |
 | <a id="rust_repository_set-exec_compatible_with"></a>exec_compatible_with |  A list of constraints for the execution platform for this toolchain.   |  `None` |
 | <a id="rust_repository_set-default_target_compatible_with"></a>default_target_compatible_with |  A list of constraints for the target platform for this toolchain when the exec platform is the same as the target platform.   |  `None` |
+| <a id="rust_repository_set-aliases"></a>aliases |  Replacement names to use for toolchains created by this macro.   |  `{}` |
 
 
 <a id="rust_test_suite"></a>
diff --git a/docs/src/rust_repositories.md b/docs/src/rust_repositories.md
index 1b84a36..e266dc2 100644
--- a/docs/src/rust_repositories.md
+++ b/docs/src/rust_repositories.md
@@ -177,7 +177,7 @@
 rust_register_toolchains(<a href="#rust_register_toolchains-dev_components">dev_components</a>, <a href="#rust_register_toolchains-edition">edition</a>, <a href="#rust_register_toolchains-allocator_library">allocator_library</a>, <a href="#rust_register_toolchains-global_allocator_library">global_allocator_library</a>,
                          <a href="#rust_register_toolchains-register_toolchains">register_toolchains</a>, <a href="#rust_register_toolchains-rustfmt_version">rustfmt_version</a>, <a href="#rust_register_toolchains-rust_analyzer_version">rust_analyzer_version</a>, <a href="#rust_register_toolchains-sha256s">sha256s</a>,
                          <a href="#rust_register_toolchains-extra_target_triples">extra_target_triples</a>, <a href="#rust_register_toolchains-extra_rustc_flags">extra_rustc_flags</a>, <a href="#rust_register_toolchains-extra_exec_rustc_flags">extra_exec_rustc_flags</a>, <a href="#rust_register_toolchains-urls">urls</a>,
-                         <a href="#rust_register_toolchains-versions">versions</a>)
+                         <a href="#rust_register_toolchains-versions">versions</a>, <a href="#rust_register_toolchains-aliases">aliases</a>)
 </pre>
 
 Emits a default set of toolchains for Linux, MacOS, and Freebsd
@@ -214,7 +214,8 @@
 | <a id="rust_register_toolchains-extra_rustc_flags"></a>extra_rustc_flags |  Dictionary of target triples to list of extra flags to pass to rustc in non-exec configuration.   |  `None` |
 | <a id="rust_register_toolchains-extra_exec_rustc_flags"></a>extra_exec_rustc_flags |  Extra flags to pass to rustc in exec configuration.   |  `None` |
 | <a id="rust_register_toolchains-urls"></a>urls |  A list of mirror urls containing the tools from the Rust-lang static file server. These must contain the '{}' used to substitute the tool being fetched (using .format).   |  `["https://static.rust-lang.org/dist/{}.tar.xz"]` |
-| <a id="rust_register_toolchains-versions"></a>versions |  A list of toolchain versions to download. This paramter only accepts one versions per channel. E.g. `["1.65.0", "nightly/2022-11-02", "beta/2020-12-30"]`.   |  `["1.82.0", "nightly/2024-10-17"]` |
+| <a id="rust_register_toolchains-versions"></a>versions |  A list of toolchain versions to download. This parameter only accepts one versions per channel. E.g. `["1.65.0", "nightly/2022-11-02", "beta/2020-12-30"]`.   |  `["1.82.0", "nightly/2024-10-17"]` |
+| <a id="rust_register_toolchains-aliases"></a>aliases |  A mapping of "full" repository name to another name to use instead.   |  `{}` |
 
 
 <a id="rust_repositories"></a>
@@ -244,7 +245,7 @@
                     <a href="#rust_repository_set-global_allocator_library">global_allocator_library</a>, <a href="#rust_repository_set-extra_target_triples">extra_target_triples</a>, <a href="#rust_repository_set-rustfmt_version">rustfmt_version</a>, <a href="#rust_repository_set-edition">edition</a>,
                     <a href="#rust_repository_set-dev_components">dev_components</a>, <a href="#rust_repository_set-extra_rustc_flags">extra_rustc_flags</a>, <a href="#rust_repository_set-extra_exec_rustc_flags">extra_exec_rustc_flags</a>, <a href="#rust_repository_set-opt_level">opt_level</a>, <a href="#rust_repository_set-sha256s">sha256s</a>,
                     <a href="#rust_repository_set-urls">urls</a>, <a href="#rust_repository_set-auth">auth</a>, <a href="#rust_repository_set-netrc">netrc</a>, <a href="#rust_repository_set-auth_patterns">auth_patterns</a>, <a href="#rust_repository_set-register_toolchain">register_toolchain</a>, <a href="#rust_repository_set-exec_compatible_with">exec_compatible_with</a>,
-                    <a href="#rust_repository_set-default_target_compatible_with">default_target_compatible_with</a>)
+                    <a href="#rust_repository_set-default_target_compatible_with">default_target_compatible_with</a>, <a href="#rust_repository_set-aliases">aliases</a>)
 </pre>
 
 Assembles a remote repository for the given toolchain params, produces a proxy repository     to contain the toolchain declaration, and registers the toolchains.
@@ -275,6 +276,7 @@
 | <a id="rust_repository_set-register_toolchain"></a>register_toolchain |  If True, the generated `rust_toolchain` target will become a registered toolchain.   |  `True` |
 | <a id="rust_repository_set-exec_compatible_with"></a>exec_compatible_with |  A list of constraints for the execution platform for this toolchain.   |  `None` |
 | <a id="rust_repository_set-default_target_compatible_with"></a>default_target_compatible_with |  A list of constraints for the target platform for this toolchain when the exec platform is the same as the target platform.   |  `None` |
+| <a id="rust_repository_set-aliases"></a>aliases |  Replacement names to use for toolchains created by this macro.   |  `{}` |
 
 
 <a id="rust_toolchain_repository"></a>
diff --git a/rust/extensions.bzl b/rust/extensions.bzl
index 683d11d..5e6edf5 100644
--- a/rust/extensions.bzl
+++ b/rust/extensions.bzl
@@ -69,7 +69,9 @@
                 urls = toolchain.urls,
                 versions = toolchain.versions,
                 register_toolchains = False,
+                aliases = toolchain.aliases,
             )
+    return module_ctx.extension_metadata(reproducible = True)
 
 _COMMON_TAG_KWARGS = dict(
     allocator_library = attr.string(
@@ -109,12 +111,21 @@
         ),
         versions = attr.string_list(
             doc = (
-                "A list of toolchain versions to download. This paramter only accepts one versions " +
+                "A list of toolchain versions to download. This parameter only accepts one version " +
                 "per channel. E.g. `[\"1.65.0\", \"nightly/2022-11-02\", \"beta/2020-12-30\"]`. " +
                 "May be set to an empty list (`[]`) to inhibit `rules_rust` from registering toolchains."
             ),
             default = _RUST_TOOLCHAIN_VERSIONS,
         ),
+        aliases = attr.string_dict(
+            doc = (
+                "Map of full toolchain repository name to an alias. If any repository is created by this " +
+                "extension matches a key in this dictionary, the name of the created repository will be " +
+                "remapped to the value instead. This may be required to work around path length limits " +
+                "on Windows."
+            ),
+            default = {},
+        ),
         **_COMMON_TAG_KWARGS
     ),
 )
diff --git a/rust/repositories.bzl b/rust/repositories.bzl
index 44a8293..761384c 100644
--- a/rust/repositories.bzl
+++ b/rust/repositories.bzl
@@ -119,7 +119,8 @@
         extra_rustc_flags = None,
         extra_exec_rustc_flags = None,
         urls = DEFAULT_STATIC_RUST_URL_TEMPLATES,
-        versions = _RUST_TOOLCHAIN_VERSIONS):
+        versions = _RUST_TOOLCHAIN_VERSIONS,
+        aliases = {}):
     """Emits a default set of toolchains for Linux, MacOS, and Freebsd
 
     Skip this macro and call the `rust_repository_set` macros directly if you need a compiler for \
@@ -151,8 +152,9 @@
         extra_rustc_flags (dict, list, optional): Dictionary of target triples to list of extra flags to pass to rustc in non-exec configuration.
         extra_exec_rustc_flags (list, optional): Extra flags to pass to rustc in exec configuration.
         urls (list, optional): A list of mirror urls containing the tools from the Rust-lang static file server. These must contain the '{}' used to substitute the tool being fetched (using .format).
-        versions (list, optional): A list of toolchain versions to download. This paramter only accepts one versions
+        versions (list, optional): A list of toolchain versions to download. This parameter only accepts one versions
             per channel. E.g. `["1.65.0", "nightly/2022-11-02", "beta/2020-12-30"]`.
+        aliases (dict, optional): A mapping of "full" repository name to another name to use instead.
     """
     if not rustfmt_version:
         if len(versions) == 1:
@@ -172,6 +174,10 @@
     if not rust_analyzer_version:
         rust_analyzer_version = select_rust_version(versions)
 
+    # Convert to an unfrozen dict to remove mappings as they are used. This will allow us to determine if there
+    # are any unused aliases requested.
+    aliases = dict(aliases)
+
     rust_analyzer_repo_name = "rust_analyzer_{}".format(rust_analyzer_version.replace("/", "-"))
 
     toolchain_names = []
@@ -218,9 +224,12 @@
             sha256s = sha256s,
             urls = urls,
             versions = versions,
+            aliases = aliases,
         )
 
         rustfmt_repo_name = "rustfmt_{}__{}".format(rustfmt_version.replace("/", "-"), exec_triple)
+        if rustfmt_repo_name in aliases:
+            rustfmt_repo_name = aliases.pop(rustfmt_repo_name)
 
         maybe(
             rustfmt_toolchain_repository,
@@ -236,7 +245,7 @@
                 rustfmt_repo_name,
             ))
 
-        for toolchain in _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, fallback_target_compatible_with = None):
+        for toolchain in _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, fallback_target_compatible_with = None, aliases = aliases):
             toolchain_names.append(toolchain.name)
             toolchain_labels[toolchain.name] = "@{}//:{}".format(toolchain.name + "_tools", "rust_toolchain")
             exec_compatible_with_by_toolchain[toolchain.name] = triple_to_constraint_set(exec_triple)
@@ -249,6 +258,9 @@
         target_compatible_with_by_toolchain[rustfmt_repo_name] = []
         toolchain_types[rustfmt_repo_name] = "@rules_rust//rust/rustfmt:toolchain_type"
 
+    if aliases:
+        fail("No repositories were created matching the requested names to alias:\n{}".format("\n".join(sorted(aliases))))
+
     toolchain_repository_hub(
         name = "rust_toolchains",
         toolchain_names = toolchain_names,
@@ -924,7 +936,7 @@
     implementation = _rust_toolchain_set_repository_impl,
 )
 
-def _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, fallback_target_compatible_with):
+def _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, fallback_target_compatible_with, aliases = {}):
     extra_target_triples_list = extra_target_triples.keys() if type(extra_target_triples) == "dict" else extra_target_triples
 
     toolchain_repos = []
@@ -955,8 +967,12 @@
 
         # Define toolchains for each requested version
         for channel in channels.values():
+            # Check if this toolchain is requested to be aliased.
+            full_name = "{}__{}__{}".format(name, target_triple, channel.name)
+            if full_name in aliases:
+                full_name = aliases.pop(full_name)
             toolchain_repos.append(struct(
-                name = "{}__{}__{}".format(name, target_triple, channel.name),
+                name = full_name,
                 target_triple = target_triple,
                 channel = channel,
                 target_constraints = target_constraints,
@@ -985,7 +1001,8 @@
         auth_patterns = None,
         register_toolchain = True,
         exec_compatible_with = None,
-        default_target_compatible_with = None):
+        default_target_compatible_with = None,
+        aliases = {}):
     """Assembles a remote repository for the given toolchain params, produces a proxy repository \
     to contain the toolchain declaration, and registers the toolchains.
 
@@ -1021,10 +1038,11 @@
         register_toolchain (bool): If True, the generated `rust_toolchain` target will become a registered toolchain.
         exec_compatible_with (list, optional): A list of constraints for the execution platform for this toolchain.
         default_target_compatible_with (list, optional): A list of constraints for the target platform for this toolchain when the exec platform is the same as the target platform.
+        aliases (dict): Replacement names to use for toolchains created by this macro.
     """
 
     all_toolchain_names = []
-    for toolchain in _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, default_target_compatible_with):
+    for toolchain in _get_toolchain_repositories(name, exec_triple, extra_target_triples, versions, default_target_compatible_with, aliases):
         # Infer toolchain-specific rustc flags depending on the type (list, dict, optional) of extra_rustc_flags
         if extra_rustc_flags == None:
             toolchain_extra_rustc_flags = []
diff --git a/test/aliased_toolchains/MODULE.bazel b/test/aliased_toolchains/MODULE.bazel
new file mode 100644
index 0000000..fb12d7b
--- /dev/null
+++ b/test/aliased_toolchains/MODULE.bazel
@@ -0,0 +1,22 @@
+module(name = "aliased_toolchains")
+
+bazel_dep(name = "rules_rust", version = "0.0.0")
+local_path_override(
+    module_name = "rules_rust",
+    path = "../..",
+)
+
+rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
+rust.toolchain(
+    aliases = {
+        "rust_linux_x86_64__x86_64-unknown-linux-gnu__stable": "rust_linux",
+        "rustfmt_nightly-2024-10-17__x86_64-pc-windows-msvc": "rustfmt_win",
+    },
+    edition = "2021",
+    rustfmt_version = "nightly/2024-10-17",
+)
+
+# Ensure the repos can be used directly and via the hub.
+use_repo(rust, "rust_linux", "rust_toolchains", "rustfmt_win")
+
+register_toolchains("@rust_toolchains//:rust_linux", "@rust_toolchains//:rustfmt_win")