237 lines
5.4 KiB
Rust

#![feature(let_chains)]
use std::{
fmt::Debug,
path::{Path, PathBuf},
process::Command,
};
use device::{QemuDevice, QemuSerialTarget};
pub mod aarch64;
pub mod i386;
pub mod x86_64;
pub mod device;
pub trait Architecture: IntoArgs {
type MachineType: IntoArgs;
type CpuType: IntoArgs;
type ImageType: IntoArgs;
const DEFAULT_COMMAND: &'static str;
}
pub trait IntoArgs {
fn add_args(&self, command: &mut Command);
}
impl<T: IntoArgs> IntoArgs for Option<T> {
fn add_args(&self, command: &mut Command) {
if let Some(val) = self {
val.add_args(command);
}
}
}
#[derive(Debug, Default)]
struct QemuCommon {
devices: Vec<QemuDevice>,
serial: Option<QemuSerialTarget>,
}
#[derive(Debug, Default)]
pub struct Qemu<A: Architecture> {
binary_override: Option<PathBuf>,
common: QemuCommon,
cpu: Option<A::CpuType>,
machine: Option<A::MachineType>,
boot_image: Option<A::ImageType>,
cdrom: Option<PathBuf>,
no_display: bool,
memory_megabytes: Option<usize>,
smp: Option<usize>,
arch: A,
}
impl Qemu<aarch64::QemuAArch64> {
pub fn new_aarch64() -> Self {
Qemu::new(aarch64::QemuAArch64)
}
}
impl Qemu<x86_64::QemuX86_64> {
pub fn new_x86_64() -> Self {
Qemu::new(x86_64::QemuX86_64::default())
}
pub fn with_bios_image(&mut self, image: PathBuf) -> &mut Self {
self.arch.bios_image = Some(image);
self
}
pub fn with_boot_slot(&mut self, slot: char) -> &mut Self {
self.arch.boot_slot = Some(slot);
self
}
}
impl Qemu<i386::QemuI386> {
pub fn new_i386() -> Self {
Qemu::new(i386::QemuI386::default())
}
}
impl<A: Architecture> Qemu<A> {
pub fn new(arch: A) -> Self {
Self {
binary_override: None,
common: QemuCommon::default(),
memory_megabytes: None,
no_display: false,
cpu: None,
machine: None,
boot_image: None,
cdrom: None,
smp: None,
arch,
}
}
pub fn with_device(&mut self, device: QemuDevice) -> &mut Self {
self.common.devices.push(device);
self
}
pub fn with_memory_megabytes(&mut self, value: usize) -> &mut Self {
self.memory_megabytes = Some(value);
self
}
pub fn with_serial(&mut self, serial: QemuSerialTarget) -> &mut Self {
self.common.serial = Some(serial);
self
}
pub fn with_boot_image(&mut self, image: A::ImageType) -> &mut Self {
self.boot_image = Some(image);
self
}
pub fn with_cdrom<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.cdrom = Some(path.as_ref().into());
self
}
pub fn with_cpu(&mut self, cpu: A::CpuType) -> &mut Self {
self.cpu = Some(cpu);
self
}
pub fn with_machine(&mut self, machine: A::MachineType) -> &mut Self {
self.machine = Some(machine);
self
}
pub fn with_smp(&mut self, smp: usize) -> &mut Self {
assert_ne!(smp, 0);
self.smp = Some(smp);
self
}
pub fn disable_display(&mut self) -> &mut Self {
self.no_display = true;
self
}
pub fn override_qemu<S: Into<PathBuf>>(&mut self, qemu: S) -> &mut Self {
self.binary_override = Some(qemu.into());
self
}
pub fn into_command(self) -> Command {
let qemu = self
.binary_override
.clone()
.unwrap_or_else(|| A::DEFAULT_COMMAND.into());
let mut command = Command::new(qemu);
self.add_args(&mut command);
command
}
}
impl IntoArgs for QemuCommon {
fn add_args(&self, command: &mut Command) {
self.serial.add_args(command);
}
}
impl<A: Architecture> IntoArgs for Qemu<A> {
fn add_args(&self, command: &mut Command) {
self.common.add_args(command);
for device in &self.common.devices {
device.add_args(command);
}
if let Some(mem) = self.memory_megabytes {
command.args(["-m", &format!("{}m", mem)]);
}
self.machine.add_args(command);
self.cpu.add_args(command);
self.boot_image.add_args(command);
if let Some(cdrom) = &self.cdrom {
command.args(["-cdrom", &format!("{}", cdrom.display())]);
}
if self.no_display {
command.args(["-display", "none"]);
}
if let Some(smp) = self.smp {
command.args(["-smp", &format!("{}", smp)]);
}
self.arch.add_args(command);
}
}
#[cfg(test)]
mod tests {
use crate::{device::QemuSerialTarget, Qemu};
#[test]
fn empty() {
let builder = Qemu::new_aarch64();
let command = builder.into_command();
assert_eq!(command.get_program(), "qemu-system-aarch64");
assert_eq!(command.get_args().len(), 0);
}
#[test]
fn qemu_override() {
let mut qemu = Qemu::new_aarch64();
qemu.override_qemu("test-qemu");
let command = qemu.into_command();
assert_eq!(command.get_program(), "test-qemu");
}
#[test]
fn common_options() {
let mut qemu = Qemu::new_aarch64();
qemu.with_serial(QemuSerialTarget::MonStdio);
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec!["-serial", "mon:stdio"]
);
}
}