about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/bootstrap/Cargo.lock4
-rw-r--r--src/bootstrap/Cargo.toml2
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs59
-rw-r--r--src/bootstrap/src/core/builder/tests.rs110
-rw-r--r--src/bootstrap/src/core/sanity.rs2
-rw-r--r--src/bootstrap/src/lib.rs2
-rw-r--r--src/bootstrap/src/utils/helpers.rs9
7 files changed, 116 insertions, 72 deletions
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock
index 9c2b7500a20..4a5d768961f 100644
--- a/src/bootstrap/Cargo.lock
+++ b/src/bootstrap/Cargo.lock
@@ -98,9 +98,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.73"
+version = "1.0.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
 
 [[package]]
 name = "cfg-if"
diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml
index e9675e20452..ca0d1fa5bd0 100644
--- a/src/bootstrap/Cargo.toml
+++ b/src/bootstrap/Cargo.toml
@@ -36,7 +36,7 @@ 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.0.73"
+cc = "=1.0.97"
 cmake = "=0.1.48"
 
 build_helper = { path = "../tools/build_helper" }
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index d4473e24039..3af1a05caa8 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -20,7 +20,9 @@ use std::sync::OnceLock;
 use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::core::config::{Config, TargetSelection};
 use crate::utils::channel;
-use crate::utils::helpers::{self, exe, get_clang_cl_resource_dir, output, t, up_to_date};
+use crate::utils::helpers::{
+    self, exe, get_clang_cl_resource_dir, output, t, unhashed_basename, up_to_date,
+};
 use crate::{generate_smart_stamp_hash, CLang, GitRepo, Kind};
 
 use build_helper::ci::CiEnv;
@@ -506,7 +508,7 @@ impl Step for Llvm {
             cfg.define("LLVM_VERSION_SUFFIX", suffix);
         }
 
-        configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
+        configure_cmake(builder, target, &mut cfg, true, ldflags, &[], &[]);
         configure_llvm(builder, target, &mut cfg);
 
         for (key, val) in &builder.config.llvm_build_config {
@@ -596,6 +598,7 @@ fn configure_cmake(
     use_compiler_launcher: bool,
     mut ldflags: LdFlags,
     extra_compiler_flags: &[&str],
+    suppressed_compiler_flag_prefixes: &[&str],
 ) {
     // Do not print installation messages for up-to-date files.
     // LLVM and LLD builds can produce a lot of those and hit CI limits on log size.
@@ -729,7 +732,17 @@ fn configure_cmake(
     }
 
     cfg.build_arg("-j").build_arg(builder.jobs().to_string());
-    let mut cflags: OsString = builder.cflags(target, GitRepo::Llvm, CLang::C).join(" ").into();
+    let mut cflags: OsString = builder
+        .cflags(target, GitRepo::Llvm, CLang::C)
+        .into_iter()
+        .filter(|flag| {
+            !suppressed_compiler_flag_prefixes
+                .iter()
+                .any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
+        })
+        .collect::<Vec<String>>()
+        .join(" ")
+        .into();
     if let Some(ref s) = builder.config.llvm_cflags {
         cflags.push(" ");
         cflags.push(s);
@@ -742,7 +755,17 @@ fn configure_cmake(
         cflags.push(&format!(" {flag}"));
     }
     cfg.define("CMAKE_C_FLAGS", cflags);
-    let mut cxxflags: OsString = builder.cflags(target, GitRepo::Llvm, CLang::Cxx).join(" ").into();
+    let mut cxxflags: OsString = builder
+        .cflags(target, GitRepo::Llvm, CLang::Cxx)
+        .into_iter()
+        .filter(|flag| {
+            !suppressed_compiler_flag_prefixes
+                .iter()
+                .any(|suppressed_prefix| flag.starts_with(suppressed_prefix))
+        })
+        .collect::<Vec<String>>()
+        .join(" ")
+        .into();
     if let Some(ref s) = builder.config.llvm_cxxflags {
         cxxflags.push(" ");
         cxxflags.push(s);
@@ -921,7 +944,7 @@ impl Step for Lld {
             ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'");
         }
 
-        configure_cmake(builder, target, &mut cfg, true, ldflags, &[]);
+        configure_cmake(builder, target, &mut cfg, true, ldflags, &[], &[]);
         configure_llvm(builder, target, &mut cfg);
 
         // Re-use the same flags as llvm to control the level of debug information
@@ -1022,6 +1045,12 @@ impl Step for Sanitizers {
         let use_compiler_launcher = !self.target.contains("apple-darwin");
         let extra_compiler_flags: &[&str] =
             if self.target.contains("apple") { &["-fembed-bitcode=off"] } else { &[] };
+        // Since v1.0.86, the cc crate adds -mmacosx-version-min to the default
+        // flags on MacOS. A long-standing bug in the CMake rules for compiler-rt
+        // causes architecture detection to be skipped when this flag is present,
+        // and compilation fails. https://github.com/llvm/llvm-project/issues/88780
+        let suppressed_compiler_flag_prefixes: &[&str] =
+            if self.target.contains("apple-darwin") { &["-mmacosx-version-min="] } else { &[] };
         configure_cmake(
             builder,
             self.target,
@@ -1029,6 +1058,7 @@ impl Step for Sanitizers {
             use_compiler_launcher,
             LdFlags::default(),
             extra_compiler_flags,
+            suppressed_compiler_flag_prefixes,
         );
 
         t!(fs::create_dir_all(&out_dir));
@@ -1190,7 +1220,7 @@ impl Step for CrtBeginEnd {
 
         let crtbegin_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtbegin.c");
         let crtend_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtend.c");
-        if up_to_date(&crtbegin_src, &out_dir.join("crtbegin.o"))
+        if up_to_date(&crtbegin_src, &out_dir.join("crtbeginS.o"))
             && up_to_date(&crtend_src, &out_dir.join("crtendS.o"))
         {
             return out_dir;
@@ -1222,10 +1252,15 @@ impl Step for CrtBeginEnd {
             .define("CRT_HAS_INITFINI_ARRAY", None)
             .define("EH_USE_FRAME_REGISTRY", None);
 
-        cfg.compile("crt");
+        let objs = cfg.compile_intermediates();
+        assert_eq!(objs.len(), 2);
+        for obj in objs {
+            let base_name = unhashed_basename(&obj);
+            assert!(base_name == "crtbegin" || base_name == "crtend");
+            t!(fs::copy(&obj, out_dir.join(format!("{}S.o", base_name))));
+            t!(fs::rename(&obj, out_dir.join(format!("{}.o", base_name))));
+        }
 
-        t!(fs::copy(out_dir.join("crtbegin.o"), out_dir.join("crtbeginS.o")));
-        t!(fs::copy(out_dir.join("crtend.o"), out_dir.join("crtendS.o")));
         out_dir
     }
 }
@@ -1372,9 +1407,9 @@ impl Step for Libunwind {
         for entry in fs::read_dir(&out_dir).unwrap() {
             let file = entry.unwrap().path().canonicalize().unwrap();
             if file.is_file() && file.extension() == Some(OsStr::new("o")) {
-                // file name starts with "Unwind-EHABI", "Unwind-seh" or "libunwind"
-                let file_name = file.file_name().unwrap().to_str().expect("UTF-8 file name");
-                if cpp_sources.iter().any(|f| file_name.starts_with(&f[..f.len() - 4])) {
+                // Object file name without the hash prefix is "Unwind-EHABI", "Unwind-seh" or "libunwind".
+                let base_name = unhashed_basename(&file);
+                if cpp_sources.iter().any(|f| *base_name == f[..f.len() - 4]) {
                     cc_cfg.object(&file);
                     count += 1;
                 }
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index caec46366dd..9898d495c02 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -32,7 +32,7 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config
         .join(&thread::current().name().unwrap_or("unknown").replace(":", "-"));
     t!(fs::create_dir_all(&dir));
     config.out = dir;
-    config.build = TargetSelection::from_user("A");
+    config.build = TargetSelection::from_user("A-A");
     config.hosts = host.iter().map(|s| TargetSelection::from_user(s)).collect();
     config.targets = target.iter().map(|s| TargetSelection::from_user(s)).collect();
     config
@@ -53,27 +53,27 @@ fn run_build(paths: &[PathBuf], config: Config) -> Cache {
 fn check_cli<const N: usize>(paths: [&str; N]) {
     run_build(
         &paths.map(PathBuf::from),
-        configure_with_args(&paths.map(String::from), &["A"], &["A"]),
+        configure_with_args(&paths.map(String::from), &["A-A"], &["A-A"]),
     );
 }
 
 macro_rules! std {
     ($host:ident => $target:ident, stage = $stage:literal) => {
         compile::Std::new(
-            Compiler { host: TargetSelection::from_user(stringify!($host)), stage: $stage },
-            TargetSelection::from_user(stringify!($target)),
+            Compiler { host: TargetSelection::from_user(concat!(stringify!($host), "-", stringify!($host))), stage: $stage },
+            TargetSelection::from_user(concat!(stringify!($target), "-", stringify!($target))),
         )
     };
 }
 
 macro_rules! doc_std {
     ($host:ident => $target:ident, stage = $stage:literal) => {{
-        let config = configure("doc", &["A"], &["A"]);
+        let config = configure("doc", &["A-A"], &["A-A"]);
         let build = Build::new(config);
         let builder = Builder::new(&build);
         doc::Std::new(
             $stage,
-            TargetSelection::from_user(stringify!($target)),
+            TargetSelection::from_user(concat!(stringify!($target), "-", stringify!($target))),
             &builder,
             DocumentationFormat::Html,
         )
@@ -83,8 +83,8 @@ macro_rules! doc_std {
 macro_rules! rustc {
     ($host:ident => $target:ident, stage = $stage:literal) => {
         compile::Rustc::new(
-            Compiler { host: TargetSelection::from_user(stringify!($host)), stage: $stage },
-            TargetSelection::from_user(stringify!($target)),
+            Compiler { host: TargetSelection::from_user(concat!(stringify!($host), "-", stringify!($host))), stage: $stage },
+            TargetSelection::from_user(concat!(stringify!($target), "-", stringify!($target))),
         )
     };
 }
@@ -117,7 +117,7 @@ fn test_intersection() {
 
 #[test]
 fn validate_path_remap() {
-    let build = Build::new(configure("test", &["A"], &["A"]));
+    let build = Build::new(configure("test", &["A-A"], &["A-A"]));
 
     PATH_REMAP
         .iter()
@@ -130,7 +130,7 @@ fn validate_path_remap() {
 
 #[test]
 fn test_exclude() {
-    let mut config = configure("test", &["A"], &["A"]);
+    let mut config = configure("test", &["A-A"], &["A-A"]);
     config.skip = vec!["src/tools/tidy".into()];
     let cache = run_build(&[], config);
 
@@ -145,7 +145,7 @@ fn test_exclude() {
 fn test_exclude_kind() {
     let path = PathBuf::from("compiler/rustc_data_structures");
 
-    let mut config = configure("test", &["A"], &["A"]);
+    let mut config = configure("test", &["A-A"], &["A-A"]);
     // Ensure our test is valid, and `test::Rustc` would be run without the exclude.
     assert!(run_build(&[], config.clone()).contains::<test::CrateLibrustc>());
     // Ensure tests for rustc are not skipped.
@@ -159,13 +159,13 @@ fn test_exclude_kind() {
 #[test]
 fn alias_and_path_for_library() {
     let mut cache =
-        run_build(&["library".into(), "core".into()], configure("build", &["A"], &["A"]));
+        run_build(&["library".into(), "core".into()], configure("build", &["A-A"], &["A-A"]));
     assert_eq!(
         first(cache.all::<compile::Std>()),
         &[std!(A => A, stage = 0), std!(A => A, stage = 1)]
     );
 
-    let mut cache = run_build(&["library".into(), "core".into()], configure("doc", &["A"], &["A"]));
+    let mut cache = run_build(&["library".into(), "core".into()], configure("doc", &["A-A"], &["A-A"]));
     assert_eq!(first(cache.all::<doc::Std>()), &[doc_std!(A => A, stage = 0)]);
 }
 
@@ -177,9 +177,9 @@ mod defaults {
 
     #[test]
     fn build_default() {
-        let mut cache = run_build(&[], configure("build", &["A"], &["A"]));
+        let mut cache = run_build(&[], configure("build", &["A-A"], &["A-A"]));
 
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
         assert_eq!(
             first(cache.all::<compile::Std>()),
             &[std!(A => A, stage = 0), std!(A => A, stage = 1),]
@@ -197,10 +197,10 @@ mod defaults {
 
     #[test]
     fn build_stage_0() {
-        let config = Config { stage: 0, ..configure("build", &["A"], &["A"]) };
+        let config = Config { stage: 0, ..configure("build", &["A-A"], &["A-A"]) };
         let mut cache = run_build(&[], config);
 
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
         assert_eq!(first(cache.all::<compile::Std>()), &[std!(A => A, stage = 0)]);
         assert!(!cache.all::<compile::Assemble>().is_empty());
         assert_eq!(
@@ -214,11 +214,11 @@ mod defaults {
 
     #[test]
     fn build_cross_compile() {
-        let config = Config { stage: 1, ..configure("build", &["A", "B"], &["A", "B"]) };
+        let config = Config { stage: 1, ..configure("build", &["A-A", "B-B"], &["A-A", "B-B"]) };
         let mut cache = run_build(&[], config);
 
-        let a = TargetSelection::from_user("A");
-        let b = TargetSelection::from_user("B");
+        let a = TargetSelection::from_user("A-A");
+        let b = TargetSelection::from_user("B-B");
 
         // Ideally, this build wouldn't actually have `target: a`
         // rustdoc/rustcc/std here (the user only requested a host=B build, so
@@ -257,11 +257,11 @@ mod defaults {
 
     #[test]
     fn doc_default() {
-        let mut config = configure("doc", &["A"], &["A"]);
+        let mut config = configure("doc", &["A-A"], &["A-A"]);
         config.compiler_docs = true;
         config.cmd = Subcommand::Doc { open: false, json: false };
         let mut cache = run_build(&[], config);
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
 
         // error_index_generator uses stage 0 to share rustdoc artifacts with the
         // rustdoc tool.
@@ -291,9 +291,9 @@ mod dist {
 
     #[test]
     fn dist_baseline() {
-        let mut cache = run_build(&[], configure(&["A"], &["A"]));
+        let mut cache = run_build(&[], configure(&["A-A"], &["A-A"]));
 
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
 
         assert_eq!(first(cache.all::<dist::Docs>()), &[dist::Docs { host: a },]);
         assert_eq!(first(cache.all::<dist::Mingw>()), &[dist::Mingw { host: a },]);
@@ -315,10 +315,10 @@ mod dist {
 
     #[test]
     fn dist_with_targets() {
-        let mut cache = run_build(&[], configure(&["A"], &["A", "B"]));
+        let mut cache = run_build(&[], configure(&["A-A"], &["A-A", "B-B"]));
 
-        let a = TargetSelection::from_user("A");
-        let b = TargetSelection::from_user("B");
+        let a = TargetSelection::from_user("A-A");
+        let b = TargetSelection::from_user("B-B");
 
         assert_eq!(
             first(cache.all::<dist::Docs>()),
@@ -344,10 +344,10 @@ mod dist {
 
     #[test]
     fn dist_with_hosts() {
-        let mut cache = run_build(&[], configure(&["A", "B"], &["A", "B"]));
+        let mut cache = run_build(&[], configure(&["A-A", "B-B"], &["A-A", "B-B"]));
 
-        let a = TargetSelection::from_user("A");
-        let b = TargetSelection::from_user("B");
+        let a = TargetSelection::from_user("A-A");
+        let b = TargetSelection::from_user("B-B");
 
         assert_eq!(
             first(cache.all::<dist::Docs>()),
@@ -386,8 +386,8 @@ mod dist {
 
     #[test]
     fn dist_only_cross_host() {
-        let b = TargetSelection::from_user("B");
-        let mut config = configure(&["A", "B"], &["A", "B"]);
+        let b = TargetSelection::from_user("B-B");
+        let mut config = configure(&["A-A", "B-B"], &["A-A", "B-B"]);
         config.docs = false;
         config.extended = true;
         config.hosts = vec![b];
@@ -405,11 +405,11 @@ mod dist {
 
     #[test]
     fn dist_with_targets_and_hosts() {
-        let mut cache = run_build(&[], configure(&["A", "B"], &["A", "B", "C"]));
+        let mut cache = run_build(&[], configure(&["A-A", "B-B"], &["A-A", "B-B", "C-C"]));
 
-        let a = TargetSelection::from_user("A");
-        let b = TargetSelection::from_user("B");
-        let c = TargetSelection::from_user("C");
+        let a = TargetSelection::from_user("A-A");
+        let b = TargetSelection::from_user("B-B");
+        let c = TargetSelection::from_user("C-C");
 
         assert_eq!(
             first(cache.all::<dist::Docs>()),
@@ -439,11 +439,11 @@ mod dist {
 
     #[test]
     fn dist_with_empty_host() {
-        let config = configure(&[], &["C"]);
+        let config = configure(&[], &["C-C"]);
         let mut cache = run_build(&[], config);
 
-        let a = TargetSelection::from_user("A");
-        let c = TargetSelection::from_user("C");
+        let a = TargetSelection::from_user("A-A");
+        let c = TargetSelection::from_user("C-C");
 
         assert_eq!(first(cache.all::<dist::Docs>()), &[dist::Docs { host: c },]);
         assert_eq!(first(cache.all::<dist::Mingw>()), &[dist::Mingw { host: c },]);
@@ -455,10 +455,10 @@ mod dist {
 
     #[test]
     fn dist_with_same_targets_and_hosts() {
-        let mut cache = run_build(&[], configure(&["A", "B"], &["A", "B"]));
+        let mut cache = run_build(&[], configure(&["A-A", "B-B"], &["A-A", "B-B"]));
 
-        let a = TargetSelection::from_user("A");
-        let b = TargetSelection::from_user("B");
+        let a = TargetSelection::from_user("A-A");
+        let b = TargetSelection::from_user("B-B");
 
         assert_eq!(
             first(cache.all::<dist::Docs>()),
@@ -506,7 +506,7 @@ mod dist {
 
     #[test]
     fn build_all() {
-        let build = Build::new(configure(&["A", "B"], &["A", "B", "C"]));
+        let build = Build::new(configure(&["A-A", "B-B"], &["A-A", "B-B", "C-C"]));
         let mut builder = Builder::new(&build);
         builder.run_step_descriptions(
             &Builder::get_step_descriptions(Kind::Build),
@@ -539,29 +539,29 @@ mod dist {
 
     #[test]
     fn llvm_out_behaviour() {
-        let mut config = configure(&["A"], &["B"]);
+        let mut config = configure(&["A-A"], &["B-B"]);
         config.llvm_from_ci = true;
         let build = Build::new(config.clone());
 
-        let target = TargetSelection::from_user("A");
+        let target = TargetSelection::from_user("A-A");
         assert!(build.llvm_out(target).ends_with("ci-llvm"));
-        let target = TargetSelection::from_user("B");
+        let target = TargetSelection::from_user("B-B");
         assert!(build.llvm_out(target).ends_with("llvm"));
 
         config.llvm_from_ci = false;
         let build = Build::new(config.clone());
-        let target = TargetSelection::from_user("A");
+        let target = TargetSelection::from_user("A-A");
         assert!(build.llvm_out(target).ends_with("llvm"));
     }
 
     #[test]
     fn build_with_empty_host() {
-        let config = configure(&[], &["C"]);
+        let config = configure(&[], &["C-C"]);
         let build = Build::new(config);
         let mut builder = Builder::new(&build);
         builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Build), &[]);
 
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
 
         assert_eq!(
             first(builder.cache.all::<compile::Std>()),
@@ -583,7 +583,7 @@ mod dist {
 
     #[test]
     fn test_with_no_doc_stage0() {
-        let mut config = configure(&["A"], &["A"]);
+        let mut config = configure(&["A-A"], &["A-A"]);
         config.stage = 0;
         config.paths = vec!["library/std".into()];
         config.cmd = Subcommand::Test {
@@ -605,7 +605,7 @@ mod dist {
         let build = Build::new(config);
         let mut builder = Builder::new(&build);
 
-        let host = TargetSelection::from_user("A");
+        let host = TargetSelection::from_user("A-A");
 
         builder.run_step_descriptions(
             &[StepDescription::from::<test::Crate>(Kind::Test)],
@@ -627,13 +627,13 @@ mod dist {
 
     #[test]
     fn doc_ci() {
-        let mut config = configure(&["A"], &["A"]);
+        let mut config = configure(&["A-A"], &["A-A"]);
         config.compiler_docs = true;
         config.cmd = Subcommand::Doc { open: false, json: false };
         let build = Build::new(config);
         let mut builder = Builder::new(&build);
         builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Doc), &[]);
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
 
         // error_index_generator uses stage 1 to share rustdoc artifacts with the
         // rustdoc tool.
@@ -656,7 +656,7 @@ mod dist {
     #[test]
     fn test_docs() {
         // Behavior of `x.py test` doing various documentation tests.
-        let mut config = configure(&["A"], &["A"]);
+        let mut config = configure(&["A-A"], &["A-A"]);
         config.cmd = Subcommand::Test {
             test_args: vec![],
             rustc_args: vec![],
@@ -678,7 +678,7 @@ mod dist {
         let mut builder = Builder::new(&build);
 
         builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Test), &[]);
-        let a = TargetSelection::from_user("A");
+        let a = TargetSelection::from_user("A-A");
 
         // error_index_generator uses stage 1 to share rustdoc artifacts with the
         // rustdoc tool.
diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs
index 0c069a54069..493ad99cc70 100644
--- a/src/bootstrap/src/core/sanity.rs
+++ b/src/bootstrap/src/core/sanity.rs
@@ -192,7 +192,7 @@ than building it.
         let target_str = target.to_string();
 
         // Ignore fake targets that are only used for unit tests in bootstrap.
-        if !["A", "B", "C"].contains(&target_str.as_str()) {
+        if !["A-A", "B-B", "C-C"].contains(&target_str.as_str()) {
             let mut has_target = false;
 
             let supported_target_list =
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index c599709a322..47b03d411cb 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -1723,7 +1723,7 @@ impl Build {
             return;
         }
         let _ = fs::remove_file(dst);
-        let metadata = t!(src.symlink_metadata());
+        let metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));
         let mut src = src.to_path_buf();
         if metadata.file_type().is_symlink() {
             if dereference_symlinks {
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index 928c9aa4dff..0d2ff4f951b 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -296,6 +296,15 @@ pub fn up_to_date(src: &Path, dst: &Path) -> bool {
     }
 }
 
+/// Returns the filename without the hash prefix added by the cc crate.
+///
+/// Since v1.0.78 of the cc crate, object files are prefixed with a 16-character hash
+/// to avoid filename collisions.
+pub fn unhashed_basename(obj: &Path) -> &str {
+    let basename = obj.file_stem().unwrap().to_str().expect("UTF-8 file name");
+    basename.split_once('-').unwrap().1
+}
+
 fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
     t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
         let meta = t!(e.metadata());