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