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