From 3332308dd9902420407da9280b29837935f86bb1 Mon Sep 17 00:00:00 2001
From: UebelAndre <github@uebelandre.com>
Date: Thu, 9 Apr 2026 07:36:10 -0700
Subject: [PATCH] Added `iso_date` and `channel` to `rust_toolchain`.

---
 rust/BUILD.bazel                              |  1 +
 rust/nightly_versions.bzl                     | 32 ++++++++
 rust/private/repository_utils.bzl             | 22 ++++--
 rust/repositories.bzl                         | 42 +++++++++-
 rust/toolchain.bzl                            | 12 ++-
 .../windows_stdlib/windows_stdlib_test.bzl    | 24 +-----
 util/fetch_shas/fetch_shas.py                 | 78 ++++++++++++++++++-
 7 files changed, 178 insertions(+), 33 deletions(-)
 create mode 100644 rust/nightly_versions.bzl

diff --git a/rust/BUILD.bazel b/rust/BUILD.bazel
index f4edbd8b61..c15c0adf82 100644
--- a/rust/BUILD.bazel
+++ b/rust/BUILD.bazel
@@ -4,6 +4,7 @@ package(default_visibility = ["//visibility:public"])
 
 exports_files([
     "known_shas.bzl",
+    "nightly_versions.bzl",
     "repositories.bzl",
     "defs.bzl",
     "toolchain.bzl",
diff --git a/rust/nightly_versions.bzl b/rust/nightly_versions.bzl
new file mode 100644
index 0000000000..3a6a3a9f4f
--- /dev/null
+++ b/rust/nightly_versions.bzl
@@ -0,0 +1,32 @@
+"""A module containing a mapping of nightly iso dates to Rust versions.
+
+This is a generated file -- see //util/fetch_shas
+"""
+
+# Each entry marks the first tracked nightly date where the Rust
+# version changed. To resolve a given iso_date, find the latest
+# entry whose date is <= the target date.
+NIGHTLY_VERSION_TRANSITIONS = {
+    "2023-09-19": "1.74.0",
+    "2023-10-05": "1.75.0",
+    "2023-11-16": "1.76.0",
+    "2023-12-28": "1.77.0",
+    "2024-02-08": "1.78.0",
+    "2024-03-21": "1.79.0",
+    "2024-05-02": "1.80.0",
+    "2024-06-13": "1.81.0",
+    "2024-07-25": "1.82.0",
+    "2024-09-05": "1.83.0",
+    "2024-10-17": "1.84.0",
+    "2024-11-28": "1.85.0",
+    "2025-01-09": "1.86.0",
+    "2025-02-20": "1.87.0",
+    "2025-04-03": "1.88.0",
+    "2025-05-13": "1.89.0",
+    "2025-08-07": "1.91.0",
+    "2025-09-18": "1.92.0",
+    "2025-10-30": "1.93.0",
+    "2025-12-11": "1.94.0",
+    "2026-01-22": "1.95.0",
+    "2026-03-05": "1.96.0",
+}
diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
index efbfa4afa4..ad8aea4814 100644
--- a/rust/private/repository_utils.bzl
+++ b/rust/private/repository_utils.bzl
@@ -369,6 +369,8 @@ rust_toolchain(
     opt_level = {opt_level},
     strip_level = {strip_level},
     version = "{version}",
+    channel = "{channel}",
+    iso_date = {iso_date},
     tags = ["rust_version={version}"],
 )
 """
@@ -378,12 +380,14 @@ def BUILD_for_rust_toolchain(
         exec_triple,
         target_triple,
         version,
-        allocator_library,
-        global_allocator_library,
-        default_edition,
-        include_rustfmt,
-        include_llvm_tools,
-        include_linker,
+        channel,
+        iso_date = None,
+        allocator_library = None,
+        global_allocator_library = None,
+        default_edition = "",
+        include_rustfmt = False,
+        include_llvm_tools = False,
+        include_linker = False,
         include_objcopy = False,
         stdlib_linkflags = None,
         extra_rustc_flags = None,
@@ -396,7 +400,9 @@ def BUILD_for_rust_toolchain(
         name (str): The name of the toolchain declaration
         exec_triple (triple): The rust-style target that this compiler runs on
         target_triple (triple): The rust-style target triple of the tool
-        version (str): The Rust version for the toolchain.
+        version (str): The semver Rust version for the toolchain (e.g. `1.94.1`).
+        channel (str): The Rust release channel (`stable`, `nightly`, or `beta`).
+        iso_date (str, optional): The ISO date of the nightly or beta release (e.g. `2026-03-26`).
         allocator_library (str, optional): Target that provides allocator functions when rust_library targets are embedded in a cc_binary.
         global_allocator_library (str, optional): Target that provides allocator functions when a global allocator is used with cc_common_link.
                                                   This target is only used in the target configuration; exec builds still use the symbols provided
@@ -476,6 +482,8 @@ def BUILD_for_rust_toolchain(
         opt_level = opt_level,
         strip_level = strip_level,
         version = version,
+        channel = channel,
+        iso_date = repr(iso_date),
     )
 
 _build_file_for_toolchain_template = """\
diff --git a/rust/repositories.bzl b/rust/repositories.bzl
index ea78cdb96b..e4bd37f4c6 100644
--- a/rust/repositories.bzl
+++ b/rust/repositories.bzl
@@ -5,6 +5,7 @@ Repository rules for defining Rust dependencies and toolchains.
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//rust:nightly_versions.bzl", "NIGHTLY_VERSION_TRANSITIONS")
 load("//rust/platform:triple.bzl", "get_host_triple", "triple")
 load("//rust/platform:triple_mappings.bzl", "triple_to_constraint_set")
 load("//rust/private:common.bzl", "rust_common")
@@ -447,6 +448,32 @@ def _include_rust_objcopy(version, iso_date):
 
     return False
 
+def _resolve_nightly_version(iso_date):
+    """Resolve a nightly iso_date to its underlying Rust semver version.
+
+    First tries a direct lookup, then falls back to scanning sorted
+    transition dates for the last entry whose date is <= the requested
+    iso_date.
+
+    Args:
+        iso_date (str): The nightly ISO date (e.g. "2026-03-26").
+
+    Returns:
+        str: The resolved Rust version (e.g. "1.96.0"), or None if
+            the date precedes all tracked transitions.
+    """
+    direct = NIGHTLY_VERSION_TRANSITIONS.get(iso_date)
+    if direct:
+        return direct
+
+    result = None
+    for transition_date in sorted(NIGHTLY_VERSION_TRANSITIONS):
+        if transition_date <= iso_date:
+            result = NIGHTLY_VERSION_TRANSITIONS[transition_date]
+        else:
+            break
+    return result
+
 def _rust_toolchain_tools_repository_impl(ctx):
     """The implementation of the rust toolchain tools repository rule."""
     sha256s = dict(ctx.attr.sha256s)
@@ -459,6 +486,17 @@ def _rust_toolchain_tools_repository_impl(ctx):
 
     check_version_valid(ctx.attr.version, iso_date)
 
+    if version in ("nightly", "beta"):
+        channel = version
+    else:
+        channel = "stable"
+
+    toolchain_version = version
+    if channel == "nightly" and iso_date:
+        resolved = _resolve_nightly_version(iso_date)
+        if resolved:
+            toolchain_version = resolved
+
     exec_triple = triple(ctx.attr.exec_triple)
 
     include_linker = True
@@ -556,7 +594,9 @@ def _rust_toolchain_tools_repository_impl(ctx):
         extra_exec_rustc_flags = ctx.attr.extra_exec_rustc_flags,
         opt_level = ctx.attr.opt_level if ctx.attr.opt_level else None,
         strip_level = ctx.attr.strip_level if ctx.attr.strip_level else None,
-        version = ctx.attr.version,
+        version = toolchain_version,
+        channel = channel,
+        iso_date = iso_date,
     ))
 
     # Not all target triples are expected to have dev components
diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl
index 74883231b8..10465b5a78 100644
--- a/rust/toolchain.bzl
+++ b/rust/toolchain.bzl
@@ -571,6 +571,7 @@ def _rust_toolchain_impl(ctx):
         all_files = depset(transitive = all_files_depsets),
         binary_ext = ctx.attr.binary_ext,
         cargo = sysroot.cargo,
+        channel = ctx.attr.channel,
         clippy_driver = sysroot.clippy,
         cargo_clippy = sysroot.cargo_clippy,
         compilation_mode_opts = compilation_mode_opts,
@@ -578,6 +579,7 @@ def _rust_toolchain_impl(ctx):
         dylib_ext = ctx.attr.dylib_ext,
         env = ctx.attr.env,
         exec_triple = exec_triple,
+        iso_date = ctx.attr.iso_date,
         libstd_and_allocator_ccinfo = make_local_ccinfo(ctx.attr.allocator_library[CcInfo], "std"),
         libstd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "std"),
         nostd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "no_std_with_alloc"),
@@ -657,6 +659,10 @@ rust_toolchain = rule(
             allow_single_file = True,
             cfg = "exec",
         ),
+        "channel": attr.string(
+            doc = "The Rust release channel (`stable`, `nightly`, or `beta`).",
+            default = "",
+        ),
         "clippy_driver": attr.label(
             doc = "The location of the `clippy-driver` binary. Can be a direct source or a filegroup containing one item.",
             allow_single_file = True,
@@ -724,6 +730,10 @@ rust_toolchain = rule(
             doc = "Target that provides allocator functions for when a global allocator is present.",
             default = Label("//rust/private/cc:global_allocator_library"),
         ),
+        "iso_date": attr.string(
+            doc = "The ISO date of the nightly or beta release (e.g. `2026-03-26`). Empty for stable releases.",
+            default = "",
+        ),
         "linker": attr.label(
             doc = "The label to an explicit linker to use (e.g. rust-lld, ld, link-ld.exe, etc.). Linker binaries must be runnable in the exec configuration, so cfg = \"exec\" is used. To choose a linker based on the target platform, use a select() when providing this attribute. The select() will be evaluated against the target platform before the exec transition is applied, allowing platform-specific linker selection while ensuring the selected linker is built for the exec platform.",
             cfg = "exec",
@@ -844,7 +854,7 @@ rust_toolchain = rule(
             ),
         ),
         "version": attr.string(
-            doc = "The version of the Rust compiler. (E.g. `1.94.1`, nightly/2026-03-26`)",
+            doc = "The version of the Rust compiler (e.g. `1.94.1`).",
             default = "",
         ),
         "_codegen_units": attr.label(
diff --git a/test/unit/windows_stdlib/windows_stdlib_test.bzl b/test/unit/windows_stdlib/windows_stdlib_test.bzl
index c32255eece..d8c9978cac 100644
--- a/test/unit/windows_stdlib/windows_stdlib_test.bzl
+++ b/test/unit/windows_stdlib/windows_stdlib_test.bzl
@@ -46,34 +46,14 @@ def _build_for_rust_toolchain_windows_flags_test_impl(ctx):
         exec_triple = msvc_triple,
         target_triple = msvc_triple,
         version = "1.75.0",
-        allocator_library = None,
-        global_allocator_library = None,
-        default_edition = "2021",
-        include_rustfmt = False,
-        include_llvm_tools = False,
-        include_linker = False,
-        stdlib_linkflags = None,
-        extra_rustc_flags = None,
-        extra_exec_rustc_flags = None,
-        opt_level = None,
-        strip_level = None,
+        channel = "stable",
     )
     rendered_gnu = BUILD_for_rust_toolchain(
         name = "tc_gnu",
         exec_triple = gnu_triple,
         target_triple = gnu_triple,
         version = "1.75.0",
-        allocator_library = None,
-        global_allocator_library = None,
-        default_edition = "2021",
-        include_rustfmt = False,
-        include_llvm_tools = False,
-        include_linker = False,
-        stdlib_linkflags = None,
-        extra_rustc_flags = None,
-        extra_exec_rustc_flags = None,
-        opt_level = None,
-        strip_level = None,
+        channel = "stable",
     )
 
     asserts.true(
diff --git a/util/fetch_shas/fetch_shas.py b/util/fetch_shas/fetch_shas.py
index 3c0d1eff1b..6cc5ba8465 100755
--- a/util/fetch_shas/fetch_shas.py
+++ b/util/fetch_shas/fetch_shas.py
@@ -1,16 +1,17 @@
 #!/usr/bin/env python3.11
-"""A script for generating the `//rust:known_shas.bzl` file."""
+"""A script for generating the `//rust:known_shas.bzl` and `//rust:nightly_versions.bzl` files."""
 
 import json
 import logging
 import os
+import re
 import shutil
 import subprocess
 import sys
 import tempfile
 import tomllib
 from pathlib import Path
-from typing import Any, Dict, Sequence
+from typing import Any, Dict, List, Sequence, Tuple
 
 KNOWN_SHAS_TEMPLATE = """\
 \"\"\"A module containing a mapping of Rust tools to checksums
@@ -21,6 +22,20 @@
 FILE_KEY_TO_SHA = {}
 """
 
+NIGHTLY_VERSIONS_TEMPLATE = """\
+\"\"\"A module containing a mapping of nightly iso dates to Rust versions.
+
+This is a generated file -- see //util/fetch_shas
+\"\"\"
+
+# Each entry marks the first tracked nightly date where the Rust
+# version changed. To resolve a given iso_date, find the latest
+# entry whose date is <= the target date.
+NIGHTLY_VERSION_TRANSITIONS = {{
+{}
+}}
+"""
+
 
 def download_manifest_data(
     stable_versions: Sequence[str], nightly_versions: Sequence[str], output_dir: Path
@@ -214,6 +229,53 @@ def download_direct_sha256s(
     }
 
 
+def extract_nightly_version_transitions(
+    manifest_data: Dict[str, Dict[str, Any]],
+) -> List[Tuple[str, str]]:
+    """Extract a transition table mapping nightly iso dates to Rust versions.
+
+    Only includes entries where the version changed from the previous entry,
+    keeping the table compact.
+
+    Args:
+        manifest_data: The parsed manifest data from download_manifest_data.
+
+    Returns:
+        A sorted list of (iso_date, version) tuples at transition points.
+    """
+    nightly_info = manifest_data.get("nightly", {})
+    date_to_version = {}
+
+    for iso_date, info in nightly_info.items():
+        rustc_pkg = info.get("pkg", {}).get("rustc", {})
+        rustc_version_str = rustc_pkg.get("version", "")
+        if not rustc_version_str:
+            logging.warning("No rustc version found for nightly %s", iso_date)
+            continue
+
+        match = re.match(r"^(\d+\.\d+\.\d+)", rustc_version_str)
+        if not match:
+            logging.warning(
+                "Could not parse version from %r for nightly %s",
+                rustc_version_str,
+                iso_date,
+            )
+            continue
+
+        date_to_version[iso_date] = match.group(1)
+
+    sorted_dates = sorted(date_to_version.keys())
+    transitions = []
+    prev_version = None
+    for date in sorted_dates:
+        version = date_to_version[date]
+        if version != prev_version:
+            transitions.append((date, version))
+            prev_version = version
+
+    return transitions
+
+
 def load_data(file: Path) -> Sequence[str]:
     """Load a `fetch_shas_*.txt` file
 
@@ -357,6 +419,11 @@ def main() -> None:
             )
         )
 
+        nightly_transitions = extract_nightly_version_transitions(manifest_data)
+        logging.info(
+            "Identified %s nightly version transitions.", len(nightly_transitions)
+        )
+
     finally:
         if not "RULES_RUST_FETCH_SHAS_DEBUG" in os.environ:
             shutil.rmtree(tmp_dir)
@@ -371,6 +438,13 @@ def main() -> None:
     )
     logging.info("Done. Wrote %s", known_shas_file.relative_to(workspace_dir))
 
+    nightly_versions_file = workspace_dir / "rust/nightly_versions.bzl"
+    transitions_str = "\n".join(
+        '    "{}": "{}",'.format(date, ver) for date, ver in nightly_transitions
+    )
+    nightly_versions_file.write_text(NIGHTLY_VERSIONS_TEMPLATE.format(transitions_str))
+    logging.info("Done. Wrote %s", nightly_versions_file.relative_to(workspace_dir))
+
 
 if __name__ == "__main__":
     main()
