1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::{BvError, Result};
8use crate::lockfile::Lockfile;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ToolDeclaration {
12 pub id: String,
13 #[serde(default)]
15 pub version: String,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct DataDeclaration {
20 pub id: String,
21 #[serde(default)]
22 pub version: String,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
26pub struct HardwareProfile {
27 pub gpu: Option<bool>,
28 pub cpu_cores: Option<u32>,
29 pub ram_gb: Option<f64>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ProjectMeta {
34 pub name: String,
35 pub description: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct RegistryConfig {
40 pub url: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
45pub struct RuntimeConfig {
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub backend: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct BvToml {
54 pub project: ProjectMeta,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub registry: Option<RegistryConfig>,
57 #[serde(default)]
58 pub tools: Vec<ToolDeclaration>,
59 #[serde(default)]
60 pub data: HashMap<String, DataDeclaration>,
61 #[serde(default)]
62 pub hardware: HardwareProfile,
63 #[serde(default, skip_serializing_if = "runtime_config_is_default")]
64 pub runtime: RuntimeConfig,
65 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
68 pub binary_overrides: HashMap<String, String>,
69}
70
71fn runtime_config_is_default(rc: &RuntimeConfig) -> bool {
72 rc.backend.is_none()
73}
74
75impl BvToml {
76 pub fn from_path(path: &Path) -> Result<Self> {
77 let s = fs::read_to_string(path)?;
78 toml::from_str(&s).map_err(|e| BvError::ManifestParse(e.to_string()))
79 }
80
81 pub fn to_path(&self, path: &Path) -> Result<()> {
82 let s = toml::to_string_pretty(self).map_err(|e| BvError::ManifestParse(e.to_string()))?;
83 atomic_write(path, &s)
84 }
85}
86
87pub struct BvLock;
88
89impl BvLock {
90 pub fn from_path(path: &Path) -> Result<Lockfile> {
91 let s = fs::read_to_string(path)?;
92 Lockfile::from_toml_str(&s)
93 }
94
95 pub fn to_path(lock: &Lockfile, path: &Path) -> Result<()> {
96 let s = lock.to_toml_string()?;
97 atomic_write(path, &s)
98 }
99}
100
101fn atomic_write(path: &Path, content: &str) -> Result<()> {
102 let parent = path.parent().unwrap_or(Path::new("."));
103 let tmp = tmp_path(parent);
104 fs::write(&tmp, content)?;
105 fs::rename(&tmp, path)?;
106 Ok(())
107}
108
109fn tmp_path(dir: &Path) -> PathBuf {
110 let id = std::time::SystemTime::now()
111 .duration_since(std::time::UNIX_EPOCH)
112 .map(|d| d.subsec_nanos())
113 .unwrap_or(0);
114 dir.join(format!(".bv-tmp-{id}"))
115}