From ee719f7cffec97060484c14c9325854d55f6112a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 17 Jun 2025 12:59:13 +0200 Subject: [PATCH 1/8] Make default check stage be 1, and error out on checking with stage 0 --- src/bootstrap/defaults/bootstrap.library.toml | 1 - src/bootstrap/src/core/config/config.rs | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/defaults/bootstrap.library.toml b/src/bootstrap/defaults/bootstrap.library.toml index 6a867093b781d..24ff87191d68b 100644 --- a/src/bootstrap/defaults/bootstrap.library.toml +++ b/src/bootstrap/defaults/bootstrap.library.toml @@ -1,7 +1,6 @@ # These defaults are meant for contributors to the standard library and documentation. [build] bench-stage = 1 -check-stage = 1 test-stage = 1 [rust] diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 49b3fec4c358d..59693dc3e4cf6 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1025,7 +1025,7 @@ impl Config { || bench_stage.is_some(); config.stage = match config.cmd { - Subcommand::Check { .. } => flags_stage.or(check_stage).unwrap_or(0), + Subcommand::Check { .. } => flags_stage.or(check_stage).unwrap_or(1), Subcommand::Clippy { .. } | Subcommand::Fix => flags_stage.or(check_stage).unwrap_or(1), // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden. Subcommand::Doc { .. } => { @@ -1052,9 +1052,16 @@ impl Config { }; // Now check that the selected stage makes sense, and if not, print a warning and end - if let (0, Subcommand::Build) = (config.stage, &config.cmd) { - eprintln!("WARNING: cannot build anything on stage 0. Use at least stage 1."); - exit!(1); + match (config.stage, &config.cmd) { + (0, Subcommand::Build) => { + eprintln!("WARNING: cannot build anything on stage 0. Use at least stage 1."); + exit!(1); + } + (0, Subcommand::Check { .. }) => { + eprintln!("WARNING: cannot check anything on stage 0. Use at least stage 1."); + exit!(1); + } + _ => {} } // CI should always run stage 2 builds, unless it specifically states otherwise From 870bb86a54130943e1f90038fa2386121d1dfe5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 18 Jun 2025 22:27:37 +0200 Subject: [PATCH 2/8] Remove `custom_stage` override from `check::Std` and make 1 be the default check stage for it --- src/bootstrap/src/core/build_steps/check.rs | 65 ++++----------------- 1 file changed, 11 insertions(+), 54 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 6c5f70b2f4381..2d68931dadee5 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -20,16 +20,13 @@ pub struct Std { /// /// [`compile::Rustc`]: crate::core::build_steps::compile::Rustc crates: Vec, - /// Never use this from outside calls. It is intended for internal use only within `check::Std::make_run` - /// and `check::Std::run`. - custom_stage: Option, } impl Std { const CRATE_OR_DEPS: &[&str] = &["sysroot", "coretests", "alloctests"]; pub fn new(target: TargetSelection) -> Self { - Self { target, crates: vec![], custom_stage: None } + Self { target, crates: vec![] } } } @@ -48,14 +45,7 @@ impl Step for Std { fn make_run(run: RunConfig<'_>) { let crates = std_crates_for_run_make(&run); - - let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 1 { - run.builder.top_stage - } else { - 1 - }; - - run.builder.ensure(Std { target: run.target, crates, custom_stage: Some(stage) }); + run.builder.ensure(Std { target: run.target, crates }); } fn run(self, builder: &Builder<'_>) { @@ -66,40 +56,20 @@ impl Step for Std { return; } - let stage = self.custom_stage.unwrap_or(builder.top_stage); - + let stage = builder.top_stage; let target = self.target; - let compiler = builder.compiler(stage, builder.config.host_target); - - if stage == 0 { - let mut is_explicitly_called = - builder.paths.iter().any(|p| p.starts_with("library") || p.starts_with("std")); - - if !is_explicitly_called { - for c in Std::CRATE_OR_DEPS { - is_explicitly_called = builder.paths.iter().any(|p| p.starts_with(c)); - } - } - - if is_explicitly_called { - eprintln!("WARNING: stage 0 std is precompiled and does nothing during `x check`."); - } - - // Reuse the stage0 libstd - builder.std(compiler, target); - return; - } + let build_compiler = builder.compiler(stage, builder.config.host_target); let mut cargo = builder::Cargo::new( builder, - compiler, + build_compiler, Mode::Std, SourceType::InTree, target, Kind::Check, ); - std_cargo(builder, target, compiler.stage, &mut cargo); + std_cargo(builder, target, build_compiler.stage, &mut cargo); if matches!(builder.config.cmd, Subcommand::Fix) { // By default, cargo tries to fix all targets. Tell it not to fix tests until we've added `test` to the sysroot. cargo.arg("--lib"); @@ -115,16 +85,9 @@ impl Step for Std { Some(stage), ); - let stamp = build_stamp::libstd_stamp(builder, compiler, target).with_prefix("check"); + let stamp = build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check"); run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); - // We skip populating the sysroot in non-zero stage because that'll lead - // to rlib/rmeta conflicts if std gets built during this session. - if compiler.stage == 0 { - let libdir = builder.sysroot_target_libdir(compiler, target); - let hostdir = builder.sysroot_target_libdir(compiler, compiler.host); - add_to_sysroot(builder, &libdir, &hostdir, &stamp); - } drop(_guard); // don't check test dependencies if we haven't built libtest @@ -140,21 +103,14 @@ impl Step for Std { // Currently only the "libtest" tree of crates does this. let mut cargo = builder::Cargo::new( builder, - compiler, + build_compiler, Mode::Std, SourceType::InTree, target, Kind::Check, ); - // If we're not in stage 0, tests and examples will fail to compile - // from `core` definitions being loaded from two different `libcore` - // .rmeta and .rlib files. - if compiler.stage == 0 { - cargo.arg("--all-targets"); - } - - std_cargo(builder, target, compiler.stage, &mut cargo); + std_cargo(builder, target, build_compiler.stage, &mut cargo); // Explicitly pass -p for all dependencies krates -- this will force cargo // to also check the tests/benches/examples for these crates, rather @@ -163,7 +119,8 @@ impl Step for Std { cargo.arg("-p").arg(krate); } - let stamp = build_stamp::libstd_stamp(builder, compiler, target).with_prefix("check-test"); + let stamp = + build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check-test"); let _guard = builder.msg_check("library test/bench/example targets", target, Some(stage)); run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); } From 4b11385b484e4dcdb79340e1b7fe1a75c35b2b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 3 Jul 2025 17:50:50 +0200 Subject: [PATCH 3/8] Make build compiler explicit in `check::Rustc` and `check::Std` --- src/bootstrap/src/core/build_steps/check.rs | 88 +++++++++++++------- src/bootstrap/src/core/build_steps/clippy.rs | 4 +- src/bootstrap/src/core/builder/tests.rs | 71 ++++------------ 3 files changed, 72 insertions(+), 91 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 2d68931dadee5..591ad3315e1f3 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -1,5 +1,7 @@ //! Implementation of compiling the compiler and standard library, in "check"-based modes. +use build_helper::exit; + use crate::core::build_steps::compile::{ add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo, std_crates_for_run_make, }; @@ -9,10 +11,12 @@ use crate::core::builder::{ }; use crate::core::config::TargetSelection; use crate::utils::build_stamp::{self, BuildStamp}; -use crate::{Mode, Subcommand}; +use crate::{Compiler, Mode, Subcommand}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Std { + /// Compiler that will check this std. + pub build_compiler: Compiler, pub target: TargetSelection, /// Whether to build only a subset of crates. /// @@ -25,8 +29,8 @@ pub struct Std { impl Std { const CRATE_OR_DEPS: &[&str] = &["sysroot", "coretests", "alloctests"]; - pub fn new(target: TargetSelection) -> Self { - Self { target, crates: vec![] } + pub fn new(build_compiler: Compiler, target: TargetSelection) -> Self { + Self { build_compiler, target, crates: vec![] } } } @@ -45,7 +49,11 @@ impl Step for Std { fn make_run(run: RunConfig<'_>) { let crates = std_crates_for_run_make(&run); - run.builder.ensure(Std { target: run.target, crates }); + run.builder.ensure(Std { + build_compiler: run.builder.compiler(run.builder.top_stage, run.target), + target: run.target, + crates, + }); } fn run(self, builder: &Builder<'_>) { @@ -56,9 +64,9 @@ impl Step for Std { return; } - let stage = builder.top_stage; + let build_compiler = self.build_compiler; + let stage = build_compiler.stage; let target = self.target; - let build_compiler = builder.compiler(stage, builder.config.host_target); let mut cargo = builder::Cargo::new( builder, @@ -69,7 +77,7 @@ impl Step for Std { Kind::Check, ); - std_cargo(builder, target, build_compiler.stage, &mut cargo); + std_cargo(builder, target, stage, &mut cargo); if matches!(builder.config.cmd, Subcommand::Fix) { // By default, cargo tries to fix all targets. Tell it not to fix tests until we've added `test` to the sysroot. cargo.arg("--lib"); @@ -126,12 +134,21 @@ impl Step for Std { } fn metadata(&self) -> Option { - Some(StepMetadata::check("std", self.target)) + Some(StepMetadata::check("std", self.target).built_by(self.build_compiler)) } } +fn default_compiler_for_checking_rustc(builder: &Builder<'_>) -> Compiler { + // When checking the stage N compiler, we want to do it with the stage N-1 compiler, + builder.compiler(builder.top_stage - 1, builder.config.host_target) +} + +/// Checks rustc using `build_compiler` and copies the built +/// .rmeta files into the sysroot of `build_copoiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { + /// Compiler that will check this rustc. + pub build_compiler: Compiler, pub target: TargetSelection, /// Whether to build only a subset of crates. /// @@ -142,13 +159,13 @@ pub struct Rustc { } impl Rustc { - pub fn new(target: TargetSelection, builder: &Builder<'_>) -> Self { + pub fn new(builder: &Builder<'_>, build_compiler: Compiler, target: TargetSelection) -> Self { let crates = builder .in_tree_crates("rustc-main", Some(target)) .into_iter() .map(|krate| krate.name.to_string()) .collect(); - Self { target, crates } + Self { build_compiler, target, crates } } } @@ -163,40 +180,46 @@ impl Step for Rustc { fn make_run(run: RunConfig<'_>) { let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc { target: run.target, crates }); + run.builder.ensure(Rustc { + target: run.target, + build_compiler: default_compiler_for_checking_rustc(run.builder), + crates, + }); } - /// Builds the compiler. + /// Check the compiler. /// - /// This will build the compiler for a particular stage of the build using + /// This will check the compiler for a particular stage of the build using /// the `compiler` targeting the `target` architecture. The artifacts /// created will also be linked into the sysroot directory. fn run(self, builder: &Builder<'_>) { - let compiler = builder.compiler(builder.top_stage, builder.config.host_target); + if builder.top_stage < 2 && builder.config.host_target != self.target { + eprintln!("Cannot do a cross-compilation check on stage 1, use stage 2"); + exit!(1); + } + + let build_compiler = self.build_compiler; let target = self.target; - if compiler.stage != 0 { - // If we're not in stage 0, then we won't have a std from the beta - // compiler around. That means we need to make sure there's one in - // the sysroot for the compiler to find. Otherwise, we're going to - // fail when building crates that need to generate code (e.g., build - // scripts and their dependencies). - builder.std(compiler, compiler.host); - builder.std(compiler, target); - } else { - builder.ensure(Std::new(target)); - } + // Build host std for compiling build scripts + builder.std(build_compiler, build_compiler.host); + + // Build target std so that the checked rustc can link to it during the check + // FIXME: maybe we can a way to only do a check of std here? + // But for that we would have to copy the stdlib rmetas to the sysroot of the build + // compiler, which conflicts with std rlibs, if we also build std. + builder.std(build_compiler, target); let mut cargo = builder::Cargo::new( builder, - compiler, + build_compiler, Mode::Rustc, SourceType::InTree, target, Kind::Check, ); - rustc_cargo(builder, &mut cargo, target, &compiler, &self.crates); + rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); // Explicitly pass -p for all compiler crates -- this will force cargo // to also check the tests/benches/examples for these crates, rather @@ -211,17 +234,18 @@ impl Step for Rustc { None, ); - let stamp = build_stamp::librustc_stamp(builder, compiler, target).with_prefix("check"); + let stamp = + build_stamp::librustc_stamp(builder, build_compiler, target).with_prefix("check"); run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); - let libdir = builder.sysroot_target_libdir(compiler, target); - let hostdir = builder.sysroot_target_libdir(compiler, compiler.host); + let libdir = builder.sysroot_target_libdir(build_compiler, target); + let hostdir = builder.sysroot_target_libdir(build_compiler, build_compiler.host); add_to_sysroot(builder, &libdir, &hostdir, &stamp); } fn metadata(&self) -> Option { - Some(StepMetadata::check("rustc", self.target)) + Some(StepMetadata::check("rustc", self.target).built_by(self.build_compiler)) } } @@ -462,7 +486,7 @@ fn run_tool_check_step( let display_name = path.rsplit('/').next().unwrap(); let compiler = builder.compiler(builder.top_stage, builder.config.host_target); - builder.ensure(Rustc::new(target, builder)); + builder.ensure(Rustc::new(builder, compiler, target)); let mut cargo = prepare_tool_cargo( builder, diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs index 1e44b5b67a44c..a0371eb71556b 100644 --- a/src/bootstrap/src/core/build_steps/clippy.rs +++ b/src/bootstrap/src/core/build_steps/clippy.rs @@ -215,7 +215,7 @@ impl Step for Rustc { builder.std(compiler, compiler.host); builder.std(compiler, target); } else { - builder.ensure(check::Std::new(target)); + builder.ensure(check::Std::new(compiler, target)); } } @@ -287,7 +287,7 @@ macro_rules! lint_any { let target = self.target; if !builder.download_rustc() { - builder.ensure(check::Rustc::new(target, builder)); + builder.ensure(check::Rustc::new(builder, compiler, target)); }; let cargo = prepare_tool_cargo( diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1c5267cb75e9b..b7ea92dad3eca 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1240,24 +1240,21 @@ mod snapshot { ctx.config("check") .path("compiler") .render_steps(), @r" - [check] std [build] llvm - [check] rustc - [check] cranelift - [check] gcc + [check] rustc 0 -> rustc 1 "); insta::assert_snapshot!( ctx.config("check") .path("rustc") .render_steps(), @r" - [check] std [build] llvm - [check] rustc + [check] rustc 0 -> rustc 1 "); } #[test] + #[should_panic] fn check_compiler_stage_0() { let ctx = TestCtx::new(); ctx.config("check").path("compiler").stage(0).run(); @@ -1272,11 +1269,7 @@ mod snapshot { .stage(1) .render_steps(), @r" [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [check] rustc - [check] cranelift - [check] gcc + [check] rustc 0 -> rustc 1 "); } @@ -1291,43 +1284,7 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 - [build] rustc 2 -> std 2 - [check] rustc - [check] cranelift - [check] gcc - "); - } - - #[test] - fn check_cross_compile() { - let ctx = TestCtx::new(); - insta::assert_snapshot!( - ctx.config("check") - .stage(2) - .targets(&[TEST_TRIPLE_1]) - .hosts(&[TEST_TRIPLE_1]) - .render_steps(), @r" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 - [build] rustc 2 -> std 2 - [build] rustc 1 -> std 1 - [build] rustc 2 -> std 2 - [check] rustc - [check] Rustdoc - [check] cranelift - [check] gcc - [check] Clippy - [check] Miri - [check] CargoMiri - [check] MiroptTestTools - [check] Rustfmt - [check] rust-analyzer - [check] TestFloatParse - [check] FeaturesStatusDump - [check] std + [check] rustc 1 -> rustc 2 "); } @@ -1340,11 +1297,12 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [check] std + [check] rustc 1 -> std 1 "); } #[test] + #[should_panic] fn check_library_stage_0() { let ctx = TestCtx::new(); ctx.config("check").path("library").stage(0).run(); @@ -1360,7 +1318,7 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [check] std + [check] rustc 1 -> std 1 "); } @@ -1376,7 +1334,7 @@ mod snapshot { [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 [build] rustc 1 -> rustc 2 - [check] std + [check] rustc 2 -> std 2 "); } @@ -1402,14 +1360,15 @@ mod snapshot { ctx.config("check") .path("miri") .render_steps(), @r" - [check] std [build] llvm - [check] rustc + [build] rustc 0 -> rustc 1 + [check] rustc 0 -> rustc 1 [check] Miri "); } #[test] + #[should_panic] fn check_miri_stage_0() { let ctx = TestCtx::new(); ctx.config("check").path("miri").stage(0).run(); @@ -1425,8 +1384,7 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [check] rustc + [check] rustc 0 -> rustc 1 [check] Miri "); } @@ -1443,8 +1401,7 @@ mod snapshot { [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 [build] rustc 1 -> rustc 2 - [build] rustc 2 -> std 2 - [check] rustc + [check] rustc 1 -> rustc 2 [check] Miri "); } From 32fa3b54ac399dbbbc3d40e1ffdea3f87c322f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 25 Jun 2025 17:24:41 +0200 Subject: [PATCH 4/8] Fixup check of rust-analyzer, codegen backends, compiletest and other tools --- src/bootstrap/src/core/build_steps/check.rs | 136 ++++++++++++++------ src/bootstrap/src/core/build_steps/tool.rs | 2 +- src/bootstrap/src/core/builder/tests.rs | 39 +++--- 3 files changed, 113 insertions(+), 64 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 591ad3315e1f3..315c12ee5b441 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -144,7 +144,7 @@ fn default_compiler_for_checking_rustc(builder: &Builder<'_>) -> Compiler { } /// Checks rustc using `build_compiler` and copies the built -/// .rmeta files into the sysroot of `build_copoiler`. +/// .rmeta files into the sysroot of `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { /// Compiler that will check this rustc. @@ -249,8 +249,19 @@ impl Step for Rustc { } } +/// Prepares a build compiler sysroot that will check a `Mode::ToolRustc` tool. +/// Also checks rustc using this compiler, to prepare .rmetas that the tool will link to. +fn prepare_compiler_for_tool_rustc(builder: &Builder<'_>, target: TargetSelection) -> Compiler { + // When we check tool stage N, we check it with compiler stage N-1 + let build_compiler = builder.compiler(builder.top_stage - 1, builder.config.host_target); + builder.ensure(Rustc::new(builder, build_compiler, target)); + build_compiler +} + +/// Checks a single codegen backend. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CodegenBackend { + pub build_compiler: Compiler, pub target: TargetSelection, pub backend: &'static str, } @@ -265,8 +276,10 @@ impl Step for CodegenBackend { } fn make_run(run: RunConfig<'_>) { + // FIXME: only check the backend(s) that were actually selected in run.paths + let build_compiler = prepare_compiler_for_tool_rustc(run.builder, run.target); for &backend in &["cranelift", "gcc"] { - run.builder.ensure(CodegenBackend { target: run.target, backend }); + run.builder.ensure(CodegenBackend { build_compiler, target: run.target, backend }); } } @@ -277,15 +290,13 @@ impl Step for CodegenBackend { return; } - let compiler = builder.compiler(builder.top_stage, builder.config.host_target); + let build_compiler = self.build_compiler; let target = self.target; let backend = self.backend; - builder.ensure(Rustc::new(target, builder)); - let mut cargo = builder::Cargo::new( builder, - compiler, + build_compiler, Mode::Codegen, SourceType::InTree, target, @@ -295,23 +306,25 @@ impl Step for CodegenBackend { cargo .arg("--manifest-path") .arg(builder.src.join(format!("compiler/rustc_codegen_{backend}/Cargo.toml"))); - rustc_cargo_env(builder, &mut cargo, target, compiler.stage); + rustc_cargo_env(builder, &mut cargo, target, build_compiler.stage); - let _guard = builder.msg_check(backend, target, None); + let _guard = builder.msg_check(&format!("rustc_codegen_{backend}"), target, None); - let stamp = build_stamp::codegen_backend_stamp(builder, compiler, target, backend) + let stamp = build_stamp::codegen_backend_stamp(builder, build_compiler, target, backend) .with_prefix("check"); run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); } fn metadata(&self) -> Option { - Some(StepMetadata::check(self.backend, self.target)) + Some(StepMetadata::check(self.backend, self.target).built_by(self.build_compiler)) } } +/// Checks Rust analyzer that links to .rmetas from a checked rustc. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RustAnalyzer { + pub build_compiler: Compiler, pub target: TargetSelection, } @@ -332,18 +345,17 @@ impl Step for RustAnalyzer { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(RustAnalyzer { target: run.target }); + let build_compiler = prepare_compiler_for_tool_rustc(run.builder, run.target); + run.builder.ensure(RustAnalyzer { build_compiler, target: run.target }); } fn run(self, builder: &Builder<'_>) { - let compiler = builder.compiler(builder.top_stage, builder.config.host_target); + let build_compiler = self.build_compiler; let target = self.target; - builder.ensure(Rustc::new(target, builder)); - let mut cargo = prepare_tool_cargo( builder, - compiler, + build_compiler, Mode::ToolRustc, target, builder.kind, @@ -360,7 +372,7 @@ impl Step for RustAnalyzer { // Cargo's output path in a given stage, compiled by a particular // compiler for the specified target. - let stamp = BuildStamp::new(&builder.cargo_out(compiler, Mode::ToolRustc, target)) + let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, Mode::ToolRustc, target)) .with_prefix("rust-analyzer-check"); let _guard = builder.msg_check("rust-analyzer artifacts", target, None); @@ -368,7 +380,7 @@ impl Step for RustAnalyzer { } fn metadata(&self) -> Option { - Some(StepMetadata::check("rust-analyzer", self.target)) + Some(StepMetadata::check("rust-analyzer", self.target).built_by(self.build_compiler)) } } @@ -405,7 +417,7 @@ impl Step for Compiletest { ); if mode != Mode::ToolBootstrap { - builder.ensure(Rustc::new(self.target, builder)); + builder.std(compiler, self.target); } let mut cargo = prepare_tool_cargo( @@ -441,12 +453,14 @@ macro_rules! tool_check_step { // The part of this path after the final '/' is also used as a display name. path: $path:literal $(, alt_path: $alt_path:literal )* + , mode: $mode:path $(, default: $default:literal )? $( , )? } ) => { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct $name { + pub build_compiler: Compiler, pub target: TargetSelection, } @@ -461,16 +475,33 @@ macro_rules! tool_check_step { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure($name { target: run.target }); + let host = run.builder.config.host_target; + let target = run.target; + let build_compiler = match $mode { + Mode::ToolBootstrap => run.builder.compiler(0, host), + Mode::ToolStd => { + // A small number of tools rely on in-tree standard + // library crates (e.g. compiletest needs libtest). + let build_compiler = run.builder.compiler(run.builder.top_stage, host); + run.builder.std(build_compiler, host); + run.builder.std(build_compiler, target); + build_compiler + } + Mode::ToolRustc => { + prepare_compiler_for_tool_rustc(run.builder, target) + } + _ => panic!("unexpected mode for tool check step: {:?}", $mode), + }; + run.builder.ensure($name { target, build_compiler }); } fn run(self, builder: &Builder<'_>) { - let Self { target } = self; - run_tool_check_step(builder, target, stringify!($name), $path); + let Self { target, build_compiler } = self; + run_tool_check_step(builder, build_compiler, target, $path, $mode); } fn metadata(&self) -> Option { - Some(StepMetadata::check(stringify!($name), self.target)) + Some(StepMetadata::check(stringify!($name), self.target).built_by(self.build_compiler)) } } } @@ -479,19 +510,17 @@ macro_rules! tool_check_step { /// Used by the implementation of `Step::run` in `tool_check_step!`. fn run_tool_check_step( builder: &Builder<'_>, + build_compiler: Compiler, target: TargetSelection, - step_type_name: &str, path: &str, + mode: Mode, ) { let display_name = path.rsplit('/').next().unwrap(); - let compiler = builder.compiler(builder.top_stage, builder.config.host_target); - - builder.ensure(Rustc::new(builder, compiler, target)); let mut cargo = prepare_tool_cargo( builder, - compiler, - Mode::ToolRustc, + build_compiler, + mode, target, builder.kind, path, @@ -503,33 +532,56 @@ fn run_tool_check_step( &[], ); + // FIXME: check bootstrap doesn't currently work with --all-targets cargo.arg("--all-targets"); - let stamp = BuildStamp::new(&builder.cargo_out(compiler, Mode::ToolRustc, target)) - .with_prefix(&format!("{}-check", step_type_name.to_lowercase())); + let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, mode, target)) + .with_prefix(&format!("{display_name}-check")); + + let stage = match mode { + // Mode::ToolRustc is included here because of how msg_sysroot_tool prints stages + Mode::Std | Mode::ToolRustc => build_compiler.stage, + _ => build_compiler.stage + 1, + }; - let _guard = builder.msg_check(format!("{display_name} artifacts"), target, None); + let _guard = + builder.msg_tool(builder.kind, mode, display_name, stage, &build_compiler.host, &target); run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); } -tool_check_step!(Rustdoc { path: "src/tools/rustdoc", alt_path: "src/librustdoc" }); +tool_check_step!(Rustdoc { + path: "src/tools/rustdoc", + alt_path: "src/librustdoc", + mode: Mode::ToolRustc +}); // Clippy, miri and Rustfmt are hybrids. They are external tools, but use a git subtree instead // of a submodule. Since the SourceType only drives the deny-warnings // behavior, treat it as in-tree so that any new warnings in clippy will be // rejected. -tool_check_step!(Clippy { path: "src/tools/clippy" }); -tool_check_step!(Miri { path: "src/tools/miri" }); -tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri" }); -tool_check_step!(Rustfmt { path: "src/tools/rustfmt" }); -tool_check_step!(MiroptTestTools { path: "src/tools/miropt-test-tools" }); -tool_check_step!(TestFloatParse { path: "src/tools/test-float-parse" }); -tool_check_step!(FeaturesStatusDump { path: "src/tools/features-status-dump" }); - -tool_check_step!(Bootstrap { path: "src/bootstrap", default: false }); +tool_check_step!(Clippy { path: "src/tools/clippy", mode: Mode::ToolRustc }); +tool_check_step!(Miri { path: "src/tools/miri", mode: Mode::ToolRustc }); +tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri", mode: Mode::ToolRustc }); +tool_check_step!(Rustfmt { path: "src/tools/rustfmt", mode: Mode::ToolRustc }); +tool_check_step!(MiroptTestTools { + path: "src/tools/miropt-test-tools", + mode: Mode::ToolBootstrap +}); +// We want to test the local std +tool_check_step!(TestFloatParse { path: "src/tools/test-float-parse", mode: Mode::ToolStd }); +tool_check_step!(FeaturesStatusDump { + path: "src/tools/features-status-dump", + mode: Mode::ToolBootstrap +}); + +tool_check_step!(Bootstrap { path: "src/bootstrap", mode: Mode::ToolBootstrap, default: false }); // `run-make-support` will be built as part of suitable run-make compiletest test steps, but support // check to make it easier to work on. -tool_check_step!(RunMakeSupport { path: "src/tools/run-make-support", default: false }); +tool_check_step!(RunMakeSupport { + path: "src/tools/run-make-support", + mode: Mode::ToolBootstrap, + default: false +}); /// Check step for the `coverage-dump` bootstrap tool. The coverage-dump tool /// is used internally by coverage tests. diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index ad3f8d8976701..b05b34b9b2280 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -77,7 +77,7 @@ impl Builder<'_> { *target, ), // doesn't depend on compiler, same as host compiler - _ => self.msg(Kind::Build, build_stage, format_args!("tool {tool}"), *host, *target), + _ => self.msg(kind, build_stage, format_args!("tool {tool}"), *host, *target), } } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index b7ea92dad3eca..4f009693e13ae 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1242,6 +1242,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [check] rustc 0 -> rustc 1 + [check] rustc 0 -> cranelift 1 + [check] rustc 0 -> gcc 1 "); insta::assert_snapshot!( @@ -1270,6 +1272,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [check] rustc 0 -> rustc 1 + [check] rustc 0 -> cranelift 1 + [check] rustc 0 -> gcc 1 "); } @@ -1285,6 +1289,8 @@ mod snapshot { [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 [check] rustc 1 -> rustc 2 + [check] rustc 1 -> cranelift 2 + [check] rustc 1 -> gcc 2 "); } @@ -1339,6 +1345,7 @@ mod snapshot { } #[test] + #[should_panic] fn check_library_cross_compile() { let ctx = TestCtx::new(); insta::assert_snapshot!( @@ -1361,9 +1368,8 @@ mod snapshot { .path("miri") .render_steps(), @r" [build] llvm - [build] rustc 0 -> rustc 1 [check] rustc 0 -> rustc 1 - [check] Miri + [check] rustc 0 -> Miri 1 "); } @@ -1383,9 +1389,8 @@ mod snapshot { .stage(1) .render_steps(), @r" [build] llvm - [build] rustc 0 -> rustc 1 [check] rustc 0 -> rustc 1 - [check] Miri + [check] rustc 0 -> Miri 1 "); } @@ -1400,9 +1405,8 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 [check] rustc 1 -> rustc 2 - [check] Miri + [check] rustc 1 -> Miri 2 "); } @@ -1423,9 +1427,9 @@ mod snapshot { .path("compiletest") .args(&["--set", "build.compiletest-use-stage0-libtest=false"]) .render_steps(), @r" - [check] std [build] llvm - [check] rustc + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 [check] compiletest "); } @@ -1437,11 +1441,10 @@ mod snapshot { ctx.config("check") .path("rustc_codegen_cranelift") .render_steps(), @r" - [check] std [build] llvm - [check] rustc - [check] cranelift - [check] gcc + [check] rustc 0 -> rustc 1 + [check] rustc 0 -> cranelift 1 + [check] rustc 0 -> gcc 1 "); } @@ -1452,10 +1455,9 @@ mod snapshot { ctx.config("check") .path("rust-analyzer") .render_steps(), @r" - [check] std [build] llvm - [check] rustc - [check] rust-analyzer + [check] rustc 0 -> rustc 1 + [check] rustc 0 -> rust-analyzer 1 "); } @@ -1465,12 +1467,7 @@ mod snapshot { insta::assert_snapshot!( ctx.config("check") .path("run-make-support") - .render_steps(), @r" - [check] std - [build] llvm - [check] rustc - [check] RunMakeSupport - "); + .render_steps(), @"[check] rustc 0 -> RunMakeSupport 1 "); } #[test] From de6a3804ac957ad3f162fba5544fa954a6d11756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 26 Jun 2025 09:24:52 +0200 Subject: [PATCH 5/8] Implement `CoverageDump` checking through the `tool_check_step` macro --- src/bootstrap/src/core/build_steps/check.rs | 71 ++------------------- 1 file changed, 5 insertions(+), 66 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 315c12ee5b441..e0456e21c3ad0 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -583,69 +583,8 @@ tool_check_step!(RunMakeSupport { default: false }); -/// Check step for the `coverage-dump` bootstrap tool. The coverage-dump tool -/// is used internally by coverage tests. -/// -/// FIXME(Zalathar): This is temporarily separate from the other tool check -/// steps so that it can use the stage 0 compiler instead of `top_stage`, -/// without introducing conflicts with the stage 0 redesign (#119899). -/// -/// After the stage 0 redesign lands, we can look into using the stage 0 -/// compiler to check all bootstrap tools (#139170). -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct CoverageDump; - -impl CoverageDump { - const PATH: &str = "src/tools/coverage-dump"; -} - -impl Step for CoverageDump { - type Output = (); - - /// Most contributors won't care about coverage-dump, so don't make their - /// check builds slower unless they opt in and check it explicitly. - const DEFAULT: bool = false; - const ONLY_HOSTS: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path(Self::PATH) - } - - fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Self {}); - } - - fn run(self, builder: &Builder<'_>) -> Self::Output { - // Make sure we haven't forgotten any fields, if there are any. - let Self {} = self; - let display_name = "coverage-dump"; - let host = builder.config.host_target; - let target = host; - let mode = Mode::ToolBootstrap; - - let compiler = builder.compiler(0, host); - let cargo = prepare_tool_cargo( - builder, - compiler, - mode, - target, - builder.kind, - Self::PATH, - SourceType::InTree, - &[], - ); - - let stamp = BuildStamp::new(&builder.cargo_out(compiler, mode, target)) - .with_prefix(&format!("{display_name}-check")); - - let _guard = builder.msg_tool( - builder.kind, - mode, - display_name, - compiler.stage, - &compiler.host, - &target, - ); - run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false); - } -} +tool_check_step!(CoverageDump { + path: "src/tools/coverage-dump", + mode: Mode::ToolBootstrap, + default: false +}); From aaf60165ee7565db4f84295ea4fe717f7bde1590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 26 Jun 2025 09:40:22 +0200 Subject: [PATCH 6/8] Unify selection of build compiler for checking in a single function --- src/bootstrap/src/core/build_steps/check.rs | 95 ++++++++++----------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index e0456e21c3ad0..9f12059d4eec0 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -50,7 +50,7 @@ impl Step for Std { fn make_run(run: RunConfig<'_>) { let crates = std_crates_for_run_make(&run); run.builder.ensure(Std { - build_compiler: run.builder.compiler(run.builder.top_stage, run.target), + build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Std), target: run.target, crates, }); @@ -138,11 +138,6 @@ impl Step for Std { } } -fn default_compiler_for_checking_rustc(builder: &Builder<'_>) -> Compiler { - // When checking the stage N compiler, we want to do it with the stage N-1 compiler, - builder.compiler(builder.top_stage - 1, builder.config.host_target) -} - /// Checks rustc using `build_compiler` and copies the built /// .rmeta files into the sysroot of `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -182,7 +177,7 @@ impl Step for Rustc { let crates = run.make_run_crates(Alias::Compiler); run.builder.ensure(Rustc { target: run.target, - build_compiler: default_compiler_for_checking_rustc(run.builder), + build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Rustc), crates, }); } @@ -193,11 +188,6 @@ impl Step for Rustc { /// the `compiler` targeting the `target` architecture. The artifacts /// created will also be linked into the sysroot directory. fn run(self, builder: &Builder<'_>) { - if builder.top_stage < 2 && builder.config.host_target != self.target { - eprintln!("Cannot do a cross-compilation check on stage 1, use stage 2"); - exit!(1); - } - let build_compiler = self.build_compiler; let target = self.target; @@ -249,13 +239,45 @@ impl Step for Rustc { } } -/// Prepares a build compiler sysroot that will check a `Mode::ToolRustc` tool. -/// Also checks rustc using this compiler, to prepare .rmetas that the tool will link to. -fn prepare_compiler_for_tool_rustc(builder: &Builder<'_>, target: TargetSelection) -> Compiler { - // When we check tool stage N, we check it with compiler stage N-1 - let build_compiler = builder.compiler(builder.top_stage - 1, builder.config.host_target); - builder.ensure(Rustc::new(builder, build_compiler, target)); - build_compiler +/// Prepares a compiler that will check something with the given `mode`. +fn prepare_compiler_for_check( + builder: &Builder<'_>, + target: TargetSelection, + mode: Mode, +) -> Compiler { + let host = builder.host_target; + match mode { + Mode::ToolBootstrap => builder.compiler(0, host), + Mode::ToolStd => { + // A small number of tools rely on in-tree standard + // library crates (e.g. compiletest needs libtest). + let build_compiler = builder.compiler(builder.top_stage, host); + builder.std(build_compiler, host); + builder.std(build_compiler, target); + build_compiler + } + Mode::ToolRustc | Mode::Codegen => { + // When checking tool stage N, we check it with compiler stage N-1 + let build_compiler = builder.compiler(builder.top_stage - 1, host); + builder.ensure(Rustc::new(builder, build_compiler, target)); + build_compiler + } + Mode::Rustc => { + if builder.top_stage < 2 && host != target { + eprintln!("Cannot do a cross-compilation check of rustc on stage 1, use stage 2"); + exit!(1); + } + + // When checking the stage N compiler, we want to do it with the stage N-1 compiler + builder.compiler(builder.top_stage - 1, host) + } + Mode::Std => { + // When checking std stage N, we want to do it with the stage N compiler + // Note: we don't need to build the host stdlib here, because when compiling std, the + // stage 0 stdlib is used to compile build scripts and proc macros. + builder.compiler(builder.top_stage, host) + } + } } /// Checks a single codegen backend. @@ -277,7 +299,7 @@ impl Step for CodegenBackend { fn make_run(run: RunConfig<'_>) { // FIXME: only check the backend(s) that were actually selected in run.paths - let build_compiler = prepare_compiler_for_tool_rustc(run.builder, run.target); + let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen); for &backend in &["cranelift", "gcc"] { run.builder.ensure(CodegenBackend { build_compiler, target: run.target, backend }); } @@ -345,7 +367,7 @@ impl Step for RustAnalyzer { } fn make_run(run: RunConfig<'_>) { - let build_compiler = prepare_compiler_for_tool_rustc(run.builder, run.target); + let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::ToolRustc); run.builder.ensure(RustAnalyzer { build_compiler, target: run.target }); } @@ -410,19 +432,11 @@ impl Step for Compiletest { } else { Mode::ToolStd }; - - let compiler = builder.compiler( - if mode == Mode::ToolBootstrap { 0 } else { builder.top_stage }, - builder.config.host_target, - ); - - if mode != Mode::ToolBootstrap { - builder.std(compiler, self.target); - } + let build_compiler = prepare_compiler_for_check(builder, self.target, mode); let mut cargo = prepare_tool_cargo( builder, - compiler, + build_compiler, mode, self.target, builder.kind, @@ -435,7 +449,7 @@ impl Step for Compiletest { cargo.arg("--all-targets"); - let stamp = BuildStamp::new(&builder.cargo_out(compiler, mode, self.target)) + let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, mode, self.target)) .with_prefix("compiletest-check"); let _guard = builder.msg_check("compiletest artifacts", self.target, None); @@ -475,23 +489,8 @@ macro_rules! tool_check_step { } fn make_run(run: RunConfig<'_>) { - let host = run.builder.config.host_target; let target = run.target; - let build_compiler = match $mode { - Mode::ToolBootstrap => run.builder.compiler(0, host), - Mode::ToolStd => { - // A small number of tools rely on in-tree standard - // library crates (e.g. compiletest needs libtest). - let build_compiler = run.builder.compiler(run.builder.top_stage, host); - run.builder.std(build_compiler, host); - run.builder.std(build_compiler, target); - build_compiler - } - Mode::ToolRustc => { - prepare_compiler_for_tool_rustc(run.builder, target) - } - _ => panic!("unexpected mode for tool check step: {:?}", $mode), - }; + let build_compiler = prepare_compiler_for_check(run.builder, target, $mode); run.builder.ensure($name { target, build_compiler }); } From f94cc7964fcd95048cce1ce871d2477099e05ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 25 Jun 2025 16:32:09 +0200 Subject: [PATCH 7/8] Add change tracker entry --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 4041e6dd5450b..77dde297cba83 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -446,4 +446,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "Added new option `build.tidy-extra-checks` to specify a default value for the --extra-checks cli flag.", }, + ChangeInfo { + change_id: 143048, + severity: ChangeSeverity::Warning, + summary: "The default check stage has been changed to 1. It is no longer possible to `x check` with stage 0. All check commands have to be on stage 1+.", + }, ]; From fae5ba23800e8dd0d02937bc18a652dfecb4d064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 3 Jul 2025 17:56:24 +0200 Subject: [PATCH 8/8] Fix CI --- src/ci/docker/host-x86_64/pr-check-1/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile index 7a8056b70dc22..a274ebdc3b1b8 100644 --- a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile +++ b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile @@ -45,11 +45,11 @@ COPY host-x86_64/pr-check-1/validate-toolstate.sh /scripts/ # We also skip the x86_64-unknown-linux-gnu target as it is well-tested by other jobs. ENV SCRIPT \ /scripts/check-default-config-profiles.sh && \ - python3 ../x.py build --stage 1 src/tools/build-manifest && \ + python3 ../x.py build src/tools/build-manifest && \ python3 ../x.py test --stage 0 src/tools/compiletest && \ python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \ - python3 ../x.py check --stage 1 --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \ - python3 ../x.py check --stage 1 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \ + python3 ../x.py check --stage 2 --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \ + python3 ../x.py check --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \ /scripts/validate-toolstate.sh && \ reuse --include-submodules lint && \ python3 ../x.py test collect-license-metadata && \