diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index a4976139fe96a..36d87db7ad018 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -167,8 +167,13 @@ impl Step for Std { fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; - // We already have std ready to be used for stage 0. - if self.build_compiler.stage == 0 { + // In most cases, we already have the std ready to be used for stage 0. + // However, if we are doing a local rebuild (so the build compiler can compile the standard + // library even on stage 0), and we're cross-compiling (so the stage0 standard library for + // *target* is not available), we still allow the stdlib to be built here. + if self.build_compiler.stage == 0 + && !(builder.local_rebuild && target != builder.host_target) + { let compiler = self.build_compiler; builder.ensure(StdLink::from_std(self, compiler)); diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 27e416359a9e4..f794f4e079aeb 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -1435,22 +1435,30 @@ impl<'a> Builder<'a> { // FIXME: make the `Std` step return some type-level "proof" that std was indeed built, // and then require passing that to all Cargo invocations that we do. - // The "stage 0" std is always precompiled and comes with the stage0 compiler, so we have - // special logic for it, to avoid creating needless and confusing Std steps that don't + // The "stage 0" std is almost always precompiled and comes with the stage0 compiler, so we + // have special logic for it, to avoid creating needless and confusing Std steps that don't // actually build anything. + // We only allow building the stage0 stdlib if we do a local rebuild, so the stage0 compiler + // actually comes from in-tree sources, and we're cross-compiling, so the stage0 for the + // given `target` is not available. if compiler.stage == 0 { if target != compiler.host { - panic!( - r"It is not possible to build the standard library for `{target}` using the stage0 compiler. + if self.local_rebuild { + self.ensure(Std::new(compiler, target)) + } else { + panic!( + r"It is not possible to build the standard library for `{target}` using the stage0 compiler. You have to build a stage1 compiler for `{}` first, and then use it to build a standard library for `{target}`. +Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler built from in-tree sources. ", - compiler.host - ) + compiler.host + ) + } + } else { + // We still need to link the prebuilt standard library into the ephemeral stage0 sysroot + self.ensure(StdLink::from_std(Std::new(compiler, target), compiler)); + None } - - // We still need to link the prebuilt standard library into the ephemeral stage0 sysroot - self.ensure(StdLink::from_std(Std::new(compiler, target), compiler)); - None } else { // This step both compiles the std and links it into the compiler's sysroot. // Yes, it's quite magical and side-effecty.. would be nice to refactor later. diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index f7067d114504a..3c2cb7828501d 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -853,6 +853,18 @@ mod snapshot { ctx.config("build").path("library").stage(0).run(); } + #[test] + fn build_library_stage_0_local_rebuild() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("library") + .stage(0) + .targets(&[TEST_TRIPLE_1]) + .args(&["--set", "build.local-rebuild=true"]) + .render_steps(), @"[build] rustc 0 -> std 0 "); + } + #[test] fn build_library_stage_1() { let ctx = TestCtx::new(); @@ -1696,6 +1708,22 @@ mod snapshot { "); } + #[test] + fn dist_library_stage_0_local_rebuild() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("dist") + .path("rust-std") + .stage(0) + .targets(&[TEST_TRIPLE_1]) + .args(&["--set", "build.local-rebuild=true"]) + .render_steps(), @r" + [build] rustc 0 -> std 0 + [build] rustc 0 -> RustInstaller 1 + [dist] rustc 0 -> std 0 + "); + } + #[test] fn check_compiler_no_explicit_stage() { let ctx = TestCtx::new(); diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 3e9c8ccb4fa0c..9249b738ad6e2 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -969,31 +969,38 @@ impl Config { | Subcommand::Vendor { .. } => flags_stage.unwrap_or(0), }; - // Now check that the selected stage makes sense, and if not, print a warning and end + let local_rebuild = build_local_rebuild.unwrap_or(false); + + let check_stage0 = |kind: &str| { + if local_rebuild { + eprintln!("WARNING: running {kind} in stage 0. This might not work as expected."); + } else { + eprintln!( + "ERROR: cannot {kind} anything on stage 0. Use at least stage 1 or set build.local-rebuild=true and use a stage0 compiler built from in-tree sources." + ); + exit!(1); + } + }; + + // Now check that the selected stage makes sense, and if not, print an error and end match (stage, &flags_cmd) { (0, Subcommand::Build { .. }) => { - eprintln!("ERROR: cannot build anything on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("build"); } (0, Subcommand::Check { .. }) => { - eprintln!("ERROR: cannot check anything on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("check"); } (0, Subcommand::Doc { .. }) => { - eprintln!("ERROR: cannot document anything on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("doc"); } (0, Subcommand::Clippy { .. }) => { - eprintln!("ERROR: cannot run clippy on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("clippy"); } (0, Subcommand::Dist) => { - eprintln!("ERROR: cannot dist anything on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("dist"); } (0, Subcommand::Install) => { - eprintln!("ERROR: cannot install anything on stage 0. Use at least stage 1."); - exit!(1); + check_stage0("install"); } _ => {} } @@ -1234,7 +1241,7 @@ impl Config { llvm_use_libcxx: llvm_use_libcxx.unwrap_or(false), llvm_use_linker, llvm_version_suffix, - local_rebuild: build_local_rebuild.unwrap_or(false), + local_rebuild, locked_deps: build_locked_deps.unwrap_or(false), low_priority: build_low_priority.unwrap_or(false), mandir: install_mandir.map(PathBuf::from), diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 073954e933781..606d88d3db44c 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -526,4 +526,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "The `optimized-compiler-builtins` option now accepts a path to an existing compiler-rt builtins library.", }, + ChangeInfo { + change_id: 145876, + severity: ChangeSeverity::Info, + summary: "It is now possible to `check/build/dist` the standard stage 0 library if you use a stage0 rustc built from in-tree sources. This is useful for quickly cross-compiling the standard library. You have to enable build.local-rebuild for this to work.", + }, ];