use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::OnceLock;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use std::{env, fs, io, str};
use build_helper::git::{get_git_merge_base, output_result, GitConfig};
use build_helper::util::fail;
use crate::core::builder::Builder;
use crate::core::config::{Config, TargetSelection};
pub use crate::utils::shared_helpers::{dylib_path, dylib_path_var};
use crate::LldMode;
#[cfg(test)]
mod tests;
#[macro_export]
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
($e:expr, $extra:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
}
};
}
pub use t;
use crate::utils::exec::{command, BootstrapCommand};
pub fn exe(name: &str, target: TargetSelection) -> String {
crate::utils::shared_helpers::exe(name, &target.triple)
}
pub fn is_dylib(name: &str) -> bool {
name.ends_with(".dylib") || name.ends_with(".so") || name.ends_with(".dll")
}
pub fn is_debug_info(name: &str) -> bool {
name.ends_with(".pdb")
}
pub fn libdir(target: TargetSelection) -> &'static str {
if target.is_windows() { "bin" } else { "lib" }
}
pub fn add_dylib_path(path: Vec<PathBuf>, cmd: &mut BootstrapCommand) {
let mut list = dylib_path();
for path in path {
list.insert(0, path);
}
cmd.env(dylib_path_var(), t!(env::join_paths(list)));
}
pub fn add_link_lib_path(path: Vec<PathBuf>, cmd: &mut BootstrapCommand) {
let mut list = link_lib_path();
for path in path {
list.insert(0, path);
}
cmd.env(link_lib_path_var(), t!(env::join_paths(list)));
}
fn link_lib_path_var() -> &'static str {
if cfg!(target_env = "msvc") { "LIB" } else { "LIBRARY_PATH" }
}
fn link_lib_path() -> Vec<PathBuf> {
let var = match env::var_os(link_lib_path_var()) {
Some(v) => v,
None => return vec![],
};
env::split_paths(&var).collect()
}
pub struct TimeIt(bool, Instant);
pub fn timeit(builder: &Builder<'_>) -> TimeIt {
TimeIt(builder.config.dry_run(), Instant::now())
}
impl Drop for TimeIt {
fn drop(&mut self) {
let time = self.1.elapsed();
if !self.0 {
println!("\tfinished in {}.{:03} seconds", time.as_secs(), time.subsec_millis());
}
}
}
pub(crate) fn program_out_of_date(stamp: &Path, key: &str) -> bool {
if !stamp.exists() {
return true;
}
t!(fs::read_to_string(stamp)) != key
}
pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<()> {
if config.dry_run() {
return Ok(());
}
let _ = fs::remove_dir_all(link);
return symlink_dir_inner(original, link);
#[cfg(not(windows))]
fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> {
use std::os::unix::fs;
fs::symlink(original, link)
}
#[cfg(windows)]
fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
junction::create(&target, &junction)
}
}
pub fn move_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
match fs::rename(&from, &to) {
#[cfg(unix)]
Err(e) if e.raw_os_error() == Some(libc::EXDEV) => {
std::fs::copy(&from, &to)?;
std::fs::remove_file(&from)
}
r => r,
}
}
pub fn forcing_clang_based_tests() -> bool {
if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") {
match &var.to_string_lossy().to_lowercase()[..] {
"1" | "yes" | "on" => true,
"0" | "no" | "off" => false,
other => {
panic!(
"Unrecognized option '{other}' set in \
RUSTBUILD_FORCE_CLANG_BASED_TESTS"
)
}
}
} else {
false
}
}
pub fn use_host_linker(target: TargetSelection) -> bool {
!(target.contains("emscripten")
|| target.contains("wasm32")
|| target.contains("nvptx")
|| target.contains("fortanix")
|| target.contains("fuchsia")
|| target.contains("bpf")
|| target.contains("switch"))
}
pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool {
if target.contains("linux") {
target.contains("x86_64")
|| target.contains("aarch64")
|| target.contains("s390x")
|| target.contains("riscv64gc")
} else if target.contains("darwin") {
target.contains("x86_64") || target.contains("aarch64")
} else if target.is_windows() {
target.contains("x86_64")
} else {
false
}
}
pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
path: &'a Path,
suite_path: P,
builder: &Builder<'_>,
) -> Option<&'a str> {
let suite_path = suite_path.as_ref();
let path = match path.strip_prefix(".") {
Ok(p) => p,
Err(_) => path,
};
if !path.starts_with(suite_path) {
return None;
}
let abs_path = builder.src.join(path);
let exists = abs_path.is_dir() || abs_path.is_file();
if !exists {
panic!(
"Invalid test suite filter \"{}\": file or directory does not exist",
abs_path.display()
);
}
match path.strip_prefix(suite_path).ok().and_then(|p| p.to_str()) {
Some(s) if !s.is_empty() => Some(s),
_ => None,
}
}
pub fn check_run(cmd: &mut BootstrapCommand, print_cmd_on_fail: bool) -> bool {
let status = match cmd.as_command_mut().status() {
Ok(status) => status,
Err(e) => {
println!("failed to execute command: {cmd:?}\nERROR: {e}");
return false;
}
};
if !status.success() && print_cmd_on_fail {
println!(
"\n\ncommand did not execute successfully: {cmd:?}\n\
expected success, got: {status}\n\n"
);
}
status.success()
}
pub fn make(host: &str) -> PathBuf {
if host.contains("dragonfly")
|| host.contains("freebsd")
|| host.contains("netbsd")
|| host.contains("openbsd")
{
PathBuf::from("gmake")
} else {
PathBuf::from("make")
}
}
#[track_caller]
pub fn output(cmd: &mut Command) -> String {
let output = match cmd.stderr(Stdio::inherit()).output() {
Ok(status) => status,
Err(e) => fail(&format!("failed to execute command: {cmd:?}\nERROR: {e}")),
};
if !output.status.success() {
panic!(
"command did not execute successfully: {:?}\n\
expected success, got: {}",
cmd, output.status
);
}
String::from_utf8(output.stdout).unwrap()
}
pub fn mtime(path: &Path) -> SystemTime {
fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
}
pub fn up_to_date(src: &Path, dst: &Path) -> bool {
if !dst.exists() {
return false;
}
let threshold = mtime(dst);
let meta = match fs::metadata(src) {
Ok(meta) => meta,
Err(e) => panic!("source {src:?} failed to get metadata: {e}"),
};
if meta.is_dir() {
dir_up_to_date(src, threshold)
} else {
meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
}
}
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());
if meta.is_dir() {
dir_up_to_date(&e.path(), threshold)
} else {
meta.modified().unwrap_or(UNIX_EPOCH) < threshold
}
})
}
pub fn get_clang_cl_resource_dir(builder: &Builder<'_>, clang_cl_path: &str) -> PathBuf {
let mut builtins_locator = command(clang_cl_path);
builtins_locator.args(["/clang:-print-libgcc-file-name", "/clang:--rtlib=compiler-rt"]);
let clang_rt_builtins = builtins_locator.run_capture_stdout(builder).stdout();
let clang_rt_builtins = Path::new(clang_rt_builtins.trim());
assert!(
clang_rt_builtins.exists(),
"`clang-cl` must correctly locate the library runtime directory"
);
let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
clang_rt_dir.to_path_buf()
}
fn lld_flag_no_threads(builder: &Builder<'_>, lld_mode: LldMode, is_windows: bool) -> &'static str {
static LLD_NO_THREADS: OnceLock<(&'static str, &'static str)> = OnceLock::new();
let new_flags = ("/threads:1", "--threads=1");
let old_flags = ("/no-threads", "--no-threads");
let (windows_flag, other_flag) = LLD_NO_THREADS.get_or_init(|| {
let newer_version = match lld_mode {
LldMode::External => {
let mut cmd = command("lld");
cmd.arg("-flavor").arg("ld").arg("--version");
let out = cmd.run_capture_stdout(builder).stdout();
match (out.find(char::is_numeric), out.find('.')) {
(Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10,
_ => true,
}
}
_ => true,
};
if newer_version { new_flags } else { old_flags }
});
if is_windows { windows_flag } else { other_flag }
}
pub fn dir_is_empty(dir: &Path) -> bool {
t!(std::fs::read_dir(dir)).next().is_none()
}
pub fn extract_beta_rev(version: &str) -> Option<String> {
let parts = version.splitn(2, "-beta.").collect::<Vec<_>>();
let count = parts.get(1).and_then(|s| s.find(' ').map(|p| s[..p].to_string()));
count
}
pub enum LldThreads {
Yes,
No,
}
pub fn linker_args(
builder: &Builder<'_>,
target: TargetSelection,
lld_threads: LldThreads,
) -> Vec<String> {
let mut args = linker_flags(builder, target, lld_threads);
if let Some(linker) = builder.linker(target) {
args.push(format!("-Clinker={}", linker.display()));
}
args
}
pub fn linker_flags(
builder: &Builder<'_>,
target: TargetSelection,
lld_threads: LldThreads,
) -> Vec<String> {
let mut args = vec![];
if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() {
args.push(String::from("-Clink-arg=-fuse-ld=lld"));
if matches!(lld_threads, LldThreads::No) {
args.push(format!(
"-Clink-arg=-Wl,{}",
lld_flag_no_threads(builder, builder.config.lld_mode, target.is_windows())
));
}
}
args
}
pub fn add_rustdoc_cargo_linker_args(
cmd: &mut BootstrapCommand,
builder: &Builder<'_>,
target: TargetSelection,
lld_threads: LldThreads,
) {
let args = linker_args(builder, target, lld_threads);
let mut flags = cmd
.get_envs()
.find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None })
.unwrap_or_default()
.to_os_string();
for arg in args {
if !flags.is_empty() {
flags.push(" ");
}
flags.push(arg);
}
if !flags.is_empty() {
cmd.env("RUSTDOCFLAGS", flags);
}
}
pub fn hex_encode<T>(input: T) -> String
where
T: AsRef<[u8]>,
{
use std::fmt::Write;
input.as_ref().iter().fold(String::with_capacity(input.as_ref().len() * 2), |mut acc, &byte| {
write!(&mut acc, "{:02x}", byte).expect("Failed to write byte to the hex String.");
acc
})
}
pub fn check_cfg_arg(name: &str, values: Option<&[&str]>) -> String {
let next = match values {
Some(values) => {
let mut tmp = values.iter().flat_map(|val| [",", "\"", val, "\""]).collect::<String>();
tmp.insert_str(1, "values(");
tmp.push(')');
tmp
}
None => "".to_string(),
};
format!("--check-cfg=cfg({name}{next})")
}
#[track_caller]
pub fn git(source_dir: Option<&Path>) -> BootstrapCommand {
let mut git = command("git");
if let Some(source_dir) = source_dir {
git.current_dir(source_dir);
git.env_remove("GIT_DIR");
git.env_remove("GIT_WORK_TREE")
.env_remove("GIT_INDEX_FILE")
.env_remove("GIT_OBJECT_DIRECTORY")
.env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES");
}
git
}
pub fn get_closest_merge_base_commit(
source_dir: Option<&Path>,
config: &GitConfig<'_>,
author: &str,
target_paths: &[PathBuf],
) -> Result<String, String> {
let mut git = git(source_dir);
let merge_base = get_git_merge_base(config, source_dir).unwrap_or_else(|_| "HEAD".into());
git.args(["rev-list", &format!("--author={author}"), "-n1", "--first-parent", &merge_base]);
if !target_paths.is_empty() {
git.arg("--").args(target_paths);
}
Ok(output_result(git.as_command_mut())?.trim().to_owned())
}
pub fn set_file_times<P: AsRef<Path>>(path: P, times: fs::FileTimes) -> io::Result<()> {
let f = if cfg!(windows) {
fs::File::options().write(true).open(path)?
} else {
fs::File::open(path)?
};
f.set_times(times)
}