pub mod cc;
pub mod clang;
mod command;
pub mod diff;
mod drop_bomb;
pub mod fs_wrapper;
pub mod llvm;
pub mod run;
pub mod rustc;
pub mod rustdoc;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::panic;
use std::path::{Path, PathBuf};
pub use bstr;
pub use gimli;
pub use object;
pub use regex;
pub use wasmparser;
pub use cc::{cc, extra_c_flags, extra_cxx_flags, Cc};
pub use clang::{clang, Clang};
pub use diff::{diff, Diff};
pub use llvm::{
llvm_filecheck, llvm_objdump, llvm_profdata, llvm_readobj, LlvmFilecheck, LlvmObjdump,
LlvmProfdata, LlvmReadobj,
};
pub use run::{cmd, run, run_fail, run_with_args};
pub use rustc::{aux_build, bare_rustc, rustc, Rustc};
pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
#[track_caller]
#[must_use]
pub fn env_var(name: &str) -> String {
match env::var(name) {
Ok(v) => v,
Err(err) => panic!("failed to retrieve environment variable {name:?}: {err:?}"),
}
}
#[track_caller]
#[must_use]
pub fn env_var_os(name: &str) -> OsString {
match env::var_os(name) {
Some(v) => v,
None => panic!("failed to retrieve environment variable {name:?}"),
}
}
#[must_use]
pub fn target() -> String {
env_var("TARGET")
}
#[track_caller]
pub fn ar(inputs: &[impl AsRef<Path>], output_path: impl AsRef<Path>) {
let output = fs::File::create(&output_path).expect(&format!(
"the file in path \"{}\" could not be created",
output_path.as_ref().display()
));
let mut builder = ar::Builder::new(output);
for input in inputs {
builder.append_path(input).unwrap();
}
}
#[must_use]
pub fn is_windows() -> bool {
target().contains("windows")
}
#[must_use]
pub fn is_msvc() -> bool {
target().contains("msvc")
}
#[must_use]
pub fn is_darwin() -> bool {
target().contains("darwin")
}
#[track_caller]
#[must_use]
pub fn python_command() -> Command {
let python_path = env_var("PYTHON");
Command::new(python_path)
}
#[track_caller]
#[must_use]
pub fn htmldocck() -> Command {
let mut python = python_command();
python.arg(source_root().join("src/etc/htmldocck.py"));
python
}
pub fn path<P: AsRef<Path>>(p: P) -> PathBuf {
cwd().join(p.as_ref())
}
#[must_use]
pub fn source_root() -> PathBuf {
env_var("SOURCE_ROOT").into()
}
#[cfg(target_family = "windows")]
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
if link.as_ref().exists() {
std::fs::remove_dir(link.as_ref()).unwrap();
}
use std::os::windows::fs;
fs::symlink_file(original.as_ref(), link.as_ref()).expect(&format!(
"failed to create symlink {:?} for {:?}",
link.as_ref().display(),
original.as_ref().display(),
));
}
#[cfg(target_family = "unix")]
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) {
if link.as_ref().exists() {
std::fs::remove_dir(link.as_ref()).unwrap();
}
use std::os::unix::fs;
fs::symlink(original.as_ref(), link.as_ref()).expect(&format!(
"failed to create symlink {:?} for {:?}",
link.as_ref().display(),
original.as_ref().display(),
));
}
#[must_use]
pub fn static_lib_name(name: &str) -> String {
assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace");
if is_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") }
}
#[must_use]
pub fn dynamic_lib_name(name: &str) -> String {
assert!(!name.contains(char::is_whitespace), "dynamic library name cannot contain whitespace");
let extension = dynamic_lib_extension();
if is_darwin() {
format!("lib{name}.{extension}")
} else if is_windows() {
format!("{name}.{extension}")
} else {
format!("lib{name}.{extension}")
}
}
#[must_use]
pub fn dynamic_lib_extension() -> &'static str {
if is_darwin() {
"dylib"
} else if is_windows() {
"dll"
} else {
"so"
}
}
#[must_use]
pub fn rust_lib_name(name: &str) -> String {
format!("lib{name}.rlib")
}
#[must_use]
pub fn bin_name(name: &str) -> String {
if is_windows() { format!("{name}.exe") } else { name.to_string() }
}
#[must_use]
pub fn cwd() -> PathBuf {
env::current_dir().unwrap()
}
#[track_caller]
pub fn test_while_readonly<P: AsRef<Path>, F: FnOnce() + std::panic::UnwindSafe>(
path: P,
closure: F,
) {
let path = path.as_ref();
if is_windows() && path.is_dir() {
eprintln!("This helper function cannot be used on Windows to make directories readonly.");
eprintln!(
"See the official documentation:
https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly"
);
panic!("`test_while_readonly` on directory detected while on Windows.");
}
let metadata = fs_wrapper::metadata(&path);
let original_perms = metadata.permissions();
let mut new_perms = original_perms.clone();
new_perms.set_readonly(true);
fs_wrapper::set_permissions(&path, new_perms);
let success = std::panic::catch_unwind(closure);
fs_wrapper::set_permissions(&path, original_perms);
success.unwrap();
}
#[track_caller]
pub fn shallow_find_files<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>(
path: P,
filter: F,
) -> Vec<PathBuf> {
let mut matching_files = Vec::new();
for entry in fs_wrapper::read_dir(path) {
let entry = entry.expect("failed to read directory entry.");
let path = entry.path();
if path.is_file() && filter(&path) {
matching_files.push(path);
}
}
matching_files
}
pub fn has_prefix<P: AsRef<Path>>(path: P, prefix: &str) -> bool {
path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().starts_with(prefix))
}
pub fn has_extension<P: AsRef<Path>>(path: P, extension: &str) -> bool {
path.as_ref().extension().is_some_and(|ext| ext == extension)
}
pub fn not_contains<P: AsRef<Path>>(path: P, expected: &str) -> bool {
!path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(expected))
}
pub fn filename_not_in_denylist<P: AsRef<Path>, V: AsRef<[String]>>(path: P, expected: V) -> bool {
let expected = expected.as_ref();
path.as_ref()
.file_name()
.is_some_and(|name| !expected.contains(&name.to_str().unwrap().to_owned()))
}
pub fn count_regex_matches_in_files_with_extension(re: ®ex::Regex, ext: &str) -> usize {
let fetched_files = shallow_find_files(cwd(), |path| has_extension(path, ext));
let mut count = 0;
for file in fetched_files {
let content = fs_wrapper::read_to_string(file);
count += content.lines().filter(|line| re.is_match(&line)).count();
}
count
}
#[track_caller]
#[must_use]
pub fn cygpath_windows<P: AsRef<Path>>(path: P) -> String {
let caller = panic::Location::caller();
let mut cygpath = Command::new("cygpath");
cygpath.arg("-w");
cygpath.arg(path.as_ref());
let output = cygpath.run();
if !output.status().success() {
handle_failed_output(&cygpath, output, caller.line());
}
output.stdout_utf8().trim().to_string()
}
#[track_caller]
#[must_use]
pub fn uname() -> String {
let caller = panic::Location::caller();
let mut uname = Command::new("uname");
let output = uname.run();
if !output.status().success() {
handle_failed_output(&uname, output, caller.line());
}
output.stdout_utf8()
}
fn handle_failed_output(cmd: &Command, output: CompletedProcess, caller_line_number: u32) -> ! {
if output.status().success() {
eprintln!("command unexpectedly succeeded at line {caller_line_number}");
} else {
eprintln!("command failed at line {caller_line_number}");
}
eprintln!("{cmd:?}");
eprintln!("output status: `{}`", output.status());
eprintln!("=== STDOUT ===\n{}\n\n", output.stdout_utf8());
eprintln!("=== STDERR ===\n{}\n\n", output.stderr_utf8());
std::process::exit(1)
}
pub fn set_host_rpath(cmd: &mut Command) {
let ld_lib_path_envvar = env_var("LD_LIB_PATH_ENVVAR");
cmd.env(&ld_lib_path_envvar, {
let mut paths = vec![];
paths.push(cwd());
paths.push(PathBuf::from(env_var("HOST_RPATH_DIR")));
for p in env::split_paths(&env_var(&ld_lib_path_envvar)) {
paths.push(p.to_path_buf());
}
env::join_paths(paths.iter()).unwrap()
});
}
#[track_caller]
pub fn invalid_utf8_contains<P: AsRef<Path>, S: AsRef<str>>(path: P, expected: S) {
let buffer = fs_wrapper::read(path.as_ref());
let expected = expected.as_ref();
if !String::from_utf8_lossy(&buffer).contains(expected) {
eprintln!("=== FILE CONTENTS (LOSSY) ===");
eprintln!("{}", String::from_utf8_lossy(&buffer));
eprintln!("=== SPECIFIED TEXT ===");
eprintln!("{}", expected);
panic!("specified text was not found in file");
}
}
#[track_caller]
pub fn invalid_utf8_not_contains<P: AsRef<Path>, S: AsRef<str>>(path: P, expected: S) {
let buffer = fs_wrapper::read(path.as_ref());
let expected = expected.as_ref();
if String::from_utf8_lossy(&buffer).contains(expected) {
eprintln!("=== FILE CONTENTS (LOSSY) ===");
eprintln!("{}", String::from_utf8_lossy(&buffer));
eprintln!("=== SPECIFIED TEXT ===");
eprintln!("{}", expected);
panic!("specified text was unexpectedly found in file");
}
}
pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
fn copy_dir_all_inner(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
let dst = dst.as_ref();
if !dst.is_dir() {
std::fs::create_dir_all(&dst)?;
}
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all_inner(entry.path(), dst.join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
if let Err(e) = copy_dir_all_inner(&src, &dst) {
panic!(
"failed to copy `{}` to `{}`: {:?}",
src.as_ref().display(),
dst.as_ref().display(),
e
);
}
}
pub fn recursive_diff(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) {
let dir2 = dir2.as_ref();
read_dir(dir1, |entry_path| {
let entry_name = entry_path.file_name().unwrap();
if entry_path.is_dir() {
recursive_diff(&entry_path, &dir2.join(entry_name));
} else {
let path2 = dir2.join(entry_name);
let file1 = fs_wrapper::read(&entry_path);
let file2 = fs_wrapper::read(&path2);
assert!(
file1 == file2,
"`{}` and `{}` have different content",
entry_path.display(),
path2.display(),
);
}
});
}
pub fn read_dir<F: FnMut(&Path)>(dir: impl AsRef<Path>, mut callback: F) {
for entry in fs_wrapper::read_dir(dir) {
callback(&entry.unwrap().path());
}
}
#[track_caller]
pub fn assert_equals<S1: AsRef<str>, S2: AsRef<str>>(actual: S1, expected: S2) {
let actual = actual.as_ref();
let expected = expected.as_ref();
if actual != expected {
eprintln!("=== ACTUAL TEXT ===");
eprintln!("{}", actual);
eprintln!("=== EXPECTED ===");
eprintln!("{}", expected);
panic!("expected text was not found in actual text");
}
}
#[track_caller]
pub fn assert_contains<S1: AsRef<str>, S2: AsRef<str>>(haystack: S1, needle: S2) {
let haystack = haystack.as_ref();
let needle = needle.as_ref();
if !haystack.contains(needle) {
eprintln!("=== HAYSTACK ===");
eprintln!("{}", haystack);
eprintln!("=== NEEDLE ===");
eprintln!("{}", needle);
panic!("needle was not found in haystack");
}
}
#[track_caller]
pub fn assert_not_contains<S1: AsRef<str>, S2: AsRef<str>>(haystack: S1, needle: S2) {
let haystack = haystack.as_ref();
let needle = needle.as_ref();
if haystack.contains(needle) {
eprintln!("=== HAYSTACK ===");
eprintln!("{}", haystack);
eprintln!("=== NEEDLE ===");
eprintln!("{}", needle);
panic!("needle was unexpectedly found in haystack");
}
}
pub fn run_in_tmpdir<F: FnOnce()>(callback: F) {
let original_dir = cwd();
let tmpdir = original_dir.join("../temporary-directory");
copy_dir_all(".", &tmpdir);
env::set_current_dir(&tmpdir).unwrap();
callback();
env::set_current_dir(original_dir).unwrap();
fs::remove_dir_all(tmpdir).unwrap();
}
macro_rules! impl_common_helpers {
($wrapper: ident) => {
impl $wrapper {
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: AsRef<::std::ffi::OsStr>,
V: AsRef<::std::ffi::OsStr>,
{
self.cmd.env(key, value);
self
}
pub fn env_remove<K>(&mut self, key: K) -> &mut Self
where
K: AsRef<::std::ffi::OsStr>,
{
self.cmd.env_remove(key);
self
}
pub fn arg<S>(&mut self, arg: S) -> &mut Self
where
S: AsRef<::std::ffi::OsStr>,
{
self.cmd.arg(arg);
self
}
pub fn args<V, S>(&mut self, args: V) -> &mut Self
where
V: AsRef<[S]>,
S: AsRef<::std::ffi::OsStr>,
{
self.cmd.args(args.as_ref());
self
}
pub fn inspect<I>(&mut self, inspector: I) -> &mut Self
where
I: FnOnce(&::std::process::Command),
{
self.cmd.inspect(inspector);
self
}
#[track_caller]
pub fn run(&mut self) -> crate::command::CompletedProcess {
self.cmd.run()
}
#[track_caller]
pub fn run_fail(&mut self) -> crate::command::CompletedProcess {
self.cmd.run_fail()
}
#[track_caller]
pub fn run_unchecked(&mut self) -> crate::command::CompletedProcess {
self.cmd.run_unchecked()
}
pub fn current_dir<P: AsRef<::std::path::Path>>(&mut self, path: P) -> &mut Self {
self.cmd.current_dir(path);
self
}
}
};
}
use crate::command::{Command, CompletedProcess};
pub(crate) use impl_common_helpers;