From 9285a7f3c49d4657389a43292ecebedb3e1c6170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 9 Jul 2026 12:14:13 +0200 Subject: [PATCH] feat(xtask): add support for custom VMM and Hermit args --- Cargo.lock | 9 ++++++++- xtask/Cargo.toml | 3 ++- xtask/src/ci/firecracker.rs | 12 +++++++++++- xtask/src/ci/firecracker_vm_config.json | 2 +- xtask/src/ci/mod.rs | 7 +++++++ xtask/src/ci/qemu.rs | 16 ++++++++++++++-- xtask/src/ci/uhyve.rs | 15 ++++++++++++--- 7 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 488f6f647a..803e1493a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,7 +311,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 1.3.0", ] [[package]] @@ -1737,6 +1737,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "simple-shell" version = "0.0.1" @@ -2412,6 +2418,7 @@ dependencies = [ "home", "libc", "ovmf-prebuilt", + "shlex 2.0.1", "sysinfo", "ureq", "vsock", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9a144eae7b..1f35ae34f9 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [features] default = ["ci"] -ci = ["dep:libc", "dep:ovmf-prebuilt", "dep:sysinfo", "dep:ureq", "dep:vsock", "dep:wait-timeout"] +ci = ["dep:libc", "dep:ovmf-prebuilt", "dep:shlex", "dep:sysinfo", "dep:ureq", "dep:vsock", "dep:wait-timeout"] [dependencies] anyhow = "1.0" @@ -14,6 +14,7 @@ goblin = { version = "0.10", default-features = false, features = ["archive", "e home = "0.5" libc = { version = "0.2", optional = true } ovmf-prebuilt = { version = "0.2", optional = true } +shlex = { version = "2", optional = true } sysinfo = { version = "0.39", optional = true } ureq = { version = "3", default-features = false, features = ["rustls"], optional = true } vsock = { version = "0.5", optional = true } diff --git a/xtask/src/ci/firecracker.rs b/xtask/src/ci/firecracker.rs index 70e4417385..61cd2af702 100644 --- a/xtask/src/ci/firecracker.rs +++ b/xtask/src/ci/firecracker.rs @@ -5,22 +5,32 @@ use anyhow::Result; use clap::Args; use xshell::cmd; +use crate::ci; + /// Run image on Firecracker. #[derive(Args)] pub struct Firecracker { /// Run Firecracker using `sudo`. #[arg(long)] sudo: bool, + + /// Arguments to pass to Firecracker and Hermit, separated by another `--`. + #[arg(last = true)] + firecracker_and_hermit_args: Vec, } impl Firecracker { pub fn run(self, image: &Path, smp: usize) -> Result<()> { let sh = crate::sh()?; + let (firecracker_args, hermit_args) = ci::split_args(&self.firecracker_and_hermit_args); + let quoted_hermit_args = shlex::try_join(hermit_args.iter().map(AsRef::as_ref))?; + let config = format!( include_str!("firecracker_vm_config.json"), kernel_image_path = "hermit-loader-x86_64-fc", initrd_path = image.display(), + boot_args = quoted_hermit_args, vcpu_count = smp, ); eprintln!("firecracker config"); @@ -39,7 +49,7 @@ impl Firecracker { for run in 1.. { let log_path = Path::new("firecracker.log"); sh.write_file(log_path, "")?; - let res = cmd!(sh, "{program} {arg...} --no-api --config-file {config_path} --log-path {log_path} --level Info --show-level --show-log-origin").run(); + let res = cmd!(sh, "{program} {arg...} --no-api --config-file {config_path} --log-path {log_path} --level Info --show-level --show-log-origin {firecracker_args...}").run(); let log = sh.read_file(log_path)?; eprintln!("firecracker log"); diff --git a/xtask/src/ci/firecracker_vm_config.json b/xtask/src/ci/firecracker_vm_config.json index 79d15055cd..ac2b6200a7 100644 --- a/xtask/src/ci/firecracker_vm_config.json +++ b/xtask/src/ci/firecracker_vm_config.json @@ -2,7 +2,7 @@ "boot-source": {{ "kernel_image_path": "{kernel_image_path}", "initrd_path": "{initrd_path}", - "boot_args": "" + "boot_args": "{boot_args}" }}, "drives": [], "machine-config": {{ diff --git a/xtask/src/ci/mod.rs b/xtask/src/ci/mod.rs index 8c4053bb84..10a96dc05f 100644 --- a/xtask/src/ci/mod.rs +++ b/xtask/src/ci/mod.rs @@ -29,6 +29,13 @@ fn in_ci() -> bool { std::env::var_os("CI") == Some("true".into()) } +fn split_args>(args: &[T]) -> (&[T], &[T]) { + match args.iter().position(|arg| arg == "--") { + Some(index) => (&args[..index], &args[index + 1..]), + None => (args, &[]), + } +} + pub fn parent_root() -> &'static Path { crate::project_root().parent().unwrap() } diff --git a/xtask/src/ci/qemu.rs b/xtask/src/ci/qemu.rs index 0e60815491..9980948621 100644 --- a/xtask/src/ci/qemu.rs +++ b/xtask/src/ci/qemu.rs @@ -14,6 +14,7 @@ use wait_timeout::ChildExt as _; use xshell::cmd; use crate::arch::Arch; +use crate::ci; const DEFAULT_GUEST_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 5, 3)); @@ -51,6 +52,10 @@ pub struct Qemu { /// Use a TAP device for networking. #[arg(long)] tap: bool, + + /// Arguments to pass to QEMU and Hermit, separated by another `--`. + #[arg(last = true)] + qemu_and_hermit_args: Vec, } #[derive(ValueEnum, PartialEq, Eq, Clone, Copy)] @@ -101,6 +106,8 @@ impl Qemu { ) -> Result<()> { let sh = crate::sh()?; + let (qemu_args, hermit_args) = ci::split_args(&self.qemu_and_hermit_args); + let virtiofsd = self .devices .iter() @@ -136,7 +143,8 @@ impl Qemu { .args(&["-m".to_owned(), format!("{memory}M")]) .args(&["-global", "virtio-mmio.force-legacy=off"]) .args(self.device_args(memory)) - .args(self.cmdline_args(image_name)); + .args(qemu_args) + .args(self.cmdline_args(image_name, hermit_args)); eprintln!("$ {qemu}"); let mut qemu = Command::from(qemu); @@ -482,10 +490,14 @@ impl Qemu { .collect() } - fn cmdline_args(&self, image_name: &str) -> Vec { + fn cmdline_args(&self, image_name: &str, hermit_args: &[String]) -> Vec { + let (user_kernel_args, user_app_args) = ci::split_args(hermit_args); + let mut cmdline = self.kernel_args(); + cmdline.extend(user_kernel_args.iter().cloned()); let mut app_args = self.app_args(image_name); + app_args.extend(user_app_args.iter().cloned()); if !app_args.is_empty() { cmdline.push("--".to_owned()); cmdline.append(&mut app_args); diff --git a/xtask/src/ci/uhyve.rs b/xtask/src/ci/uhyve.rs index c1e9b82d39..a5010db981 100644 --- a/xtask/src/ci/uhyve.rs +++ b/xtask/src/ci/uhyve.rs @@ -11,20 +11,29 @@ pub struct Uhyve { /// Run Uhyve using `sudo`. #[arg(long)] sudo: bool, + + /// Arguments to pass to Uhyve and Hermit, separated by another `--`. + #[arg(last = true)] + uhyve_and_hermit_args: Vec, } impl Uhyve { pub fn run(self, image: &Path, smp: usize) -> Result<()> { let sh = crate::sh()?; + let uhyve_and_hermit_args = &self.uhyve_and_hermit_args[..]; + let uhyve = env::var("UHYVE").unwrap_or_else(|_| "uhyve".to_owned()); let program = if self.sudo { "sudo" } else { uhyve.as_str() }; let arg = self.sudo.then_some(uhyve.as_str()); let smp_arg = format!("--cpu-count={smp}"); - cmd!(sh, "{program} {arg...} {smp_arg} {image}") - .env("RUST_LOG", "debug") - .run()?; + cmd!( + sh, + "{program} {arg...} {smp_arg} {image} {uhyve_and_hermit_args...}" + ) + .env("RUST_LOG", "debug") + .run()?; Ok(()) }