use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
pub struct Scratch {
path: PathBuf,
}
impl Deref for Scratch {
type Target = Path;
fn deref(&self) -> &Path {
&self.path
}
}
impl AsRef<Path> for Scratch {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl AsRef<std::ffi::OsStr> for Scratch {
fn as_ref(&self) -> &std::ffi::OsStr {
self.path.as_os_str()
}
}
impl Drop for Scratch {
fn drop(&mut self) {
if std::thread::panicking() {
eprintln!("scratch kept for inspection: {}", self.path.display());
return;
}
let _ = std::fs::remove_dir_all(&self.path);
}
}
pub fn case(name: &str) -> Scratch {
static NEXT: AtomicU64 = AtomicU64::new(0);
let serial = NEXT.fetch_add(1, Ordering::Relaxed);
let path = root().join(format!("{name}-{}-{serial}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
std::fs::create_dir_all(&path)
.unwrap_or_else(|err| panic!("cannot create {}: {err}", path.display()));
Scratch { path }
}
fn root() -> PathBuf {
let path = match option_env!("CARGO_TARGET_TMPDIR") {
Some(path) => PathBuf::from(path),
None => target_dir().join("tmp"),
};
std::fs::create_dir_all(&path)
.unwrap_or_else(|err| panic!("cannot create {}: {err}", path.display()));
path
}
fn target_dir() -> PathBuf {
if let Some(dir) = std::env::var_os("CARGO_TARGET_DIR") {
let dir = PathBuf::from(dir);
if dir.is_absolute() {
return dir.canonicalize().unwrap_or(dir);
}
}
if let Ok(executable) = std::env::current_exe() {
if let Some(dir) = executable_target_dir(&executable) {
return dir;
}
}
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
manifest
.parent()
.and_then(Path::parent)
.unwrap_or(manifest)
.join("target")
}
#[cfg(unix)]
#[allow(dead_code)] pub fn supports_raw_byte_names(root: &Path) -> bool {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let path = root.join(OsStr::from_bytes(b"raw-name-probe-\xff"));
match std::fs::write(&path, []) {
Ok(()) => {
let exact_name = std::fs::read_dir(root)
.unwrap()
.any(|entry| entry.unwrap().file_name().as_bytes() == b"raw-name-probe-\xff");
std::fs::remove_file(path).unwrap();
exact_name
}
Err(error)
if error.kind() == std::io::ErrorKind::InvalidInput
|| error.raw_os_error() == Some(libc::EILSEQ) =>
{
false
}
Err(error) => panic!("raw filename probe failed unexpectedly: {error}"),
}
}
fn executable_target_dir(executable: &Path) -> Option<PathBuf> {
if !executable.is_absolute() {
return None;
}
let deps = executable.parent()?;
if deps.file_name()? != "deps" {
return None;
}
Some(deps.parent()?.parent()?.to_path_buf())
}
#[cfg(test)]
mod target_directory_tests {
use super::*;
#[test]
fn test_executable_layouts_produce_absolute_scratch_roots() {
let base = Path::new(env!("CARGO_MANIFEST_DIR")).join("rars-scratch-layout-probe");
for profile in ["debug", "release", "custom-profile"] {
let executable = base.join(profile).join("deps").join("rars-tests");
assert_eq!(executable_target_dir(&executable), Some(base.clone()));
}
let cross_target = base.join("aarch64-apple-darwin");
assert_eq!(
executable_target_dir(&cross_target.join("debug/deps/rars-tests")),
Some(cross_target),
);
assert_eq!(
executable_target_dir(Path::new("target/debug/deps/test")),
None
);
assert_eq!(executable_target_dir(&base.join("relocated-tests")), None);
}
}