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