Implement the next OSDK

This commit is contained in:
Zhang Junyang 2024-04-13 01:08:36 +08:00 committed by Tate, Hongliang Tian
parent 79bdbbe4f9
commit e4c2151566
41 changed files with 1819 additions and 1466 deletions

View File

@ -58,7 +58,7 @@ jobs:
- name: Boot Test (MicroVM)
id: boot_test_microvm
run: make run AUTO_TEST=boot ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
run: make run AUTO_TEST=boot ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1
- name: Boot Test (Linux Legacy 32-bit Boot Protocol)
id: boot_test_linux_legacy32
@ -74,7 +74,7 @@ jobs:
- name: Syscall Test at Ext2 (MicroVM)
id: syscall_test_at_ext2
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1
- name: Syscall Test at Exfat
id: syscall_test_at_exfat_linux
@ -82,4 +82,4 @@ jobs:
- name: Regression Test (MicroVM)
id: regression_test_linux
run: make run AUTO_TEST=regression ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
run: make run AUTO_TEST=regression ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1

View File

@ -1,38 +1,37 @@
# SPDX-License-Identifier: MPL-2.0
# Project-wide options.
# Global options.
ARCH ?= x86_64
# End of project-wide options.
BOOT_METHOD ?= grub-rescue-iso
BOOT_PROTOCOL ?= multiboot2
BUILD_SYSCALL_TEST ?= 0
ENABLE_KVM ?= 1
INTEL_TDX ?= 0
RELEASE_MODE ?= 0
SCHEME ?= ""
# End of global options.
# The Makefile provides a way to run arbitrary tests in the kernel
# mode using the kernel command line.
# Here are the options for the auto test feature.
AUTO_TEST ?= none
BOOT_LOADER ?= grub
BOOT_PROTOCOL ?= multiboot2
BUILD_SYSCALL_TEST ?= 0
ENABLE_KVM ?= 1
EXTRA_BLOCKLISTS_DIRS ?= ""
INTEL_TDX ?= 0
SCHEMA ?= ""
RELEASE_MODE ?= 0
SKIP_GRUB_MENU ?= 1
SYSCALL_TEST_DIR ?= /tmp
# End of auto test features.
CARGO_OSDK := ~/.cargo/bin/cargo-osdk
CARGO_OSDK_ARGS := --arch=$(ARCH)
CARGO_OSDK_ARGS := --target-arch=$(ARCH)
ifeq ($(AUTO_TEST), syscall)
BUILD_SYSCALL_TEST := 1
CARGO_OSDK_ARGS += --kcmd_args+="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
CARGO_OSDK_ARGS += --kcmd_args+="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
CARGO_OSDK_ARGS += --init_args+="/opt/syscall_test/run_syscall_test.sh"
CARGO_OSDK_ARGS += --kcmd-args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
CARGO_OSDK_ARGS += --kcmd-args="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
CARGO_OSDK_ARGS += --init-args="/opt/syscall_test/run_syscall_test.sh"
else ifeq ($(AUTO_TEST), regression)
CARGO_OSDK_ARGS += --init_args+="/regression/run_regression_test.sh"
CARGO_OSDK_ARGS += --init-args="/regression/run_regression_test.sh"
else ifeq ($(AUTO_TEST), boot)
CARGO_OSDK_ARGS += --init_args+="/regression/boot_hello.sh"
CARGO_OSDK_ARGS += --init-args="/regression/boot_hello.sh"
endif
ifeq ($(RELEASE_MODE), 1)
@ -43,21 +42,26 @@ ifeq ($(INTEL_TDX), 1)
CARGO_OSDK_ARGS += --features intel_tdx
endif
CARGO_OSDK_ARGS += --bootloader="$(BOOT_LOADER)"
CARGO_OSDK_ARGS += --boot_protocol="$(BOOT_PROTOCOL)"
ifneq ($(SCHEMA), "")
CARGO_OSDK_ARGS += --schema $(SCHEMA)
ifneq ($(SCHEME), "")
CARGO_OSDK_ARGS += --scheme $(SCHEME)
else
CARGO_OSDK_ARGS += --boot-method="$(BOOT_METHOD)"
endif
# To test the linux-efi-handover64 boot protocol, we need to use Debian's
# GRUB release, which is installed in /usr/bin in our Docker image.
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
else ifeq ($(BOOT_PROTOCOL), linux-legacy32)
CARGO_OSDK_ARGS += --linux-x86-legacy-boot
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
else
CARGO_OSDK_ARGS += --grub-boot-protocol=$(BOOT_PROTOCOL)
endif
ifeq ($(ENABLE_KVM), 1)
CARGO_OSDK_ARGS += --qemu_args+="--enable-kvm"
CARGO_OSDK_ARGS += --qemu-args="--enable-kvm"
endif
# Pass make variables to all subdirectory makes
@ -186,9 +190,11 @@ check: $(CARGO_OSDK)
(echo "Error: STD_CRATES and NOSTD_CRATES combined is not the same as all workspace members" && exit 1)
@rm /tmp/all_crates /tmp/combined_crates
@for dir in $(NON_OSDK_CRATES); do \
echo "Checking $$dir"; \
(cd $$dir && cargo clippy -- -D warnings) || exit 1; \
done
@for dir in $(OSDK_CRATES); do \
echo "Checking $$dir"; \
(cd $$dir && cargo osdk clippy -- -- -D warnings) || exit 1; \
done
@make --no-print-directory -C regression check

178
OSDK.toml
View File

@ -1,7 +1,19 @@
[project]
type = "kernel"
vars = [
["SMP", "1"],
["MEM", "2G"],
["EXT2_IMG", "$OSDK_CWD/regression/build/ext2.img"],
["EXFAT_IMG", "$OSDK_CWD/regression/build/exfat.img"],
]
[boot]
method = "grub-rescue-iso"
[run]
vars = [
["OVMF_PATH", "/usr/share/OVMF"],
]
[run.boot]
kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
@ -12,117 +24,67 @@ kcmd_args = [
]
init_args = ["sh", "-l"]
initramfs = "regression/build/initramfs.cpio.gz"
boot_protocol = "multiboot2"
bootloader = "grub"
ovmf = "/root/ovmf/release"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
[test]
boot_protocol = "multiboot"
bootloader = "qemu"
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
boot.method = "qemu-direct"
[grub]
protocol = "multiboot2"
['cfg(arch="x86_64", schema="iommu")'.run]
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtconsole,chardev=mux",
"-device intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]
[qemu]
args = "$(./tools/qemu_args.sh)"
['cfg(arch="x86_64", schema="microvm")'.run]
bootloader = "qemu"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine microvm,rtc=on",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-nodefaults",
"-no-user-config",
"-device virtio-blk-device,drive=x0,serial=vext2",
"-device virtio-blk-device,drive=x1,serial=vexfat",
"-device virtio-keyboard-device",
"-device virtio-net-device,netdev=net01",
"-device virtio-serial-device",
"-device virtconsole,chardev=mux",
[scheme."microvm"]
boot.method = "qemu-direct"
vars = [
["MICROVM", "true"],
]
qemu.args = "$(./tools/qemu_args.sh)"
['cfg(arch="riscv64")'.run]
qemu_args = [
"-machine virt",
"--no-reboot",
"-m 2G",
"-nographic",
[scheme."iommu"]
supported_archs = ["x86_64"]
vars = [
["IOMMU_DEV_EXTRA", ",iommu_platform=on,ats=on"],
["IOMMU_EXTRA_ARGS", """\
-device intel-iommu,intremap=on,device-iotlb=on \
-device ioh3420,id=pcie.0,chassis=1\
"""],
]
qemu.args = "$(./tools/qemu_args.sh)"
[scheme."tdx"]
supported_archs = ["x86_64"]
build.features = ["intel_tdx"]
vars = [
["MEM", "8G"],
["OVMF_PATH", "~/tdx-tools/ovmf"],
]
boot.method = "grub-qcow2"
grub.mkrescue_path = "~/tdx-tools/grub"
grub.protocol = "linux"
qemu.args = """\
-accel kvm \
-name process=tdxvm,debug-threads=on \
-m $MEM \
-smp $SMP \
-vga none \
-nographic \
-monitor pty \
-no-hpet \
-nodefaults \
-monitor telnet:127.0.0.1:9003,server,nowait \
-bios $OVMF_PATH/OVMF_VARS.fd \
-object tdx-guest,sept-ve-disable,id=tdx,quote-generation-service=vsock:2:4050 \
-cpu host,-kvm-steal-time,pmu=off,tsc-freq=1000000000 \
-machine q35,kernel_irqchip=split,confidential-guest-support=tdx \
-device virtio-net-pci,netdev=mynet0,disable-legacy=on,disable-modern=off \
-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off \
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off \
-drive file=fs.img,if=none,format=raw,id=x0 \
-netdev user,id=mynet0,hostfwd=tcp::10027-:22,hostfwd=tcp::54136-:8090 \
-chardev stdio,id=mux,mux=on,logfile=$OSDK_CWD/$(date '+%Y-%m-%dT%H%M%S').log \
-device virtio-serial,romfile= \
-device virtconsole,chardev=mux \
-monitor chardev:mux \
-serial chardev:mux \
"""

View File

@ -58,7 +58,7 @@ qemu_args = [ # <10>
"-m 2G",
]
['cfg(arch="x86_64", schema=microvm)'.run] # <12>
['cfg(arch="x86_64", scheme=microvm)'.run] # <12>
bootloader = "qemu"
qemu_args = [ # <10>
"-machine microvm,rtc=on",
@ -169,12 +169,12 @@ which is used to create a GRUB CD_ROM.
Cfg is an advanced feature to create multiple profiles for
the same actions under different scenarios. Currently we
have two configurable keys, which are `arch` and `schema`.
have two configurable keys, which are `arch` and `scheme`.
The key `arch` has a fixed set of values which is aligned
with the CLI `--arch` argument. If an action has no specified
arch, it matches all the architectures. The key `schema` allows
user-defined values and can be selected by the `--schema` CLI
argument. The key `schema` can be used to create special settings
arch, it matches all the architectures. The key `scheme` allows
user-defined values and can be selected by the `--scheme` CLI
argument. The key `scheme` can be used to create special settings
(especially special QEMU configurations). If a cfg action is
matched, unspecified and required arguments will be inherited
from the action that has no cfg (i.e. the default action setting).

7
osdk/Cargo.lock generated
View File

@ -135,6 +135,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"shlex",
"syn",
"toml",
]
@ -470,6 +471,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.0"

View File

@ -19,6 +19,7 @@ quote = "1.0.35"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
sha2 = "0.10.8"
shlex = "1.3.0"
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
toml = { version = "0.8.8", features = ["preserve_order"] }

View File

@ -12,8 +12,11 @@ use std::fmt::{self, Display, Formatter};
/// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Arch {
#[serde(rename = "aarch64")]
Aarch64,
#[serde(rename = "riscv64")]
X86_64,
#[serde(rename = "x86_64")]
RiscV64,
}
@ -33,11 +36,19 @@ impl ValueEnum for Arch {
impl Arch {
/// Get the target triple for the architecture.
pub fn triple(&self) -> String {
pub fn triple(&self) -> &'static str {
match self {
Arch::Aarch64 => "aarch64-unknown-none".to_owned(),
Arch::RiscV64 => "riscv64gc-unknown-none-elf".to_owned(),
Arch::X86_64 => "x86_64-unknown-none".to_owned(),
Arch::Aarch64 => "aarch64-unknown-none",
Arch::RiscV64 => "riscv64gc-unknown-none-elf",
Arch::X86_64 => "x86_64-unknown-none",
}
}
pub fn system_qemu(&self) -> &'static str {
match self {
Arch::Aarch64 => "qemu-system-aarch64",
Arch::RiscV64 => "qemu-system-riscv64",
Arch::X86_64 => "qemu-system-x86_64",
}
}

View File

@ -7,7 +7,7 @@ pub mod vm_image;
use bin::AsterBin;
use file::{BundleFile, Initramfs};
use std::process;
use vm_image::AsterVmImage;
use vm_image::{AsterVmImage, AsterVmImageType};
use std::{
path::{Path, PathBuf},
@ -16,11 +16,9 @@ use std::{
};
use crate::{
arch::Arch,
cli::CargoArgs,
config_manager::{
action::{ActionSettings, Bootloader},
RunConfig,
config::{
scheme::{ActionChoice, BootMethod},
Config,
},
error::Errno,
error_msg,
@ -42,16 +40,20 @@ pub struct BundleManifest {
pub initramfs: Option<Initramfs>,
pub aster_bin: Option<AsterBin>,
pub vm_image: Option<AsterVmImage>,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub config: Config,
pub action: ActionChoice,
pub last_modified: SystemTime,
}
impl Bundle {
/// This function creates a new `Bundle` without adding any files.
pub fn new(path: impl AsRef<Path>, settings: ActionSettings, cargo_args: CargoArgs) -> Self {
pub fn new(path: impl AsRef<Path>, config: &Config, action: ActionChoice) -> Self {
std::fs::create_dir_all(path.as_ref()).unwrap();
let initramfs = if let Some(ref initramfs) = settings.initramfs {
let config_initramfs = match action {
ActionChoice::Run => config.run.boot.initramfs.as_ref(),
ActionChoice::Test => config.test.boot.initramfs.as_ref(),
};
let initramfs = if let Some(ref initramfs) = config_initramfs {
if !initramfs.exists() {
error_msg!("initramfs file not found: {}", initramfs.display());
process::exit(Errno::BuildCrate as _);
@ -65,8 +67,8 @@ impl Bundle {
initramfs,
aster_bin: None,
vm_image: None,
settings,
cargo_args,
config: config.clone(),
action,
last_modified: SystemTime::now(),
},
path: path.as_ref().to_path_buf(),
@ -109,105 +111,139 @@ impl Bundle {
})
}
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
// Compare the manifest with the run configuration.
// TODO: This pairwise comparison will result in some false negatives. We may
// fix it by pondering upon each fields with more care.
if self.manifest.settings != config.settings
|| self.manifest.cargo_args != config.cargo_args
pub fn can_run_with_config(&self, config: &Config, action: ActionChoice) -> Result<(), String> {
// If built for testing, better not to run it. Vice versa.
if self.manifest.action != action {
return Err(format!(
"The bundle is built for {:?}",
self.manifest.action
));
}
let self_action = match self.manifest.action {
ActionChoice::Run => &self.manifest.config.run,
ActionChoice::Test => &self.manifest.config.test,
};
let config_action = match action {
ActionChoice::Run => &config.run,
ActionChoice::Test => &config.test,
};
// Compare the manifest with the run configuration except the initramfs and the boot method.
if self_action.grub != config_action.grub
|| self_action.qemu != config_action.qemu
|| self_action.build != config_action.build
|| self_action.boot.kcmdline != config_action.boot.kcmdline
{
return false;
return Err("The bundle is not compatible with the run configuration".to_owned());
}
// Checkout if the files on disk supports the boot method
match config_action.boot.method {
BootMethod::QemuDirect => {
if self.manifest.aster_bin.is_none() {
return Err("Kernel binary is required for direct QEMU booting".to_owned());
};
}
BootMethod::GrubRescueIso => {
let Some(ref vm_image) = self.manifest.vm_image else {
return Err("VM image is required for QEMU booting".to_owned());
};
if !matches!(vm_image.typ(), AsterVmImageType::GrubIso(_)) {
return Err("VM image in the bundle is not a Grub ISO image".to_owned());
}
}
BootMethod::GrubQcow2 => {
let Some(ref vm_image) = self.manifest.vm_image else {
return Err("VM image is required for QEMU booting".to_owned());
};
if !matches!(vm_image.typ(), AsterVmImageType::Qcow2(_)) {
return Err("VM image in the bundle is not a Qcow2 image".to_owned());
}
}
}
// Compare the initramfs.
match (&self.manifest.initramfs, &config.settings.initramfs) {
let initramfs_err =
"The initramfs in the bundle is different from the one in the run configuration"
.to_owned();
match (&self.manifest.initramfs, &config_action.boot.initramfs) {
(Some(initramfs), Some(initramfs_path)) => {
let config_initramfs = Initramfs::new(initramfs_path);
if initramfs.sha256sum() != config_initramfs.sha256sum() {
return false;
return Err(initramfs_err);
}
}
(None, None) => {}
_ => {
return false;
return Err(initramfs_err);
}
};
true
Ok(())
}
pub fn last_modified_time(&self) -> SystemTime {
self.manifest.last_modified
}
pub fn run(&self, config: &RunConfig) {
if !self.can_run_with_config(config) {
error_msg!("The bundle is not compatible with the run configuration");
std::process::exit(Errno::RunBundle as _);
}
let mut qemu_cmd = Command::new(config.settings.qemu_exe.clone().unwrap_or_else(|| {
PathBuf::from(match config.arch {
Arch::Aarch64 => "qemu-system-aarch64",
Arch::RiscV64 => "qemu-system-riscv64",
Arch::X86_64 => "qemu-system-x86_64",
})
}));
// FIXME: Arguments like "-m 2G" sould be separated into "-m" and "2G". This
// is a dirty hack to make it work. Anything like space in the paths will
// break this.
for arg in &config.settings.qemu_args {
for part in arg.split_whitespace() {
qemu_cmd.arg(part);
pub fn run(&self, config: &Config, action: ActionChoice) {
match self.can_run_with_config(config, action) {
Ok(()) => {}
Err(msg) => {
error_msg!("{}", msg);
std::process::exit(Errno::RunBundle as _);
}
}
match config.settings.bootloader {
Some(Bootloader::Qemu) => {
let Some(ref aster_bin) = self.manifest.aster_bin else {
error_msg!("Kernel ELF binary is required for direct QEMU booting");
std::process::exit(Errno::RunBundle as _);
};
let action = match action {
ActionChoice::Run => &config.run,
ActionChoice::Test => &config.test,
};
let mut qemu_cmd = Command::new(&action.qemu.path);
match shlex::split(&action.qemu.args) {
Some(v) => {
for arg in v {
qemu_cmd.arg(arg);
}
}
None => {
error_msg!("Failed to parse qemu args: {:#?}", &action.qemu.args);
process::exit(Errno::ParseMetadata as _);
}
}
match action.boot.method {
BootMethod::QemuDirect => {
let aster_bin = self.manifest.aster_bin.as_ref().unwrap();
qemu_cmd
.arg("-kernel")
.arg(self.path.join(aster_bin.path()));
if let Some(ref initramfs) = config.settings.initramfs {
if let Some(ref initramfs) = action.boot.initramfs {
qemu_cmd.arg("-initrd").arg(initramfs);
} else {
info!("No initramfs specified");
};
qemu_cmd
.arg("-append")
.arg(config.settings.combined_kcmd_args().join(" "));
qemu_cmd.arg("-append").arg(action.boot.kcmdline.join(" "));
}
Some(Bootloader::Grub) => {
let Some(ref vm_image) = self.manifest.vm_image else {
error_msg!("VM image is required for QEMU booting");
std::process::exit(Errno::RunBundle as _);
};
BootMethod::GrubRescueIso => {
let vm_image = self.manifest.vm_image.as_ref().unwrap();
assert!(matches!(vm_image.typ(), AsterVmImageType::GrubIso(_)));
qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path()));
if let Some(ovmf) = &config.settings.ovmf {
qemu_cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=0,readonly=on,file={}",
ovmf.join("OVMF_CODE.fd").display()
));
qemu_cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=1,file={}",
ovmf.join("OVMF_VARS.fd").display()
));
}
}
None => {
error_msg!("Bootloader is required for QEMU booting");
std::process::exit(Errno::RunBundle as _);
BootMethod::GrubQcow2 => {
let vm_image = self.manifest.vm_image.as_ref().unwrap();
assert!(matches!(vm_image.typ(), AsterVmImageType::Qcow2(_)));
qemu_cmd.arg("-drive").arg(format!(
"file={},index=0,media=disk,format=qcow2",
self.path
.join(vm_image.path())
.into_os_string()
.into_string()
.unwrap()
));
}
};
for drive_file in &config.settings.drive_files {
qemu_cmd.arg("-drive").arg(format!(
"file={},{}",
drive_file.path.display(),
drive_file.append,
));
}
info!("Running QEMU: {:#?}", qemu_cmd);
let exit_status = qemu_cmd.status().unwrap();
if !exit_status.success() {

View File

@ -18,7 +18,7 @@ pub struct AsterVmImage {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AsterVmImageType {
GrubIso(AsterGrubIsoImageMeta),
// TODO: add more vm image types such as qcow2, etc.
Qcow2(AsterQcow2ImageMeta),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -26,6 +26,11 @@ pub struct AsterGrubIsoImageMeta {
pub grub_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterQcow2ImageMeta {
pub grub_version: String,
}
impl BundleFile for AsterVmImage {
fn path(&self) -> &PathBuf {
&self.path
@ -50,6 +55,10 @@ impl AsterVmImage {
}
}
pub fn typ(&self) -> &AsterVmImageType {
&self.typ
}
/// Move the binary to the `base` directory and convert the path to a relative path.
pub fn move_to(self, base: impl AsRef<Path>) -> Self {
let file_name = self.path.file_name().unwrap();

View File

@ -10,37 +10,40 @@ use crate::{
execute_build_command, execute_debug_command, execute_forwarded_command,
execute_new_command, execute_run_command, execute_test_command,
},
config_manager::{
action::{BootProtocol, Bootloader},
manifest::ProjectType,
BuildConfig, DebugConfig, RunConfig, TestConfig,
config::{
manifest::{ProjectType, TomlManifest},
scheme::{BootMethod, BootProtocol},
Config,
},
};
pub fn main() {
let osdk_subcommand = match Cli::parse() {
let (osdk_subcommand, common_args) = match Cli::parse() {
Cli {
cargo_subcommand: CargoSubcommand::Osdk(osdk_subcommand),
} => osdk_subcommand,
common_args,
} => (osdk_subcommand, common_args),
};
let load_config = || {
let manifest = TomlManifest::load(&common_args.build_args.features);
let scheme = manifest.get_scheme(common_args.scheme.as_ref());
Config::new(scheme, &common_args)
};
match &osdk_subcommand {
OsdkSubcommand::New(args) => execute_new_command(args),
OsdkSubcommand::Build(build_args) => {
let build_config = BuildConfig::parse(build_args);
execute_build_command(&build_config);
execute_build_command(&load_config(), build_args);
}
OsdkSubcommand::Run(run_args) => {
let run_config = RunConfig::parse(run_args);
execute_run_command(&run_config);
execute_run_command(&load_config(), &run_args.gdb_server_args);
}
OsdkSubcommand::Debug(debug_args) => {
let debug_config = DebugConfig::parse(debug_args);
execute_debug_command(&debug_config);
execute_debug_command(&load_config().run.build.profile, debug_args);
}
OsdkSubcommand::Test(test_args) => {
let test_config = TestConfig::parse(test_args);
execute_test_command(&test_config);
execute_test_command(&load_config(), test_args);
}
OsdkSubcommand::Check(args) => execute_forwarded_command("check", &args.args),
OsdkSubcommand::Clippy(args) => execute_forwarded_command("clippy", &args.args),
@ -54,6 +57,8 @@ pub fn main() {
pub struct Cli {
#[clap(subcommand)]
cargo_subcommand: CargoSubcommand,
#[command(flatten)]
common_args: CommonArgs,
}
#[derive(Debug, Parser)]
@ -136,18 +141,23 @@ impl NewArgs {
#[derive(Debug, Parser)]
pub struct BuildArgs {
#[command(flatten)]
pub cargo_args: CargoArgs,
#[command(flatten)]
pub osdk_args: OsdkArgs,
#[arg(
long = "for-test",
help = "Build for running unit tests",
default_value_t
)]
pub for_test: bool,
#[arg(
long = "output",
short = 'o',
help = "Output directory for all generated artifacts",
value_name = "DIR"
)]
pub output: Option<PathBuf>,
}
#[derive(Debug, Parser)]
pub struct RunArgs {
#[command(flatten)]
pub cargo_args: CargoArgs,
#[command(flatten)]
pub osdk_args: OsdkArgs,
#[command(flatten)]
pub gdb_server_args: GdbServerArgs,
}
@ -181,10 +191,6 @@ pub struct GdbServerArgs {
#[derive(Debug, Parser)]
pub struct DebugArgs {
#[command(flatten)]
pub cargo_args: CargoArgs,
#[command(flatten)]
pub osdk_args: OsdkArgs,
#[arg(
long,
help = "Specify the address of the remote target",
@ -195,15 +201,11 @@ pub struct DebugArgs {
#[derive(Debug, Parser)]
pub struct TestArgs {
#[command(flatten)]
pub cargo_args: CargoArgs,
#[arg(
name = "TESTNAME",
help = "Only run tests containing this string in their names"
)]
pub test_name: Option<String>,
#[command(flatten)]
pub osdk_args: OsdkArgs,
}
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
@ -211,79 +213,120 @@ pub struct CargoArgs {
#[arg(
long,
help = "The Cargo build profile (built-in candidates are 'dev', 'release', 'test' and 'bench')",
default_value = "dev",
conflicts_with = "release"
conflicts_with = "release",
global = true
)]
pub profile: String,
pub profile: Option<String>,
#[arg(
long,
help = "Build artifacts in release mode",
conflicts_with = "profile"
conflicts_with = "profile",
global = true
)]
pub release: bool,
#[arg(long, value_name = "FEATURES", help = "List of features to activate", value_delimiter = ',', num_args = 1..)]
#[arg(
long,
value_name = "FEATURES",
help = "List of features to activate",
value_delimiter = ',',
num_args = 1..,
global = true,
)]
pub features: Vec<String>,
}
impl CargoArgs {
pub fn profile(&self) -> Option<String> {
if self.release {
Some("release".to_owned())
} else {
self.profile.clone()
}
}
}
#[derive(Debug, Args)]
pub struct OsdkArgs {
#[arg(long, value_name = "ARCH", help = "The architecture to build for")]
pub arch: Option<Arch>,
pub struct CommonArgs {
#[command(flatten)]
pub build_args: CargoArgs,
#[arg(
long = "schema",
help = "Select the specific configuration schema provided in the OSDK manifest",
value_name = "SCHEMA"
long = "linux-x86-legacy-boot",
help = "Enable legacy 32-bit boot support for the Linux x86 boot protocol",
global = true
)]
pub schema: Option<String>,
pub linux_x86_legacy_boot: bool,
#[arg(
long = "kcmd_args+",
long = "target-arch",
value_name = "ARCH",
help = "The architecture to build for",
global = true
)]
pub target_arch: Option<Arch>,
#[arg(
long = "scheme",
help = "Select the specific configuration scheme provided in the OSDK manifest",
value_name = "SCHEME",
global = true
)]
pub scheme: Option<String>,
#[arg(
long = "kcmd-args",
require_equals = true,
help = "Extra or overriding command line arguments for guest kernel",
value_name = "ARGS"
value_name = "ARGS",
global = true
)]
pub kcmd_args: Vec<String>,
#[arg(
long = "init_args+",
long = "init-args",
require_equals = true,
help = "Extra command line arguments for init process",
value_name = "ARGS"
value_name = "ARGS",
global = true
)]
pub init_args: Vec<String>,
#[arg(long, help = "Path of initramfs", value_name = "PATH")]
#[arg(long, help = "Path of initramfs", value_name = "PATH", global = true)]
pub initramfs: Option<PathBuf>,
#[arg(long = "ovmf", help = "Path of OVMF", value_name = "PATH")]
pub ovmf: Option<PathBuf>,
#[arg(long = "opensbi", help = "Path of OpenSBI", value_name = "PATH")]
pub opensbi: Option<PathBuf>,
#[arg(
long = "bootloader",
long = "boot-method",
help = "Loader for booting the kernel",
value_name = "BOOTLOADER"
value_name = "BOOTMETHOD",
global = true
)]
pub bootloader: Option<Bootloader>,
pub boot_method: Option<BootMethod>,
#[arg(
long = "display-grub-menu",
help = "Display the GRUB menu if booting with GRUB",
global = true
)]
pub display_grub_menu: bool,
#[arg(
long = "grub-mkrescue",
help = "Path of grub-mkrescue",
value_name = "PATH"
value_name = "PATH",
global = true
)]
pub grub_mkrescue: Option<PathBuf>,
#[arg(
long = "boot_protocol",
long = "grub-boot-protocol",
help = "Protocol for booting the kernel",
value_name = "BOOT_PROTOCOL"
value_name = "BOOT_PROTOCOL",
global = true
)]
pub boot_protocol: Option<BootProtocol>,
pub grub_boot_protocol: Option<BootProtocol>,
#[arg(
long = "qemu_exe",
long = "qemu-exe",
help = "The QEMU executable file",
value_name = "FILE"
value_name = "FILE",
global = true
)]
pub qemu_exe: Option<PathBuf>,
#[arg(
long = "qemu_args+",
long = "qemu-args",
require_equals = true,
help = "Extra arguments or overriding arguments for running QEMU",
value_name = "ARGS"
value_name = "ARGS",
global = true
)]
pub qemu_args_add: Vec<String>,
pub qemu_args: Vec<String>,
}

View File

@ -14,7 +14,6 @@ use crate::{
bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta},
file::BundleFile,
},
config_manager::action::BootProtocol,
util::get_current_crate_info,
};
@ -22,13 +21,13 @@ pub fn make_install_bzimage(
install_dir: impl AsRef<Path>,
target_dir: impl AsRef<Path>,
aster_elf: &AsterBin,
protocol: &BootProtocol,
linux_x86_legacy_boot: bool,
) -> AsterBin {
let target_name = get_current_crate_info().name;
let image_type = match protocol {
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
_ => unreachable!(),
let image_type = if linux_x86_legacy_boot {
BzImageType::Legacy32
} else {
BzImageType::Efi64
};
let setup_bin = {
let setup_install_dir = target_dir.as_ref();
@ -60,9 +59,9 @@ pub fn make_install_bzimage(
AsterBin::new(
&install_path,
AsterBinType::BzImage(AsterBzImageMeta {
support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32),
support_legacy32_boot: linux_x86_legacy_boot,
support_efi_boot: false,
support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64),
support_efi_handover: !linux_x86_legacy_boot,
}),
aster_elf.version().clone(),
aster_elf.stripped(),

View File

@ -12,7 +12,10 @@ use crate::{
file::BundleFile,
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
},
config_manager::{action::BootProtocol, BuildConfig},
config::{
scheme::{ActionChoice, BootProtocol},
Config,
},
util::get_current_crate_info,
};
@ -20,11 +23,16 @@ pub fn create_bootdev_image(
target_dir: impl AsRef<Path>,
aster_bin: &AsterBin,
initramfs_path: Option<impl AsRef<Path>>,
config: &BuildConfig,
config: &Config,
action: ActionChoice,
) -> AsterVmImage {
let target_name = get_current_crate_info().name;
let iso_root = &target_dir.as_ref().join("iso_root");
let protocol = &config.settings.boot_protocol;
let action = match &action {
ActionChoice::Run => &config.run,
ActionChoice::Test => &config.test,
};
let protocol = &action.grub.boot_protocol;
// Clear or make the iso dir.
if iso_root.exists() {
@ -43,12 +51,12 @@ pub fn create_bootdev_image(
// Make the kernel image and place it in the boot directory.
match protocol {
Some(BootProtocol::LinuxLegacy32) | Some(BootProtocol::LinuxEfiHandover64) => {
BootProtocol::Linux => {
make_install_bzimage(
iso_root.join("boot"),
&target_dir,
aster_bin,
&protocol.clone().unwrap(),
action.build.linux_x86_legacy_boot,
);
}
_ => {
@ -65,22 +73,17 @@ pub fn create_bootdev_image(
None
};
let grub_cfg = generate_grub_cfg(
&config.settings.combined_kcmd_args().join(" "),
true,
&action.boot.kcmdline.join(" "),
!action.grub.display_grub_menu,
initramfs_in_image,
&protocol.clone().unwrap_or(BootProtocol::Multiboot2),
protocol,
);
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
fs::write(grub_cfg_path, grub_cfg).unwrap();
// Make the boot device CDROM image using `grub-mkrescue`.
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
let grub_mkrescue_bin = &config
.settings
.grub_mkrescue
.clone()
.unwrap_or_else(|| PathBuf::from("grub-mkrescue"));
let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
let mut grub_mkrescue_cmd = std::process::Command::new(action.grub.grub_mkrescue.as_os_str());
grub_mkrescue_cmd
.arg(iso_root.as_os_str())
.arg("-o")
@ -92,7 +95,7 @@ pub fn create_bootdev_image(
AsterVmImage::new(
iso_path,
AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
grub_version: get_grub_mkrescue_version(grub_mkrescue_bin),
grub_version: get_grub_mkrescue_version(&action.grub.grub_mkrescue),
}),
aster_bin.version().clone(),
)
@ -115,7 +118,7 @@ fn generate_grub_cfg(
"#GRUB_TIMEOUT_STYLE#",
if skip_grub_menu { "hidden" } else { "menu" },
)
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" });
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "5" });
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
// Replace the grub commands according to the protocol selected.
@ -147,7 +150,7 @@ fn generate_grub_cfg(
"".to_owned()
},
),
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => grub_cfg
BootProtocol::Linux => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(

View File

@ -3,7 +3,12 @@
mod bin;
mod grub;
use std::{ffi::OsString, path::Path, process};
use std::{
ffi::OsString,
path::{Path, PathBuf},
process,
time::{Duration, SystemTime},
};
use bin::strip_elf_for_qemu;
@ -15,39 +20,53 @@ use crate::{
bin::{AsterBin, AsterBinType, AsterElfMeta},
Bundle,
},
cli::CargoArgs,
config_manager::{action::Bootloader, BuildConfig},
cli::BuildArgs,
config::{
scheme::{ActionChoice, BootMethod},
Config,
},
error::Errno,
error_msg,
util::{get_current_crate_info, get_target_directory},
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
};
pub fn execute_build_command(config: &BuildConfig) {
let ws_target_directory = get_target_directory();
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
if !osdk_target_directory.exists() {
std::fs::create_dir_all(&osdk_target_directory).unwrap();
pub fn execute_build_command(config: &Config, build_args: &BuildArgs) {
let cargo_target_directory = get_target_directory();
let osdk_output_directory = build_args
.output
.clone()
.unwrap_or(cargo_target_directory.join(DEFAULT_TARGET_RELPATH));
if !osdk_output_directory.exists() {
std::fs::create_dir_all(&osdk_output_directory).unwrap();
}
let target_info = get_current_crate_info();
let bundle_path = osdk_target_directory.join(target_info.name);
let bundle_path = osdk_output_directory.join(target_info.name);
let _bundle = create_base_and_build(
let action = if build_args.for_test {
ActionChoice::Test
} else {
ActionChoice::Run
};
let _bundle = create_base_and_cached_build(
bundle_path,
&osdk_target_directory,
&ws_target_directory,
&osdk_output_directory,
&cargo_target_directory,
config,
action,
&[],
);
}
pub fn create_base_and_build(
pub fn create_base_and_cached_build(
bundle_path: impl AsRef<Path>,
osdk_target_directory: impl AsRef<Path>,
osdk_output_directory: impl AsRef<Path>,
cargo_target_directory: impl AsRef<Path>,
config: &BuildConfig,
config: &Config,
action: ActionChoice,
rustflags: &[&str],
) -> Bundle {
let base_crate_path = osdk_target_directory.as_ref().join("base");
let base_crate_path = osdk_output_directory.as_ref().join("base");
new_base_crate(
&base_crate_path,
&get_current_crate_info().name,
@ -55,55 +74,108 @@ pub fn create_base_and_build(
);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&base_crate_path).unwrap();
let bundle = do_build(
let bundle = do_cached_build(
&bundle_path,
&osdk_target_directory,
&osdk_output_directory,
&cargo_target_directory,
config,
action,
rustflags,
);
std::env::set_current_dir(original_dir).unwrap();
bundle
}
/// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
pub fn do_cached_build(
bundle_path: impl AsRef<Path>,
osdk_output_directory: impl AsRef<Path>,
cargo_target_directory: impl AsRef<Path>,
config: &Config,
action: ActionChoice,
rustflags: &[&str],
) -> Bundle {
let build_a_new_one = || {
do_build(
&bundle_path,
&osdk_output_directory,
&cargo_target_directory,
config,
action,
rustflags,
)
};
let existing_bundle = Bundle::load(&bundle_path);
let Some(existing_bundle) = existing_bundle else {
return build_a_new_one();
};
if existing_bundle.can_run_with_config(config, action).is_err() {
return build_a_new_one();
}
let Ok(built_since) = SystemTime::now().duration_since(existing_bundle.last_modified_time())
else {
return build_a_new_one();
};
if built_since > Duration::from_secs(600) {
return build_a_new_one();
}
let workspace_root = {
let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
PathBuf::from(meta.get("workspace_root").unwrap().as_str().unwrap())
};
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time() {
return existing_bundle;
}
build_a_new_one()
}
pub fn do_build(
bundle_path: impl AsRef<Path>,
osdk_target_directory: impl AsRef<Path>,
osdk_output_directory: impl AsRef<Path>,
cargo_target_directory: impl AsRef<Path>,
config: &BuildConfig,
config: &Config,
action: ActionChoice,
rustflags: &[&str],
) -> Bundle {
if bundle_path.as_ref().exists() {
std::fs::remove_dir_all(&bundle_path).unwrap();
}
let mut bundle = Bundle::new(
&bundle_path,
config.settings.clone(),
config.cargo_args.clone(),
);
let mut bundle = Bundle::new(&bundle_path, config, action);
info!("Building kernel ELF");
let aster_elf = build_kernel_elf(
&config.arch,
&config.cargo_args,
&config.target_arch,
&config.build.profile,
&config.build.features[..],
&cargo_target_directory,
rustflags,
);
if matches!(config.settings.bootloader, Some(Bootloader::Qemu)) {
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
bundle.consume_aster_bin(stripped_elf);
}
let boot = match action {
ActionChoice::Run => &config.run.boot,
ActionChoice::Test => &config.test.boot,
};
if matches!(config.settings.bootloader, Some(Bootloader::Grub)) {
info!("Building boot device image");
let bootdev_image = grub::create_bootdev_image(
&osdk_target_directory,
&aster_elf,
config.settings.initramfs.as_ref(),
config,
);
bundle.consume_vm_image(bootdev_image);
match boot.method {
BootMethod::GrubRescueIso => {
info!("Building boot device image");
let bootdev_image = grub::create_bootdev_image(
&osdk_output_directory,
&aster_elf,
boot.initramfs.as_ref(),
config,
action,
);
bundle.consume_vm_image(bootdev_image);
}
BootMethod::QemuDirect => {
let stripped_elf = strip_elf_for_qemu(&osdk_output_directory, &aster_elf);
bundle.consume_aster_bin(stripped_elf);
}
BootMethod::GrubQcow2 => {
todo!()
}
}
bundle
@ -111,7 +183,8 @@ pub fn do_build(
fn build_kernel_elf(
arch: &Arch,
cargo_args: &CargoArgs,
profile: &str,
features: &[String],
cargo_target_directory: impl AsRef<Path>,
rustflags: &[&str],
) -> AsterBin {
@ -135,12 +208,13 @@ fn build_kernel_elf(
command.env_remove("RUSTUP_TOOLCHAIN");
command.env("RUSTFLAGS", rustflags.join(" "));
command.arg("build");
command.arg("--features").arg(features.join(" "));
command.arg("--target").arg(&target_os_string);
command
.arg("--target-dir")
.arg(cargo_target_directory.as_ref());
command.args(COMMON_CARGO_ARGS);
command.arg("--profile=".to_string() + &cargo_args.profile);
command.arg("--profile=".to_string() + profile);
let status = command.status().unwrap();
if !status.success() {
error_msg!("Cargo build failed");
@ -148,10 +222,10 @@ fn build_kernel_elf(
}
let aster_bin_path = cargo_target_directory.as_ref().join(&target_os_string);
let aster_bin_path = if cargo_args.profile == "dev" {
let aster_bin_path = if profile == "dev" {
aster_bin_path.join("debug")
} else {
aster_bin_path.join(&cargo_args.profile)
aster_bin_path.join(profile)
}
.join(get_current_crate_info().name);
@ -167,3 +241,21 @@ fn build_kernel_elf(
false,
)
}
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
let mut last_modified = SystemTime::UNIX_EPOCH;
for entry in std::fs::read_dir(path).unwrap() {
let entry = entry.unwrap();
if entry.file_name() == "target" {
continue;
}
let metadata = entry.metadata().unwrap();
if metadata.is_dir() {
last_modified = std::cmp::max(last_modified, get_last_modified_time(&entry.path()));
} else {
last_modified = std::cmp::max(last_modified, metadata.modified().unwrap());
}
}
last_modified
}

View File

@ -1,15 +1,13 @@
// SPDX-License-Identifier: MPL-2.0
use crate::commands::util::{bin_file_name, profile_adapter};
use crate::config_manager::DebugConfig;
use crate::commands::util::bin_file_name;
use crate::util::get_target_directory;
use crate::{cli::DebugArgs, util::get_target_directory};
use std::process::Command;
pub fn execute_debug_command(config: &DebugConfig) {
let DebugConfig { cargo_args, remote } = config;
pub fn execute_debug_command(profile: &String, args: &DebugArgs) {
let remote = &args.remote;
let profile = profile_adapter(&cargo_args.profile);
let file_path = get_target_directory()
.join("x86_64-unknown-none")
.join(profile)

View File

@ -19,12 +19,11 @@ use crate::arch::get_default_arch;
/// Execute the forwarded cargo command with args containing the subcommand and its arguments.
pub fn execute_forwarded_command(subcommand: &str, args: &Vec<String>) -> ! {
let mut cargo = util::cargo();
cargo
.arg(subcommand)
.args(util::COMMON_CARGO_ARGS)
.arg("--target")
.arg(get_default_arch().triple())
.args(args);
cargo.arg(subcommand).args(util::COMMON_CARGO_ARGS);
if !args.contains(&"--target".to_owned()) {
cargo.arg("--target").arg(get_default_arch().triple());
}
cargo.args(args);
let status = cargo.status().expect("Failed to execute cargo");
std::process::exit(status.code().unwrap_or(1));
}

View File

@ -1,18 +1,25 @@
[project]
type = "kernel"
project_type = "kernel"
[run]
bootloader = "grub"
ovmf = "/usr/share/OVMF"
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
vars = [
["OVMF_PATH", "/usr/share/OVMF"],
]
[boot]
method = "grub-rescue-iso"
[qemu]
args = """\
-machine q35,kernel-irqchip=split \
-cpu Icelake-Server,+x2apic \
--no-reboot \
-m 2G \
-smp 1 \
-nographic \
-serial chardev:mux \
-monitor chardev:mux \
-chardev stdio,id=mux,mux=on,signal=off \
-display none \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
-drive if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_PATH/OVMF_CODE.fd \
-drive if=pflash,format=raw,unit=1,file=$OVMF_PATH/OVMF_VARS.fd \
"""

View File

@ -1,17 +1,19 @@
[project]
type = "library"
project_type = "lib"
[test]
bootloader = "qemu"
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
]
[boot]
method = "qemu-direct"
[qemu]
args = """\
-machine q35,kernel-irqchip=split \
-cpu Icelake-Server,+x2apic \
--no-reboot \
-m 2G \
-smp 1 \
-nographic \
-serial chardev:mux \
-monitor chardev:mux \
-chardev stdio,id=mux,mux=on,signal=off \
-display none \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
"""

View File

@ -4,7 +4,7 @@ use std::{fs, path::PathBuf, process, str::FromStr};
use crate::{
cli::NewArgs,
config_manager::manifest::ProjectType,
config::manifest::ProjectType,
error::Errno,
error_msg,
util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata},

View File

@ -1,19 +1,14 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
path::{Path, PathBuf},
time::{Duration, SystemTime},
};
use super::{build::create_base_and_build, util::DEFAULT_TARGET_RELPATH};
use super::{build::create_base_and_cached_build, util::DEFAULT_TARGET_RELPATH};
use crate::{
bundle::Bundle,
config_manager::{BuildConfig, RunConfig},
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
cli::GdbServerArgs,
config::{scheme::ActionChoice, Config},
util::{get_current_crate_info, get_target_directory},
};
pub fn execute_run_command(config: &RunConfig) {
if config.gdb_server_args.is_gdb_enabled {
pub fn execute_run_command(config: &Config, gdb_server_args: &GdbServerArgs) {
if gdb_server_args.is_gdb_enabled {
use std::env;
env::set_var(
"RUSTFLAGS",
@ -21,111 +16,48 @@ pub fn execute_run_command(config: &RunConfig) {
);
}
let ws_target_directory = get_target_directory();
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
let cargo_target_directory = get_target_directory();
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
let target_name = get_current_crate_info().name;
let default_bundle_directory = osdk_target_directory.join(target_name);
let existing_bundle = Bundle::load(&default_bundle_directory);
let config = RunConfig {
settings: {
if config.gdb_server_args.is_gdb_enabled {
let qemu_gdb_args: Vec<_> = {
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
match gdb::stub_type_of(gdb_stub_addr) {
gdb::StubAddrType::Unix => {
let chardev = format!(
"-chardev socket,path={},server=on,wait=off,id=gdb0",
gdb_stub_addr
);
let stub = "-gdb chardev:gdb0".to_owned();
vec![chardev, stub, "-S".into()]
}
gdb::StubAddrType::Tcp => {
vec![
format!(
"-gdb tcp:{}",
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
),
"-S".into(),
]
}
}
};
let qemu_gdb_args: Vec<_> = qemu_gdb_args
.into_iter()
.filter(|arg| !config.settings.qemu_args.iter().any(|x| x == arg))
.map(|x| x.to_string())
.collect();
let mut settings = config.settings.clone();
settings.qemu_args.extend(qemu_gdb_args);
settings
} else {
config.settings.clone()
}
},
..config.clone()
};
let _vsc_launch_file = config.gdb_server_args.vsc_launch_file.then(|| {
vsc::check_gdb_config(&config.gdb_server_args);
let profile = super::util::profile_adapter(&config.cargo_args.profile);
vsc::VscLaunchConfig::new(profile, &config.gdb_server_args.gdb_server_addr)
});
// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
if let Some(existing_bundle) = existing_bundle {
if existing_bundle.can_run_with_config(&config) {
if let Ok(built_since) =
SystemTime::now().duration_since(existing_bundle.last_modified_time())
{
if built_since < Duration::from_secs(600) {
let workspace_root = {
let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
PathBuf::from(meta.get("workspace_root").unwrap().as_str().unwrap())
};
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time()
{
existing_bundle.run(&config);
return;
}
let mut config = config.clone();
if gdb_server_args.is_gdb_enabled {
let qemu_gdb_args = {
let gdb_stub_addr = gdb_server_args.gdb_server_addr.as_str();
match gdb::stub_type_of(gdb_stub_addr) {
gdb::StubAddrType::Unix => {
format!(
" -chardev socket,path={},server=on,wait=off,id=gdb0 -gdb chardev:gdb0 -S",
gdb_stub_addr
)
}
gdb::StubAddrType::Tcp => {
format!(
" -gdb tcp:{} -S",
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
)
}
}
}
};
config.run.qemu.args += &qemu_gdb_args;
}
let _vsc_launch_file = gdb_server_args.vsc_launch_file.then(|| {
vsc::check_gdb_config(gdb_server_args);
let profile = super::util::profile_adapter(&config.build.profile);
vsc::VscLaunchConfig::new(profile, &gdb_server_args.gdb_server_addr)
});
let required_build_config = BuildConfig {
arch: config.arch,
settings: config.settings.clone(),
cargo_args: config.cargo_args.clone(),
};
let bundle = create_base_and_build(
let default_bundle_directory = osdk_output_directory.join(target_name);
let bundle = create_base_and_cached_build(
default_bundle_directory,
&osdk_target_directory,
&ws_target_directory,
&required_build_config,
&osdk_output_directory,
&cargo_target_directory,
&config,
ActionChoice::Run,
&[],
);
bundle.run(&config);
}
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
let mut last_modified = SystemTime::UNIX_EPOCH;
for entry in std::fs::read_dir(path).unwrap() {
let entry = entry.unwrap();
if entry.file_name() == "target" {
continue;
}
let metadata = entry.metadata().unwrap();
if metadata.is_dir() {
last_modified = std::cmp::max(last_modified, get_last_modified_time(&entry.path()));
} else {
last_modified = std::cmp::max(last_modified, metadata.modified().unwrap());
}
}
last_modified
bundle.run(&config, ActionChoice::Run);
}
mod gdb {

View File

@ -2,38 +2,38 @@
use std::fs;
use super::{build::do_build, util::DEFAULT_TARGET_RELPATH};
use super::{build::do_cached_build, util::DEFAULT_TARGET_RELPATH};
use crate::{
base_crate::new_base_crate,
cli::GdbServerArgs,
config_manager::{BuildConfig, RunConfig, TestConfig},
cli::TestArgs,
config::{scheme::ActionChoice, Config},
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
};
pub fn execute_test_command(config: &TestConfig) {
pub fn execute_test_command(config: &Config, args: &TestArgs) {
let crates = get_workspace_default_members();
for crate_path in crates {
std::env::set_current_dir(crate_path).unwrap();
test_current_crate(config);
test_current_crate(config, args);
}
}
pub fn test_current_crate(config: &TestConfig) {
pub fn test_current_crate(config: &Config, args: &TestArgs) {
let current_crate = get_current_crate_info();
let ws_target_directory = get_target_directory();
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
let target_crate_dir = osdk_target_directory.join("base");
let cargo_target_directory = get_target_directory();
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
let target_crate_dir = osdk_output_directory.join("base");
new_base_crate(&target_crate_dir, &current_crate.name, &current_crate.path);
let main_rs_path = target_crate_dir.join("src").join("main.rs");
let ktest_test_whitelist = match &config.test_name {
let ktest_test_whitelist = match &args.test_name {
Some(name) => format!(r#"Some(&["{}"])"#, name),
None => r#"None"#.to_string(),
};
let mut ktest_crate_whitelist = vec![current_crate.name];
if let Some(name) = &config.test_name {
if let Some(name) = &args.test_name {
ktest_crate_whitelist.push(name.clone());
}
@ -54,32 +54,21 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
// Build the kernel with the given base crate
let target_name = get_current_crate_info().name;
let default_bundle_directory = osdk_target_directory.join(target_name);
let required_build_config = BuildConfig {
arch: config.arch,
settings: config.settings.clone(),
cargo_args: config.cargo_args.clone(),
};
let default_bundle_directory = osdk_output_directory.join(target_name);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&target_crate_dir).unwrap();
let bundle = do_build(
let bundle = do_cached_build(
default_bundle_directory,
&osdk_target_directory,
&ws_target_directory,
&required_build_config,
&osdk_output_directory,
&cargo_target_directory,
config,
ActionChoice::Test,
&["--cfg ktest"],
);
std::env::remove_var("RUSTFLAGS");
std::env::set_current_dir(original_dir).unwrap();
let required_run_config = RunConfig {
arch: config.arch,
settings: required_build_config.settings.clone(),
cargo_args: required_build_config.cargo_args.clone(),
gdb_server_args: GdbServerArgs::default(),
};
bundle.run(&required_run_config);
bundle.run(config, ActionChoice::Test);
}
fn get_workspace_default_members() -> Vec<String> {

48
osdk/src/config/eval.rs Normal file
View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: MPL-2.0
//! The module implementing the evaluation feature.
use std::{io, process};
pub type Vars = Vec<(String, String)>;
/// This function is used to evaluate the string using the host's shell recursively
/// in order.
pub fn eval(vars: &Vars, s: &String) -> io::Result<String> {
let mut vars = vars.clone();
for i in 0..vars.len() {
vars[i].1 = eval_with_finalized_vars(&vars[..i], &vars[i].1)?;
}
eval_with_finalized_vars(&vars[..], s)
}
fn eval_with_finalized_vars(vars: &[(String, String)], s: &String) -> io::Result<String> {
let env_keys: Vec<String> = std::env::vars().map(|(key, _)| key).collect();
let mut eval = process::Command::new("bash");
let mut cwd = std::env::current_dir()?;
for (key, value) in vars {
// If the key is in the environment, we should ignore it.
// This allows users to override with the environment variables in CLI.
if env_keys.contains(key) {
continue;
}
eval.env(key, value);
if key == "OSDK_CWD" {
cwd = std::path::PathBuf::from(value);
}
}
eval.arg("-c");
eval.arg(format!("echo \"{}\"", s));
eval.current_dir(cwd);
let output = eval.output()?;
if !output.stderr.is_empty() {
println!(
"[Info] {}",
String::from_utf8_lossy(&output.stderr).trim_end_matches('\n')
);
}
Ok(String::from_utf8_lossy(&output.stdout)
.trim_end_matches('\n')
.to_string())
}

355
osdk/src/config/manifest.rs Normal file
View File

@ -0,0 +1,355 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
collections::HashMap,
fmt, fs,
path::{Path, PathBuf},
process,
};
use clap::ValueEnum;
use serde::{de, Deserialize, Deserializer, Serialize};
use super::scheme::Scheme;
use crate::{error::Errno, error_msg, util::get_cargo_metadata};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OsdkMeta {
#[serde(rename(serialize = "type", deserialize = "type"))]
pub type_: ProjectType,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ProjectType {
Kernel,
#[value(alias("lib"))]
Library,
Module,
}
/// The osdk manifest from configuration file `OSDK.toml`.
#[derive(Debug, Clone)]
pub struct TomlManifest {
pub project_type: Option<ProjectType>,
pub default_scheme: Scheme,
pub map: HashMap<String, Scheme>,
}
impl TomlManifest {
pub fn load(feature_strings: &Vec<String>) -> Self {
let workspace_root = {
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(feature_strings)).unwrap();
PathBuf::from(
cargo_metadata
.get("workspace_root")
.unwrap()
.as_str()
.unwrap(),
)
};
// All the custom schemes should inherit settings from the default scheme, this is a helper.
fn finalize(current_manifest: Option<TomlManifest>) -> TomlManifest {
let Some(mut current_manifest) = current_manifest else {
error_msg!(
"Cannot find `OSDK.toml` in the current directory or the workspace root"
);
process::exit(Errno::GetMetadata as _);
};
for scheme in current_manifest.map.values_mut() {
scheme.inherit(&current_manifest.default_scheme);
}
current_manifest
}
// Search for OSDK.toml in the current directory first.
let current_manifest_path = PathBuf::from("OSDK.toml").canonicalize().ok();
let mut current_manifest = match &current_manifest_path {
Some(path) => deserialize_toml_manifest(path),
None => None,
};
// Then search in the workspace root.
let workspace_manifest_path = workspace_root.join("OSDK.toml").canonicalize().ok();
// The case that the current directory is also the workspace root.
if let Some(current) = &current_manifest_path {
if let Some(workspace) = &workspace_manifest_path {
if current == workspace {
return finalize(current_manifest);
}
}
}
let workspace_manifest = match workspace_manifest_path {
Some(path) => deserialize_toml_manifest(path),
None => None,
};
// The current manifest should inherit settings from the workspace manifest.
if let Some(workspace_manifest) = workspace_manifest {
if current_manifest.is_none() {
current_manifest = Some(workspace_manifest);
} else {
// Inherit one scheme at a time.
let current_manifest = current_manifest.as_mut().unwrap();
current_manifest
.default_scheme
.inherit(&workspace_manifest.default_scheme);
for (scheme_string, scheme) in workspace_manifest.map {
let current_scheme = current_manifest
.map
.entry(scheme_string)
.or_insert_with(Scheme::empty);
current_scheme.inherit(&scheme);
}
}
}
finalize(current_manifest)
}
/// Get the scheme given the scheme from the command line arguments.
pub fn get_scheme(&self, scheme: Option<impl ToString>) -> &Scheme {
if let Some(scheme) = scheme {
let selected_scheme = self.map.get(&scheme.to_string());
if selected_scheme.is_none() {
error_msg!("Scheme `{}` not found in `OSDK.toml`", scheme.to_string());
process::exit(Errno::ParseMetadata as _);
}
selected_scheme.unwrap()
} else {
&self.default_scheme
}
}
}
fn deserialize_toml_manifest(path: impl AsRef<Path>) -> Option<TomlManifest> {
if !path.as_ref().exists() || !path.as_ref().is_file() {
return None;
}
// Read the file content
let contents = fs::read_to_string(&path).unwrap_or_else(|err| {
error_msg!(
"Cannot read file {}, {}",
path.as_ref().to_string_lossy(),
err,
);
process::exit(Errno::GetMetadata as _);
});
// Parse the TOML content
let mut manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
let span = err.span().unwrap();
let wider_span =
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
error_msg!(
"Cannot parse TOML file, {}. {}:{:?}:\n {}",
err.message(),
path.as_ref().to_string_lossy(),
span,
&contents[wider_span],
);
process::exit(Errno::ParseMetadata as _);
});
// Preprocess the parsed manifest
let cwd = path.as_ref().parent().unwrap();
// Canonicalize all the path fields
let canonicalize = |target: &mut PathBuf| {
let last_cwd = std::env::current_dir().unwrap();
std::env::set_current_dir(cwd).unwrap();
*target = target.canonicalize().unwrap_or_else(|err| {
error_msg!(
"Cannot canonicalize path `{}`: {}",
target.to_string_lossy(),
err,
);
std::env::set_current_dir(&last_cwd).unwrap();
process::exit(Errno::GetMetadata as _);
});
std::env::set_current_dir(last_cwd).unwrap();
};
let canonicalize_scheme = |scheme: &mut Scheme| {
macro_rules! canonicalize_paths_in_scheme {
($scheme:expr) => {
if let Some(ref mut boot) = $scheme.boot {
if let Some(ref mut initramfs) = boot.initramfs {
canonicalize(initramfs);
}
}
if let Some(ref mut qemu) = $scheme.qemu {
if let Some(ref mut qemu_path) = qemu.path {
canonicalize(qemu_path);
}
}
if let Some(ref mut grub) = $scheme.grub {
if let Some(ref mut grub_mkrescue_path) = grub.grub_mkrescue {
canonicalize(grub_mkrescue_path);
}
}
};
}
canonicalize_paths_in_scheme!(scheme);
if let Some(ref mut run) = scheme.run {
canonicalize_paths_in_scheme!(run);
}
if let Some(ref mut test) = scheme.test {
canonicalize_paths_in_scheme!(test);
}
};
canonicalize_scheme(&mut manifest.default_scheme);
for (_, scheme) in manifest.map.iter_mut() {
canonicalize_scheme(scheme);
}
// Set the magic variable `OSDK_CWD` before any variable evaluation
let var = ("OSDK_CWD".to_owned(), cwd.to_string_lossy().to_string());
manifest.default_scheme.vars = {
let mut vars = vec![var.clone()];
vars.extend(manifest.default_scheme.vars.clone());
vars
};
for (_, scheme) in manifest.map.iter_mut() {
scheme.vars = {
let mut vars = vec![var.clone()];
vars.extend(scheme.vars.clone());
vars
};
}
Some(manifest)
}
impl<'de> Deserialize<'de> for TomlManifest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
ProjectType,
SupportedArchs,
Vars,
Boot,
Grub,
Qemu,
Build,
Run,
Test,
Scheme,
}
const EXPECTED: &[&str] = &[
"project_type",
"supported_archs",
"vars",
"boot",
"grub",
"qemu",
"build",
"run",
"test",
"scheme",
];
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> de::Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(&EXPECTED.join(", "))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match v {
"project_type" => Ok(Field::ProjectType),
"supported_archs" => Ok(Field::SupportedArchs),
"vars" => Ok(Field::Vars),
"boot" => Ok(Field::Boot),
"grub" => Ok(Field::Grub),
"qemu" => Ok(Field::Qemu),
"build" => Ok(Field::Build),
"run" => Ok(Field::Run),
"test" => Ok(Field::Test),
"scheme" => Ok(Field::Scheme),
_ => Err(de::Error::unknown_field(v, EXPECTED)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct TomlManifestVisitor;
impl<'de> de::Visitor<'de> for TomlManifestVisitor {
type Value = TomlManifest;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Scheme")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut project_type = None;
let mut default_scheme = Scheme::empty();
let mut scheme_map = HashMap::<String, Scheme>::new();
macro_rules! match_and_add_option {
($field:ident) => {{
let value = map.next_value()?;
if default_scheme.$field.is_some() {
error_msg!("Duplicated field `{}`", stringify!($field));
process::exit(Errno::ParseMetadata as _);
}
default_scheme.$field = Some(value);
}};
}
macro_rules! match_and_add_vec {
($field:ident) => {{
let value = map.next_value()?;
if !default_scheme.$field.is_empty() {
error_msg!("Duplicated field `{}`", stringify!($field));
process::exit(Errno::ParseMetadata as _);
}
default_scheme.$field = value;
}};
}
while let Some(key) = map.next_key()? {
match key {
Field::ProjectType => {
let value: ProjectType = map.next_value()?;
project_type = Some(value);
}
Field::SupportedArchs => match_and_add_vec!(supported_archs),
Field::Vars => match_and_add_vec!(vars),
Field::Boot => match_and_add_option!(boot),
Field::Grub => match_and_add_option!(grub),
Field::Qemu => match_and_add_option!(qemu),
Field::Build => match_and_add_option!(build),
Field::Run => match_and_add_option!(run),
Field::Test => match_and_add_option!(test),
Field::Scheme => {
let scheme: HashMap<String, Scheme> = map.next_value()?;
scheme_map = scheme;
}
}
}
Ok(TomlManifest {
project_type,
default_scheme,
map: scheme_map,
})
}
}
deserializer.deserialize_struct("TomlManifest", EXPECTED, TomlManifestVisitor)
}
}

130
osdk/src/config/mod.rs Normal file
View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: MPL-2.0
//! This module is responsible for parsing configuration files and combining them with command-line parameters
//! to obtain the final configuration, it will also try searching system to fill valid values for specific
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`,
//! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
mod eval;
pub mod manifest;
pub mod scheme;
pub mod unix_args;
#[cfg(test)]
mod test;
use scheme::{
Action, ActionScheme, BootScheme, Build, BuildScheme, GrubScheme, QemuScheme, Scheme,
};
use crate::{
arch::{get_default_arch, Arch},
cli::CommonArgs,
config::unix_args::apply_kv_array,
};
/// The global configuration for the OSDK actions.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Config {
pub target_arch: Arch,
pub build: Build,
pub run: Action,
pub test: Action,
}
fn apply_args_before_finalize(action_scheme: &mut ActionScheme, args: &CommonArgs) {
if action_scheme.build.is_none() {
action_scheme.build = Some(BuildScheme::default());
}
if let Some(ref mut build) = action_scheme.build {
if let Some(profile) = &args.build_args.profile() {
build.profile = Some(profile.clone());
}
build.features.extend(args.build_args.features.clone());
if args.linux_x86_legacy_boot {
build.linux_x86_legacy_boot = true;
}
}
if action_scheme.grub.is_none() {
action_scheme.grub = Some(GrubScheme::default());
}
if let Some(ref mut grub) = action_scheme.grub {
if let Some(grub_mkrescue) = &args.grub_mkrescue {
grub.grub_mkrescue = Some(grub_mkrescue.clone());
}
if let Some(grub_boot_protocol) = args.grub_boot_protocol {
grub.boot_protocol = Some(grub_boot_protocol);
}
}
if action_scheme.boot.is_none() {
action_scheme.boot = Some(BootScheme::default());
}
if let Some(ref mut boot) = action_scheme.boot {
apply_kv_array(&mut boot.kcmd_args, &args.kcmd_args, "=", &[]);
for init_arg in &args.init_args {
for seperated_arg in init_arg.split(' ') {
boot.init_args.push(seperated_arg.to_string());
}
}
if let Some(initramfs) = &args.initramfs {
boot.initramfs = Some(initramfs.clone());
}
if let Some(boot_method) = args.boot_method {
boot.method = Some(boot_method);
}
}
if action_scheme.qemu.is_none() {
action_scheme.qemu = Some(QemuScheme::default());
}
if let Some(ref mut qemu) = action_scheme.qemu {
if let Some(path) = &args.qemu_exe {
qemu.path = Some(path.clone());
}
}
}
fn apply_args_after_finalize(action: &mut Action, args: &CommonArgs) {
action.qemu.apply_qemu_args(&args.qemu_args);
if args.display_grub_menu {
action.grub.display_grub_menu = true;
}
}
impl Config {
pub fn new(scheme: &Scheme, common_args: &CommonArgs) -> Self {
let target_arch = common_args.target_arch.unwrap_or(get_default_arch());
let default_scheme = ActionScheme {
vars: scheme.vars.clone(),
boot: scheme.boot.clone(),
grub: scheme.grub.clone(),
qemu: scheme.qemu.clone(),
build: scheme.build.clone(),
};
let run = {
let mut run = scheme.run.clone().unwrap_or_default();
run.inherit(&default_scheme);
apply_args_before_finalize(&mut run, common_args);
let mut run = run.finalize(target_arch);
apply_args_after_finalize(&mut run, common_args);
run
};
let test = {
let mut test = scheme.test.clone().unwrap_or_default();
test.inherit(&default_scheme);
apply_args_before_finalize(&mut test, common_args);
let mut test = test.finalize(target_arch);
apply_args_after_finalize(&mut test, common_args);
test
};
Self {
target_arch,
build: scheme.build.clone().unwrap_or_default().finalize(),
run,
test,
}
}
}

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: MPL-2.0
use super::{inherit_optional, Boot, BootScheme, Grub, GrubScheme, Qemu, QemuScheme};
use crate::config::{scheme::Vars, Arch};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ActionChoice {
Run,
Test,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildScheme {
pub profile: Option<String>,
pub features: Vec<String>,
/// Whether to turn on the support for the
/// [Linux legacy x86 32-bit boot protocol](https://www.kernel.org/doc/html/v5.6/x86/boot.html)
#[serde(default)]
pub linux_x86_legacy_boot: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Build {
pub profile: String,
pub features: Vec<String>,
#[serde(default)]
pub linux_x86_legacy_boot: bool,
}
impl Default for Build {
fn default() -> Self {
Self {
profile: "dev".to_string(),
features: Vec::new(),
linux_x86_legacy_boot: false,
}
}
}
impl BuildScheme {
pub fn inherit(&mut self, parent: &Self) {
if parent.profile.is_some() {
self.profile = parent.profile.clone();
}
self.features = {
let mut features = parent.features.clone();
features.extend(self.features.clone());
features
};
if parent.linux_x86_legacy_boot {
self.linux_x86_legacy_boot = true;
}
}
pub fn finalize(self) -> Build {
Build {
profile: self.profile.unwrap_or_else(|| "dev".to_string()),
features: self.features,
linux_x86_legacy_boot: self.linux_x86_legacy_boot,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActionScheme {
#[serde(default)]
pub vars: Vars,
pub boot: Option<BootScheme>,
pub grub: Option<GrubScheme>,
pub qemu: Option<QemuScheme>,
pub build: Option<BuildScheme>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Action {
pub boot: Boot,
pub grub: Grub,
pub qemu: Qemu,
pub build: Build,
}
impl ActionScheme {
pub fn inherit(&mut self, from: &Self) {
self.vars = {
let mut vars = from.vars.clone();
vars.extend(self.vars.clone());
vars
};
inherit_optional!(from, self, .boot);
inherit_optional!(from, self, .grub);
inherit_optional!(from, self, .qemu);
inherit_optional!(from, self, .build);
}
pub fn finalize(self, arch: Arch) -> Action {
Action {
boot: self.boot.unwrap_or_default().finalize(),
grub: self.grub.unwrap_or_default().finalize(),
qemu: self.qemu.unwrap_or_default().finalize(&self.vars, arch),
build: self.build.unwrap_or_default().finalize(),
}
}
}

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MPL-2.0
use clap::ValueEnum;
use std::path::PathBuf;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BootScheme {
/// Command line arguments for the guest kernel
#[serde(default)]
pub kcmd_args: Vec<String>,
/// Command line arguments for the guest init process
#[serde(default)]
pub init_args: Vec<String>,
/// The path of initramfs
pub initramfs: Option<PathBuf>,
/// The infrastructures used to boot the guest
pub method: Option<BootMethod>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum BootMethod {
/// Boot the kernel by making a rescue CD image.
GrubRescueIso,
/// Boot the kernel by making a Qcow2 image with Grub as the bootloader.
GrubQcow2,
/// Use the [QEMU direct boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html)
/// to boot the kernel with QEMU's built-in Seabios and Coreboot utilites.
QemuDirect,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Boot {
pub kcmdline: Vec<String>,
pub initramfs: Option<PathBuf>,
pub method: BootMethod,
}
impl Default for Boot {
fn default() -> Self {
Boot {
kcmdline: vec![],
initramfs: None,
method: BootMethod::QemuDirect,
}
}
}
impl BootScheme {
pub fn inherit(&mut self, from: &Self) {
self.kcmd_args = {
let mut kcmd_args = from.kcmd_args.clone();
kcmd_args.extend(self.kcmd_args.clone());
kcmd_args
};
self.init_args = {
let mut init_args = from.init_args.clone();
init_args.extend(self.init_args.clone());
init_args
};
if self.initramfs.is_none() {
self.initramfs = from.initramfs.clone();
}
if self.method.is_none() {
self.method = from.method;
}
}
pub fn finalize(self) -> Boot {
let mut kcmdline = self.kcmd_args;
kcmdline.push("--".to_owned());
kcmdline.extend(self.init_args);
Boot {
kcmdline,
initramfs: self.initramfs,
method: self.method.unwrap_or(BootMethod::QemuDirect),
}
}
}

View File

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MPL-2.0
use clap::ValueEnum;
use std::path::PathBuf;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GrubScheme {
/// The path of `grub_mkrecue`. Only needed if `boot.method` is `grub`
pub grub_mkrescue: Option<PathBuf>,
/// The boot protocol specified in the GRUB configuration
pub boot_protocol: Option<BootProtocol>,
/// Whether to display the GRUB menu, defaults to `false`
#[serde(default)]
pub display_grub_menu: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum BootProtocol {
Linux,
Multiboot,
Multiboot2,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Grub {
pub grub_mkrescue: PathBuf,
pub boot_protocol: BootProtocol,
pub display_grub_menu: bool,
}
impl Default for Grub {
fn default() -> Self {
Grub {
grub_mkrescue: PathBuf::from("grub-mkrescue"),
boot_protocol: BootProtocol::Multiboot2,
display_grub_menu: false,
}
}
}
impl GrubScheme {
pub fn inherit(&mut self, from: &Self) {
if self.grub_mkrescue.is_none() {
self.grub_mkrescue = from.grub_mkrescue.clone();
}
if self.boot_protocol.is_none() {
self.boot_protocol = from.boot_protocol;
}
// `display_grub_menu` is not inherited
}
pub fn finalize(self) -> Grub {
Grub {
grub_mkrescue: self.grub_mkrescue.unwrap_or(PathBuf::from("grub-mkrescue")),
boot_protocol: self.boot_protocol.unwrap_or(BootProtocol::Multiboot2),
display_grub_menu: self.display_grub_menu,
}
}
}

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MPL-2.0
use super::eval::Vars;
use crate::arch::Arch;
mod action;
pub use action::*;
mod boot;
pub use boot::*;
mod grub;
pub use grub::*;
mod qemu;
pub use qemu::*;
/// All the configurable fields within a scheme.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Scheme {
#[serde(default)]
pub supported_archs: Vec<Arch>,
#[serde(default)]
pub vars: Vars,
pub boot: Option<BootScheme>,
pub grub: Option<GrubScheme>,
pub qemu: Option<QemuScheme>,
pub build: Option<BuildScheme>,
pub run: Option<ActionScheme>,
pub test: Option<ActionScheme>,
}
macro_rules! inherit_optional {
($from: ident, $to:ident, .$field:ident) => {
if $from.$field.is_some() {
if let Some($field) = &mut $to.$field {
$field.inherit($from.$field.as_ref().unwrap());
} else {
$to.$field = $from.$field.clone();
}
}
};
}
use inherit_optional;
impl Scheme {
pub fn empty() -> Self {
Scheme {
supported_archs: vec![],
vars: vec![],
boot: None,
grub: None,
qemu: None,
build: None,
run: None,
test: None,
}
}
pub fn inherit(&mut self, from: &Self) {
// Supported archs are not inherited
self.vars = {
let mut vars = from.vars.clone();
vars.extend(self.vars.clone());
vars
};
inherit_optional!(from, self, .boot);
inherit_optional!(from, self, .grub);
inherit_optional!(from, self, .qemu);
inherit_optional!(from, self, .build);
inherit_optional!(from, self, .run);
inherit_optional!(from, self, .test);
}
}

View File

@ -0,0 +1,147 @@
// SPDX-License-Identifier: MPL-2.0
//! A module about QEMU settings and arguments.
use std::{path::PathBuf, process};
use crate::{
arch::{get_default_arch, Arch},
config::{
eval::{eval, Vars},
unix_args::{apply_kv_array, get_key},
},
error::Errno,
error_msg,
};
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QemuScheme {
/// The additional arguments for running QEMU, in the form of raw
/// command line arguments.
pub args: Option<String>,
/// The path of qemu
pub path: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Qemu {
pub args: String,
pub path: PathBuf,
}
impl Default for Qemu {
fn default() -> Self {
Qemu {
args: String::new(),
path: PathBuf::from(get_default_arch().system_qemu()),
}
}
}
impl Qemu {
pub fn apply_qemu_args(&mut self, args: &Vec<String>) {
let target = match shlex::split(&self.args) {
Some(v) => v,
None => {
error_msg!("Failed to parse qemu args: {:#?}", &self.args);
process::exit(Errno::ParseMetadata as _);
}
};
// Join the key value arguments as a single element
let mut joined = Vec::new();
let mut consumed = false;
for (first, second) in target.iter().zip(target.iter().skip(1)) {
if consumed {
consumed = false;
continue;
}
if first.starts_with('-') && !first.starts_with("--") && !second.starts_with('-') {
joined.push(format!("{} {}", first, second));
consumed = true;
} else {
joined.push(first.clone());
}
}
if !consumed {
joined.push(target.last().unwrap().clone());
}
// Check the soundness of qemu arguments
for arg in joined.iter() {
check_qemu_arg(arg);
}
for arg in joined.iter() {
check_qemu_arg(arg);
}
apply_kv_array(&mut joined, args, " ", MULTI_VALUE_KEYS);
self.args = joined.join(" ");
}
}
impl QemuScheme {
pub fn inherit(&mut self, from: &Self) {
if from.args.is_some() {
self.args = from.args.clone();
}
if from.path.is_some() {
self.path = from.path.clone();
}
}
pub fn finalize(self, vars: &Vars, arch: Arch) -> Qemu {
Qemu {
args: self
.args
.map(|args| match eval(vars, &args) {
Ok(v) => v,
Err(e) => {
error_msg!("Failed to evaluate qemu args: {:#?}", e);
process::exit(Errno::ParseMetadata as _);
}
})
.unwrap_or_default(),
path: self.path.unwrap_or(PathBuf::from(arch.system_qemu())),
}
}
}
// Below are checked keys in qemu arguments. The key list is non-exhaustive.
/// Keys with multiple values
const MULTI_VALUE_KEYS: &[&str] = &[
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
];
/// Keys with only single value
const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
/// Keys with no value
const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
/// Keys are not allowed to set in configuration files and command line
const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-append", "-initrd"];
fn check_qemu_arg(arg: &str) {
let key = if let Some(key) = get_key(arg, " ") {
key
} else {
arg.to_string()
};
if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
error_msg!("`{}` is not allowed to set", arg);
process::exit(Errno::ParseMetadata as _);
}
if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
error_msg!("`{}` cannot have value", arg);
process::exit(Errno::ParseMetadata as _);
}
if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
&& key.as_str() == arg
{
error_msg!("`{}` should have value", arg);
process::exit(Errno::ParseMetadata as _);
}
}

View File

@ -0,0 +1,66 @@
project_type = "kernel"
supported_archs = ["x86_64"]
vars = [
["SMP", "1"],
["MEM", "2G"],
["EXT2_IMG", "$OSDK_CWD/regression/build/ext2.img"],
["EXFAT_IMG", "$OSDK_CWD/regression/build/exfat.img"],
]
[boot]
method = "grub-rescue-iso"
[run]
vars = [
["OVMF_PATH", "/usr/share/OVMF"],
]
boot.kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
"HOME=/",
"USER=root",
"PATH=/bin:/benchmark",
"init=/usr/bin/busybox",
]
boot.init_args = ["sh", "-l"]
boot.initramfs = "/tmp/osdk_test_file"
[test]
boot.method = "qemu-direct"
[grub]
protocol = "multiboot2"
display_grub_menu = true
[qemu]
args = """\
-machine q35 \
-smp $SMP \
-m $MEM \
"""
[scheme."iommu"]
supported_archs = ["x86_64"]
vars = [
["IOMMU_DEV_EXTRA", ",iommu_platform=on,ats=on"],
]
qemu.args = """\
-device intel-iommu,intremap=on,device-iotlb=on \
-device ioh3420,id=pcie.0,chassis=1\
"""
[scheme."tdx"]
supported_archs = ["x86_64"]
build.features = ["intel_tdx"]
vars = [
["MEM", "8G"],
["OVMF_PATH", "~/tdx-tools/ovmf"],
]
boot.method = "grub-qcow2"
grub.mkrescue_path = "/tmp/osdk_test_file"
grub.protocol = "linux"
qemu.path = "/tmp/osdk_test_file"
qemu.args = """\
-name process=tdxvm,debug-threads=on \
"""

View File

@ -0,0 +1,60 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
fs::{self, File},
path::PathBuf,
};
use super::*;
#[test]
fn deserialize_toml_manifest() {
let content = include_str!("OSDK.toml.full");
let toml_manifest: manifest::TomlManifest = toml::from_str(content).unwrap();
let type_ = toml_manifest.project_type.unwrap();
assert!(type_ == manifest::ProjectType::Kernel);
let vars = toml_manifest.default_scheme.vars;
assert!(vars.contains(&("SMP".to_owned(), "1".to_owned())));
}
#[test]
fn conditional_manifest() {
let tmp_file = "/tmp/osdk_test_file";
File::create(tmp_file).unwrap();
let toml_manifest: manifest::TomlManifest = {
let content = include_str!("OSDK.toml.full");
toml::from_str(content).unwrap()
};
// Default scheme
let scheme = toml_manifest.get_scheme(None::<String>);
assert!(scheme
.qemu
.as_ref()
.unwrap()
.args
.as_ref()
.unwrap()
.contains(&String::from("-machine q35",)));
// Iommu
let scheme = toml_manifest.get_scheme(Some("iommu".to_owned()));
assert!(scheme
.qemu
.as_ref()
.unwrap()
.args
.as_ref()
.unwrap()
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1",)));
// Tdx
let scheme = toml_manifest.get_scheme(Some("tdx".to_owned()));
assert_eq!(
scheme.qemu.as_ref().unwrap().path.as_ref().unwrap(),
&PathBuf::from(tmp_file)
);
fs::remove_file(tmp_file).unwrap();
}

View File

@ -1,131 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{path::PathBuf, process};
use clap::ValueEnum;
use super::{qemu, unix_args::apply_kv_array};
use crate::{config_manager::OsdkArgs, error::Errno, error_msg};
/// The settings for an action (running or testing).
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActionSettings {
/// Command line arguments for the guest kernel
#[serde(default)]
pub kcmd_args: Vec<String>,
/// Command line arguments for the guest init process
#[serde(default)]
pub init_args: Vec<String>,
/// The path of initramfs
pub initramfs: Option<PathBuf>,
pub bootloader: Option<Bootloader>,
pub boot_protocol: Option<BootProtocol>,
/// The path of `grub_mkrecue`. Only be `Some(_)` if `loader` is `Bootloader::grub`
pub grub_mkrescue: Option<PathBuf>,
/// The path of OVMF binaries. Only required if `protocol` is `BootProtocol::LinuxEfiHandover64`
pub ovmf: Option<PathBuf>,
/// The path of OpenSBI binaries. Only required for RISC-V.
pub opensbi: Option<PathBuf>,
/// QEMU's available machines appended with various machine configurations
pub qemu_machine: Option<String>,
/// The additional arguments for running QEMU, except `-cpu` and `-machine`
#[serde(default)]
pub qemu_args: Vec<String>,
/// The additional drive files attaching to QEMU
#[serde(default)]
pub drive_files: Vec<DriveFile>,
/// The path of qemu
pub qemu_exe: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum Bootloader {
Grub,
Qemu,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum BootProtocol {
LinuxEfiHandover64,
LinuxLegacy32,
Multiboot,
Multiboot2,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DriveFile {
pub path: PathBuf,
pub append: String,
}
impl ActionSettings {
pub fn canonicalize_paths(&mut self, cur_dir: impl AsRef<std::path::Path>) {
macro_rules! canonicalize_path {
($path:expr) => {{
let path = if $path.is_relative() {
cur_dir.as_ref().join($path)
} else {
$path.clone()
};
path.canonicalize().unwrap_or_else(|_| {
error_msg!("File specified but not found: {:#?}", path);
process::exit(Errno::ParseMetadata as _);
})
}};
}
macro_rules! canonicalize_optional_path {
($path:expr) => {
if let Some(path_inner) = &$path {
Some(canonicalize_path!(path_inner))
} else {
None
}
};
}
self.initramfs = canonicalize_optional_path!(self.initramfs);
self.grub_mkrescue = canonicalize_optional_path!(self.grub_mkrescue);
self.ovmf = canonicalize_optional_path!(self.ovmf);
self.qemu_exe = canonicalize_optional_path!(self.qemu_exe);
self.opensbi = canonicalize_optional_path!(self.opensbi);
for drive_file in &mut self.drive_files {
drive_file.path = canonicalize_path!(&drive_file.path);
}
}
pub fn apply_cli_args(&mut self, args: &OsdkArgs) {
macro_rules! apply {
($item:expr, $arg:expr) => {
if let Some(arg) = $arg.clone() {
$item = Some(arg);
}
};
}
apply!(self.initramfs, &args.initramfs);
apply!(self.ovmf, &args.ovmf);
apply!(self.opensbi, &args.opensbi);
apply!(self.grub_mkrescue, &args.grub_mkrescue);
apply!(self.bootloader, &args.bootloader);
apply!(self.boot_protocol, &args.boot_protocol);
apply!(self.qemu_exe, &args.qemu_exe);
apply_kv_array(&mut self.kcmd_args, &args.kcmd_args, "=", &[]);
for init_arg in &args.init_args {
for seperated_arg in init_arg.split(' ') {
self.init_args.push(seperated_arg.to_string());
}
}
qemu::apply_qemu_args_addition(&mut self.qemu_args, &args.qemu_args_add);
}
pub fn combined_kcmd_args(&self) -> Vec<String> {
let mut kcmd_args = self.kcmd_args.clone();
kcmd_args.push("--".to_owned());
kcmd_args.extend(self.init_args.clone());
kcmd_args
}
}

View File

@ -1,127 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
//! A module for handling configurations.
use std::{
collections::BTreeMap,
fmt::{self, Display},
};
/// A configuration that looks like "cfg(k1=v1, k2=v2, ...)".
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Serialize)]
pub struct Cfg(BTreeMap<String, String>);
#[derive(Debug)]
pub struct CfgParseError(String);
impl fmt::Display for CfgParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Failed to parse cfg: {}", self.0)
}
}
impl serde::ser::StdError for CfgParseError {}
impl serde::de::Error for CfgParseError {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self(msg.to_string())
}
}
impl CfgParseError {
pub fn new(s: &str) -> Self {
Self(s.to_string())
}
}
/// This allows literal constructions like `Cfg::from([("arch", "foo"), ("schema", "bar")])`.
impl<K, V, const N: usize> From<[(K, V); N]> for Cfg
where
K: Into<String>,
V: Into<String>,
{
fn from(array: [(K, V); N]) -> Self {
let mut cfg = BTreeMap::new();
for (k, v) in array.into_iter() {
cfg.insert(k.into(), v.into());
}
Self(cfg)
}
}
impl Cfg {
pub fn empty() -> Self {
Self(BTreeMap::new())
}
pub fn from_str(s: &str) -> Result<Self, CfgParseError> {
let s = s.trim();
// Match the leading "cfg(" and trailing ")"
if !s.starts_with("cfg(") || !s.ends_with(')') {
return Err(CfgParseError::new(s));
}
let s = &s[4..s.len() - 1];
let mut cfg = BTreeMap::new();
for kv in s.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
let kv: Vec<_> = kv.split('=').collect();
if kv.len() != 2 {
return Err(CfgParseError::new(s));
}
cfg.insert(
kv[0].trim().to_string(),
kv[1].trim().trim_matches('\"').to_string(),
);
}
Ok(Self(cfg))
}
pub fn map(&self) -> &BTreeMap<String, String> {
&self.0
}
}
impl Display for Cfg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cfg(")?;
for (i, (k, v)) in self.0.iter().enumerate() {
write!(f, "{}=\"{}\"", k, v)?;
if i != self.0.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_cfg_from_str() {
let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", schema=\"foo\", )").unwrap();
let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",schema=\"foo\")").unwrap();
let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", schema=\"foo\" )").unwrap();
assert_eq!(cfg, cfg1);
assert_eq!(cfg, cfg2);
assert_eq!(cfg, cfg3);
}
#[test]
fn test_cfg_display() {
let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
let cfg_string = cfg.to_string();
let cfg_back = Cfg::from_str(&cfg_string).unwrap();
assert_eq!(cfg_string, "cfg(arch=\"x86_64\", schema=\"foo\")");
assert_eq!(cfg, cfg_back);
}
#[test]
fn test_bad_cfg_strings() {
assert!(Cfg::from_str("fg(,,,,arch=\"x86_64 \", schema=\"foo\")").is_err());
assert!(Cfg::from_str("cfg(arch=\"x86_64\", schema=\"foo\"").is_err());
assert!(Cfg::from_str("cfgarch=x86_64,,, schema=\"foo\") ").is_err());
}
}

View File

@ -1,256 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{collections::BTreeMap, fmt, path::Path, process};
use clap::ValueEnum;
use serde::{de, Deserialize, Deserializer, Serialize};
use super::{action::ActionSettings, cfg::Cfg};
use crate::{config_manager::Arch, error::Errno, error_msg};
/// The settings for the actions summarized from the command line arguments
/// and the configuration file `OSDK.toml`.
#[derive(Debug, Clone)]
pub struct OsdkManifest {
pub project: Project,
pub run: Option<ActionSettings>,
pub test: Option<ActionSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
#[serde(rename(serialize = "type", deserialize = "type"))]
pub type_: ProjectType,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ProjectType {
Kernel,
#[value(alias("lib"))]
Library,
Module,
}
/// The osdk manifest from configuration file `OSDK.toml`.
#[derive(Debug, Clone)]
pub struct TomlManifest {
pub project: Project,
cfg_map: BTreeMap<Cfg, CfgArgs>,
}
impl TomlManifest {
/// Get the action manifest given the architecture and the schema from the command line arguments.
///
/// If any entry in the `OSDK.toml` manifest doesn't specify an architecture, we regard it matching
/// all the architectures.
pub fn get_osdk_manifest(
&self,
path_of_self: impl AsRef<Path>,
arch: Arch,
schema: Option<String>,
) -> OsdkManifest {
let filtered_by_arch = self.cfg_map.iter().filter(|(cfg, _)| {
if let Some(got) = cfg.map().get("arch") {
got == &arch.to_string()
} else {
true
}
});
let filtered_by_schema = if let Some(schema) = schema {
filtered_by_arch
.filter(|(cfg, _)| {
if let Some(got) = cfg.map().get("schema") {
got == &schema
} else {
false
}
})
.collect::<Vec<_>>()
} else {
filtered_by_arch
.filter(|(cfg, _)| cfg == &&Cfg::empty())
.collect::<Vec<_>>()
};
let filtered = filtered_by_schema;
if filtered.len() > 1 {
error_msg!("Multiple entries in OSDK.toml match the given architecture and schema");
process::exit(Errno::ParseMetadata as _);
}
if filtered.is_empty() {
error_msg!("No entry in OSDK.toml matches the given architecture and schema");
process::exit(Errno::ParseMetadata as _);
}
let final_cfg_args = filtered.first().unwrap().1;
let mut run = final_cfg_args.run.clone();
if let Some(run_inner) = &mut run {
run_inner.canonicalize_paths(&path_of_self);
}
let mut test = final_cfg_args.test.clone();
if let Some(test_inner) = &mut test {
test_inner.canonicalize_paths(&path_of_self);
}
OsdkManifest {
project: self.project.clone(),
run,
test,
}
}
}
/// A inner adapter for `TomlManifest` to allow the `cfg` field to be optional.
/// The fields should be identical to `TomlManifest` except the `cfg` field.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
struct CfgArgs {
pub run: Option<ActionSettings>,
pub test: Option<ActionSettings>,
}
impl CfgArgs {
pub fn try_accept(&mut self, another: CfgArgs) {
if another.run.is_some() {
if self.run.is_some() {
error_msg!("Duplicate `run` field in OSDK.toml");
process::exit(Errno::ParseMetadata as _);
}
self.run = another.run;
}
if another.test.is_some() {
if self.test.is_some() {
error_msg!("Duplicate `test` field in OSDK.toml");
process::exit(Errno::ParseMetadata as _);
}
self.test = another.test;
}
}
}
impl<'de> Deserialize<'de> for TomlManifest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
Project,
Run,
Test,
Cfg(Cfg),
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> de::Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`project`, `run`, `test` or cfg")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match v {
"project" => Ok(Field::Project),
"run" => Ok(Field::Run),
"test" => Ok(Field::Test),
v => Ok(Field::Cfg(Cfg::from_str(v).unwrap_or_else(|e| {
error_msg!("Error parsing cfg: {}", e);
process::exit(Errno::ParseMetadata as _);
}))),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct TomlManifestVisitor;
impl<'de> de::Visitor<'de> for TomlManifestVisitor {
type Value = TomlManifest;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct TomlManifest")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut project: Option<Project> = None;
let default_cfg = Cfg::empty();
let mut cfg_map = BTreeMap::<Cfg, CfgArgs>::new();
while let Some(key) = map.next_key()? {
match key {
Field::Project => {
let value = map.next_value()?;
project = Some(value);
}
Field::Run => {
let value: ActionSettings = map.next_value()?;
cfg_map
.entry(default_cfg.clone())
.and_modify(|v| {
v.try_accept(CfgArgs {
run: Some(value.clone()),
test: None,
})
})
.or_insert(CfgArgs {
run: Some(value.clone()),
test: None,
});
}
Field::Test => {
let value: ActionSettings = map.next_value()?;
cfg_map
.entry(default_cfg.clone())
.and_modify(|v| {
v.try_accept(CfgArgs {
run: None,
test: Some(value.clone()),
})
})
.or_insert(CfgArgs {
run: None,
test: Some(value.clone()),
});
}
Field::Cfg(cfg) => {
let value: CfgArgs = map.next_value()?;
cfg_map
.entry(cfg)
.and_modify(|v| v.try_accept(value.clone()))
.or_insert(value.clone());
}
}
}
Ok(TomlManifest {
project: project.unwrap_or_else(|| {
error_msg!("`project` field is required in OSDK.toml");
process::exit(Errno::ParseMetadata as _);
}),
cfg_map,
})
}
}
deserializer.deserialize_struct(
"TomlManifest",
&["run", "test", "cfg"],
TomlManifestVisitor,
)
}
}

View File

@ -1,206 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
//! This module is responsible for parsing configuration files and combining them with command-line parameters
//! to obtain the final configuration, it will also try searching system to fill valid values for specific
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`,
//! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
pub mod action;
pub mod cfg;
pub mod manifest;
pub mod qemu;
pub mod unix_args;
use action::ActionSettings;
#[cfg(test)]
mod test;
use std::{fs, path::PathBuf, process};
use self::manifest::{OsdkManifest, TomlManifest};
use crate::{
arch::{get_default_arch, Arch},
cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs},
error::Errno,
error_msg,
util::get_cargo_metadata,
};
/// Configurations for build subcommand
#[derive(Debug)]
pub struct BuildConfig {
pub arch: Arch,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
}
impl BuildConfig {
pub fn parse(args: &BuildArgs) -> Self {
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args);
let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
if let Some(run) = manifest.run.as_mut() {
run.apply_cli_args(&args.osdk_args);
}
Self {
arch,
settings: manifest.run.unwrap(),
cargo_args,
}
}
}
/// Configurations for run subcommand
#[derive(Debug, Clone)]
pub struct RunConfig {
pub arch: Arch,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub gdb_server_args: GdbServerArgs,
}
impl RunConfig {
pub fn parse(args: &RunArgs) -> Self {
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args);
let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
if let Some(run) = manifest.run.as_mut() {
run.apply_cli_args(&args.osdk_args);
}
Self {
arch,
settings: manifest.run.unwrap(),
cargo_args,
gdb_server_args: args.gdb_server_args.clone(),
}
}
}
#[derive(Debug)]
pub struct DebugConfig {
pub cargo_args: CargoArgs,
pub remote: String,
}
impl DebugConfig {
pub fn parse(args: &DebugArgs) -> Self {
Self {
cargo_args: parse_cargo_args(&args.cargo_args),
remote: args.remote.clone(),
}
}
}
/// Configurations for test subcommand
#[derive(Debug)]
pub struct TestConfig {
pub arch: Arch,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub test_name: Option<String>,
}
impl TestConfig {
pub fn parse(args: &TestArgs) -> Self {
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args);
let manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
// Use run settings if test settings are not provided
let mut test = if let Some(test) = manifest.test {
test
} else {
manifest.run.unwrap()
};
test.apply_cli_args(&args.osdk_args);
Self {
arch,
settings: test,
cargo_args,
test_name: args.test_name.clone(),
}
}
}
fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
let feature_strings = get_feature_strings(cargo_args);
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap();
let workspace_root = PathBuf::from(
cargo_metadata
.get("workspace_root")
.unwrap()
.as_str()
.unwrap(),
);
// Search for OSDK.toml in the current directory. If not, dive into the workspace root.
let manifest_path = PathBuf::from("OSDK.toml");
let (contents, manifest_path) = if let Ok(contents) = fs::read_to_string("OSDK.toml") {
(contents, manifest_path)
} else {
let manifest_path = workspace_root.join("OSDK.toml");
let Ok(contents) = fs::read_to_string(&manifest_path) else {
error_msg!(
"Cannot read file {}",
manifest_path.to_string_lossy().to_string()
);
process::exit(Errno::GetMetadata as _);
};
(contents, manifest_path)
};
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
let span = err.span().unwrap();
let wider_span =
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
error_msg!(
"Cannot parse TOML file, {}. {}:{:?}:\n {}",
err.message(),
manifest_path.to_string_lossy().to_string(),
span,
&contents[wider_span],
);
process::exit(Errno::ParseMetadata as _);
});
let osdk_manifest = toml_manifest.get_osdk_manifest(
workspace_root,
osdk_args.arch.unwrap_or_else(get_default_arch),
osdk_args.schema.as_ref().map(|s| s.to_string()),
);
osdk_manifest
}
/// Parse cargo args.
/// 1. Split `features` in `cargo_args` to ensure each string contains exactly one feature.
/// 2. Change `profile` to `release` if `--release` is set.
fn parse_cargo_args(cargo_args: &CargoArgs) -> CargoArgs {
let mut features = Vec::new();
for feature in cargo_args.features.iter() {
for feature in feature.split(',') {
if !feature.is_empty() {
features.push(feature.to_string());
}
}
}
let profile = if cargo_args.release {
"release".to_string()
} else {
cargo_args.profile.clone()
};
CargoArgs {
profile,
release: cargo_args.release,
features,
}
}
fn get_feature_strings(cargo_args: &CargoArgs) -> Vec<String> {
cargo_args
.features
.iter()
.map(|feature| format!("--features={}", feature))
.collect()
}

View File

@ -1,59 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
//! A module about QEMU arguments.
use std::process;
use super::unix_args::{apply_kv_array, get_key};
use crate::{error::Errno, error_msg};
pub fn apply_qemu_args_addition(target: &mut Vec<String>, args: &Vec<String>) {
// check qemu_args
for arg in target.iter() {
check_qemu_arg(arg);
}
for arg in args.iter() {
check_qemu_arg(arg);
}
apply_kv_array(target, args, " ", MULTI_VALUE_KEYS);
}
// Below are keys in qemu arguments. The key list is not complete.
/// Keys with multiple values
const MULTI_VALUE_KEYS: &[&str] = &[
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
];
/// Keys with only single value
const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
/// Keys with no value
const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
/// Keys are not allowed to set in configuration files and command line
const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-initrd"];
fn check_qemu_arg(arg: &str) {
let key = if let Some(key) = get_key(arg, " ") {
key
} else {
arg.to_string()
};
if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
error_msg!("`{}` is not allowed to set", arg);
process::exit(Errno::ParseMetadata as _);
}
if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
error_msg!("`{}` cannot have value", arg);
process::exit(Errno::ParseMetadata as _);
}
if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
&& key.as_str() == arg
{
error_msg!("`{}` should have value", arg);
process::exit(Errno::ParseMetadata as _);
}
}

View File

@ -1,110 +0,0 @@
[project]
type = "kernel"
[run]
kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
"HOME=/",
"USER=root",
"PATH=/bin:/benchmark",
"init=/usr/bin/busybox",
]
init_args = ["sh", "-l"]
initramfs = "/usr/bin/bash"
boot_protocol = "multiboot2"
bootloader = "grub"
ovmf = "/usr/bin/bash"
opensbi = "/usr/bin/bash"
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
['cfg(arch="x86_64", schema="iommu")'.run]
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtconsole,chardev=mux",
"-device intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]
['cfg(arch="x86_64", schema="microvm")'.run]
bootloader = "qemu"
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine microvm,rtc=on",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-nodefaults",
"-no-user-config",
"-device virtio-blk-device,drive=x0,serial=vext2",
"-device virtio-blk-device,drive=x1,serial=vexfat",
"-device virtio-keyboard-device",
"-device virtio-net-device,netdev=net01",
"-device virtio-serial-device",
"-device virtconsole,chardev=mux",
]
['cfg(schema="intel_tdx")'.run]
qemu_exe = "/usr/bin/bash"
['cfg(arch="riscv64")'.run]
qemu_args = [
"-machine virt",
"--no-reboot",
"-m 2G",
"-nographic",
]

View File

@ -1,43 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use super::*;
#[test]
fn deserialize_toml_manifest() {
let content = include_str!("OSDK.toml.full");
let toml_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(toml_manifest.project.type_ == manifest::ProjectType::Kernel);
}
#[test]
fn conditional_manifest() {
let toml_manifest: TomlManifest = {
let content = include_str!("OSDK.toml.full");
toml::from_str(content).unwrap()
};
let arch = crate::arch::Arch::X86_64;
// Default schema
let schema: Option<String> = None;
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert!(manifest.run.unwrap().qemu_args.contains(&String::from(
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
)));
// Iommu
let schema: Option<String> = Some("iommu".to_owned());
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert!(manifest
.run
.unwrap()
.qemu_args
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
// Tdx
let schema: Option<String> = Some("intel_tdx".to_owned());
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert_eq!(
manifest.run.unwrap().qemu_exe.unwrap(),
PathBuf::from("/usr/bin/bash")
);
}

View File

@ -12,7 +12,7 @@ mod base_crate;
mod bundle;
mod cli;
mod commands;
mod config_manager;
mod config;
mod error;
mod util;

View File

@ -6,7 +6,7 @@ use crate::util::*;
fn cli_help_message() {
let output = cargo_osdk(&["-h"]).output().unwrap();
assert_success(&output);
assert_stdout_contains_msg(&output, "cargo osdk <COMMAND>");
assert_stdout_contains_msg(&output, "cargo osdk [OPTIONS] <COMMAND>");
}
#[test]

65
tools/qemu_args.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
# SPDX-License-Identifier: MPL-2.0
RAND_PORT_NUM1=$(shuf -i 1024-65535 -n 1)
RAND_PORT_NUM2=$(shuf -i 1024-65535 -n 1)
echo "Forwarded QEMU guest port: $RAND_PORT_NUM1->22; $RAND_PORT_NUM2->8080" 1>&2
COMMON_QEMU_ARGS="\
-cpu Icelake-Server,+x2apic \
-smp $SMP \
-m $MEM \
--no-reboot \
-nographic \
-display none \
-serial chardev:mux \
-monitor chardev:mux \
-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \
-netdev user,id=net01,hostfwd=tcp::$RAND_PORT_NUM1-:22,hostfwd=tcp::$RAND_PORT_NUM2-:8080 \
-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
-drive if=none,format=raw,id=x0,file=$EXT2_IMG \
-drive if=none,format=raw,id=x1,file=$EXFAT_IMG \
"
QEMU_ARGS="\
$COMMON_QEMU_ARGS \
-machine q35,kernel-irqchip=split \
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtio-serial-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtconsole,chardev=mux \
$IOMMU_EXTRA_ARGS \
"
MICROVM_QEMU_ARGS="\
$COMMON_QEMU_ARGS \
-machine microvm,rtc=on \
-nodefaults \
-no-user-config \
-device virtio-blk-device,drive=x0,serial=vext2 \
-device virtio-blk-device,drive=x1,serial=vexfat \
-device virtio-keyboard-device \
-device virtio-net-device,netdev=net01 \
-device virtio-serial-device \
-device virtconsole,chardev=mux \
"
if [ "$MICROVM" ]; then
QEMU_ARGS=$MICROVM_QEMU_ARGS
echo $QEMU_ARGS
exit 0
fi
if [ "$OVMF_PATH" ]; then
QEMU_ARGS="${QEMU_ARGS}\
-drive if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_PATH/OVMF_CODE.fd \
-drive if=pflash,format=raw,unit=1,file=$OVMF_PATH/OVMF_VARS.fd \
"
fi
echo $QEMU_ARGS