1use std::ffi::OsStr;
7use std::path::{Path, PathBuf};
8use std::process::{Command, Stdio};
9use std::sync::OnceLock;
10use std::thread::panicking;
11use std::time::{Instant, SystemTime, UNIX_EPOCH};
12use std::{env, fs, io, panic, str};
13
14use build_helper::util::fail;
15use object::read::archive::ArchiveFile;
16
17use crate::LldMode;
18use crate::core::builder::Builder;
19use crate::core::config::{Config, TargetSelection};
20use crate::utils::exec::{BootstrapCommand, command};
21pub use crate::utils::shared_helpers::{dylib_path, dylib_path_var};
22
23#[cfg(test)]
24mod tests;
25
26pub struct PanicTracker<'a>(pub &'a panic::Location<'a>);
29
30impl Drop for PanicTracker<'_> {
31 fn drop(&mut self) {
32 if panicking() {
33 eprintln!(
34 "Panic was initiated from {}:{}:{}",
35 self.0.file(),
36 self.0.line(),
37 self.0.column()
38 );
39 }
40 }
41}
42
43#[macro_export]
52macro_rules! t {
53 ($e:expr) => {{
54 let _panic_guard = $crate::PanicTracker(std::panic::Location::caller());
55 match $e {
56 Ok(e) => e,
57 Err(e) => panic!("{} failed with {}", stringify!($e), e),
58 }
59 }};
60 ($e:expr, $extra:expr) => {{
62 let _panic_guard = $crate::PanicTracker(std::panic::Location::caller());
63 match $e {
64 Ok(e) => e,
65 Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
66 }
67 }};
68}
69
70pub use t;
71pub fn exe(name: &str, target: TargetSelection) -> String {
72 crate::utils::shared_helpers::exe(name, &target.triple)
73}
74
75pub fn split_debuginfo(name: impl Into<PathBuf>) -> Option<PathBuf> {
77 let path = name.into();
80 let pdb = path.with_extension("pdb");
81 if pdb.exists() {
82 return Some(pdb);
83 }
84
85 let file_name = pdb.file_name()?.to_str()?.replace("-", "_");
87
88 let pdb: PathBuf = [path.parent()?, Path::new(&file_name)].into_iter().collect();
89 pdb.exists().then_some(pdb)
90}
91
92pub fn is_dylib(path: &Path) -> bool {
94 path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| {
95 ext == "dylib" || ext == "so" || ext == "dll" || (ext == "a" && is_aix_shared_archive(path))
96 })
97}
98
99pub fn submodule_path_of(builder: &Builder<'_>, path: &str) -> Option<String> {
101 let submodule_paths = build_helper::util::parse_gitmodules(&builder.src);
102 submodule_paths.iter().find_map(|submodule_path| {
103 if path.starts_with(submodule_path) { Some(submodule_path.to_string()) } else { None }
104 })
105}
106
107fn is_aix_shared_archive(path: &Path) -> bool {
108 let file = match fs::File::open(path) {
109 Ok(file) => file,
110 Err(_) => return false,
111 };
112 let reader = object::ReadCache::new(file);
113 let archive = match ArchiveFile::parse(&reader) {
114 Ok(result) => result,
115 Err(_) => return false,
116 };
117
118 archive
119 .members()
120 .filter_map(Result::ok)
121 .any(|entry| String::from_utf8_lossy(entry.name()).contains(".so"))
122}
123
124pub fn is_debug_info(name: &str) -> bool {
126 name.ends_with(".pdb")
128}
129
130pub fn libdir(target: TargetSelection) -> &'static str {
133 if target.is_windows() || target.contains("cygwin") { "bin" } else { "lib" }
134}
135
136pub fn add_dylib_path(path: Vec<PathBuf>, cmd: &mut BootstrapCommand) {
139 let mut list = dylib_path();
140 for path in path {
141 list.insert(0, path);
142 }
143 cmd.env(dylib_path_var(), t!(env::join_paths(list)));
144}
145
146pub struct TimeIt(bool, Instant);
147
148pub fn timeit(builder: &Builder<'_>) -> TimeIt {
150 TimeIt(builder.config.dry_run(), Instant::now())
151}
152
153impl Drop for TimeIt {
154 fn drop(&mut self) {
155 let time = self.1.elapsed();
156 if !self.0 {
157 println!("\tfinished in {}.{:03} seconds", time.as_secs(), time.subsec_millis());
158 }
159 }
160}
161
162pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<()> {
165 if config.dry_run() {
166 return Ok(());
167 }
168 let _ = fs::remove_dir_all(link);
169 return symlink_dir_inner(original, link);
170
171 #[cfg(not(windows))]
172 fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> {
173 use std::os::unix::fs;
174 fs::symlink(original, link)
175 }
176
177 #[cfg(windows)]
178 fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
179 junction::create(target, junction)
180 }
181}
182
183pub fn move_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
186 match fs::rename(&from, &to) {
187 Err(e) if e.kind() == io::ErrorKind::CrossesDevices => {
188 std::fs::copy(&from, &to)?;
189 std::fs::remove_file(&from)
190 }
191 r => r,
192 }
193}
194
195pub fn forcing_clang_based_tests() -> bool {
196 if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") {
197 match &var.to_string_lossy().to_lowercase()[..] {
198 "1" | "yes" | "on" => true,
199 "0" | "no" | "off" => false,
200 other => {
201 panic!(
203 "Unrecognized option '{other}' set in \
204 RUSTBUILD_FORCE_CLANG_BASED_TESTS"
205 )
206 }
207 }
208 } else {
209 false
210 }
211}
212
213pub fn use_host_linker(target: TargetSelection) -> bool {
214 !(target.contains("emscripten")
217 || target.contains("wasm32")
218 || target.contains("nvptx")
219 || target.contains("fortanix")
220 || target.contains("fuchsia")
221 || target.contains("bpf")
222 || target.contains("switch"))
223}
224
225pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool {
226 if target.contains("linux") {
227 target.contains("x86_64")
228 || target.contains("aarch64")
229 || target.contains("s390x")
230 || target.contains("riscv64gc")
231 } else if target.contains("darwin") {
232 target.contains("x86_64") || target.contains("aarch64")
233 } else if target.is_windows() {
234 target.contains("x86_64")
235 } else {
236 false
237 }
238}
239
240pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
241 path: &'a Path,
242 suite_path: P,
243 builder: &Builder<'_>,
244) -> Option<&'a str> {
245 let suite_path = suite_path.as_ref();
246 let path = match path.strip_prefix(".") {
247 Ok(p) => p,
248 Err(_) => path,
249 };
250 if !path.starts_with(suite_path) {
251 return None;
252 }
253 let abs_path = builder.src.join(path);
254 let exists = abs_path.is_dir() || abs_path.is_file();
255 if !exists {
256 panic!(
257 "Invalid test suite filter \"{}\": file or directory does not exist",
258 abs_path.display()
259 );
260 }
261 match path.strip_prefix(suite_path).ok().and_then(|p| p.to_str()) {
268 Some(s) if !s.is_empty() => Some(s),
269 _ => None,
270 }
271}
272
273pub fn check_run(cmd: &mut BootstrapCommand, print_cmd_on_fail: bool) -> bool {
275 let status = match cmd.as_command_mut().status() {
276 Ok(status) => status,
277 Err(e) => {
278 println!("failed to execute command: {cmd:?}\nERROR: {e}");
279 return false;
280 }
281 };
282 if !status.success() && print_cmd_on_fail {
283 println!(
284 "\n\ncommand did not execute successfully: {cmd:?}\n\
285 expected success, got: {status}\n\n"
286 );
287 }
288 status.success()
289}
290
291pub fn make(host: &str) -> PathBuf {
292 if host.contains("dragonfly")
293 || host.contains("freebsd")
294 || host.contains("netbsd")
295 || host.contains("openbsd")
296 {
297 PathBuf::from("gmake")
298 } else {
299 PathBuf::from("make")
300 }
301}
302
303#[track_caller]
304pub fn output(cmd: &mut Command) -> String {
305 #[cfg(feature = "tracing")]
306 let _run_span = crate::trace_cmd!(cmd);
307
308 let output = match cmd.stderr(Stdio::inherit()).output() {
309 Ok(status) => status,
310 Err(e) => fail(&format!("failed to execute command: {cmd:?}\nERROR: {e}")),
311 };
312 if !output.status.success() {
313 panic!(
314 "command did not execute successfully: {:?}\n\
315 expected success, got: {}",
316 cmd, output.status
317 );
318 }
319 String::from_utf8(output.stdout).unwrap()
320}
321
322#[track_caller]
326pub fn start_process(cmd: &mut Command) -> impl FnOnce() -> String + use<> {
327 let child = match cmd.stderr(Stdio::inherit()).stdout(Stdio::piped()).spawn() {
328 Ok(child) => child,
329 Err(e) => fail(&format!("failed to execute command: {cmd:?}\nERROR: {e}")),
330 };
331
332 let command = format!("{cmd:?}");
333
334 move || {
335 let output = child.wait_with_output().unwrap();
336
337 if !output.status.success() {
338 panic!(
339 "command did not execute successfully: {}\n\
340 expected success, got: {}",
341 command, output.status
342 );
343 }
344
345 String::from_utf8(output.stdout).unwrap()
346 }
347}
348
349pub fn mtime(path: &Path) -> SystemTime {
351 fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
352}
353
354pub fn up_to_date(src: &Path, dst: &Path) -> bool {
359 if !dst.exists() {
360 return false;
361 }
362 let threshold = mtime(dst);
363 let meta = match fs::metadata(src) {
364 Ok(meta) => meta,
365 Err(e) => panic!("source {src:?} failed to get metadata: {e}"),
366 };
367 if meta.is_dir() {
368 dir_up_to_date(src, threshold)
369 } else {
370 meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
371 }
372}
373
374pub fn unhashed_basename(obj: &Path) -> &str {
379 let basename = obj.file_stem().unwrap().to_str().expect("UTF-8 file name");
380 basename.split_once('-').unwrap().1
381}
382
383fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
384 t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
385 let meta = t!(e.metadata());
386 if meta.is_dir() {
387 dir_up_to_date(&e.path(), threshold)
388 } else {
389 meta.modified().unwrap_or(UNIX_EPOCH) < threshold
390 }
391 })
392}
393
394pub fn get_clang_cl_resource_dir(builder: &Builder<'_>, clang_cl_path: &str) -> PathBuf {
400 let mut builtins_locator = command(clang_cl_path);
403 builtins_locator.args(["/clang:-print-libgcc-file-name", "/clang:--rtlib=compiler-rt"]);
404
405 let clang_rt_builtins = builtins_locator.run_capture_stdout(builder).stdout();
406 let clang_rt_builtins = Path::new(clang_rt_builtins.trim());
407 assert!(
408 clang_rt_builtins.exists(),
409 "`clang-cl` must correctly locate the library runtime directory"
410 );
411
412 let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
415 clang_rt_dir.to_path_buf()
416}
417
418fn lld_flag_no_threads(builder: &Builder<'_>, lld_mode: LldMode, is_windows: bool) -> &'static str {
422 static LLD_NO_THREADS: OnceLock<(&'static str, &'static str)> = OnceLock::new();
423
424 let new_flags = ("/threads:1", "--threads=1");
425 let old_flags = ("/no-threads", "--no-threads");
426
427 let (windows_flag, other_flag) = LLD_NO_THREADS.get_or_init(|| {
428 let newer_version = match lld_mode {
429 LldMode::External => {
430 let mut cmd = command("lld");
431 cmd.arg("-flavor").arg("ld").arg("--version");
432 let out = cmd.run_capture_stdout(builder).stdout();
433 match (out.find(char::is_numeric), out.find('.')) {
434 (Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10,
435 _ => true,
436 }
437 }
438 _ => true,
439 };
440 if newer_version { new_flags } else { old_flags }
441 });
442 if is_windows { windows_flag } else { other_flag }
443}
444
445pub fn dir_is_empty(dir: &Path) -> bool {
446 t!(std::fs::read_dir(dir), dir).next().is_none()
447}
448
449pub fn extract_beta_rev(version: &str) -> Option<String> {
454 let parts = version.splitn(2, "-beta.").collect::<Vec<_>>();
455 parts.get(1).and_then(|s| s.find(' ').map(|p| s[..p].to_string()))
456}
457
458pub enum LldThreads {
459 Yes,
460 No,
461}
462
463pub fn linker_args(
465 builder: &Builder<'_>,
466 target: TargetSelection,
467 lld_threads: LldThreads,
468) -> Vec<String> {
469 let mut args = linker_flags(builder, target, lld_threads);
470
471 if let Some(linker) = builder.linker(target) {
472 args.push(format!("-Clinker={}", linker.display()));
473 }
474
475 args
476}
477
478pub fn linker_flags(
481 builder: &Builder<'_>,
482 target: TargetSelection,
483 lld_threads: LldThreads,
484) -> Vec<String> {
485 let mut args = vec![];
486 if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() {
487 match builder.config.lld_mode {
488 LldMode::External => {
489 args.push("-Zlinker-features=+lld".to_string());
490 args.push("-Zunstable-options".to_string());
492 }
493 LldMode::SelfContained => {
494 args.push("-Zlinker-features=+lld".to_string());
495 args.push("-Clink-self-contained=+linker".to_string());
496 args.push("-Zunstable-options".to_string());
498 }
499 LldMode::Unused => unreachable!(),
500 };
501
502 if matches!(lld_threads, LldThreads::No) {
503 args.push(format!(
504 "-Clink-arg=-Wl,{}",
505 lld_flag_no_threads(builder, builder.config.lld_mode, target.is_windows())
506 ));
507 }
508 }
509 args
510}
511
512pub fn add_rustdoc_cargo_linker_args(
513 cmd: &mut BootstrapCommand,
514 builder: &Builder<'_>,
515 target: TargetSelection,
516 lld_threads: LldThreads,
517) {
518 let args = linker_args(builder, target, lld_threads);
519 let mut flags = cmd
520 .get_envs()
521 .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None })
522 .unwrap_or_default()
523 .to_os_string();
524 for arg in args {
525 if !flags.is_empty() {
526 flags.push(" ");
527 }
528 flags.push(arg);
529 }
530 if !flags.is_empty() {
531 cmd.env("RUSTDOCFLAGS", flags);
532 }
533}
534
535pub fn hex_encode<T>(input: T) -> String
537where
538 T: AsRef<[u8]>,
539{
540 use std::fmt::Write;
541
542 input.as_ref().iter().fold(String::with_capacity(input.as_ref().len() * 2), |mut acc, &byte| {
543 write!(&mut acc, "{byte:02x}").expect("Failed to write byte to the hex String.");
544 acc
545 })
546}
547
548pub fn check_cfg_arg(name: &str, values: Option<&[&str]>) -> String {
551 let next = match values {
554 Some(values) => {
555 let mut tmp = values.iter().flat_map(|val| [",", "\"", val, "\""]).collect::<String>();
556
557 tmp.insert_str(1, "values(");
558 tmp.push(')');
559 tmp
560 }
561 None => "".to_string(),
562 };
563 format!("--check-cfg=cfg({name}{next})")
564}
565
566#[track_caller]
574pub fn git(source_dir: Option<&Path>) -> BootstrapCommand {
575 let mut git = command("git");
576
577 if let Some(source_dir) = source_dir {
578 git.current_dir(source_dir);
579 git.env_remove("GIT_DIR");
582 git.env_remove("GIT_WORK_TREE")
586 .env_remove("GIT_INDEX_FILE")
587 .env_remove("GIT_OBJECT_DIRECTORY")
588 .env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES");
589 }
590
591 git
592}
593
594pub fn set_file_times<P: AsRef<Path>>(path: P, times: fs::FileTimes) -> io::Result<()> {
596 let f = if cfg!(windows) {
599 fs::File::options().write(true).open(path)?
600 } else {
601 fs::File::open(path)?
602 };
603 f.set_times(times)
604}