use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use camino::{Utf8Path, Utf8PathBuf};
#[cfg(windows)]
pub(super) const LOADER_VAR: &str = "PATH";
#[cfg(target_os = "macos")]
pub(super) const LOADER_VAR: &str = "DYLD_FALLBACK_LIBRARY_PATH";
#[cfg(not(any(windows, target_os = "macos")))]
pub(super) const LOADER_VAR: &str = "LD_LIBRARY_PATH";
pub const UNDER_GAMMA_VAR: &str = "CARGO_GAMMA";
pub(super) const STACK_VAR: &str = "RUST_MIN_STACK";
const STACK_FLOOR: usize = 16 * 1024 * 1024;
#[derive(Debug)]
pub(super) struct Launch {
pub(super) loader: Option<OsString>,
pub(super) stack: String,
}
impl Launch {
pub(super) fn derive(libraries: &[Utf8PathBuf]) -> Self {
Self {
loader: loader_path(libraries),
stack: stack_floor(env::var(STACK_VAR).ok().as_deref()),
}
}
}
pub(super) fn configure_loader(command: &mut Command, launch: &Launch) {
if let Some(path) = launch.loader.as_ref() {
let _ = command.env(LOADER_VAR, path);
}
}
fn stack_floor(inherited: Option<&str>) -> String {
let inherited = inherited.and_then(|value| value.trim().parse::<usize>().ok());
inherited.unwrap_or(0).max(STACK_FLOOR).to_string()
}
pub(super) fn loader_path(libraries: &[Utf8PathBuf]) -> Option<OsString> {
joined(libraries, env::var_os(LOADER_VAR))
}
fn joined(libraries: &[Utf8PathBuf], existing: Option<OsString>) -> Option<OsString> {
if libraries.is_empty() {
return None;
}
let paths = libraries.iter().map(|path| PathBuf::from(path.as_str()));
existing.map_or_else(
|| env::join_paths(paths.clone()).ok(),
|current| {
let all = paths.clone().chain(env::split_paths(¤t));
env::join_paths(all).ok()
},
)
}
pub(super) fn toolchain_libraries(root: &Utf8Path, target: &Utf8Path) -> Vec<Utf8PathBuf> {
let mut libraries = vec![target.join("debug").join("deps")];
let output = Command::new(env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()))
.current_dir(root.as_std_path())
.args(["--print", "target-libdir", "--print", "sysroot"])
.output();
if let Ok(output) = output
&& output.status.success()
&& let Ok(printed) = String::from_utf8(output.stdout)
{
let mut lines = printed.lines();
if let Some(libdir) = lines.next() {
libraries.push(Utf8PathBuf::from(libdir.trim()));
}
if let Some(sysroot) = lines.next() {
libraries.push(Utf8PathBuf::from(sysroot.trim()).join("lib"));
}
}
libraries
}
#[cfg(test)]
#[cfg(not(miri))]
mod tests {
use super::*;
#[test]
fn stack_floor_keeps_a_larger_inherited_size() {
let large = (STACK_FLOOR * 3).to_string();
assert_eq!(stack_floor(Some(large.as_str())), large, "a larger explicit choice is kept");
assert_eq!(stack_floor(None), STACK_FLOOR.to_string(), "nothing exported means the floor");
assert_eq!(
stack_floor(Some("1")),
STACK_FLOOR.to_string(),
"a smaller choice is raised to the floor"
);
assert_eq!(
stack_floor(Some(&format!(" {} ", STACK_FLOOR * 2))),
(STACK_FLOOR * 2).to_string(),
"surrounding whitespace is what an exported value often carries"
);
assert_eq!(
stack_floor(Some("not a number")),
STACK_FLOOR.to_string(),
"an unparsable value is ignored"
);
}
#[test]
fn an_empty_library_list_does_not_set_a_loader_path() {
assert_eq!(loader_path(&[]), None);
}
#[test]
fn a_caller_with_no_search_path_gets_only_the_toolchain_directories() {
let joined = joined(&[Utf8PathBuf::from("/one"), Utf8PathBuf::from("/two")], None).expect("a path");
assert_eq!(env::split_paths(&joined).count(), 2);
}
#[test]
fn an_inherited_search_path_is_kept_behind_the_toolchain_directories() {
let existing = env::join_paths([PathBuf::from("/inherited")]).expect("a path");
let joined = joined(&[Utf8PathBuf::from("/one")], Some(existing)).expect("a path");
let parts: Vec<PathBuf> = env::split_paths(&joined).collect();
assert_eq!(parts, vec![PathBuf::from("/one"), PathBuf::from("/inherited")]);
}
#[test]
fn the_loader_variable_is_the_one_this_platform_actually_reads() {
let expected = if cfg!(windows) {
"PATH"
} else if cfg!(target_os = "macos") {
"DYLD_FALLBACK_LIBRARY_PATH"
} else {
"LD_LIBRARY_PATH"
};
assert_eq!(LOADER_VAR, expected);
}
#[test]
fn no_libraries_means_no_search_path_even_with_one_inherited() {
assert_eq!(joined(&[], Some(OsString::from("/inherited"))), None);
}
#[test]
fn the_target_deps_directory_is_always_on_the_toolchain_library_path() {
let temporary = tempfile::tempdir().unwrap();
let root = Utf8PathBuf::from_path_buf(temporary.path().join("root")).unwrap();
let target = Utf8PathBuf::from_path_buf(temporary.path().join("target")).unwrap();
std::fs::create_dir_all(root.as_std_path()).unwrap();
let libraries = toolchain_libraries(&root, &target);
assert_eq!(libraries[0], target.join("debug").join("deps"));
}
}