1use crate::{NativeRuntimeManifest, manifest::NATIVE_RUNTIME_MANIFEST_FILE};
2use anyhow::{Context, Result};
3use serde::{Deserialize, Serialize};
4use std::{
5 fs,
6 path::{Path, PathBuf},
7};
8
9#[cfg(target_os = "windows")]
14pub const GPU_BENCHMARK_TOOL_PATH: &str = "tools/mesh-llm-gpu-benchmark.exe";
15#[cfg(not(target_os = "windows"))]
16pub const GPU_BENCHMARK_TOOL_PATH: &str = "tools/mesh-llm-gpu-benchmark";
17
18#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
19pub struct NativeRuntimeCacheRoot {
20 pub path: PathBuf,
21}
22
23#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
24pub struct InstalledNativeRuntime {
25 pub mesh_version: String,
26 pub native_runtime_id: String,
27 pub flavor: String,
28 pub path: PathBuf,
29 pub manifest: NativeRuntimeManifest,
30}
31
32impl InstalledNativeRuntime {
33 pub fn gpu_benchmark_tool(&self) -> Result<PathBuf> {
35 if !self
36 .manifest
37 .runtime
38 .tools
39 .contains_key(GPU_BENCHMARK_TOOL_PATH)
40 {
41 anyhow::bail!(
42 "native runtime {} does not provide the GPU benchmark tool",
43 self.native_runtime_id
44 );
45 }
46 let path = self.path.join(GPU_BENCHMARK_TOOL_PATH);
47 if !path.is_file() {
48 anyhow::bail!(
49 "native runtime GPU benchmark tool is missing: {}",
50 path.display()
51 );
52 }
53 #[cfg(unix)]
54 {
55 use std::os::unix::fs::PermissionsExt;
56
57 if fs::metadata(&path)?.permissions().mode() & 0o111 == 0 {
58 anyhow::bail!(
59 "native runtime GPU benchmark tool is not executable: {}",
60 path.display()
61 );
62 }
63 }
64 Ok(path)
65 }
66}
67
68#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum NativeRuntimePruneMode {
71 KeepActiveAndPrevious,
72 ActiveOnly,
73}
74
75#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
76pub struct CachePrunePlan {
77 #[serde(default)]
78 pub remove_dirs: Vec<PathBuf>,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
82pub struct NativeRuntimeCache {
83 root: PathBuf,
84}
85
86impl NativeRuntimeCache {
87 pub fn new(root: impl Into<PathBuf>) -> Self {
88 Self { root: root.into() }
89 }
90
91 pub fn root(&self) -> &Path {
92 &self.root
93 }
94
95 pub fn runtime_dir(&self, mesh_version: &str, native_runtime_id: &str) -> PathBuf {
96 self.root.join(mesh_version).join(native_runtime_id)
97 }
98
99 pub fn installed(&self) -> Result<Vec<InstalledNativeRuntime>> {
100 let mut installed = Vec::new();
101 if !self.root.exists() {
102 return Ok(installed);
103 }
104 for version_entry in fs::read_dir(&self.root)
105 .with_context(|| format!("read native runtime cache {}", self.root.display()))?
106 {
107 let version_entry = version_entry?;
108 if !version_entry.file_type()?.is_dir() {
109 continue;
110 }
111 installed.extend(installed_in_version_dir(&version_entry.path())?);
112 }
113 installed.sort_by(|left, right| {
114 (&left.mesh_version, &left.native_runtime_id)
115 .cmp(&(&right.mesh_version, &right.native_runtime_id))
116 });
117 Ok(installed)
118 }
119
120 pub(crate) fn installed_for_version(
126 &self,
127 mesh_version: &str,
128 ) -> Result<Vec<InstalledNativeRuntime>> {
129 installed_in_version_dir(&self.root.join(mesh_version))
130 }
131
132 pub fn find_installed(
133 &self,
134 mesh_version: &str,
135 native_runtime_id: &str,
136 ) -> Result<Option<InstalledNativeRuntime>> {
137 let dir = self.runtime_dir(mesh_version, native_runtime_id);
138 if !dir.join(NATIVE_RUNTIME_MANIFEST_FILE).exists() {
139 return Ok(None);
140 }
141 installed_runtime_from_dir(&dir)
142 }
143
144 pub fn install_from_dir(&self, source_dir: &Path) -> Result<InstalledNativeRuntime> {
145 let manifest = NativeRuntimeManifest::read_from_dir(source_dir)?;
146 manifest.validate()?;
147 let mesh_version = manifest
148 .runtime
149 .mesh_version
150 .as_deref()
151 .unwrap_or("unknown");
152 let target = self.runtime_dir(mesh_version, manifest.runtime.native_runtime_id());
153 if target.exists() {
154 fs::remove_dir_all(&target)
155 .with_context(|| format!("replace native runtime {}", target.display()))?;
156 }
157 copy_dir_recursive(source_dir, &target)?;
158 installed_runtime_from_dir(&target)?.context("installed native runtime manifest missing")
159 }
160
161 pub fn remove(&self, mesh_version: &str, native_runtime_id: &str) -> Result<bool> {
162 let dir = self.runtime_dir(mesh_version, native_runtime_id);
163 if !dir.exists() {
164 return Ok(false);
165 }
166 fs::remove_dir_all(&dir)
167 .with_context(|| format!("remove native runtime {}", dir.display()))?;
168 Ok(true)
169 }
170
171 pub fn prune_plan(
172 &self,
173 active_mesh_version: &str,
174 mode: NativeRuntimePruneMode,
175 ) -> Result<CachePrunePlan> {
176 let mut versions = self.installed_versions()?;
177 versions.sort();
178 let previous = match mode {
179 NativeRuntimePruneMode::ActiveOnly => None,
180 NativeRuntimePruneMode::KeepActiveAndPrevious => versions
181 .iter()
182 .rfind(|version| version.as_str() != active_mesh_version)
183 .cloned(),
184 };
185 let remove_dirs = versions
186 .into_iter()
187 .filter(|version| version != active_mesh_version)
188 .filter(|version| Some(version) != previous.as_ref())
189 .map(|version| self.root.join(version))
190 .collect();
191 Ok(CachePrunePlan { remove_dirs })
192 }
193
194 pub fn prune(
195 &self,
196 active_mesh_version: &str,
197 mode: NativeRuntimePruneMode,
198 ) -> Result<CachePrunePlan> {
199 let plan = self.prune_plan(active_mesh_version, mode)?;
200 for dir in &plan.remove_dirs {
201 if dir.exists() {
202 fs::remove_dir_all(dir)
203 .with_context(|| format!("remove native runtime cache {}", dir.display()))?;
204 }
205 }
206 Ok(plan)
207 }
208
209 fn installed_versions(&self) -> Result<Vec<String>> {
210 if !self.root.exists() {
211 return Ok(Vec::new());
212 }
213 let mut versions = Vec::new();
214 for entry in fs::read_dir(&self.root)
215 .with_context(|| format!("read native runtime cache {}", self.root.display()))?
216 {
217 let entry = entry?;
218 if entry.file_type()?.is_dir() {
219 versions.push(entry.file_name().to_string_lossy().to_string());
220 }
221 }
222 Ok(versions)
223 }
224}
225
226pub fn native_runtime_cache_root(base_cache_dir: &Path) -> PathBuf {
227 base_cache_dir.join("mesh-llm").join("native-runtimes")
228}
229
230fn installed_runtime_from_dir(dir: &Path) -> Result<Option<InstalledNativeRuntime>> {
231 if !dir.join(NATIVE_RUNTIME_MANIFEST_FILE).exists() {
232 return Ok(None);
233 }
234 let manifest = NativeRuntimeManifest::read_from_dir(dir)?;
235 let mesh_version = manifest
236 .runtime
237 .mesh_version
238 .clone()
239 .unwrap_or_else(|| "unknown".to_string());
240 Ok(Some(InstalledNativeRuntime {
241 mesh_version,
242 native_runtime_id: manifest.runtime.id.clone(),
243 flavor: manifest.runtime.backend.kind.to_string(),
244 path: dir.to_path_buf(),
245 manifest,
246 }))
247}
248
249fn installed_in_version_dir(version_dir: &Path) -> Result<Vec<InstalledNativeRuntime>> {
250 let mut installed = Vec::new();
251 if !version_dir.is_dir() {
252 return Ok(installed);
253 }
254 for runtime_entry in fs::read_dir(version_dir)
255 .with_context(|| format!("read native runtime cache {}", version_dir.display()))?
256 {
257 let runtime_entry = runtime_entry?;
258 if !runtime_entry.file_type()?.is_dir() {
259 continue;
260 }
261 if let Some(runtime) = installed_runtime_from_dir(&runtime_entry.path())? {
262 installed.push(runtime);
263 }
264 }
265 installed.sort_by(|left, right| left.native_runtime_id.cmp(&right.native_runtime_id));
266 Ok(installed)
267}
268
269fn copy_dir_recursive(source: &Path, target: &Path) -> Result<()> {
270 fs::create_dir_all(target).with_context(|| format!("create {}", target.display()))?;
271 for entry in fs::read_dir(source).with_context(|| format!("read {}", source.display()))? {
272 let entry = entry?;
273 let source_path = entry.path();
274 let target_path = target.join(entry.file_name());
275 if entry.file_type()?.is_dir() {
276 copy_dir_recursive(&source_path, &target_path)?;
277 } else {
278 fs::copy(&source_path, &target_path).with_context(|| {
279 format!(
280 "copy {} to {}",
281 source_path.display(),
282 target_path.display()
283 )
284 })?;
285 }
286 }
287 Ok(())
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::{
294 NativeRuntimeArtifact, NativeRuntimeBackend, NativeRuntimeManifest, NativeRuntimePlatform,
295 };
296
297 fn write_runtime(dir: &Path, version: &str, id: &str) {
298 fs::create_dir_all(dir.join("lib")).unwrap();
299 fs::write(dir.join("lib/libmeshllm_ffi.so"), b"native runtime").unwrap();
300 let manifest = NativeRuntimeManifest {
301 runtime: NativeRuntimeArtifact {
302 id: id.to_string(),
303 mesh_version: Some(version.to_string()),
304 skippy_abi: "0.1.25".to_string(),
305 platform: NativeRuntimePlatform {
306 os: "linux".to_string(),
307 arch: "x86_64".to_string(),
308 target: None,
309 },
310 backend: NativeRuntimeBackend::cpu(),
311 rank: 0,
312 libraries: vec!["lib/libmeshllm_ffi.so".to_string()],
313 files: Default::default(),
314 tools: Default::default(),
315 url: None,
316 sha256: None,
317 signature: None,
318 },
319 };
320 manifest.write_to_dir(dir).unwrap();
321 }
322
323 #[test]
324 fn installs_bundle_runtime_into_versioned_cache() {
325 let temp = tempfile::tempdir().unwrap();
326 let source = temp.path().join("source");
327 write_runtime(&source, "0.68.0", "meshllm-native-linux-x86_64-cpu");
328
329 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
330 let installed = cache.install_from_dir(&source).unwrap();
331
332 assert_eq!(installed.mesh_version, "0.68.0");
333 assert!(installed.path.ends_with("meshllm-native-linux-x86_64-cpu"));
334 }
335
336 #[test]
337 fn installed_for_version_ignores_legacy_cache_versions() {
338 let temp = tempfile::tempdir().unwrap();
339 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
340 write_runtime(
341 &cache.runtime_dir("0.75.0", "meshllm-native-linux-x86_64-cpu"),
342 "0.75.0",
343 "meshllm-native-linux-x86_64-cpu",
344 );
345
346 let legacy = cache.runtime_dir("0.74.0", "meshllm-native-linux-x86_64-cpu");
347 fs::create_dir_all(legacy.join("lib")).unwrap();
348 fs::write(legacy.join("lib/libmeshllm_ffi.so"), b"legacy runtime").unwrap();
349 fs::write(
350 legacy.join(NATIVE_RUNTIME_MANIFEST_FILE),
351 r#"{
352 "runtime": {
353 "id": "meshllm-native-linux-x86_64-cpu",
354 "mesh_version": "0.74.0",
355 "skippy_abi": "0.1.25",
356 "platform": {"os": "linux", "arch": "x86_64"},
357 "backend": {"kind": "cpu"},
358 "libraries": ["lib/libmeshllm_ffi.so"]
359 }
360}"#,
361 )
362 .unwrap();
363
364 let installed = cache.installed_for_version("0.75.0").unwrap();
365
366 assert_eq!(installed.len(), 1);
367 assert_eq!(installed[0].mesh_version, "0.75.0");
368 assert!(cache.installed().is_err());
369 }
370
371 #[test]
372 fn installed_for_version_ignores_file_at_version_path() {
373 let temp = tempfile::tempdir().unwrap();
374 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
375 fs::create_dir_all(cache.root()).unwrap();
376 fs::write(cache.root().join("0.75.0"), b"partial cache artifact").unwrap();
377
378 let installed = cache.installed_for_version("0.75.0").unwrap();
379
380 assert!(installed.is_empty());
381 }
382
383 #[test]
384 fn prune_keeps_active_and_previous_by_default() {
385 let temp = tempfile::tempdir().unwrap();
386 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
387 for version in ["0.67.0", "0.68.0", "0.69.0"] {
388 write_runtime(
389 &cache.runtime_dir(version, "meshllm-native-linux-x86_64-cpu"),
390 version,
391 "meshllm-native-linux-x86_64-cpu",
392 );
393 }
394
395 let plan = cache
396 .prune_plan("0.69.0", NativeRuntimePruneMode::KeepActiveAndPrevious)
397 .unwrap();
398
399 assert_eq!(plan.remove_dirs, vec![cache.root().join("0.67.0")]);
400 }
401
402 #[test]
403 fn installed_runtime_exposes_load_plan() {
404 let temp = tempfile::tempdir().unwrap();
405 let source = temp.path().join("source");
406 write_runtime(&source, "0.68.0", "meshllm-native-linux-x86_64-cpu");
407
408 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
409 let installed = cache.install_from_dir(&source).unwrap();
410 let plan = installed.load_plan().unwrap();
411
412 assert_eq!(plan.native_runtime_id, "meshllm-native-linux-x86_64-cpu");
413 assert_eq!(
414 plan.libraries,
415 vec![
416 cache
417 .runtime_dir("0.68.0", "meshllm-native-linux-x86_64-cpu")
418 .join("lib/libmeshllm_ffi.so")
419 ]
420 );
421 }
422
423 #[cfg(unix)]
424 #[test]
425 fn gpu_benchmark_tool_must_be_declared_and_executable() {
426 use std::os::unix::fs::PermissionsExt;
427
428 let temp = tempfile::tempdir().unwrap();
429 let source = temp.path().join("source");
430 write_runtime(&source, "0.68.0", "meshllm-native-linux-x86_64-cuda12");
431
432 let cache = NativeRuntimeCache::new(temp.path().join("cache"));
433 let mut installed = cache.install_from_dir(&source).unwrap();
434 let tool = installed.path.join(GPU_BENCHMARK_TOOL_PATH);
435 fs::create_dir_all(tool.parent().unwrap()).unwrap();
436 fs::write(&tool, b"benchmark tool").unwrap();
437 installed
438 .manifest
439 .runtime
440 .tools
441 .insert(GPU_BENCHMARK_TOOL_PATH.to_string(), "0".repeat(64));
442
443 let error = installed.gpu_benchmark_tool().unwrap_err();
444 assert!(error.to_string().contains("not executable"));
445
446 let mut permissions = fs::metadata(&tool).unwrap().permissions();
447 permissions.set_mode(0o755);
448 fs::set_permissions(&tool, permissions).unwrap();
449 assert_eq!(installed.gpu_benchmark_tool().unwrap(), tool);
450 }
451}