Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<commit-sha>"

# =============================================================================
# Distribution options
#
Expand Down
84 changes: 68 additions & 16 deletions src/bootstrap/src/core/build_steps/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -4630,7 +4630,13 @@ 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.
///
/// 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,
Expand All @@ -4651,19 +4657,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),
Expand All @@ -4673,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;
Expand All @@ -4687,7 +4705,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")
Expand All @@ -4698,7 +4716,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);
}
}
}
}
}
4 changes: 4 additions & 0 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ pub struct Config {
pub rustdoc_pgo: PgoConfig,
pub cargo_pgo: PgoConfig,

pub stdlib_semver_baseline: Option<String>,

pub llvm_libunwind_default: Option<LlvmLibunwind>,
pub enable_bolt_settings: bool,

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/src/core/config/toml/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ define_config! {
std_features: Option<BTreeSet<String>> = "std-features",
break_on_ice: Option<bool> = "break-on-ice",
parallel_frontend_threads: Option<u32> = "parallel-frontend-threads",
stdlib_semver_baseline: Option<String> = "stdlib-semver-baseline",
}
}

Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/bootstrap/stdlib-semver-check-stamp
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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"
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"
24 changes: 24 additions & 0 deletions src/ci/docker/scripts/std-semver-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/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
# 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

# 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}
3 changes: 3 additions & 0 deletions src/ci/github-actions/jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,9 @@ auto:
- name: x86_64-gnu-miri
<<: *job-linux-4c

- name: x86_64-gnu-stdlib-semver-check
<<: *job-linux-4c
Comment on lines +494 to +495

@jieyouxu jieyouxu Aug 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remark: also thinking about the failure handling

View changes since the review


- name: optional-x86_64-gnu-autodiff
continue_on_error: true
doc_url: https://rustc-dev-guide.rust-lang.org/tests/autodiff-ci-job.html
Expand Down
Loading