about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/Cargo.lock4
-rw-r--r--src/bootstrap/Cargo.toml4
-rwxr-xr-xsrc/bootstrap/configure.py2
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs9
-rw-r--r--src/bootstrap/src/core/build_steps/doc.rs4
-rw-r--r--src/bootstrap/src/core/build_steps/gcc.rs51
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs65
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs17
-rw-r--r--src/bootstrap/src/core/builder/mod.rs2
-rw-r--r--src/bootstrap/src/core/config/config.rs20
-rw-r--r--src/bootstrap/src/core/config/tests.rs61
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs5
-rw-r--r--src/bootstrap/src/utils/helpers.rs14
13 files changed, 213 insertions, 45 deletions
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock
index a47f3af60cb..890e64e2bab 100644
--- a/src/bootstrap/Cargo.lock
+++ b/src/bootstrap/Cargo.lock
@@ -88,9 +88,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.0"
+version = "1.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
+checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0"
 dependencies = [
  "shlex",
 ]
diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml
index ed51862390d..2c1d85b01e6 100644
--- a/src/bootstrap/Cargo.toml
+++ b/src/bootstrap/Cargo.toml
@@ -37,7 +37,9 @@ test = false
 # Most of the time updating these dependencies requires modifications to the
 # bootstrap codebase(e.g., https://github.com/rust-lang/rust/issues/124565);
 # otherwise, some targets will fail. That's why these dependencies are explicitly pinned.
-cc = "=1.2.0"
+#
+# Do not upgrade this crate unless https://github.com/rust-lang/cc-rs/issues/1317 is fixed.
+cc = "=1.1.22"
 cmake = "=0.1.48"
 
 build_helper = { path = "../build_helper" }
diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py
index ac971a64d7c..e3f58d97cbc 100755
--- a/src/bootstrap/configure.py
+++ b/src/bootstrap/configure.py
@@ -776,6 +776,6 @@ if __name__ == "__main__":
         f.write(contents)
 
     p("")
-    p("run `python {}/x.py --help`".format(rust_dir))
+    p("run `{} {}/x.py --help`".format(os.path.basename(sys.executable), rust_dir))
     if "GITHUB_ACTIONS" in os.environ:
         print("::endgroup::")
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 9d3d07c83d2..d645d922ef4 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -19,6 +19,7 @@ use serde_derive::Deserialize;
 #[cfg(feature = "tracing")]
 use tracing::{instrument, span};
 
+use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags};
 use crate::core::build_steps::tool::SourceType;
 use crate::core::build_steps::{dist, llvm};
 use crate::core::builder;
@@ -1614,6 +1615,14 @@ impl Step for CodegenBackend {
             .arg(builder.src.join(format!("compiler/rustc_codegen_{backend}/Cargo.toml")));
         rustc_cargo_env(builder, &mut cargo, target, compiler.stage);
 
+        // Ideally, we'd have a separate step for the individual codegen backends,
+        // like we have in tests (test::CodegenGCC) but that would require a lot of restructuring.
+        // If the logic gets more complicated, it should probably be done.
+        if backend == "gcc" {
+            let gcc = builder.ensure(Gcc { target });
+            add_cg_gcc_cargo_flags(&mut cargo, &gcc);
+        }
+
         let tmp_stamp = BuildStamp::new(&out_dir).with_prefix("tmp");
 
         let _guard = builder.msg_build(compiler, format_args!("codegen backend {backend}"), target);
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index aee56fe78e2..8e913f68184 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -987,9 +987,7 @@ macro_rules! tool_doc {
                 cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
                 cargo.rustdocflag("--enable-index-page");
                 cargo.rustdocflag("--show-type-layout");
-                // FIXME: `--generate-link-to-definition` tries to resolve cfged out code
-                // see https://github.com/rust-lang/rust/pull/122066#issuecomment-1983049222
-                // cargo.rustdocflag("--generate-link-to-definition");
+                cargo.rustdocflag("--generate-link-to-definition");
 
                 let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc");
                 $(for krate in $crates {
diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs
index 98b8635132b..e8b6a577cea 100644
--- a/src/bootstrap/src/core/build_steps/gcc.rs
+++ b/src/bootstrap/src/core/build_steps/gcc.rs
@@ -9,13 +9,13 @@
 //! ensure that they're always in place if needed.
 
 use std::fs;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::sync::OnceLock;
 
 use build_helper::ci::CiEnv;
 
 use crate::Kind;
-use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
+use crate::core::builder::{Builder, Cargo, RunConfig, ShouldRun, Step};
 use crate::core::config::TargetSelection;
 use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash};
 use crate::utils::exec::command;
@@ -29,7 +29,8 @@ pub struct Meta {
 }
 
 pub enum GccBuildStatus {
-    AlreadyBuilt,
+    /// libgccjit is already built at this path
+    AlreadyBuilt(PathBuf),
     ShouldBuild(Meta),
 }
 
@@ -41,9 +42,6 @@ pub fn prebuilt_gcc_config(builder: &Builder<'_>, target: TargetSelection) -> Gc
     // Initialize the gcc submodule if not initialized already.
     builder.config.update_submodule("src/gcc");
 
-    // FIXME (GuillaumeGomez): To be done once gccjit has been built in the CI.
-    // builder.config.maybe_download_ci_gcc();
-
     let root = builder.src.join("src/gcc");
     let out_dir = builder.gcc_out(target).join("build");
     let install_dir = builder.gcc_out(target).join("install");
@@ -70,19 +68,37 @@ pub fn prebuilt_gcc_config(builder: &Builder<'_>, target: TargetSelection) -> Gc
                 stamp.path().display()
             ));
         }
-        return GccBuildStatus::AlreadyBuilt;
+        let path = libgccjit_built_path(&install_dir);
+        if path.is_file() {
+            return GccBuildStatus::AlreadyBuilt(path);
+        } else {
+            builder.info(&format!(
+                "GCC stamp is up-to-date, but the libgccjit.so file was not found at `{}`",
+                path.display(),
+            ));
+        }
     }
 
     GccBuildStatus::ShouldBuild(Meta { stamp, out_dir, install_dir, root })
 }
 
+/// Returns the path to a libgccjit.so file in the install directory of GCC.
+fn libgccjit_built_path(install_dir: &Path) -> PathBuf {
+    install_dir.join("lib/libgccjit.so")
+}
+
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Gcc {
     pub target: TargetSelection,
 }
 
+#[derive(Clone)]
+pub struct GccOutput {
+    pub libgccjit: PathBuf,
+}
+
 impl Step for Gcc {
-    type Output = bool;
+    type Output = GccOutput;
 
     const ONLY_HOSTS: bool = true;
 
@@ -94,14 +110,14 @@ impl Step for Gcc {
         run.builder.ensure(Gcc { target: run.target });
     }
 
-    /// Compile GCC for `target`.
-    fn run(self, builder: &Builder<'_>) -> bool {
+    /// Compile GCC (specifically `libgccjit`) for `target`.
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
         let target = self.target;
 
         // If GCC has already been built, we avoid building it again.
         let Meta { stamp, out_dir, install_dir, root } = match prebuilt_gcc_config(builder, target)
         {
-            GccBuildStatus::AlreadyBuilt => return true,
+            GccBuildStatus::AlreadyBuilt(path) => return GccOutput { libgccjit: path },
             GccBuildStatus::ShouldBuild(m) => m,
         };
 
@@ -110,8 +126,9 @@ impl Step for Gcc {
         let _time = helpers::timeit(builder);
         t!(fs::create_dir_all(&out_dir));
 
+        let libgccjit_path = libgccjit_built_path(&install_dir);
         if builder.config.dry_run() {
-            return true;
+            return GccOutput { libgccjit: libgccjit_path };
         }
 
         // GCC creates files (e.g. symlinks to the downloaded dependencies)
@@ -173,11 +190,17 @@ impl Step for Gcc {
 
         let lib_alias = install_dir.join("lib/libgccjit.so.0");
         if !lib_alias.exists() {
-            t!(builder.symlink_file(install_dir.join("lib/libgccjit.so"), lib_alias,));
+            t!(builder.symlink_file(&libgccjit_path, lib_alias));
         }
 
         t!(stamp.write());
 
-        true
+        GccOutput { libgccjit: libgccjit_path }
     }
 }
+
+/// Configures a Cargo invocation so that it can build the GCC codegen backend.
+pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) {
+    // Add the path to libgccjit.so to the linker search paths.
+    cargo.rustflag(&format!("-L{}", gcc.libgccjit.parent().unwrap().to_str().unwrap()));
+}
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index d335ce65ad5..c9fa2e9ba96 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -12,6 +12,7 @@ use clap_complete::shells;
 
 use crate::core::build_steps::compile::run_cargo;
 use crate::core::build_steps::doc::DocumentationFormat;
+use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags};
 use crate::core::build_steps::llvm::get_llvm_version;
 use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget;
 use crate::core::build_steps::tool::{self, SourceType, Tool};
@@ -268,7 +269,13 @@ impl Step for Cargotest {
             .args(builder.config.test_args())
             .env("RUSTC", builder.rustc(compiler))
             .env("RUSTDOC", builder.rustdoc(compiler));
-        add_rustdoc_cargo_linker_args(&mut cmd, builder, compiler.host, LldThreads::No);
+        add_rustdoc_cargo_linker_args(
+            &mut cmd,
+            builder,
+            compiler.host,
+            LldThreads::No,
+            compiler.stage,
+        );
         cmd.delay_failure().run(builder);
     }
 }
@@ -293,17 +300,27 @@ impl Step for Cargo {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(Cargo { stage: run.builder.top_stage, host: run.target });
+        // If stage is explicitly set or not lower than 2, keep it. Otherwise, make sure it's at least 2
+        // as tests for this step don't work with a lower stage.
+        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
+            run.builder.top_stage
+        } else {
+            2
+        };
+
+        run.builder.ensure(Cargo { stage, host: run.target });
     }
 
     /// Runs `cargo test` for `cargo` packaged with Rust.
     fn run(self, builder: &Builder<'_>) {
-        if self.stage < 2 {
-            eprintln!("WARNING: cargo tests on stage {} may not behave well.", self.stage);
+        let stage = self.stage;
+
+        if stage < 2 {
+            eprintln!("WARNING: cargo tests on stage {stage} may not behave well.");
             eprintln!("HELP: consider using stage 2");
         }
 
-        let compiler = builder.compiler(self.stage, self.host);
+        let compiler = builder.compiler(stage, self.host);
 
         let cargo = builder.ensure(tool::Cargo { compiler, target: self.host });
         let compiler = cargo.build_compiler;
@@ -340,7 +357,7 @@ impl Step for Cargo {
                 crates: vec!["cargo".into()],
                 target: self.host.triple.to_string(),
                 host: self.host.triple.to_string(),
-                stage: self.stage,
+                stage,
             },
             builder,
         );
@@ -739,7 +756,15 @@ impl Step for Clippy {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(Clippy { stage: run.builder.top_stage, host: run.target });
+        // If stage is explicitly set or not lower than 2, keep it. Otherwise, make sure it's at least 2
+        // as tests for this step don't work with a lower stage.
+        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
+            run.builder.top_stage
+        } else {
+            2
+        };
+
+        run.builder.ensure(Clippy { stage, host: run.target });
     }
 
     /// Runs `cargo test` for clippy.
@@ -828,7 +853,7 @@ impl Step for RustdocTheme {
             .env("CFG_RELEASE_CHANNEL", &builder.config.channel)
             .env("RUSTDOC_REAL", builder.rustdoc(self.compiler))
             .env("RUSTC_BOOTSTRAP", "1");
-        cmd.args(linker_args(builder, self.compiler.host, LldThreads::No));
+        cmd.args(linker_args(builder, self.compiler.host, LldThreads::No, self.compiler.stage));
 
         cmd.delay_failure().run(builder);
     }
@@ -1004,7 +1029,13 @@ impl Step for RustdocGUI {
         cmd.env("RUSTDOC", builder.rustdoc(self.compiler))
             .env("RUSTC", builder.rustc(self.compiler));
 
-        add_rustdoc_cargo_linker_args(&mut cmd, builder, self.compiler.host, LldThreads::No);
+        add_rustdoc_cargo_linker_args(
+            &mut cmd,
+            builder,
+            self.compiler.host,
+            LldThreads::No,
+            self.compiler.stage,
+        );
 
         for path in &builder.paths {
             if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) {
@@ -1774,14 +1805,18 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         cmd.arg("--src-root").arg(&builder.src);
         cmd.arg("--src-test-suite-root").arg(builder.src.join("tests").join(suite));
 
-        cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite));
+        // N.B. it's important to distinguish between the *root* build directory, the *host* build
+        // directory immediately under the root build directory, and the test-suite-specific build
+        // directory.
+        cmd.arg("--build-root").arg(&builder.out);
+        cmd.arg("--build-test-suite-root").arg(testdir(builder, compiler.host).join(suite));
 
         // When top stage is 0, that means that we're testing an externally provided compiler.
         // In that case we need to use its specific sysroot for tests to pass.
         let sysroot = if builder.top_stage == 0 {
             builder.initial_sysroot.clone()
         } else {
-            builder.sysroot(compiler).to_path_buf()
+            builder.sysroot(compiler)
         };
 
         cmd.arg("--sysroot-base").arg(sysroot);
@@ -1864,7 +1899,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
 
         let mut hostflags = flags.clone();
         hostflags.push(format!("-Lnative={}", builder.test_helpers_out(compiler.host).display()));
-        hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No));
+        hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No, compiler.stage));
 
         let mut targetflags = flags;
         targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display()));
@@ -3531,6 +3566,8 @@ impl Step for CodegenGCC {
         let compiler = self.compiler;
         let target = self.target;
 
+        let gcc = builder.ensure(Gcc { target });
+
         builder.ensure(
             compile::Std::new(compiler, target)
                 .extra_rust_args(&["-Csymbol-mangling-version=v0", "-Cpanic=abort"]),
@@ -3557,6 +3594,7 @@ impl Step for CodegenGCC {
                 .arg("--manifest-path")
                 .arg(builder.src.join("compiler/rustc_codegen_gcc/build_system/Cargo.toml"));
             compile::rustc_cargo_env(builder, &mut cargo, target, compiler.stage);
+            add_cg_gcc_cargo_flags(&mut cargo, &gcc);
 
             // Avoid incremental cache issues when changing rustc
             cargo.env("CARGO_BUILD_INCREMENTAL", "false");
@@ -3589,9 +3627,10 @@ impl Step for CodegenGCC {
             .env("CG_RUSTFLAGS", "-Alinker-messages")
             .arg("--")
             .arg("test")
-            .arg("--use-system-gcc")
             .arg("--use-backend")
             .arg("gcc")
+            .arg("--gcc-path")
+            .arg(gcc.libgccjit.parent().unwrap())
             .arg("--out-dir")
             .arg(builder.stage_out(compiler, Mode::ToolRustc).join("cg_gcc"))
             .arg("--release")
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index f08d229c76d..d1d52d82eaa 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -260,7 +260,7 @@ impl Cargo {
             }
         }
 
-        for arg in linker_args(builder, compiler.host, LldThreads::Yes) {
+        for arg in linker_args(builder, compiler.host, LldThreads::Yes, 0) {
             self.hostflags.arg(&arg);
         }
 
@@ -270,10 +270,10 @@ impl Cargo {
         }
         // We want to set -Clinker using Cargo, therefore we only call `linker_flags` and not
         // `linker_args` here.
-        for flag in linker_flags(builder, target, LldThreads::Yes) {
+        for flag in linker_flags(builder, target, LldThreads::Yes, compiler.stage) {
             self.rustflags.arg(&flag);
         }
-        for arg in linker_args(builder, target, LldThreads::Yes) {
+        for arg in linker_args(builder, target, LldThreads::Yes, compiler.stage) {
             self.rustdocflags.arg(&arg);
         }
 
@@ -608,11 +608,10 @@ impl Builder<'_> {
         }
 
         // FIXME: the following components don't build with `-Zrandomize-layout` yet:
-        // - wasm-component-ld, due to the `wast`crate
         // - rust-analyzer, due to the rowan crate
-        // so we exclude entire categories of steps here due to lack of fine-grained control over
+        // so we exclude an entire category of steps here due to lack of fine-grained control over
         // rustflags.
-        if self.config.rust_randomize_layout && mode != Mode::ToolStd && mode != Mode::ToolRustc {
+        if self.config.rust_randomize_layout && mode != Mode::ToolRustc {
             rustflags.arg("-Zrandomize-layout");
         }
 
@@ -776,6 +775,12 @@ impl Builder<'_> {
             Mode::Codegen => metadata.push_str("codegen"),
             _ => {}
         }
+        // `rustc_driver`'s version number is always `0.0.0`, which can cause linker search path
+        // problems on side-by-side installs because we don't include the version number of the
+        // `rustc_driver` being built. This can cause builds of different version numbers to produce
+        // `librustc_driver*.so` artifacts that end up with identical filename hashes.
+        metadata.push_str(&self.version);
+
         cargo.env("__CARGO_DEFAULT_LIB_METADATA", &metadata);
 
         if cmd_kind == Kind::Clippy {
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 9c04f097bee..c8e2856bdc8 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -1462,7 +1462,7 @@ impl<'a> Builder<'a> {
             cmd.arg("-Dwarnings");
         }
         cmd.arg("-Znormalize-docs");
-        cmd.args(linker_args(self, compiler.host, LldThreads::Yes));
+        cmd.args(linker_args(self, compiler.host, LldThreads::Yes, compiler.stage));
         cmd
     }
 
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index d4b72ead045..d0e0ed50ad8 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -218,6 +218,8 @@ pub struct Config {
     pub stderr_is_tty: bool,
 
     pub on_fail: Option<String>,
+    pub explicit_stage_from_cli: bool,
+    pub explicit_stage_from_config: bool,
     pub stage: u32,
     pub keep_stage: Vec<u32>,
     pub keep_stage_std: Vec<u32>,
@@ -1775,7 +1777,11 @@ impl Config {
 
         let is_user_configured_rust_channel =
             if let Some(channel) = toml.rust.as_ref().and_then(|r| r.channel.clone()) {
-                config.channel = channel;
+                if channel == "auto-detect" {
+                    config.channel = ci_channel.into();
+                } else {
+                    config.channel = channel;
+                }
                 true
             } else {
                 false
@@ -2323,6 +2329,14 @@ impl Config {
         config.compiletest_diff_tool = compiletest_diff_tool;
 
         let download_rustc = config.download_rustc_commit.is_some();
+        config.explicit_stage_from_cli = flags.stage.is_some();
+        config.explicit_stage_from_config = test_stage.is_some()
+            || build_stage.is_some()
+            || doc_stage.is_some()
+            || dist_stage.is_some()
+            || install_stage.is_some()
+            || check_stage.is_some()
+            || bench_stage.is_some();
         // See https://github.com/rust-lang/compiler-team/issues/326
         config.stage = match config.cmd {
             Subcommand::Check { .. } => flags.stage.or(check_stage).unwrap_or(0),
@@ -2392,6 +2406,10 @@ impl Config {
         }
     }
 
+    pub fn is_explicit_stage(&self) -> bool {
+        self.explicit_stage_from_cli || self.explicit_stage_from_config
+    }
+
     /// Runs a command, printing out nice contextual information if it fails.
     /// Exits if the command failed to execute at all, otherwise returns its
     /// `status.success()`.
diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs
index f0a185ee3a7..eff5e033742 100644
--- a/src/bootstrap/src/core/config/tests.rs
+++ b/src/bootstrap/src/core/config/tests.rs
@@ -454,3 +454,64 @@ fn check_rustc_if_unchanged_paths() {
         assert!(config.src.join(p).exists(), "{p} doesn't exist.");
     }
 }
+
+#[test]
+fn test_explicit_stage() {
+    let config = Config::parse_inner(
+        Flags::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]),
+        |&_| {
+            toml::from_str(
+                r#"
+            [build]
+            test-stage = 1
+        "#,
+            )
+        },
+    );
+
+    assert!(!config.explicit_stage_from_cli);
+    assert!(config.explicit_stage_from_config);
+    assert!(config.is_explicit_stage());
+
+    let config = Config::parse_inner(
+        Flags::parse(&[
+            "check".to_owned(),
+            "--stage=2".to_owned(),
+            "--config=/does/not/exist".to_owned(),
+        ]),
+        |&_| toml::from_str(""),
+    );
+
+    assert!(config.explicit_stage_from_cli);
+    assert!(!config.explicit_stage_from_config);
+    assert!(config.is_explicit_stage());
+
+    let config = Config::parse_inner(
+        Flags::parse(&[
+            "check".to_owned(),
+            "--stage=2".to_owned(),
+            "--config=/does/not/exist".to_owned(),
+        ]),
+        |&_| {
+            toml::from_str(
+                r#"
+            [build]
+            test-stage = 1
+        "#,
+            )
+        },
+    );
+
+    assert!(config.explicit_stage_from_cli);
+    assert!(config.explicit_stage_from_config);
+    assert!(config.is_explicit_stage());
+
+    let config = Config::parse_inner(
+        Flags::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]),
+        |&_| toml::from_str(""),
+    );
+
+    assert!(!config.explicit_stage_from_cli);
+    assert!(!config.explicit_stage_from_config);
+    assert!(!config.is_explicit_stage());
+}
diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index 8dfe0d3a35e..5f49c50c5ad 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -360,4 +360,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Info,
         summary: "Added `build.test-stage = 2` to 'tools' profile defaults",
     },
+    ChangeInfo {
+        change_id: 137220,
+        severity: ChangeSeverity::Info,
+        summary: "`rust.channel` now supports \"auto-detect\" to load the channel from `src/ci/channel`",
+    },
 ];
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index 3fee397da09..7ad308cd067 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -430,8 +430,9 @@ pub fn linker_args(
     builder: &Builder<'_>,
     target: TargetSelection,
     lld_threads: LldThreads,
+    stage: u32,
 ) -> Vec<String> {
-    let mut args = linker_flags(builder, target, lld_threads);
+    let mut args = linker_flags(builder, target, lld_threads, stage);
 
     if let Some(linker) = builder.linker(target) {
         args.push(format!("-Clinker={}", linker.display()));
@@ -446,12 +447,18 @@ pub fn linker_flags(
     builder: &Builder<'_>,
     target: TargetSelection,
     lld_threads: LldThreads,
+    stage: u32,
 ) -> Vec<String> {
     let mut args = vec![];
     if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() {
         match builder.config.lld_mode {
             LldMode::External => {
-                args.push("-Clinker-flavor=gnu-lld-cc".to_string());
+                // cfg(bootstrap) - remove after updating bootstrap compiler (#137498)
+                if stage == 0 && target.is_windows() {
+                    args.push("-Clink-arg=-fuse-ld=lld".to_string());
+                } else {
+                    args.push("-Clinker-flavor=gnu-lld-cc".to_string());
+                }
                 // FIXME(kobzol): remove this flag once MCP510 gets stabilized
                 args.push("-Zunstable-options".to_string());
             }
@@ -479,8 +486,9 @@ pub fn add_rustdoc_cargo_linker_args(
     builder: &Builder<'_>,
     target: TargetSelection,
     lld_threads: LldThreads,
+    stage: u32,
 ) {
-    let args = linker_args(builder, target, lld_threads);
+    let args = linker_args(builder, target, lld_threads, stage);
     let mut flags = cmd
         .get_envs()
         .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None })