use std::path::PathBuf;
use std::process::{Command, Stdio};
use directories::ProjectDirs;
pub fn cache_root(proj_dirs: &ProjectDirs) -> PathBuf {
proj_dirs.cache_dir().to_path_buf()
}
pub fn routines_root(proj_dirs: &ProjectDirs) -> PathBuf {
cache_root(proj_dirs).join("routines")
}
pub fn routine_root(proj_dirs: &ProjectDirs, name: &str) -> PathBuf {
routines_root(proj_dirs).join(name)
}
pub fn routine_version_root(proj_dirs: &ProjectDirs, name: &str, version: &str) -> PathBuf {
routine_root(proj_dirs, name).join(version)
}
pub fn routine_target_root(
proj_dirs: &ProjectDirs,
name: &str,
version: &str,
target_triple: &str,
) -> PathBuf {
routine_version_root(proj_dirs, name, version).join(target_triple)
}
pub fn plugin_dir(
proj_dirs: &ProjectDirs,
name: &str,
version: &str,
target_triple: &str,
) -> PathBuf {
routine_target_root(proj_dirs, name, version, target_triple).join("plugin")
}
pub fn descriptor_path(
proj_dirs: &ProjectDirs,
name: &str,
version: &str,
target_triple: &str,
) -> PathBuf {
routine_target_root(proj_dirs, name, version, target_triple).join("descriptor.json")
}
pub fn platform_library_filename(crate_name: &str) -> String {
if cfg!(windows) {
format!("{crate_name}.dll")
} else if cfg!(target_os = "macos") {
format!("lib{crate_name}.dylib")
} else {
format!("lib{crate_name}.so")
}
}
pub fn host_target_triple() -> Result<String, String> {
let out = Command::new("rustc")
.args(["-vV"])
.stderr(Stdio::inherit())
.output()
.map_err(|e| format!("Failed to run rustc: {e}"))?;
if !out.status.success() {
return Err("rustc -vV failed".to_string());
}
let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines() {
if let Some(rest) = line.strip_prefix("host: ") {
return Ok(rest.trim().to_string());
}
}
Err("Failed to parse rustc host triple".to_string())
}
pub fn installed_routine_names(proj_dirs: &ProjectDirs) -> Vec<String> {
let root = routines_root(proj_dirs);
if !root.exists() {
return Vec::new();
}
let mut names: Vec<String> = std::fs::read_dir(&root)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
.collect();
names.sort();
names
}
pub fn installed_versions_for_routine(proj_dirs: &ProjectDirs, name: &str) -> Vec<String> {
let root = routine_root(proj_dirs, name);
if !root.exists() {
return Vec::new();
}
let mut versions: Vec<String> = std::fs::read_dir(&root)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
.collect();
versions.sort();
versions
}
pub fn find_latest_installed_version(proj_dirs: &ProjectDirs, name: &str) -> Option<String> {
let root = routine_root(proj_dirs, name);
let mut versions: Vec<String> = std::fs::read_dir(&root)
.ok()?
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
.collect();
versions.sort();
versions.pop()
}
pub fn plugin_library_path(
proj_dirs: &ProjectDirs,
name: &str,
version: &str,
target_triple: &str,
) -> PathBuf {
plugin_dir(proj_dirs, name, version, target_triple).join(platform_library_filename(name))
}
pub fn is_installed_for_target(
proj_dirs: &ProjectDirs,
name: &str,
version: &str,
target_triple: &str,
) -> bool {
plugin_library_path(proj_dirs, name, version, target_triple).exists()
}
#[cfg(test)]
mod tests {
use super::*;
use directories::ProjectDirs;
fn test_proj_dirs() -> ProjectDirs {
ProjectDirs::from("com", "cotis", "cotis-cli-test").expect("valid qualifier/org/app")
}
#[test]
fn platform_library_filename_matches_host() {
if cfg!(windows) {
assert_eq!(platform_library_filename("my_routine"), "my_routine.dll");
} else if cfg!(target_os = "macos") {
assert_eq!(
platform_library_filename("my_routine"),
"libmy_routine.dylib"
);
} else {
assert_eq!(platform_library_filename("my_routine"), "libmy_routine.so");
}
}
#[test]
fn descriptor_path_follows_cache_layout() {
let proj_dirs = test_proj_dirs();
let path = descriptor_path(&proj_dirs, "web_builder", "0.1.0", "x86_64-pc-windows-msvc");
assert!(
path.ends_with(
PathBuf::from("routines")
.join("web_builder")
.join("0.1.0")
.join("x86_64-pc-windows-msvc")
.join("descriptor.json")
)
);
}
#[test]
fn plugin_dir_is_under_target_root() {
let proj_dirs = test_proj_dirs();
let path = plugin_dir(
&proj_dirs,
"web_builder",
"dev",
"aarch64-unknown-linux-gnu",
);
assert!(
path.ends_with(
PathBuf::from("routines")
.join("web_builder")
.join("dev")
.join("aarch64-unknown-linux-gnu")
.join("plugin")
)
);
}
}