1use indexmap::IndexMap;
11use serde::{Deserialize, Serialize};
12use shikumi::TieredConfig;
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct GenConfig {
17 pub workspace: WorkspaceConfig,
18 pub cache: CacheConfig,
19 pub render: RenderConfig,
20 pub target: TargetConfig,
21}
22
23impl Default for GenConfig {
24 fn default() -> Self {
25 Self::prescribed_default()
26 }
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub struct WorkspaceConfig {
32 pub root: String,
34 pub adapter_routing: IndexMap<String, String>,
39 pub force_adapter: Option<String>,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct CacheConfig {
48 pub substituters: Vec<String>,
52 pub trusted_public_keys: Vec<String>,
54 pub always_build: bool,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
63pub struct RenderConfig {
64 pub mode: RenderMode,
65 pub output_path: String,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "kebab-case")]
72pub enum RenderMode {
73 PerCrate,
76 PerTree,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
83pub struct TargetConfig {
84 pub os: String,
85 pub cpu: String,
86 pub libc: Option<String>,
87}
88
89impl TieredConfig for GenConfig {
90 fn bare() -> Self {
91 Self {
92 workspace: WorkspaceConfig {
93 root: String::new(),
94 adapter_routing: IndexMap::new(),
95 force_adapter: None,
96 },
97 cache: CacheConfig {
98 substituters: Vec::new(),
99 trusted_public_keys: Vec::new(),
100 always_build: false,
101 },
102 render: RenderConfig {
103 mode: RenderMode::PerCrate,
104 output_path: String::new(),
105 },
106 target: TargetConfig {
107 os: String::new(),
108 cpu: String::new(),
109 libc: None,
110 },
111 }
112 }
113
114 fn prescribed_default() -> Self {
115 let mut adapter_routing = IndexMap::new();
116 adapter_routing.insert("Cargo.toml".to_string(), "cargo".to_string());
117 adapter_routing.insert("package.json".to_string(), "npm".to_string());
118 adapter_routing.insert("Gemfile".to_string(), "bundler".to_string());
119 adapter_routing.insert("pyproject.toml".to_string(), "pip".to_string());
120 adapter_routing.insert("go.mod".to_string(), "gomod".to_string());
121 adapter_routing.insert("Chart.yaml".to_string(), "helm".to_string());
122
123 Self {
124 workspace: WorkspaceConfig {
125 root: ".".to_string(),
126 adapter_routing,
127 force_adapter: None,
128 },
129 cache: CacheConfig {
130 substituters: vec![
131 "https://cache.nixos.org".to_string(),
132 "http://cache.plo.quero.cloud/nexus".to_string(),
133 ],
134 trusted_public_keys: vec![
135 "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=".to_string(),
136 ],
137 always_build: false,
138 },
139 render: RenderConfig {
140 mode: RenderMode::PerCrate,
141 output_path: String::new(),
142 },
143 target: TargetConfig {
144 os: host_os(),
145 cpu: host_cpu(),
146 libc: host_libc(),
147 },
148 }
149 }
150
151 fn discovered() -> Self {
152 let mut c = Self::bare();
153 c.target = TargetConfig {
154 os: host_os(),
155 cpu: host_cpu(),
156 libc: host_libc(),
157 };
158 if let Ok(cwd) = std::env::current_dir() {
159 c.workspace.root = cwd.display().to_string();
160 }
161 c
162 }
163}
164
165fn host_os() -> String {
166 #[cfg(target_os = "linux")]
167 {
168 "linux".to_string()
169 }
170 #[cfg(target_os = "macos")]
171 {
172 "macos".to_string()
173 }
174 #[cfg(target_os = "windows")]
175 {
176 "windows".to_string()
177 }
178 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
179 {
180 "unknown".to_string()
181 }
182}
183
184fn host_cpu() -> String {
185 #[cfg(target_arch = "x86_64")]
186 {
187 "x86_64".to_string()
188 }
189 #[cfg(target_arch = "aarch64")]
190 {
191 "aarch64".to_string()
192 }
193 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
194 {
195 "unknown".to_string()
196 }
197}
198
199fn host_libc() -> Option<String> {
200 #[cfg(all(target_os = "linux", target_env = "gnu"))]
201 {
202 Some("gnu".to_string())
203 }
204 #[cfg(all(target_os = "linux", target_env = "musl"))]
205 {
206 Some("musl".to_string())
207 }
208 #[cfg(not(target_os = "linux"))]
209 {
210 None
211 }
212 #[cfg(all(target_os = "linux", not(any(target_env = "gnu", target_env = "musl"))))]
213 {
214 None
215 }
216}
217
218#[must_use]
221pub fn target_from_config(t: &TargetConfig) -> gen_types::Target {
222 gen_types::Target {
223 os: t.os.clone(),
224 cpu: t.cpu.clone(),
225 libc: t.libc.clone(),
226 engines: indexmap::IndexMap::new(),
227 python_env_markers: indexmap::IndexMap::new(),
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn bare_is_empty() {
237 let c = GenConfig::bare();
238 assert!(c.workspace.root.is_empty());
239 assert!(c.workspace.adapter_routing.is_empty());
240 assert!(c.cache.substituters.is_empty());
241 assert!(c.target.os.is_empty());
242 }
243
244 #[test]
245 fn prescribed_default_has_fleet_substituters() {
246 let c = GenConfig::prescribed_default();
247 assert!(c.cache.substituters.iter().any(|s| s.contains("cache.nixos.org")));
248 assert!(c.cache.substituters.iter().any(|s| s.contains("plo.quero.cloud")));
249 }
250
251 #[test]
252 fn prescribed_default_routes_cargo() {
253 let c = GenConfig::prescribed_default();
254 assert_eq!(
255 c.workspace.adapter_routing.get("Cargo.toml").map(String::as_str),
256 Some("cargo")
257 );
258 }
259
260 #[test]
261 fn discovered_seeds_target_from_host() {
262 let c = GenConfig::discovered();
263 assert!(matches!(c.target.os.as_str(), "linux" | "macos" | "windows" | "unknown"));
264 assert!(matches!(c.target.cpu.as_str(), "x86_64" | "aarch64" | "unknown"));
265 }
266
267 #[test]
268 fn round_trips_through_serde_yaml() {
269 let c = GenConfig::prescribed_default();
270 let y = serde_yaml::to_string(&c).unwrap();
271 let parsed: GenConfig = serde_yaml::from_str(&y).unwrap();
272 assert_eq!(c, parsed);
273 }
274
275 #[test]
276 fn target_from_config_round_trips() {
277 let c = GenConfig::prescribed_default();
278 let t = target_from_config(&c.target);
279 assert_eq!(t.os, c.target.os);
280 assert_eq!(t.cpu, c.target.cpu);
281 }
282}