Files
aya-mirror/scripts/rust-toolchain-pins.py
Tamir Duberstein 4704f70e7b ci: share immutable Rust toolchain discovery
bpf-linker's beta CI broke when the mutable beta channel moved from
LLVM 22 to LLVM 23 [1]. Its updater needs the exact compiler identity,
not only the channel name.

Expose a read-only reusable workflow that resolves stable to a numbered
release and beta/nightly to dated releases, together with their rustc
commits. Verify that each immutable manifest matches the mutable one so
consumers can apply their own compatibility and promotion policies.

Keep Aya's stable and nightly pins in named MODULE.bazel declarations.
Read and update their version attributes with the pinned Buildozer,
rejecting regressions before editing either channel. Register only
nightly for Bazel's eBPF targets; Cargo CI installs the same committed
versions instead of mutable channel labels.

Read both pins once in CI's prepare job and pass the versions explicitly
to toolchain setup and codegen, avoiding a Bazel bootstrap in each Cargo
job. The update workflow passes its validated nightly to codegen directly.

Refresh the Bazel lockfile after advancing the pins to keep archive
facts synchronized.

[1]: https://github.com/aya-rs/bpf-linker/actions/runs/32331638741/job/96313377731
2026-08-23 15:31:48 -04:00

96 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Read and update Aya's Rust toolchain declarations with Buildozer."""
from __future__ import annotations
import argparse
import json
import subprocess
from pathlib import Path
from rust_toolchains import Channel, Toolchain, read_toolchain, selection_key
TOOLCHAINS = {
Channel.STABLE: "stable_rust_toolchains",
Channel.NIGHTLY: "default_rust_toolchains",
}
MODULE = Path("MODULE.bazel").resolve()
def read_pins(text: str) -> dict[Channel, Toolchain]:
value = json.loads(text)
if not isinstance(value, dict) or value.keys() != TOOLCHAINS.keys():
raise ValueError("expected stable and nightly Rust toolchains")
return {channel: read_toolchain(value[channel], channel) for channel in TOOLCHAINS}
def buildozer(*commands: str) -> str:
result = subprocess.run(
[
"bazel",
"run",
"--lockfile_mode=error",
"@buildifier_prebuilt//:buildozer",
"--",
"-f",
"-",
],
input="\n".join(commands) + "\n",
stdout=subprocess.PIPE,
text=True,
)
# Buildozer returns 3 when an edit leaves the file unchanged.
if result.returncode not in (0, 3):
result.check_returncode()
return result.stdout
def current_toolchains() -> dict[Channel, str]:
versions = buildozer(
*(f"print version|{MODULE}:{repository}" for repository in TOOLCHAINS.values())
).splitlines()
current = {}
for channel, version in zip(TOOLCHAINS, versions, strict=True):
rust = version.replace("/", "-", 1)
selection_key(rust, channel)
current[channel] = rust
return current
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest="command", required=True)
commands.add_parser("current")
update = commands.add_parser("update")
update.add_argument("toolchains", type=read_pins)
arguments = parser.parse_args()
if arguments.command == "current":
print(json.dumps(current_toolchains()))
return
candidates: dict[Channel, Toolchain] = arguments.toolchains
current = current_toolchains()
edits = []
for channel, candidate in candidates.items():
previous = current[channel]
if selection_key(candidate.rust, channel) < selection_key(previous, channel):
raise ValueError(
f"Rust {channel} regressed from {previous} to {candidate.rust}"
)
if candidate.rust != previous:
version = candidate.rust.replace("-", "/", 1)
edits.append(
f"set version {json.dumps(version)}|{MODULE}:{TOOLCHAINS[channel]}"
)
# Validate both channels before asking Buildozer to write the file once.
if edits:
buildozer(*edits)
if __name__ == "__main__":
main()