use alloc::string::String;
use std::path::{Path, PathBuf};
pub(crate) type SharedPackageCacheLease =
alloc::sync::Arc<std::sync::OnceLock<Result<PackageCacheLease, String>>>;
pub(crate) use midenc_frontend_wasm_metadata::package_cache::PACKAGE_CACHE_ENV;
#[derive(Debug)]
pub(crate) enum PackageCacheLease {
Leased(tempfile::TempDir),
Adopted(PathBuf),
}
impl PackageCacheLease {
pub(crate) fn create(target_dir: &Path) -> Result<Self, String> {
Self::from_env_value(std::env::var_os(PACKAGE_CACHE_ENV), target_dir)
}
fn from_env_value(
env_value: Option<std::ffi::OsString>,
target_dir: &Path,
) -> Result<Self, String> {
match env_value {
Some(value) if !value.is_empty() => Self::adopt(PathBuf::from(value)),
_ => Self::lease(target_dir),
}
}
fn adopt(dir: PathBuf) -> Result<Self, String> {
let dir = std::path::absolute(&dir).unwrap_or(dir);
std::fs::create_dir_all(&dir).map_err(|err| {
format!(
"cannot create the package cache '{}' named by {PACKAGE_CACHE_ENV}: {err}",
dir.display()
)
})?;
log::debug!(
target: "package-cache",
"adopted the caller-provided package cache '{}'",
dir.display()
);
Ok(Self::Adopted(dir))
}
fn lease(target_dir: &Path) -> Result<Self, String> {
let parent = package_cache_parent(target_dir);
std::fs::create_dir_all(&parent).map_err(|err| {
format!("cannot create the package cache parent '{}': {err}", parent.display())
})?;
let dir = tempfile::Builder::new().prefix("build-").tempdir_in(&parent).map_err(|err| {
format!("cannot create a package cache under '{}': {err}", parent.display())
})?;
log::debug!(
target: "package-cache",
"created package cache '{}' for this build",
dir.path().display()
);
Ok(Self::Leased(dir))
}
pub(crate) fn path(&self) -> &Path {
match self {
Self::Leased(dir) => dir.path(),
Self::Adopted(dir) => dir.as_path(),
}
}
}
pub(crate) fn package_cache_parent(target_dir: &Path) -> PathBuf {
target_dir.join("packages")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn leases_are_unique_and_deleted_on_drop() {
let temp = tempfile::TempDir::new().unwrap();
let first = PackageCacheLease::from_env_value(None, temp.path()).unwrap();
let second = PackageCacheLease::from_env_value(None, temp.path()).unwrap();
assert_ne!(first.path(), second.path(), "two leases must never share a directory");
assert_eq!(first.path().parent().unwrap(), package_cache_parent(temp.path()));
assert!(first.path().is_dir());
assert!(second.path().is_dir());
let first_path = first.path().to_path_buf();
drop(first);
assert!(!first_path.exists(), "dropping the lease must delete its directory");
assert!(second.path().is_dir(), "an unrelated lease must survive");
}
#[test]
fn an_env_named_directory_is_adopted_and_never_deleted() {
let temp = tempfile::TempDir::new().unwrap();
let caller_dir = temp.path().join("caller-cache");
let adopted = PackageCacheLease::from_env_value(
Some(caller_dir.clone().into_os_string()),
temp.path(),
)
.unwrap();
assert_eq!(adopted.path(), caller_dir.as_path(), "the caller names the directory");
assert!(caller_dir.is_dir(), "adoption must create the directory");
assert!(
!package_cache_parent(temp.path()).exists(),
"adoption must not mint a lease under the project"
);
drop(adopted);
assert!(caller_dir.is_dir(), "an adopted directory is the caller's; never delete it");
}
#[test]
fn an_empty_env_value_counts_as_unset() {
let temp = tempfile::TempDir::new().unwrap();
let lease = PackageCacheLease::from_env_value(Some(std::ffi::OsString::new()), temp.path())
.unwrap();
assert_eq!(lease.path().parent().unwrap(), package_cache_parent(temp.path()));
}
#[test]
fn creation_fails_closed_on_an_unwritable_parent() {
let temp = tempfile::TempDir::new().unwrap();
let target_dir = temp.path().join("target-dir");
std::fs::create_dir_all(&target_dir).unwrap();
std::fs::write(package_cache_parent(&target_dir), b"not a directory").unwrap();
let error = PackageCacheLease::from_env_value(None, &target_dir).unwrap_err();
assert!(error.contains("package cache"), "unexpected error text: {error}");
}
}