From 1cdee120e2568adc24142fa1340ac612e2fbc9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 30 Jul 2026 09:23:49 +0200 Subject: [PATCH 1/6] Allow specifying the baseline commit for stdlib semver check --- bootstrap.example.toml | 9 ++++++ src/bootstrap/src/core/build_steps/test.rs | 32 +++++++++++++--------- src/bootstrap/src/core/config/config.rs | 4 +++ src/bootstrap/src/core/config/toml/rust.rs | 2 ++ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index a8bab85fda087..a98c0e381e6a0 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -935,6 +935,15 @@ # #rust.parallel-frontend-threads = 1 +# Baseline commit SHA for comparing semver breakages in the Rust standard library. +# The in-tree stdlib API will be evaluated for semver breakages against this commit. +# Used for the `./x test std-semver-check` command. +# If unset, the first upstream parent commit will be used. +# +# The SHA must point to a merge commit merged into the mainline rust-lang/rust `main` branch, +# because bootstrap will attempt to download the JSON docs data for this commit from its CI. +#rust.stdlib-semver-baseline = "" + # ============================================================================= # Distribution options # diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index a2184f75487fe..bee0eb38b7f81 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4630,6 +4630,9 @@ fn check_if_cargo_semver_checks_is_installed(builder: &Builder<'_>) -> bool { /// Run cargo-semver-checks on the standard library and compare its API /// versus a previous baseline, using rustdoc JSON data. /// +/// The baseline commit can be configured using `rust.stdlib-semver-baseline`. +/// If unset, the first upstream parent commit will be used. +/// /// Fails if a semver-breaking change is detected. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StdSemverCheck { @@ -4651,19 +4654,22 @@ impl CommandLineStep for StdSemverCheck { panic!("cargo-semver-checks was not found, please install it"); } - let baseline_commit = match get_closest_upstream_commit( - Some(&run.builder.config.src), - &run.builder.config.git_config(), - run.builder.config.ci_env, - ) { - Ok(Some(commit)) => commit, - Ok(None) => { - panic!("No baseline parent commit found for std-semver-check"); - } - Err(error) => { - panic!("Cannot get baseline parent commit for std-semver-check: {error:?}"); - } - }; + let baseline_commit = + run.builder.config.stdlib_semver_baseline.clone().unwrap_or_else(|| { + match get_closest_upstream_commit( + Some(&run.builder.config.src), + &run.builder.config.git_config(), + run.builder.config.ci_env, + ) { + Ok(Some(commit)) => commit, + Ok(None) => { + panic!("No baseline parent commit found for std-semver-check"); + } + Err(error) => { + panic!("Cannot get baseline parent commit for std-semver-check: {error:?}"); + } + } + }); run.builder.ensure(Self { build_compiler: run.builder.compiler_for_std(run.builder.top_stage), diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index b3ed7d4c6beb7..a07369b48b122 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -240,6 +240,8 @@ pub struct Config { pub rustdoc_pgo: PgoConfig, pub cargo_pgo: PgoConfig, + pub stdlib_semver_baseline: Option, + pub llvm_libunwind_default: Option, pub enable_bolt_settings: bool, @@ -610,6 +612,7 @@ impl Config { std_features: rust_std_features, break_on_ice: rust_break_on_ice, rustflags: rust_rustflags, + stdlib_semver_baseline: rust_stdlib_semver_baseline, } = toml_rust.unwrap_or_default(); let Llvm { @@ -1594,6 +1597,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to .or(rust_rustc_debug_assertions) .unwrap_or(rust_debug == Some(true)), stderr_is_tty: std::io::stderr().is_terminal(), + stdlib_semver_baseline: rust_stdlib_semver_baseline, stdout_is_tty: std::io::stdout().is_terminal(), submodules: build_submodules, sysconfdir: install_sysconfdir.map(PathBuf::from), diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index f8f383ef18e73..d7c22e5012d69 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -77,6 +77,7 @@ define_config! { std_features: Option> = "std-features", break_on_ice: Option = "break-on-ice", parallel_frontend_threads: Option = "parallel-frontend-threads", + stdlib_semver_baseline: Option = "stdlib-semver-baseline", } } @@ -384,6 +385,7 @@ pub fn check_incompatible_options_for_ci_rustc( parallel_frontend_threads: _, bootstrap_override_lld: _, rustflags: _, + stdlib_semver_baseline: _, } = ci_rust_config; // There are two kinds of checks for CI rustc incompatible options: From 4774caa20f55eba9bc3b31df7eafc6670475ea9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 31 Jul 2026 09:23:23 +0200 Subject: [PATCH 2/6] Handle non-fatal errors from cargo-semver-checks --- src/bootstrap/src/core/build_steps/test.rs | 36 +++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index bee0eb38b7f81..e5bd3897d09d1 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4704,7 +4704,41 @@ impl CommandLineStep for StdSemverCheck { .arg(directory.join(format!("{library}.json"))) .arg("--baseline-rustdoc") .arg(baseline_dir.join(format!("{library}.json"))); - cmd.run(builder); + + // We use run_capture to get the exit status + let res = cmd.allow_failure().run_capture(builder); + match res.status() { + Some(status) if status.success() => { + println!("{}\n{}", res.stdout(), res.stderr()); + } + // 101 marks that csc was unable to parse the JSON data, but it did not fail with a + // semver breakage. + Some(status) if status.code() == Some(101) => { + eprintln!( + "cargo-semver-checks was unable to process {library} (this is not a fatal error)\n{}\n{}", + res.stderr(), + res.stdout() + ); + } + // 100 marks semver breakage + Some(status) if status.code() == Some(100) => { + let error = format!( + "cargo-semver-checks found semver breakage in {library}\n{}\n{}", + res.stderr(), + res.stdout() + ); + if builder.fail_fast { + eprintln!("{error}",); + exit!(1); + } else { + builder.config.exec_ctx().add_to_delay_failure(error); + } + } + _ => { + eprintln!("cargo-semver-checks failed.\n{}\n{}", res.stderr(), res.stdout()); + exit!(1); + } + } } } } From f6f7f3b279d5c055359bb6c03505777610cde88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 31 Jul 2026 09:39:06 +0200 Subject: [PATCH 3/6] Add CI job for checking stdlib semver compatibility --- src/bootstrap/src/core/build_steps/test.rs | 4 +-- .../x86_64-gnu-stdlib-semver-check/Dockerfile | 30 +++++++++++++++++++ src/ci/docker/scripts/std-semver-check.sh | 21 +++++++++++++ src/ci/github-actions/jobs.yml | 3 ++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile create mode 100755 src/ci/docker/scripts/std-semver-check.sh diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index e5bd3897d09d1..a69b9932ef6b0 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4617,7 +4617,7 @@ impl CommandLineStep for RemoteTestClientTests { } fn check_if_cargo_semver_checks_is_installed(builder: &Builder<'_>) -> bool { - command("cargo") + command(&builder.initial_cargo) .allow_failure() .arg("semver-checks") .arg("--version") @@ -4693,7 +4693,7 @@ impl CommandLineStep for StdSemverCheck { for library in ["core", "alloc", "std"] { println!("Checking semver compatibility of {library}"); - let mut cmd = command("cargo"); + let mut cmd = command(&builder.initial_cargo); cmd.arg("semver-checks") .arg("-Z") .arg("unstable-options") diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile new file mode 100644 index 0000000000000..404fba5174243 --- /dev/null +++ b/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:26.04 + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + g++ \ + make \ + ninja-build \ + file \ + curl \ + ca-certificates \ + python3 \ + git \ + cmake \ + sudo \ + gdb \ + libssl-dev \ + pkg-config \ + xz-utils \ + mingw-w64 \ + zlib1g-dev \ + libzstd-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +ENV RUST_CONFIGURE_ARGS="--build=x86_64-unknown-linux-gnu" + +COPY /scripts/std-semver-check.sh /tmp/std-semver-check.sh +ENV SCRIPT="bash /tmp/std-semver-check.sh" diff --git a/src/ci/docker/scripts/std-semver-check.sh b/src/ci/docker/scripts/std-semver-check.sh new file mode 100755 index 0000000000000..886defeb1ea43 --- /dev/null +++ b/src/ci/docker/scripts/std-semver-check.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -euo pipefail + +BUILD_DIR=$(realpath ./build/x86_64-unknown-linux-gnu) + +# Install the latest version of cargo-semver-checks, so that once the JSON doc format changes, +# we will eventually get a csc version that supports it +RUSTC="${BUILD_DIR}"/stage0/bin/rustc "${BUILD_DIR}"/stage0/bin/cargo install \ + cargo-semver-checks --locked + +# Provide path to cargo-semver-checks +export PATH=${PATH}:/cargo/bin + +# Explicitly compute the baseline commit (the first git parent, which is the latest upstream main +# commit), so that it is shown in the commit log and so that the command can be easily reproduced +# locally. +PARENT=$(git rev-parse HEAD^1) + +# Run the test +python3 ../x.py test std-semver-check --set rust.stdlib-semver-baseline=${PARENT} diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index d27414ca84d59..6a81ab2c4761d 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -491,6 +491,9 @@ auto: - name: x86_64-gnu-miri <<: *job-linux-4c + - name: x86_64-gnu-stdlib-semver-check + <<: *job-linux-4c + - name: optional-x86_64-gnu-autodiff continue_on_error: true doc_url: https://rustc-dev-guide.rust-lang.org/tests/autodiff-ci-job.html From 4ba53b5d68ad328266df7231b38728ed9c97f5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 3 Aug 2026 12:57:53 +0200 Subject: [PATCH 4/6] Use sccache to speed up compilation of cargo-semver-checks --- .../docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile index 404fba5174243..63e939e5210f0 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-stdlib-semver-check/Dockerfile @@ -25,6 +25,7 @@ COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh ENV RUST_CONFIGURE_ARGS="--build=x86_64-unknown-linux-gnu" +ENV RUSTC_WRAPPER=/usr/local/bin/sccache COPY /scripts/std-semver-check.sh /tmp/std-semver-check.sh ENV SCRIPT="bash /tmp/std-semver-check.sh" From 1ae00c822aa756b97f8fc0be2515b06f273ef389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 3 Aug 2026 14:13:35 +0200 Subject: [PATCH 5/6] Add a mechanism to skip running stdlib semver check --- src/bootstrap/src/core/build_steps/test.rs | 12 ++++++++++++ src/bootstrap/stdlib-semver-check-stamp | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 src/bootstrap/stdlib-semver-check-stamp diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index a69b9932ef6b0..f25116de38718 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4634,6 +4634,9 @@ fn check_if_cargo_semver_checks_is_installed(builder: &Builder<'_>) -> bool { /// If unset, the first upstream parent commit will be used. /// /// Fails if a semver-breaking change is detected. +/// +/// If you want to allow a breaking change in a given PR, or if cargo-semver-checks has a false +/// positive, modify the `src/bootstrap/stdlib-semver-check-stamp` file. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StdSemverCheck { build_compiler: Compiler, @@ -4679,6 +4682,15 @@ impl CommandLineStep for StdSemverCheck { } fn run(self, builder: &Builder<'_>) { + const STDLIB_SEMVER_CHECK_STAMP_PATH: &str = "src/bootstrap/stdlib-semver-check-stamp"; + + if builder.config.ci_env.is_running_in_ci() + && builder.config.has_changes_from_upstream(&[STDLIB_SEMVER_CHECK_STAMP_PATH]) + { + builder.info(&format!("Skipping stdlib semver check, because {STDLIB_SEMVER_CHECK_STAMP_PATH} was modified.")); + return; + } + let Some(docs_dir) = builder.config.download_std_json_docs(self.target, &self.commit) else { return; diff --git a/src/bootstrap/stdlib-semver-check-stamp b/src/bootstrap/stdlib-semver-check-stamp new file mode 100644 index 0000000000000..d93ba46345c85 --- /dev/null +++ b/src/bootstrap/stdlib-semver-check-stamp @@ -0,0 +1,5 @@ +Change this file to explicitly acknowledge making a breaking change to the Rust standard library. +If this file is modified in the same PR as the breaking change, then CI will not fail due to the +breaking change being detected by cargo-semver-checks. + +Last change is for: https://github.com/rust-lang/rust/pull/160253 From 732127e61aec6cdef8ab5f12c5dee65af05ec301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 3 Aug 2026 14:16:29 +0200 Subject: [PATCH 6/6] Speed up compilation of cargo-semver-checks --- src/ci/docker/scripts/std-semver-check.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ci/docker/scripts/std-semver-check.sh b/src/ci/docker/scripts/std-semver-check.sh index 886defeb1ea43..d97470ac1a102 100755 --- a/src/ci/docker/scripts/std-semver-check.sh +++ b/src/ci/docker/scripts/std-semver-check.sh @@ -6,8 +6,11 @@ BUILD_DIR=$(realpath ./build/x86_64-unknown-linux-gnu) # Install the latest version of cargo-semver-checks, so that once the JSON doc format changes, # we will eventually get a csc version that supports it -RUSTC="${BUILD_DIR}"/stage0/bin/rustc "${BUILD_DIR}"/stage0/bin/cargo install \ - cargo-semver-checks --locked +# Speed up compilation by reducing optimizations settings a bit +RUSTC="${BUILD_DIR}"/stage0/bin/rustc \ +CARGO_PROFILE_RELEASE_LTO=false \ +CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 \ +"${BUILD_DIR}"/stage0/bin/cargo install cargo-semver-checks --locked # Provide path to cargo-semver-checks export PATH=${PATH}:/cargo/bin