Skip to main content

ghactions_toolcache/
tool.rs

1//! # Tool from ToolCache
2
3use glob::{MatchOptions, glob_with};
4use std::{fmt::Display, path::PathBuf};
5
6use super::ToolCacheArch;
7
8/// Tool
9#[derive(Debug, Clone)]
10pub struct Tool {
11    /// Tool Name
12    name: String,
13    /// Tool Version
14    version: String,
15    /// Tool Architecture
16    arch: ToolCacheArch,
17
18    /// Tool Path
19    path: PathBuf,
20}
21
22impl Tool {
23    /// Create a new Tool
24    pub fn new(
25        name: impl Into<String>,
26        version: impl Into<String>,
27        arch: impl Into<ToolCacheArch>,
28        path: impl Into<PathBuf>,
29    ) -> Self {
30        Self {
31            name: name.into(),
32            version: version.into(),
33            arch: arch.into(),
34            path: path.into(),
35        }
36    }
37
38    /// Get the Tool name
39    pub fn name(&self) -> &str {
40        &self.name
41    }
42
43    /// Get the Tool version
44    pub fn version(&self) -> &str {
45        &self.version
46    }
47
48    /// Get the Tool architecture
49    pub fn arch(&self) -> &ToolCacheArch {
50        &self.arch
51    }
52
53    /// Get the Tool path
54    pub fn path(&self) -> &PathBuf {
55        &self.path
56    }
57
58    /// Join a path to the Tool path
59    pub fn join(&self, path: impl Into<PathBuf>) -> PathBuf {
60        self.path.join(path.into())
61    }
62
63    /// Find a tool in the cache
64    pub(crate) fn find(
65        toolcache_root: impl Into<PathBuf>,
66        tool_name: impl Into<String>,
67        version: impl Into<String>,
68        arch: impl Into<ToolCacheArch>,
69    ) -> Result<Vec<Tool>, crate::ToolCacheError> {
70        let tool_name = tool_name.into();
71        let version = version.into();
72        let arch = arch.into();
73
74        let tool_path = Tool::tool_path(toolcache_root, tool_name.clone(), version.clone(), arch);
75        let tool_path_str = tool_path.to_str().unwrap();
76
77        let mut results: Vec<Tool> = vec![];
78
79        let options = MatchOptions {
80            case_sensitive: false,
81            require_literal_separator: true,
82            require_literal_leading_dot: false,
83        };
84
85        for entry in glob_with(tool_path_str, options).expect("Failed to read tool cache") {
86            let path = entry.expect("Failed to read tool cache");
87
88            if path.is_dir() && path.exists() {
89                match Tool::try_from(path) {
90                    Ok(tool) => results.push(tool),
91                    Err(e) => {
92                        log::debug!("Failed to create Tool from path: {:?}", e);
93                    }
94                };
95            }
96        }
97
98        Ok(results)
99    }
100
101    /// Get the path to a tool in the cache
102    pub(crate) fn tool_path(
103        toolcache_root: impl Into<PathBuf>,
104        tool: impl Into<String>,
105        version: impl Into<String>,
106        arch: impl Into<ToolCacheArch>,
107    ) -> PathBuf {
108        let toolcache_root = toolcache_root.into();
109        // TODO: Validate the tool name
110        let tool = tool.into();
111        let mut version = version.into();
112        // Replace x with *, this allows for dynamic versions
113        if version.contains('x') {
114            version = version.replace("x", "*");
115        }
116        let arch = match arch.into() {
117            ToolCacheArch::X64 => "x64",
118            ToolCacheArch::ARM64 => "arm64",
119            ToolCacheArch::Any => "**",
120        };
121        // Trailling slash is important
122        toolcache_root.join(tool).join(version).join(arch).join("")
123    }
124}
125
126impl TryFrom<PathBuf> for Tool {
127    type Error = crate::ToolCacheError;
128
129    fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
130        let parts: Vec<&str> = value.iter().map(|p| p.to_str().unwrap()).collect();
131
132        let arch = match parts.last() {
133            Some(arch) => arch,
134            None => {
135                return Err(crate::ToolCacheError::GenericError(
136                    "Invalid Tool Path".to_string(),
137                ));
138            }
139        };
140        let version = match parts.get(parts.len() - 2) {
141            Some(version) => version,
142            None => {
143                return Err(crate::ToolCacheError::GenericError(
144                    "Invalid Tool Path".to_string(),
145                ));
146            }
147        };
148        let name = match parts.get(parts.len() - 3) {
149            Some(name) => name,
150            None => {
151                return Err(crate::ToolCacheError::GenericError(
152                    "Invalid Tool Path".to_string(),
153                ));
154            }
155        };
156
157        Ok(Self {
158            name: name.to_string(),
159            version: version.to_string(),
160            arch: ToolCacheArch::from(*arch),
161            path: value,
162        })
163    }
164}
165
166impl Display for Tool {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(f, "{:?}", self.path)
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_tool_from_path() {
178        let path = PathBuf::from("node/12.7.0/x64");
179        let tool = Tool::try_from(path.clone()).unwrap();
180
181        assert_eq!(tool.path(), &path);
182        assert_eq!(tool.name(), "node");
183        assert_eq!(tool.version(), "12.7.0");
184        assert_eq!(tool.arch(), &ToolCacheArch::X64);
185    }
186
187    #[test]
188    #[cfg(target_os = "linux")]
189    fn test_tool_path() {
190        let cwd = std::env::current_dir()
191            .unwrap()
192            .join("..")
193            .canonicalize()
194            .unwrap();
195
196        let toolcache_root = cwd.clone();
197        let tool_path = Tool::tool_path(&toolcache_root, "node", "12.7.0", ToolCacheArch::X64);
198
199        assert_eq!(tool_path, cwd.join("node/12.7.0/x64/"));
200
201        let tool_path = Tool::tool_path(&toolcache_root, "node", "12.x", ToolCacheArch::X64);
202        assert_eq!(tool_path, cwd.join("node/12.*/x64/"));
203    }
204}