cargo_image_runner/config/
mod.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7mod loader;
8pub use loader::ConfigLoader;
9
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct Config {
13 #[serde(default)]
15 pub boot: BootConfig,
16
17 #[serde(default)]
19 pub bootloader: BootloaderConfig,
20
21 #[serde(default)]
23 pub image: ImageConfig,
24
25 #[serde(default)]
27 pub runner: RunnerConfig,
28
29 #[serde(default)]
31 pub test: TestConfig,
32
33 #[serde(default)]
35 pub run: RunConfig,
36
37 #[serde(default)]
39 pub variables: HashMap<String, String>,
40
41 #[serde(default)]
43 pub verbose: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct BootConfig {
49 #[serde(rename = "type")]
51 pub boot_type: BootType,
52}
53
54impl Default for BootConfig {
55 fn default() -> Self {
56 Self {
57 boot_type: BootType::Uefi,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "lowercase")]
65pub enum BootType {
66 Bios,
68 Uefi,
70 Hybrid,
72}
73
74impl BootType {
75 pub fn needs_bios(self) -> bool {
77 matches!(self, BootType::Bios | BootType::Hybrid)
78 }
79
80 pub fn needs_uefi(self) -> bool {
82 matches!(self, BootType::Uefi | BootType::Hybrid)
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, Default)]
88pub struct BootloaderConfig {
89 pub kind: BootloaderKind,
91
92 #[serde(rename = "config-file")]
94 pub config_file: Option<PathBuf>,
95
96 #[serde(default, rename = "extra-files")]
98 pub extra_files: Vec<PathBuf>,
99
100 #[serde(default)]
102 pub limine: LimineConfig,
103
104 #[serde(default)]
106 pub grub: GrubConfig,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
111#[serde(rename_all = "lowercase")]
112pub enum BootloaderKind {
113 Limine,
115 Grub,
117 #[default]
119 None,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct LimineConfig {
125 pub version: String,
127}
128
129impl Default for LimineConfig {
130 fn default() -> Self {
131 Self {
132 version: "v8.x-binary".to_string(),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, Default)]
139pub struct GrubConfig {
140 #[serde(default)]
142 pub modules: Vec<String>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ImageConfig {
148 pub format: ImageFormat,
150
151 pub output: Option<PathBuf>,
153
154 #[serde(default = "default_volume_label")]
156 pub volume_label: String,
157}
158
159impl Default for ImageConfig {
160 fn default() -> Self {
161 Self {
162 format: ImageFormat::Directory,
163 output: None,
164 volume_label: default_volume_label(),
165 }
166 }
167}
168
169fn default_volume_label() -> String {
170 "BOOT".to_string()
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
175#[serde(rename_all = "lowercase")]
176pub enum ImageFormat {
177 Iso,
179 Fat,
181 #[default]
183 Directory,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, Default)]
188pub struct RunnerConfig {
189 pub kind: RunnerKind,
191
192 #[serde(default)]
194 pub qemu: QemuConfig,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
199#[serde(rename_all = "lowercase")]
200pub enum RunnerKind {
201 #[default]
203 Qemu,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(default)]
209pub struct QemuConfig {
210 #[serde(default = "default_qemu_binary")]
212 pub binary: String,
213
214 #[serde(default = "default_machine")]
216 pub machine: String,
217
218 #[serde(default = "default_memory")]
220 pub memory: u32,
221
222 #[serde(default = "default_cores")]
224 pub cores: u32,
225
226 #[serde(default = "default_true")]
228 pub kvm: bool,
229
230 #[serde(default)]
232 pub extra_args: Vec<String>,
233}
234
235fn default_qemu_binary() -> String {
236 "qemu-system-x86_64".to_string()
237}
238
239fn default_machine() -> String {
240 "q35".to_string()
241}
242
243fn default_memory() -> u32 {
244 1024
245}
246
247fn default_cores() -> u32 {
248 1
249}
250
251impl Default for QemuConfig {
252 fn default() -> Self {
253 Self {
254 binary: "qemu-system-x86_64".to_string(),
255 machine: "q35".to_string(),
256 memory: 1024,
257 cores: 1,
258 kvm: true,
259 extra_args: Vec::new(),
260 }
261 }
262}
263
264fn default_true() -> bool {
265 true
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize, Default)]
270pub struct TestConfig {
271 #[serde(rename = "success-exit-code")]
273 pub success_exit_code: Option<i32>,
274
275 #[serde(default, rename = "extra-args")]
277 pub extra_args: Vec<String>,
278
279 pub timeout: Option<u64>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, Default)]
285pub struct RunConfig {
286 #[serde(default, rename = "extra-args")]
288 pub extra_args: Vec<String>,
289
290 #[serde(default)]
292 pub gui: bool,
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_config_default_values() {
301 let config = Config::default();
302 assert_eq!(config.boot.boot_type, BootType::Uefi);
303 assert_eq!(config.bootloader.kind, BootloaderKind::None);
304 assert!(config.bootloader.config_file.is_none());
305 assert!(config.bootloader.extra_files.is_empty());
306 assert_eq!(config.image.format, ImageFormat::Directory);
307 assert!(config.image.output.is_none());
308 assert_eq!(config.image.volume_label, "BOOT");
309 assert_eq!(config.runner.kind, RunnerKind::Qemu);
310 assert!(config.test.success_exit_code.is_none());
311 assert!(config.test.extra_args.is_empty());
312 assert!(config.test.timeout.is_none());
313 assert!(!config.run.gui);
314 assert!(config.run.extra_args.is_empty());
315 assert!(config.variables.is_empty());
316 assert!(!config.verbose);
317 }
318
319 #[test]
320 fn test_config_deserialize_minimal() {
321 let toml_str = r#"
322 [boot]
323 type = "uefi"
324 "#;
325 let config: Config = toml::from_str(toml_str).unwrap();
326 assert_eq!(config.boot.boot_type, BootType::Uefi);
327 assert_eq!(config.bootloader.kind, BootloaderKind::None);
328 assert_eq!(config.image.format, ImageFormat::Directory);
329 }
330
331 #[test]
332 fn test_config_deserialize_full() {
333 let toml_str = r#"
334 verbose = true
335
336 [boot]
337 type = "hybrid"
338
339 [bootloader]
340 kind = "limine"
341 config-file = "limine.conf"
342 extra-files = ["extra.bin"]
343
344 [bootloader.limine]
345 version = "v8.4.0-binary"
346
347 [image]
348 format = "iso"
349 output = "my-os.iso"
350 volume_label = "MYOS"
351
352 [runner]
353 kind = "qemu"
354
355 [runner.qemu]
356 binary = "qemu-system-x86_64"
357 memory = 2048
358 cores = 2
359 kvm = false
360
361 [test]
362 success-exit-code = 33
363 timeout = 30
364 extra-args = ["-device", "isa-debug-exit"]
365
366 [run]
367 gui = true
368 extra-args = ["-serial", "stdio"]
369
370 [variables]
371 TIMEOUT = "5"
372 "#;
373 let config: Config = toml::from_str(toml_str).unwrap();
374 assert_eq!(config.boot.boot_type, BootType::Hybrid);
375 assert_eq!(config.bootloader.kind, BootloaderKind::Limine);
376 assert_eq!(
377 config.bootloader.config_file,
378 Some(PathBuf::from("limine.conf"))
379 );
380 assert_eq!(config.bootloader.limine.version, "v8.4.0-binary");
381 assert_eq!(config.image.format, ImageFormat::Iso);
382 assert_eq!(config.image.output, Some(PathBuf::from("my-os.iso")));
383 assert_eq!(config.image.volume_label, "MYOS");
384 assert_eq!(config.runner.qemu.memory, 2048);
385 assert_eq!(config.runner.qemu.cores, 2);
386 assert!(!config.runner.qemu.kvm);
387 assert_eq!(config.test.success_exit_code, Some(33));
388 assert_eq!(config.test.timeout, Some(30));
389 assert!(config.run.gui);
390 assert_eq!(config.variables.get("TIMEOUT").unwrap(), "5");
391 assert!(config.verbose);
392 }
393
394 #[test]
395 fn test_config_deserialize_bios_boot_type() {
396 let toml_str = r#"
397 [boot]
398 type = "bios"
399
400 [bootloader]
401 kind = "grub"
402
403 [image]
404 format = "fat"
405 "#;
406 let config: Config = toml::from_str(toml_str).unwrap();
407 assert_eq!(config.boot.boot_type, BootType::Bios);
408 assert_eq!(config.bootloader.kind, BootloaderKind::Grub);
409 assert_eq!(config.image.format, ImageFormat::Fat);
410 }
411
412 #[test]
413 fn test_config_deserialize_invalid_boot_type() {
414 let toml_str = r#"
415 [boot]
416 type = "invalid"
417 "#;
418 let result: std::result::Result<Config, _> = toml::from_str(toml_str);
419 assert!(result.is_err());
420 }
421
422 #[test]
423 fn test_boot_type_needs_bios() {
424 assert!(BootType::Bios.needs_bios());
425 assert!(!BootType::Uefi.needs_bios());
426 assert!(BootType::Hybrid.needs_bios());
427 }
428
429 #[test]
430 fn test_boot_type_needs_uefi() {
431 assert!(!BootType::Bios.needs_uefi());
432 assert!(BootType::Uefi.needs_uefi());
433 assert!(BootType::Hybrid.needs_uefi());
434 }
435
436 #[test]
437 fn test_qemu_config_defaults() {
438 let qemu = QemuConfig::default();
439 assert_eq!(qemu.binary, "qemu-system-x86_64");
440 assert_eq!(qemu.machine, "q35");
441 assert_eq!(qemu.memory, 1024);
442 assert_eq!(qemu.cores, 1);
443 assert!(qemu.kvm);
444 assert!(qemu.extra_args.is_empty());
445 }
446
447 #[test]
448 fn test_limine_config_default_version() {
449 let limine = LimineConfig::default();
450 assert_eq!(limine.version, "v8.x-binary");
451 }
452}