use std::path::PathBuf;
#[cfg(feature = "download")]
use std::sync::{Arc, OnceLock};
use super::{Tool, ToolCacheArch, platform::ToolPlatform};
use crate::ToolCacheError;
use crate::builder::ToolCacheBuilder;
pub(crate) const RETRY_COUNT: u8 = 10;
#[cfg(target_family = "unix")]
const TOOL_CACHE_PATHS: [&str; 3] = [
"/opt/hostedtoolcache",
"/usr/local/share/toolcache",
"/tmp/toolcache",
];
#[cfg(target_family = "windows")]
const TOOL_CACHE_PATHS: [&str; 3] = [
"C:\\hostedtoolcache",
"C:\\Program Files\\toolcache",
"C:\\tmp\\toolcache",
];
#[derive(Debug, Clone)]
pub struct ToolCache {
pub(crate) tool_cache: PathBuf,
pub(crate) arch: ToolCacheArch,
pub(crate) platform: ToolPlatform,
pub(crate) retry_count: u8,
#[cfg(feature = "download")]
pub(crate) client: Arc<OnceLock<reqwest::Client>>,
}
impl ToolCache {
pub fn new() -> Self {
Self::default()
}
pub fn build() -> ToolCacheBuilder {
ToolCacheBuilder::new()
}
pub fn platform(&self) -> ToolPlatform {
self.platform
}
pub fn arch(&self) -> ToolCacheArch {
self.arch
}
pub fn get_tool_cache(&self) -> &PathBuf {
&self.tool_cache
}
pub async fn find(
&self,
tool: impl Into<String>,
version: impl Into<String>,
) -> Result<Tool, ToolCacheError> {
match self.platform() {
ToolPlatform::Windows => self.find_with_arch(tool, version, ToolCacheArch::X64).await,
ToolPlatform::Linux => self.find_with_arch(tool, version, ToolCacheArch::X64).await,
ToolPlatform::MacOS => {
self.find_with_arch(tool, version, ToolCacheArch::ARM64)
.await
}
ToolPlatform::Any => self.find_with_arch(tool, version, ToolCacheArch::Any).await,
}
}
pub async fn find_all_version(
&self,
tool: impl Into<String>,
) -> Result<Vec<Tool>, ToolCacheError> {
Tool::find(self.get_tool_cache(), tool, "*", ToolCacheArch::Any)
}
pub async fn find_with_arch(
&self,
tool: impl Into<String>,
version: impl Into<String>,
arch: impl Into<ToolCacheArch>,
) -> Result<Tool, ToolCacheError> {
let tool = tool.into();
let version = version.into();
let arch = arch.into();
Tool::find(self.get_tool_cache(), tool.clone(), &version, arch)?
.into_iter()
.find(|t| t.name() == tool)
.ok_or(crate::ToolCacheError::ToolNotFound {
name: tool,
version,
arch: Some(arch),
})
}
pub fn new_tool_path(&self, tool: impl Into<String>, version: impl Into<String>) -> PathBuf {
Tool::tool_path(self.get_tool_cache(), tool, version, self.arch())
}
#[deprecated(since = "0.17.0", note = "Use the ToolCacheBuilder instead")]
pub fn set_retry_count(&mut self, count: u8) {
self.retry_count = count;
}
}
pub(crate) fn get_tool_cache_path() -> PathBuf {
let tool_cache = std::env::var("RUNNER_TOOL_CACHE")
.map(PathBuf::from)
.unwrap_or_else(|_| {
TOOL_CACHE_PATHS
.iter()
.find_map(|path| {
let path = PathBuf::from(path);
if let Err(err) = std::fs::create_dir_all(&path) {
log::trace!("Error creating tool cache dir: {:?}", err);
None
} else {
log::debug!("Using tool cache found at: {:?}", path);
Some(path)
}
})
.unwrap_or_else(|| PathBuf::from("./.toolcache").canonicalize().unwrap())
});
if !tool_cache.exists() {
log::debug!("Creating tool cache at: {:?}", tool_cache);
std::fs::create_dir_all(&tool_cache)
.unwrap_or_else(|_| panic!("Failed to create tool cache directory: {:?}", tool_cache));
}
tool_cache
}
impl From<&str> for ToolCache {
fn from(cache: &str) -> Self {
let tool_cache = PathBuf::from(cache);
if !tool_cache.exists() {
panic!("Tool Cache does not exist: {:?}", tool_cache);
}
Self {
tool_cache,
..Default::default()
}
}
}
impl From<PathBuf> for ToolCache {
fn from(value: PathBuf) -> Self {
let tool_cache = value;
if !tool_cache.exists() {
panic!("Tool Cache does not exist: {:?}", tool_cache);
}
Self {
tool_cache,
..Default::default()
}
}
}
impl Default for ToolCache {
fn default() -> Self {
let tool_cache = get_tool_cache_path();
Self {
tool_cache,
retry_count: RETRY_COUNT,
arch: match std::env::consts::ARCH {
"x86_64" | "amd64" => ToolCacheArch::X64,
"aarch64" => ToolCacheArch::ARM64,
_ => ToolCacheArch::Any,
},
platform: ToolPlatform::from_current_os(),
#[cfg(feature = "download")]
client: Arc::new(OnceLock::new()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn local_toolcache() -> (PathBuf, ToolCache) {
let cwd = std::env::current_dir()
.unwrap()
.join("..")
.canonicalize()
.unwrap();
(cwd.clone(), ToolCache::from(cwd.join("examples/toolcache")))
}
#[test]
fn test_tool_cache() {
let tool_cache = ToolCache::default();
if let Ok(env_path) = std::env::var("RUNNER_TOOL_CACHE") {
assert_eq!(tool_cache.get_tool_cache(), &PathBuf::from(env_path));
} else {
assert!(tool_cache.get_tool_cache().exists());
}
}
#[tokio::test]
async fn test_find_all_version() {
let (_cwd, tool_cache) = local_toolcache();
let versions = tool_cache.find_all_version("node").await.unwrap();
assert!(!versions.is_empty());
}
}