1use std::ffi::OsStr;
7use std::path::{Path, PathBuf};
8use std::sync::OnceLock;
9use std::thread::panicking;
10use std::time::{Instant, SystemTime, UNIX_EPOCH};
11use std::{env, fs, io, panic, str};
12
13use build_helper::ci::CiEnv;
14use object::read::archive::ArchiveFile;
15pub(crate) use shim_utils::{dylib_path, dylib_path_var};
16
17use crate::core::builder::Builder;
18use crate::core::config::{Config, TargetSelection};
19use crate::utils::exec::{BootstrapCommand, command};
20use crate::{BootstrapOverrideLld, StepStack};
21
22#[cfg(test)]
23mod tests;
24
25pub struct PanicTracker<'a>(pub &'a panic::Location<'a>);
28
29impl Drop for PanicTracker<'_> {
30 fn drop(&mut self) {
31 if panicking() {
32 eprintln!(
33 "Panic was initiated from {}:{}:{}",
34 self.0.file(),
35 self.0.line(),
36 self.0.column()
37 );
38 }
39 }
40}
41
42#[macro_export]
51macro_rules! t {
52 ($e:expr) => {{
53 let _panic_guard = $crate::PanicTracker(std::panic::Location::caller());
54 match $e {
55 Ok(e) => e,
56 Err(e) => panic!("{} failed with {}", stringify!($e), e),
57 }
58 }};
59 ($e:expr, $extra:expr) => {{
61 let _panic_guard = $crate::PanicTracker(std::panic::Location::caller());
62 match $e {
63 Ok(e) => e,
64 Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
65 }
66 }};
67}
68
69pub use t;
70pub fn exe(name: &str, target: TargetSelection) -> String {
71 shim_utils::exe(name, &target.triple)
72}
73
74pub fn split_debuginfo(name: impl Into<PathBuf>) -> Option<PathBuf> {
76 let path = name.into();
79 let pdb = path.with_extension("pdb");
80 if pdb.exists() {
81 return Some(pdb);
82 }
83
84 let file_name = pdb.file_name()?.to_str()?.replace("-", "_");
86
87 let pdb: PathBuf = [path.parent()?, Path::new(&file_name)].into_iter().collect();
88 pdb.exists().then_some(pdb)
89}
90
91pub fn is_dylib(path: &Path) -> bool {
93 path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| {
94 ext == "dylib" || ext == "so" || ext == "dll" || (ext == "a" && is_aix_shared_archive(path))
95 })
96}
97
98pub fn submodule_path_of(builder: &Builder<'_>, path: &str) -> Option<String> {
100 submodule_path_of_paths(builder.submodule_paths(), path)
101}
102
103fn submodule_path_of_paths(submodule_paths: &[String], path: &str) -> Option<String> {
104 let path = Path::new(path);
105 submodule_paths.iter().find_map(|submodule_path| {
106 if path.starts_with(submodule_path) { Some(submodule_path.to_string()) } else { None }
107 })
108}
109
110fn is_aix_shared_archive(path: &Path) -> bool {
111 let file = match fs::File::open(path) {
112 Ok(file) => file,
113 Err(_) => return false,
114 };
115 let reader = object::ReadCache::new(file);
116 let archive = match ArchiveFile::parse(&reader) {
117 Ok(result) => result,
118 Err(_) => return false,
119 };
120
121 archive
122 .members()
123 .filter_map(Result::ok)
124 .any(|entry| String::from_utf8_lossy(entry.name()).contains(".so"))
125}
126
127pub fn is_debug_info(name: &str) -> bool {
129 name.ends_with(".pdb")
131}
132
133pub fn libdir(target: TargetSelection) -> &'static str {
136 if target.is_windows() || target.contains("cygwin") { "bin" } else { "lib" }
137}
138
139pub fn add_dylib_path(path: Vec<PathBuf>, cmd: &mut BootstrapCommand) {
142 let mut list = dylib_path();
143 for path in path {
144 list.insert(0, path);
145 }
146 cmd.env(dylib_path_var(), t!(env::join_paths(list)));
147}
148
149pub struct TimeIt(bool, Instant);
150
151pub fn timeit(builder: &Builder<'_>) -> TimeIt {
153 TimeIt(builder.config.dry_run(), Instant::now())
154}
155
156impl Drop for TimeIt {
157 fn drop(&mut self) {
158 let time = self.1.elapsed();
159 if !self.0 {
160 println!("\tfinished in {}.{:03} seconds", time.as_secs(), time.subsec_millis());
161 }
162 }
163}
164
165pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<()> {
168 if config.dry_run() {
169 return Ok(());
170 }
171 let _ = fs::remove_dir_all(link);
172 return symlink_dir_inner(original, link);
173
174 #[cfg(not(windows))]
175 fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> {
176 use std::os::unix::fs;
177 fs::symlink(original, link)
178 }
179
180 #[cfg(windows)]
181 fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
182 junction::create(target, junction)
183 }
184}
185
186pub fn get_host_target() -> TargetSelection {
188 TargetSelection::from_user(env!("BUILD_TRIPLE"))
189}
190
191pub fn move_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
194 match fs::rename(&from, &to) {
195 Err(e) if e.kind() == io::ErrorKind::CrossesDevices => {
196 std::fs::copy(&from, &to)?;
197 std::fs::remove_file(&from)
198 }
199 r => r,
200 }
201}
202
203pub fn forcing_clang_based_tests() -> bool {
204 if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") {
205 match &var.to_string_lossy().to_lowercase()[..] {
206 "1" | "yes" | "on" => true,
207 "0" | "no" | "off" => false,
208 other => {
209 panic!(
211 "Unrecognized option '{other}' set in \
212 RUSTBUILD_FORCE_CLANG_BASED_TESTS"
213 )
214 }
215 }
216 } else {
217 false
218 }
219}
220
221pub fn use_host_linker(target: TargetSelection) -> bool {
222 !(target.contains("emscripten")
225 || target.contains("wasm32")
226 || target.contains("nvptx")
227 || target.contains("fortanix")
228 || target.contains("fuchsia")
229 || target.contains("bpf")
230 || target.contains("switch")
231 || target.contains("l4re"))
232}
233
234pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool {
235 if target.contains("linux") {
236 target.contains("x86_64")
237 || target.contains("aarch64")
238 || target.contains("s390x")
239 || target.contains("riscv64gc")
240 } else if target.contains("darwin") {
241 target.contains("x86_64") || target.contains("aarch64")
242 } else if target.is_windows() {
243 target.contains("x86_64")
244 } else {
245 false
246 }
247}
248
249pub enum TestFilterCategory<'a> {
252 Fullsuite,
254 Arg(&'a str),
257 Uninteresting,
259}
260
261pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
262 path: &'a Path,
263 suite_path: P,
264 builder: &Builder<'_>,
265) -> TestFilterCategory<'a> {
266 let suite_path = suite_path.as_ref();
267 let path = match path.strip_prefix(".") {
268 Ok(p) => p,
269 Err(_) => path,
270 };
271 if !path.starts_with(suite_path) {
272 return TestFilterCategory::Uninteresting;
273 }
274 let abs_path = builder.src.join(path);
275 let exists = abs_path.is_dir() || abs_path.is_file();
276 if !exists {
277 panic!(
278 "Invalid test suite filter \"{}\": file or directory does not exist",
279 abs_path.display()
280 );
281 }
282 match path.strip_prefix(suite_path).ok().and_then(|p| p.to_str()) {
289 Some(s) if !s.is_empty() => TestFilterCategory::Arg(s),
290 _ => TestFilterCategory::Fullsuite,
291 }
292}
293
294pub fn make(host: &str) -> PathBuf {
295 if host.contains("dragonfly")
296 || host.contains("freebsd")
297 || host.contains("netbsd")
298 || host.contains("openbsd")
299 {
300 PathBuf::from("gmake")
301 } else {
302 PathBuf::from("make")
303 }
304}
305
306pub fn mtime(path: &Path) -> SystemTime {
308 fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
309}
310
311pub fn up_to_date(src: &Path, dst: &Path) -> bool {
316 if !dst.exists() {
317 return false;
318 }
319 let threshold = mtime(dst);
320 let meta = match fs::metadata(src) {
321 Ok(meta) => meta,
322 Err(e) => panic!("source {src:?} failed to get metadata: {e}"),
323 };
324 if meta.is_dir() {
325 dir_up_to_date(src, threshold)
326 } else {
327 meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
328 }
329}
330
331pub fn unhashed_basename(obj: &Path) -> &str {
336 let basename = obj.file_stem().unwrap().to_str().expect("UTF-8 file name");
337 basename.split_once('-').unwrap().1
338}
339
340fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
341 t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
342 let meta = t!(e.metadata());
343 if meta.is_dir() {
344 dir_up_to_date(&e.path(), threshold)
345 } else {
346 meta.modified().unwrap_or(UNIX_EPOCH) < threshold
347 }
348 })
349}
350
351pub fn get_clang_cl_resource_dir(builder: &Builder<'_>, clang_cl_path: &str) -> PathBuf {
357 let mut builtins_locator = command(clang_cl_path);
360 builtins_locator.args(["/clang:-print-libgcc-file-name", "/clang:--rtlib=compiler-rt"]);
361
362 let clang_rt_builtins = builtins_locator.run_capture_stdout(builder).stdout();
363 let clang_rt_builtins = Path::new(clang_rt_builtins.trim());
364 assert!(
365 clang_rt_builtins.exists(),
366 "`clang-cl` must correctly locate the library runtime directory"
367 );
368
369 let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
372 clang_rt_dir.to_path_buf()
373}
374
375fn lld_flag_no_threads(
379 builder: &Builder<'_>,
380 bootstrap_override_lld: BootstrapOverrideLld,
381 is_windows: bool,
382) -> &'static str {
383 static LLD_NO_THREADS: OnceLock<(&'static str, &'static str)> = OnceLock::new();
384
385 let new_flags = ("/threads:1", "--threads=1");
386 let old_flags = ("/no-threads", "--no-threads");
387
388 let (windows_flag, other_flag) = LLD_NO_THREADS.get_or_init(|| {
389 let newer_version = match bootstrap_override_lld {
390 BootstrapOverrideLld::External => {
391 let mut cmd = command("lld");
392 cmd.arg("-flavor").arg("ld").arg("--version");
393 let out = cmd.run_capture_stdout(builder).stdout();
394 match (out.find(char::is_numeric), out.find('.')) {
395 (Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10,
396 _ => true,
397 }
398 }
399 _ => true,
400 };
401 if newer_version { new_flags } else { old_flags }
402 });
403 if is_windows { windows_flag } else { other_flag }
404}
405
406pub fn dir_is_empty(dir: &Path) -> bool {
407 t!(std::fs::read_dir(dir), dir).next().is_none()
408}
409
410pub fn extract_beta_rev(version: &str) -> Option<String> {
415 let parts = version.splitn(2, "-beta.").collect::<Vec<_>>();
416 parts.get(1).and_then(|s| s.find(' ').map(|p| s[..p].to_string()))
417}
418
419pub enum LldThreads {
420 Yes,
421 No,
422}
423
424pub fn linker_args(
426 builder: &Builder<'_>,
427 target: TargetSelection,
428 lld_threads: LldThreads,
429) -> Vec<String> {
430 let mut args = linker_flags(builder, target, lld_threads);
431
432 if let Some(linker) = builder.linker(target) {
433 args.push(format!("-Clinker={}", linker.display()));
434 }
435
436 args
437}
438
439pub fn linker_flags(
442 builder: &Builder<'_>,
443 target: TargetSelection,
444 lld_threads: LldThreads,
445) -> Vec<String> {
446 let mut args = vec![];
447 if !builder.is_lld_direct_linker(target) && builder.config.bootstrap_override_lld.is_used() {
448 match builder.config.bootstrap_override_lld {
449 BootstrapOverrideLld::External => {
450 args.push("-Clinker-features=+lld".to_string());
451 args.push("-Clink-self-contained=-linker".to_string());
452 args.push("-Zunstable-options".to_string());
453 }
454 BootstrapOverrideLld::SelfContained => {
455 args.push("-Clinker-features=+lld".to_string());
456 args.push("-Clink-self-contained=+linker".to_string());
457 args.push("-Zunstable-options".to_string());
458 }
459 BootstrapOverrideLld::None => unreachable!(),
460 };
461
462 if matches!(lld_threads, LldThreads::No) {
463 args.push(format!(
464 "-Clink-arg=-Wl,{}",
465 lld_flag_no_threads(
466 builder,
467 builder.config.bootstrap_override_lld,
468 target.is_windows()
469 )
470 ));
471 }
472 }
473 args
474}
475
476pub fn add_rustdoc_cargo_linker_args(
477 cmd: &mut BootstrapCommand,
478 builder: &Builder<'_>,
479 target: TargetSelection,
480 lld_threads: LldThreads,
481) {
482 let args = linker_args(builder, target, lld_threads);
483 let mut flags = cmd
484 .get_envs()
485 .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None })
486 .unwrap_or_default()
487 .to_os_string();
488 for arg in args {
489 if !flags.is_empty() {
490 flags.push(" ");
491 }
492 flags.push(arg);
493 }
494 if !flags.is_empty() {
495 cmd.env("RUSTDOCFLAGS", flags);
496 }
497}
498
499pub fn hex_encode<T>(input: T) -> String
501where
502 T: AsRef<[u8]>,
503{
504 use std::fmt::Write;
505
506 input.as_ref().iter().fold(String::with_capacity(input.as_ref().len() * 2), |mut acc, &byte| {
507 write!(&mut acc, "{byte:02x}").expect("Failed to write byte to the hex String.");
508 acc
509 })
510}
511
512pub fn check_cfg_arg(name: &str, values: Option<&[&str]>) -> String {
515 let next = match values {
518 Some(values) => {
519 let mut tmp = values.iter().flat_map(|val| [",", "\"", val, "\""]).collect::<String>();
520
521 tmp.insert_str(1, "values(");
522 tmp.push(')');
523 tmp
524 }
525 None => "".to_string(),
526 };
527 format!("--check-cfg=cfg({name}{next})")
528}
529
530#[track_caller]
538pub fn git(source_dir: Option<&Path>) -> BootstrapCommand {
539 let mut git = command("git");
540 git.cached();
542
543 if let Some(source_dir) = source_dir {
544 git.current_dir(source_dir);
545 git.env_remove("GIT_DIR");
548 git.env_remove("GIT_WORK_TREE")
552 .env_remove("GIT_INDEX_FILE")
553 .env_remove("GIT_OBJECT_DIRECTORY")
554 .env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES");
555 }
556
557 git
558}
559
560pub fn set_file_times<P: AsRef<Path>>(path: P, times: fs::FileTimes) -> io::Result<()> {
562 let f = if cfg!(windows) {
565 fs::File::options().write(true).open(path)?
566 } else {
567 fs::File::open(path)?
568 };
569 f.set_times(times)
570}
571
572pub fn detail_exit(code: i32, is_test: bool) -> ! {
575 if is_test {
577 panic!("status code: {code}");
578 } else {
579 if CiEnv::is_ci() {
582 let bootstrap_args =
584 std::env::args().skip(1).map(|a| a.to_string()).collect::<Vec<_>>().join(" ");
585 eprintln!("Bootstrap failed while executing `{bootstrap_args}`");
586 eprintln!("Currently active steps:");
587 StepStack::with_current(|stack| {
588 for step in stack.get_active_steps() {
589 eprintln!("{} at {}", step.info, step.location);
590 }
591 });
592 }
593
594 std::process::exit(code);
596 }
597}
598
599pub fn fail(s: &str) -> ! {
600 eprintln!("\n\n{s}\n\n");
601 detail_exit(1, cfg!(test));
602}