Overhaul OSDK

This commit is contained in:
Zhang Junyang 2024-03-22 13:50:45 +08:00 committed by Tate, Hongliang Tian
parent 735d7b7b11
commit 33c53dcf2b
40 changed files with 995 additions and 1208 deletions

View File

@ -11,11 +11,10 @@ AUTO_TEST ?= none
BOOT_LOADER ?= grub BOOT_LOADER ?= grub
BOOT_PROTOCOL ?= multiboot2 BOOT_PROTOCOL ?= multiboot2
BUILD_SYSCALL_TEST ?= 0 BUILD_SYSCALL_TEST ?= 0
EMULATE_IOMMU ?= 0
ENABLE_KVM ?= 1 ENABLE_KVM ?= 1
EXTRA_BLOCKLISTS_DIRS ?= "" EXTRA_BLOCKLISTS_DIRS ?= ""
INTEL_TDX ?= 0 INTEL_TDX ?= 0
QEMU_MACHINE ?= q35 SCHEMA ?= ""
RELEASE_MODE ?= 0 RELEASE_MODE ?= 0
SKIP_GRUB_MENU ?= 1 SKIP_GRUB_MENU ?= 1
SYSCALL_TEST_DIR ?= /tmp SYSCALL_TEST_DIR ?= /tmp
@ -27,13 +26,13 @@ CARGO_OSDK_ARGS := --arch=$(ARCH)
ifeq ($(AUTO_TEST), syscall) ifeq ($(AUTO_TEST), syscall)
BUILD_SYSCALL_TEST := 1 BUILD_SYSCALL_TEST := 1
CARGO_OSDK_ARGS += --kcmd_args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)" 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 += --kcmd_args+="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
CARGO_OSDK_ARGS += --init_args="/opt/syscall_test/run_syscall_test.sh" CARGO_OSDK_ARGS += --init_args+="/opt/syscall_test/run_syscall_test.sh"
else ifeq ($(AUTO_TEST), regression) 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) else ifeq ($(AUTO_TEST), boot)
CARGO_OSDK_ARGS += --init_args="/regression/boot_hello.sh" CARGO_OSDK_ARGS += --init_args+="/regression/boot_hello.sh"
endif endif
ifeq ($(RELEASE_MODE), 1) ifeq ($(RELEASE_MODE), 1)
@ -44,26 +43,21 @@ ifeq ($(INTEL_TDX), 1)
CARGO_OSDK_ARGS += --features intel_tdx CARGO_OSDK_ARGS += --features intel_tdx
endif endif
CARGO_OSDK_ARGS += --boot.loader="$(BOOT_LOADER)" CARGO_OSDK_ARGS += --bootloader="$(BOOT_LOADER)"
CARGO_OSDK_ARGS += --boot.protocol="$(BOOT_PROTOCOL)" CARGO_OSDK_ARGS += --boot_protocol="$(BOOT_PROTOCOL)"
CARGO_OSDK_ARGS += --qemu.machine="$(QEMU_MACHINE)"
ifeq ($(QEMU_MACHINE), microvm) ifneq ($(SCHEMA), "")
CARGO_OSDK_ARGS += --select microvm CARGO_OSDK_ARGS += --schema $(SCHEMA)
endif endif
# To test the linux-efi-handover64 boot protocol, we need to use Debian's # 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. # GRUB release, which is installed in /usr/bin in our Docker image.
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64) ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
CARGO_OSDK_ARGS += --boot.grub-mkrescue=/usr/bin/grub-mkrescue CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue
endif
ifeq ($(EMULATE_IOMMU), 1)
CARGO_OSDK_ARGS += --select iommu
endif endif
ifeq ($(ENABLE_KVM), 1) ifeq ($(ENABLE_KVM), 1)
CARGO_OSDK_ARGS += --qemu.args="--enable-kvm" CARGO_OSDK_ARGS += --qemu_args+="--enable-kvm"
endif endif
# Pass make variables to all subdirectory makes # Pass make variables to all subdirectory makes

View File

@ -1,3 +1,7 @@
[project]
type = "kernel"
[run]
kcmd_args = [ kcmd_args = [
"SHELL=/bin/sh", "SHELL=/bin/sh",
"LOGNAME=root", "LOGNAME=root",
@ -8,20 +12,16 @@ kcmd_args = [
] ]
init_args = ["sh", "-l"] init_args = ["sh", "-l"]
initramfs = "regression/build/initramfs.cpio.gz" initramfs = "regression/build/initramfs.cpio.gz"
boot_protocol = "multiboot2"
[boot] bootloader = "grub"
protocol = "multiboot2"
loader = "grub"
ovmf = "/root/ovmf/release" ovmf = "/root/ovmf/release"
opensbi = "/root/opensbi-virt-firmware"
[qemu.'cfg(arch="x86_64")']
machine = "q35"
drive_files = [ drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"],
] ]
args = [ qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot", "--no-reboot",
"-m 2G", "-m 2G",
"-nographic", "-nographic",
@ -40,13 +40,36 @@ args = [
"-device virtconsole,chardev=mux", "-device virtconsole,chardev=mux",
] ]
[qemu.'cfg(arch="x86_64", select="iommu")'] [test]
machine = "q35" 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",
]
['cfg(arch="x86_64", schema="iommu")'.run]
drive_files = [ drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"],
] ]
args = [ qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot", "--no-reboot",
"-m 2G", "-m 2G",
"-nographic", "-nographic",
@ -67,13 +90,15 @@ args = [
"-device ioh3420,id=pcie.0,chassis=1", "-device ioh3420,id=pcie.0,chassis=1",
] ]
[qemu.'cfg(arch="x86_64", select="microvm")'] ['cfg(arch="x86_64", schema="microvm")'.run]
machine = "microvm" bootloader = "qemu"
drive_files = [ drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"],
] ]
args = [ qemu_args = [
"-machine microvm,rtc=on",
"-cpu Icelake-Server,+x2apic",
"--no-reboot", "--no-reboot",
"-m 2G", "-m 2G",
"-nographic", "-nographic",
@ -94,9 +119,9 @@ args = [
"-device virtconsole,chardev=mux", "-device virtconsole,chardev=mux",
] ]
[qemu.'cfg(arch="riscv64")'] ['cfg(arch="riscv64")'.run]
machine = "virt" qemu_args = [
args = [ "-machine virt",
"--no-reboot", "--no-reboot",
"-m 2G", "-m 2G",
"-nographic", "-nographic",

View File

@ -13,7 +13,7 @@ Creating a new kernel project is simple.
You only need to execute the following command: You only need to execute the following command:
```bash ```bash
cargo osdk new --kernel myos cargo osdk new --type kernel myos
``` ```
## Creating a new library project ## Creating a new library project

View File

@ -8,7 +8,7 @@ Suppose you have created a new kernel project named `myos`
and you are in the project directory: and you are in the project directory:
```bash ```bash
cargo osdk new --kernel myos && cd myos cargo osdk new --type kernel myos && cd myos
``` ```
## Build the project ## Build the project

View File

@ -24,7 +24,7 @@ Then, add the following content to `Cargo.toml`:
The two projects can be created using the following commands: The two projects can be created using the following commands:
```bash ```bash
cargo osdk new --kernel myos cargo osdk new --type kernel myos
cargo osdk new mymodule cargo osdk new mymodule
``` ```

View File

@ -17,7 +17,7 @@ cargo osdk new [OPTIONS] <name>
## Options ## Options
`--kernel`: `--type kernel`:
Use the kernel template. Use the kernel template.
If this option is not set, If this option is not set,
the library template will be used by default. the library template will be used by default.
@ -27,7 +27,7 @@ the library template will be used by default.
- Create a new kernel named `myos`: - Create a new kernel named `myos`:
```bash ```bash
cargo osdk new --kernel myos cargo osdk new --type kernel myos
``` ```
- Create a new library named `mymodule`: - Create a new library named `mymodule`:

72
osdk/Cargo.lock generated
View File

@ -80,12 +80,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -143,7 +137,6 @@ dependencies = [
"sha2", "sha2",
"syn", "syn",
"toml", "toml",
"which",
] ]
[[package]] [[package]]
@ -239,12 +232,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.0" version = "0.1.0"
@ -274,16 +261,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@ -306,15 +283,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -353,18 +321,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
name = "linux-bzimage-builder" name = "linux-bzimage-builder"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"bytemuck", "bytemuck",
"serde", "serde",
"xmas-elf", "xmas-elf",
] ]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.20" version = "0.4.20"
@ -377,12 +339,6 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "predicates" name = "predicates"
version = "3.1.0" version = "3.1.0"
@ -457,19 +413,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@ -618,19 +561,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "which"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
"windows-sys",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"

View File

@ -21,7 +21,6 @@ serde_json = "1.0.111"
sha2 = "0.10.8" sha2 = "0.10.8"
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] } syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
toml = { version = "0.8.8", features = ["preserve_order"] } toml = { version = "0.8.8", features = ["preserve_order"] }
which = "6.0.0"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.13" assert_cmd = "2.0.13"

View File

@ -56,7 +56,7 @@ Here we provide a simple demo to demonstrate how to create and run a simple kern
With `cargo-osdk`, a kernel project can be created by one command With `cargo-osdk`, a kernel project can be created by one command
```bash ```bash
cargo osdk new --kernel my-first-os cargo osdk new --type kernel my-first-os
``` ```
Then, you can run the kernel with Then, you can run the kernel with

View File

@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter};
/// element of the target triple, but akin to the "target_arch" cfg /// element of the target triple, but akin to the "target_arch" cfg
/// of Cargo: /// of Cargo:
/// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch> /// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Arch { pub enum Arch {
Aarch64, Aarch64,
X86_64, X86_64,
@ -41,7 +41,7 @@ impl Arch {
} }
} }
pub fn to_str(&self) -> &'static str { pub fn to_str(self) -> &'static str {
match self { match self {
Arch::Aarch64 => "aarch64", Arch::Aarch64 => "aarch64",
Arch::RiscV64 => "riscv64", Arch::RiscV64 => "riscv64",

View File

@ -6,6 +6,7 @@ pub mod vm_image;
use bin::AsterBin; use bin::AsterBin;
use file::{BundleFile, Initramfs}; use file::{BundleFile, Initramfs};
use std::process;
use vm_image::AsterVmImage; use vm_image::AsterVmImage;
use std::{ use std::{
@ -15,10 +16,10 @@ use std::{
}; };
use crate::{ use crate::{
arch::Arch,
cli::CargoArgs, cli::CargoArgs,
config_manager::{ config_manager::{
boot::Boot, action::{ActionSettings, Bootloader},
qemu::{Qemu, QemuMachine},
RunConfig, RunConfig,
}, },
error::Errno, error::Errno,
@ -38,34 +39,33 @@ pub struct Bundle {
/// The osdk bundle artifact manifest that stores as `bundle.toml`. /// The osdk bundle artifact manifest that stores as `bundle.toml`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleManifest { pub struct BundleManifest {
pub kcmd_args: Vec<String>,
pub initramfs: Option<Initramfs>, pub initramfs: Option<Initramfs>,
pub aster_bin: Option<AsterBin>, pub aster_bin: Option<AsterBin>,
pub vm_image: Option<AsterVmImage>, pub vm_image: Option<AsterVmImage>,
pub boot: Boot, pub settings: ActionSettings,
pub qemu: Qemu,
pub cargo_args: CargoArgs, pub cargo_args: CargoArgs,
pub last_modified: SystemTime, pub last_modified: SystemTime,
} }
impl Bundle { impl Bundle {
/// This function creates a new `Bundle` without adding any files. /// This function creates a new `Bundle` without adding any files.
pub fn new( pub fn new(path: impl AsRef<Path>, settings: ActionSettings, cargo_args: CargoArgs) -> Self {
path: impl AsRef<Path>,
kcmd_args: Vec<String>,
boot: Boot,
qemu: Qemu,
cargo_args: CargoArgs,
) -> Self {
std::fs::create_dir_all(path.as_ref()).unwrap(); std::fs::create_dir_all(path.as_ref()).unwrap();
let initramfs = if let Some(ref initramfs) = settings.initramfs {
if !initramfs.exists() {
error_msg!("initramfs file not found: {}", initramfs.display());
process::exit(Errno::BuildCrate as _);
}
Some(Initramfs::new(initramfs).copy_to(&path))
} else {
None
};
let mut created = Self { let mut created = Self {
manifest: BundleManifest { manifest: BundleManifest {
kcmd_args, initramfs,
initramfs: None,
aster_bin: None, aster_bin: None,
vm_image: None, vm_image: None,
boot, settings,
qemu,
cargo_args, cargo_args,
last_modified: SystemTime::now(), last_modified: SystemTime::now(),
}, },
@ -113,16 +113,14 @@ impl Bundle {
// Compare the manifest with the run configuration. // Compare the manifest with the run configuration.
// TODO: This pairwise comparison will result in some false negatives. We may // TODO: This pairwise comparison will result in some false negatives. We may
// fix it by pondering upon each fields with more care. // fix it by pondering upon each fields with more care.
if self.manifest.kcmd_args != config.manifest.kcmd_args if self.manifest.settings != config.settings
|| self.manifest.boot != config.manifest.boot
|| self.manifest.qemu != config.manifest.qemu
|| self.manifest.cargo_args != config.cargo_args || self.manifest.cargo_args != config.cargo_args
{ {
return false; return false;
} }
// Compare the initramfs. // Compare the initramfs.
match (&self.manifest.initramfs, &config.manifest.initramfs) { match (&self.manifest.initramfs, &config.settings.initramfs) {
(Some(initramfs), Some(initramfs_path)) => { (Some(initramfs), Some(initramfs_path)) => {
let config_initramfs = Initramfs::new(initramfs_path); let config_initramfs = Initramfs::new(initramfs_path);
if initramfs.sha256sum() != config_initramfs.sha256sum() { if initramfs.sha256sum() != config_initramfs.sha256sum() {
@ -147,42 +145,46 @@ impl Bundle {
error_msg!("The bundle is not compatible with the run configuration"); error_msg!("The bundle is not compatible with the run configuration");
std::process::exit(Errno::RunBundle as _); std::process::exit(Errno::RunBundle as _);
} }
let mut qemu_cmd = Command::new(config.manifest.qemu.path.clone().unwrap()); 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 // 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 // is a dirty hack to make it work. Anything like space in the paths will
// break this. // break this.
for arg in &config.manifest.qemu.args { for arg in &config.settings.qemu_args {
for part in arg.split_whitespace() { for part in arg.split_whitespace() {
qemu_cmd.arg(part); qemu_cmd.arg(part);
} }
} }
match config.manifest.qemu.machine { match config.settings.bootloader {
QemuMachine::Microvm => { Some(Bootloader::Qemu) => {
qemu_cmd.arg("-machine").arg("microvm,rtc=on");
let Some(ref aster_bin) = self.manifest.aster_bin else { let Some(ref aster_bin) = self.manifest.aster_bin else {
error_msg!("Kernel ELF binary is required for Microvm"); error_msg!("Kernel ELF binary is required for direct QEMU booting");
std::process::exit(Errno::RunBundle as _); std::process::exit(Errno::RunBundle as _);
}; };
qemu_cmd qemu_cmd
.arg("-kernel") .arg("-kernel")
.arg(self.path.join(aster_bin.path())); .arg(self.path.join(aster_bin.path()));
let Some(ref initramfs) = config.manifest.initramfs else { if let Some(ref initramfs) = config.settings.initramfs {
error_msg!("Initramfs is required for Microvm");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-initrd").arg(initramfs); qemu_cmd.arg("-initrd").arg(initramfs);
} else {
info!("No initramfs specified");
};
qemu_cmd qemu_cmd
.arg("-append") .arg("-append")
.arg(config.manifest.kcmd_args.join(" ")); .arg(config.settings.combined_kcmd_args().join(" "));
} }
QemuMachine::Q35 => { Some(Bootloader::Grub) => {
qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split");
let Some(ref vm_image) = self.manifest.vm_image else { let Some(ref vm_image) = self.manifest.vm_image else {
error_msg!("VM image is required for QEMU booting"); error_msg!("VM image is required for QEMU booting");
std::process::exit(Errno::RunBundle as _); std::process::exit(Errno::RunBundle as _);
}; };
qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path())); qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path()));
if let Some(ovmf) = &config.manifest.boot.ovmf { if let Some(ovmf) = &config.settings.ovmf {
qemu_cmd.arg("-drive").arg(format!( qemu_cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=0,readonly=on,file={}", "if=pflash,format=raw,unit=0,readonly=on,file={}",
ovmf.join("OVMF_CODE.fd").display() ovmf.join("OVMF_CODE.fd").display()
@ -193,31 +195,13 @@ impl Bundle {
)); ));
} }
} }
QemuMachine::Virt => { None => {
qemu_cmd.arg("-machine").arg("virt"); error_msg!("Bootloader is required for QEMU booting");
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 _); std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-kernel").arg(self.path.join(vm_image.path()));
let Some(ref initramfs) = config.manifest.initramfs else {
error_msg!("Initramfs is required for Virt machine");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-initrd").arg(initramfs);
qemu_cmd
.arg("-append")
.arg(config.manifest.kcmd_args.join(" "));
let Some(ref opensbi) = self.manifest.boot.opensbi else {
error_msg!("OpenSBI is required for Virt machine");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-bios").arg(opensbi);
} }
}; };
qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic");
for drive_file in &config.manifest.qemu.drive_files { for drive_file in &config.settings.drive_files {
qemu_cmd.arg("-drive").arg(format!( qemu_cmd.arg("-drive").arg(format!(
"file={},{}", "file={},{}",
drive_file.path.display(), drive_file.path.display(),
@ -256,15 +240,6 @@ impl Bundle {
self.write_manifest_to_fs(); self.write_manifest_to_fs();
} }
/// Copy the initramfs into the bundle.
pub fn add_initramfs(&mut self, initramfs: Initramfs) {
if self.manifest.initramfs.is_some() {
panic!("initramfs already exists");
}
self.manifest.initramfs = Some(initramfs.copy_to(&self.path));
self.write_manifest_to_fs();
}
fn write_manifest_to_fs(&mut self) { fn write_manifest_to_fs(&mut self) {
self.manifest.last_modified = SystemTime::now(); self.manifest.last_modified = SystemTime::now();
let manifest_file_content = toml::to_string(&self.manifest).unwrap(); let manifest_file_content = toml::to_string(&self.manifest).unwrap();

View File

@ -11,8 +11,8 @@ use crate::{
execute_new_command, execute_run_command, execute_test_command, execute_new_command, execute_run_command, execute_test_command,
}, },
config_manager::{ config_manager::{
boot::{BootLoader, BootProtocol}, action::{BootProtocol, Bootloader},
qemu::QemuMachine, manifest::ProjectType,
BuildConfig, DebugConfig, RunConfig, TestConfig, BuildConfig, DebugConfig, RunConfig, TestConfig,
}, },
}; };
@ -96,8 +96,13 @@ pub struct ForwardedArguments {
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct NewArgs { pub struct NewArgs {
#[arg(long, default_value = "false", help = "Use the kernel template")] #[arg(
pub kernel: bool, long = "type",
short = 't',
default_value = "library",
help = "The type of the project to create"
)]
pub type_: ProjectType,
#[arg(name = "name", required = true)] #[arg(name = "name", required = true)]
pub crate_name: String, pub crate_name: String,
} }
@ -189,7 +194,7 @@ pub struct CargoArgs {
conflicts_with = "profile" conflicts_with = "profile"
)] )]
pub release: bool, pub release: bool,
#[arg(long, value_name = "FEATURES", help = "List of features to activate")] #[arg(long, value_name = "FEATURES", help = "List of features to activate", value_delimiter = ',', num_args = 1..)]
pub features: Vec<String>, pub features: Vec<String>,
} }
@ -198,57 +203,60 @@ pub struct OsdkArgs {
#[arg(long, value_name = "ARCH", help = "The architecture to build for")] #[arg(long, value_name = "ARCH", help = "The architecture to build for")]
pub arch: Option<Arch>, pub arch: Option<Arch>,
#[arg( #[arg(
long = "select", long = "schema",
help = "Select the specific configuration provided in the OSDK manifest", help = "Select the specific configuration schema provided in the OSDK manifest",
value_name = "SELECTION" value_name = "SCHEMA"
)] )]
pub select: Option<String>, pub schema: Option<String>,
#[arg( #[arg(
long = "kcmd_args", long = "kcmd_args+",
help = "Command line arguments for guest kernel", require_equals = true,
help = "Extra or overriding command line arguments for guest kernel",
value_name = "ARGS" value_name = "ARGS"
)] )]
pub kcmd_args: Vec<String>, pub kcmd_args: Vec<String>,
#[arg( #[arg(
long = "init_args", long = "init_args+",
help = "Command line arguments for init process", require_equals = true,
help = "Extra command line arguments for init process",
value_name = "ARGS" value_name = "ARGS"
)] )]
pub init_args: Vec<String>, pub init_args: Vec<String>,
#[arg(long, help = "Path of initramfs", value_name = "PATH")] #[arg(long, help = "Path of initramfs", value_name = "PATH")]
pub initramfs: Option<PathBuf>, pub initramfs: Option<PathBuf>,
#[arg(long = "boot.ovmf", help = "Path of OVMF", value_name = "PATH")] #[arg(long = "ovmf", help = "Path of OVMF", value_name = "PATH")]
pub boot_ovmf: Option<PathBuf>, pub ovmf: Option<PathBuf>,
#[arg(long = "opensbi", help = "Path of OpenSBI", value_name = "PATH")]
pub opensbi: Option<PathBuf>,
#[arg( #[arg(
long = "boot.loader", long = "bootloader",
help = "Loader for booting the kernel", help = "Loader for booting the kernel",
value_name = "LOADER" value_name = "BOOTLOADER"
)] )]
pub boot_loader: Option<BootLoader>, pub bootloader: Option<Bootloader>,
#[arg( #[arg(
long = "boot.grub-mkrescue", long = "grub-mkrescue",
help = "Path of grub-mkrescue", help = "Path of grub-mkrescue",
value_name = "PATH" value_name = "PATH"
)] )]
pub boot_grub_mkrescue: Option<PathBuf>, pub grub_mkrescue: Option<PathBuf>,
#[arg( #[arg(
long = "boot.protocol", long = "boot_protocol",
help = "Protocol for booting the kernel", help = "Protocol for booting the kernel",
value_name = "PROTOCOL" value_name = "BOOT_PROTOCOL"
)] )]
pub boot_protocol: Option<BootProtocol>, pub boot_protocol: Option<BootProtocol>,
#[arg(long = "qemu.path", help = "Path of QEMU", value_name = "PATH")]
pub qemu_path: Option<PathBuf>,
#[arg( #[arg(
long = "qemu.machine", long = "qemu_exe",
help = "QEMU machine type", help = "The QEMU executable file",
value_name = "MACHINE" value_name = "FILE"
)] )]
pub qemu_machine: Option<QemuMachine>, pub qemu_exe: Option<PathBuf>,
#[arg( #[arg(
long = "qemu.args", long = "qemu_args+",
help = "Arguments for running QEMU", require_equals = true,
help = "Extra arguments or overriding arguments for running QEMU",
value_name = "ARGS" value_name = "ARGS"
)] )]
pub qemu_args: Vec<String>, pub qemu_args_add: Vec<String>,
} }

View File

@ -14,7 +14,7 @@ use crate::{
bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta}, bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta},
file::BundleFile, file::BundleFile,
}, },
config_manager::boot::BootProtocol, config_manager::action::BootProtocol,
util::get_current_crate_info, util::get_current_crate_info,
}; };
@ -153,7 +153,6 @@ fn install_setup_with_arch(
cmd.arg("install").arg("linux-bzimage-setup"); cmd.arg("install").arg("linux-bzimage-setup");
cmd.arg("--force"); cmd.arg("--force");
cmd.arg("--root").arg(install_dir.as_ref()); cmd.arg("--root").arg(install_dir.as_ref());
// TODO: Use the latest revision when modifications on the `osdk` branch is merged.
cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK); cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK);
cmd.arg("--rev").arg(crate::util::ASTER_GIT_REV); cmd.arg("--rev").arg(crate::util::ASTER_GIT_REV);
cmd.arg("--target").arg(match arch { cmd.arg("--target").arg(match arch {

View File

@ -12,7 +12,7 @@ use crate::{
file::BundleFile, file::BundleFile,
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType}, vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
}, },
config_manager::{boot::BootProtocol, BuildConfig}, config_manager::{action::BootProtocol, BuildConfig},
util::get_current_crate_info, util::get_current_crate_info,
}; };
@ -24,7 +24,7 @@ pub fn create_bootdev_image(
) -> AsterVmImage { ) -> AsterVmImage {
let target_name = get_current_crate_info().name; let target_name = get_current_crate_info().name;
let iso_root = &target_dir.as_ref().join("iso_root"); let iso_root = &target_dir.as_ref().join("iso_root");
let protocol = &config.manifest.boot.protocol; let protocol = &config.settings.boot_protocol;
// Clear or make the iso dir. // Clear or make the iso dir.
if iso_root.exists() { if iso_root.exists() {
@ -43,10 +43,15 @@ pub fn create_bootdev_image(
// Make the kernel image and place it in the boot directory. // Make the kernel image and place it in the boot directory.
match protocol { match protocol {
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => { Some(BootProtocol::LinuxLegacy32) | Some(BootProtocol::LinuxEfiHandover64) => {
make_install_bzimage(iso_root.join("boot"), &target_dir, aster_bin, protocol); make_install_bzimage(
iso_root.join("boot"),
&target_dir,
aster_bin,
&protocol.clone().unwrap(),
);
} }
BootProtocol::Multiboot | BootProtocol::Multiboot2 => { _ => {
// Copy the kernel image to the boot directory. // Copy the kernel image to the boot directory.
let target_path = iso_root.join("boot").join(&target_name); let target_path = iso_root.join("boot").join(&target_name);
fs::copy(aster_bin.path(), target_path).unwrap(); fs::copy(aster_bin.path(), target_path).unwrap();
@ -60,17 +65,21 @@ pub fn create_bootdev_image(
None None
}; };
let grub_cfg = generate_grub_cfg( let grub_cfg = generate_grub_cfg(
&config.manifest.kcmd_args.join(" "), &config.settings.combined_kcmd_args().join(" "),
true, true,
initramfs_in_image, initramfs_in_image,
protocol, &protocol.clone().unwrap_or(BootProtocol::Multiboot2),
); );
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg"); let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
fs::write(grub_cfg_path, grub_cfg).unwrap(); fs::write(grub_cfg_path, grub_cfg).unwrap();
// Make the boot device CDROM image using `grub-mkrescue`. // Make the boot device CDROM image using `grub-mkrescue`.
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso"); let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
let grub_mkrescue_bin = &config.manifest.boot.grub_mkrescue.clone().unwrap(); 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(grub_mkrescue_bin.as_os_str());
grub_mkrescue_cmd grub_mkrescue_cmd
.arg(iso_root.as_os_str()) .arg(iso_root.as_os_str())

View File

@ -13,11 +13,10 @@ use crate::{
base_crate::new_base_crate, base_crate::new_base_crate,
bundle::{ bundle::{
bin::{AsterBin, AsterBinType, AsterElfMeta}, bin::{AsterBin, AsterBinType, AsterElfMeta},
file::Initramfs,
Bundle, Bundle,
}, },
cli::CargoArgs, cli::CargoArgs,
config_manager::{qemu::QemuMachine, BuildConfig}, config_manager::{action::Bootloader, BuildConfig},
error::Errno, error::Errno,
error_msg, error_msg,
util::{get_current_crate_info, get_target_directory}, util::{get_current_crate_info, get_target_directory},
@ -79,20 +78,10 @@ pub fn do_build(
} }
let mut bundle = Bundle::new( let mut bundle = Bundle::new(
&bundle_path, &bundle_path,
config.manifest.kcmd_args.clone(), config.settings.clone(),
config.manifest.boot.clone(),
config.manifest.qemu.clone(),
config.cargo_args.clone(), config.cargo_args.clone(),
); );
if let Some(ref initramfs) = config.manifest.initramfs {
if !initramfs.exists() {
error_msg!("initramfs file not found: {}", initramfs.display());
process::exit(Errno::BuildCrate as _);
}
bundle.add_initramfs(Initramfs::new(initramfs));
};
info!("Building kernel ELF"); info!("Building kernel ELF");
let aster_elf = build_kernel_elf( let aster_elf = build_kernel_elf(
&config.arch, &config.arch,
@ -101,20 +90,17 @@ pub fn do_build(
rustflags, rustflags,
); );
if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) { if matches!(config.settings.bootloader, Some(Bootloader::Qemu)) {
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf); let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
bundle.consume_aster_bin(stripped_elf); bundle.consume_aster_bin(stripped_elf);
} }
// TODO: A boot device is required if we use GRUB. Actually you can boot if matches!(config.settings.bootloader, Some(Bootloader::Grub)) {
// a multiboot kernel with Q35 machine directly without a bootloader.
// We are currently ignoring this case.
if matches!(config.manifest.qemu.machine, QemuMachine::Q35) {
info!("Building boot device image"); info!("Building boot device image");
let bootdev_image = grub::create_bootdev_image( let bootdev_image = grub::create_bootdev_image(
&osdk_target_directory, &osdk_target_directory,
&aster_elf, &aster_elf,
config.manifest.initramfs.as_ref(), config.settings.initramfs.as_ref(),
config, config,
); );
bundle.consume_vm_image(bootdev_image); bundle.consume_vm_image(bootdev_image);
@ -134,7 +120,7 @@ fn build_kernel_elf(
let env_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); let env_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
let mut rustflags = Vec::from(rustflags); let mut rustflags = Vec::from(rustflags);
// We disable RELRO and PIC here because they cause link failures // Asterinas does not support PIC yet.
rustflags.extend(vec![ rustflags.extend(vec![
&env_rustflags, &env_rustflags,
&rustc_linker_script_arg, &rustc_linker_script_arg,

View File

@ -0,0 +1,18 @@
[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",
]

View File

@ -0,0 +1,17 @@
[project]
type = "library"
[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",
]

View File

@ -4,6 +4,7 @@ use std::{fs, path::PathBuf, process, str::FromStr};
use crate::{ use crate::{
cli::NewArgs, cli::NewArgs,
config_manager::manifest::ProjectType,
error::Errno, error::Errno,
error_msg, error_msg,
util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata}, util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata},
@ -13,13 +14,9 @@ pub fn execute_new_command(args: &NewArgs) {
cargo_new_lib(&args.crate_name); cargo_new_lib(&args.crate_name);
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap(); let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap();
add_manifest_dependencies(&cargo_metadata, &args.crate_name); add_manifest_dependencies(&cargo_metadata, &args.crate_name);
create_osdk_manifest(&cargo_metadata); create_osdk_manifest(&cargo_metadata, &args.type_);
exclude_osdk_base(&cargo_metadata); exclude_osdk_base(&cargo_metadata);
if args.kernel { write_src_template(&cargo_metadata, &args.crate_name, &args.type_);
write_kernel_template(&cargo_metadata, &args.crate_name);
} else {
write_library_template(&cargo_metadata, &args.crate_name);
}
add_rust_toolchain(&cargo_metadata); add_rust_toolchain(&cargo_metadata);
} }
@ -83,7 +80,7 @@ fn exclude_osdk_base(metadata: &serde_json::Value) {
fs::write(workspace_manifest_path, content).unwrap(); fs::write(workspace_manifest_path, content).unwrap();
} }
fn create_osdk_manifest(cargo_metadata: &serde_json::Value) { fn create_osdk_manifest(cargo_metadata: &serde_json::Value, type_: &ProjectType) {
let osdk_manifest_path = { let osdk_manifest_path = {
let workspace_root = get_workspace_root(cargo_metadata); let workspace_root = get_workspace_root(cargo_metadata);
PathBuf::from(workspace_root).join("OSDK.toml") PathBuf::from(workspace_root).join("OSDK.toml")
@ -95,42 +92,34 @@ fn create_osdk_manifest(cargo_metadata: &serde_json::Value) {
} }
// Create `OSDK.toml` for the workspace // Create `OSDK.toml` for the workspace
// FIXME: we need ovmf for grub-efi, the user may not have it. let contents = match type_ {
// The apt OVMF repo installs to `/usr/share/OVMF` ProjectType::Kernel => {
fs::write( include_str!("kernel.OSDK.toml.template")
osdk_manifest_path, }
r#" ProjectType::Library => {
[boot] include_str!("lib.OSDK.toml.template")
ovmf = "/usr/share/OVMF" }
protocol = "multiboot" ProjectType::Module => {
[qemu] todo!()
machine = "q35" }
args = [ };
"--no-reboot", fs::write(osdk_manifest_path, contents).unwrap();
"-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",
]
"#,
)
.unwrap();
}
/// Write the default content of `src/kernel.rs`, with contents in provided template.
fn write_kernel_template(cargo_metadata: &serde_json::Value, crate_name: &str) {
let src_path = get_src_path(cargo_metadata, crate_name);
let contents = include_str!("kernel.template");
fs::write(src_path, contents).unwrap();
} }
/// Write the default content of `src/lib.rs`, with contents in provided template. /// Write the default content of `src/lib.rs`, with contents in provided template.
fn write_library_template(cargo_metadata: &serde_json::Value, crate_name: &str) { fn write_src_template(cargo_metadata: &serde_json::Value, crate_name: &str, type_: &ProjectType) {
let src_path = get_src_path(cargo_metadata, crate_name); let src_path = get_src_path(cargo_metadata, crate_name);
let contents = include_str!("lib.template"); let contents = match type_ {
ProjectType::Kernel => {
include_str!("kernel.template")
}
ProjectType::Library => {
include_str!("lib.template")
}
ProjectType::Module => {
todo!()
}
};
fs::write(src_path, contents).unwrap(); fs::write(src_path, contents).unwrap();
} }

View File

@ -28,7 +28,7 @@ pub fn execute_run_command(config: &RunConfig) {
let existing_bundle = Bundle::load(&default_bundle_directory); let existing_bundle = Bundle::load(&default_bundle_directory);
let config = RunConfig { let config = RunConfig {
manifest: { settings: {
if config.gdb_server_args.is_gdb_enabled { if config.gdb_server_args.is_gdb_enabled {
let qemu_gdb_args: Vec<_> = { let qemu_gdb_args: Vec<_> = {
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str(); let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
@ -55,14 +55,14 @@ pub fn execute_run_command(config: &RunConfig) {
let qemu_gdb_args: Vec<_> = qemu_gdb_args let qemu_gdb_args: Vec<_> = qemu_gdb_args
.into_iter() .into_iter()
.filter(|arg| !config.manifest.qemu.args.iter().any(|x| x == arg)) .filter(|arg| !config.settings.qemu_args.iter().any(|x| x == arg))
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect(); .collect();
let mut manifest = config.manifest.clone(); let mut settings = config.settings.clone();
manifest.qemu.args.extend(qemu_gdb_args); settings.qemu_args.extend(qemu_gdb_args);
manifest settings
} else { } else {
config.manifest.clone() config.settings.clone()
} }
}, },
..config.clone() ..config.clone()
@ -95,8 +95,8 @@ pub fn execute_run_command(config: &RunConfig) {
} }
let required_build_config = BuildConfig { let required_build_config = BuildConfig {
arch: config.arch.clone(), arch: config.arch,
manifest: config.manifest.clone(), settings: config.settings.clone(),
cargo_args: config.cargo_args.clone(), cargo_args: config.cargo_args.clone(),
}; };

View File

@ -56,8 +56,8 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
let target_name = get_current_crate_info().name; let target_name = get_current_crate_info().name;
let default_bundle_directory = osdk_target_directory.join(target_name); let default_bundle_directory = osdk_target_directory.join(target_name);
let required_build_config = BuildConfig { let required_build_config = BuildConfig {
arch: config.arch.clone(), arch: config.arch,
manifest: config.manifest.clone(), settings: config.settings.clone(),
cargo_args: config.cargo_args.clone(), cargo_args: config.cargo_args.clone(),
}; };
let original_dir = std::env::current_dir().unwrap(); let original_dir = std::env::current_dir().unwrap();
@ -73,8 +73,8 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
std::env::set_current_dir(original_dir).unwrap(); std::env::set_current_dir(original_dir).unwrap();
let required_run_config = RunConfig { let required_run_config = RunConfig {
arch: config.arch.clone(), arch: config.arch,
manifest: required_build_config.manifest.clone(), settings: required_build_config.settings.clone(),
cargo_args: required_build_config.cargo_args.clone(), cargo_args: required_build_config.cargo_args.clone(),
gdb_server_args: GdbServerArgs::default(), gdb_server_args: GdbServerArgs::default(),
}; };

View File

@ -0,0 +1,131 @@
// 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,67 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{path::PathBuf, process};
use crate::{error::Errno, error_msg};
/// Arguments for creating bootdev image and how to boot with vmm.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Boot {
#[serde(default)]
pub loader: BootLoader,
#[serde(default)]
pub protocol: 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>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BootLoader {
#[default]
Grub,
Qemu,
}
impl<'a> From<&'a str> for BootLoader {
fn from(value: &'a str) -> Self {
match value {
"grub" => Self::Grub,
"qemu" => Self::Qemu,
_ => {
error_msg!("`{}` is not a valid option for `boot.loader`. Allowed options are `grub` and `qemu`.",value);
process::exit(Errno::ParseMetadata as _);
}
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BootProtocol {
LinuxEfiHandover64,
LinuxLegacy32,
Multiboot,
#[default]
Multiboot2,
}
impl<'a> From<&'a str> for BootProtocol {
fn from(value: &'a str) -> Self {
match value {
"linux-efi-handover64" => Self::LinuxEfiHandover64,
"linux-legacy32" => Self::LinuxLegacy32,
"multiboot" => Self::Multiboot,
"multiboot2" => Self::Multiboot2,
_ => {
error_msg!("`{}` is not a valid option for `boot.protocol`. Allowed options are `linux-efi-handover64`, `linux-legacy32`, `multiboot`, `multiboot2`", value);
process::exit(Errno::ParseMetadata as _);
}
}
}
}

View File

@ -33,8 +33,7 @@ impl CfgParseError {
} }
} }
/// This is to allow constructions like `Cfg::from([("arch", "foo"), ("select", "bar")])`. /// This allows literal constructions like `Cfg::from([("arch", "foo"), ("schema", "bar")])`.
/// Making things easier for testing.
impl<K, V, const N: usize> From<[(K, V); N]> for Cfg impl<K, V, const N: usize> From<[(K, V); N]> for Cfg
where where
K: Into<String>, K: Into<String>,
@ -50,7 +49,7 @@ where
} }
impl Cfg { impl Cfg {
pub fn new() -> Self { pub fn empty() -> Self {
Self(BTreeMap::new()) Self(BTreeMap::new())
} }
@ -77,17 +76,8 @@ impl Cfg {
Ok(Self(cfg)) Ok(Self(cfg))
} }
pub fn check_allowed(&self, allowed_keys: &[&str]) -> bool { pub fn map(&self) -> &BTreeMap<String, String> {
for (k, _) in self.0.iter() { &self.0
if allowed_keys.iter().all(|&key| k != key) {
return false;
}
}
true
}
pub fn insert(&mut self, k: String, v: String) {
self.0.insert(k, v);
} }
} }
@ -110,10 +100,10 @@ mod test {
#[test] #[test]
fn test_cfg_from_str() { fn test_cfg_from_str() {
let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]); let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", select=\"foo\", )").unwrap(); let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", schema=\"foo\", )").unwrap();
let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",select=\"foo\")").unwrap(); let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",schema=\"foo\")").unwrap();
let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", select=\"foo\" )").unwrap(); let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", schema=\"foo\" )").unwrap();
assert_eq!(cfg, cfg1); assert_eq!(cfg, cfg1);
assert_eq!(cfg, cfg2); assert_eq!(cfg, cfg2);
assert_eq!(cfg, cfg3); assert_eq!(cfg, cfg3);
@ -121,17 +111,17 @@ mod test {
#[test] #[test]
fn test_cfg_display() { fn test_cfg_display() {
let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]); let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
let cfg_string = cfg.to_string(); let cfg_string = cfg.to_string();
let cfg_back = Cfg::from_str(&cfg_string).unwrap(); let cfg_back = Cfg::from_str(&cfg_string).unwrap();
assert_eq!(cfg_string, "cfg(arch=\"x86_64\", select=\"foo\")"); assert_eq!(cfg_string, "cfg(arch=\"x86_64\", schema=\"foo\")");
assert_eq!(cfg, cfg_back); assert_eq!(cfg, cfg_back);
} }
#[test] #[test]
fn test_bad_cfg_strings() { fn test_bad_cfg_strings() {
Cfg::from_str("cfg(,,,,arch=\"x86_64 \", select=\"foo\")").is_err(); assert!(Cfg::from_str("fg(,,,,arch=\"x86_64 \", schema=\"foo\")").is_err());
Cfg::from_str("cfg(arch=\"x86_64\", select=\"foo\"").is_err(); assert!(Cfg::from_str("cfg(arch=\"x86_64\", schema=\"foo\"").is_err());
Cfg::from_str("cfg(arch=x86_64,,, select=\"foo\") ").is_err(); assert!(Cfg::from_str("cfgarch=x86_64,,, schema=\"foo\") ").is_err());
} }
} }

View File

@ -1,163 +1,256 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::{ use std::{collections::BTreeMap, fmt, path::Path, process};
path::{Path, PathBuf},
process,
};
use serde::Deserialize; use clap::ValueEnum;
use serde::{de, Deserialize, Deserializer, Serialize};
use super::{ use super::{action::ActionSettings, cfg::Cfg};
boot::Boot,
qemu::{CfgQemu, Qemu},
};
use crate::config_manager::cfg::Cfg;
use crate::{error::Errno, error_msg};
/// The osdk manifest from configuration file and command line arguments. 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)] #[derive(Debug, Clone)]
pub struct OsdkManifest { pub struct OsdkManifest {
pub kcmd_args: Vec<String>, pub project: Project,
pub initramfs: Option<PathBuf>, pub run: Option<ActionSettings>,
pub boot: Boot, pub test: Option<ActionSettings>,
pub qemu: Qemu,
} }
impl OsdkManifest { #[derive(Debug, Clone, Serialize, Deserialize)]
pub fn from_toml_manifest( pub struct Project {
toml_manifest: TomlManifest, #[serde(rename(serialize = "type", deserialize = "type"))]
arch: Option<String>, pub type_: ProjectType,
selection: Option<String>, }
) -> Self {
let TomlManifest {
mut kcmd_args,
mut init_args,
initramfs,
boot,
qemu,
} = toml_manifest;
let CfgQemu { default, cfg_map } = qemu;
let Some(cfg_map) = cfg_map else { #[derive(Debug, Clone, Serialize, Deserialize, ValueEnum, PartialEq, Eq)]
return Self { #[serde(rename_all = "kebab-case")]
kcmd_args, pub enum ProjectType {
initramfs, Kernel,
boot, #[value(alias("lib"))]
qemu: default, Library,
}; Module,
};
for cfg in cfg_map.keys() {
const ALLOWED_KEYS: &[&str] = &["arch", "select"];
if !cfg.check_allowed(ALLOWED_KEYS) {
error_msg!("cfg {:#?} is not allowed to be used in `OSDK.toml`", cfg);
process::exit(Errno::ParseMetadata as _);
}
}
let mut qemu_args = None;
let mut args_matches: Vec<_> = if arch.is_none() && selection.is_none() {
vec![]
} else {
let mut need_cfg = Cfg::new();
if let Some(arch) = arch {
need_cfg.insert("arch".to_string(), arch);
}
if let Some(selection) = selection {
need_cfg.insert("select".to_string(), selection);
}
cfg_map
.into_iter()
.filter_map(
|(cfg, args)| {
if need_cfg == cfg {
Some(args)
} else {
None
}
},
)
.collect()
};
if args_matches.len() > 1 {
error_msg!("Multiple CFGs matched using the command line arguments");
process::exit(Errno::ParseMetadata as _);
} else if args_matches.len() == 1 {
qemu_args = Some(args_matches.remove(0));
} else if args_matches.is_empty() {
qemu_args = Some(default);
}
check_args("kcmd_args", &kcmd_args);
check_args("init_args", &init_args);
kcmd_args.push("--".to_string());
kcmd_args.append(&mut init_args);
OsdkManifest {
kcmd_args,
initramfs,
boot,
qemu: qemu_args.unwrap(),
}
}
pub fn check_canonicalize_all_paths(&mut self, manifest_file_dir: impl AsRef<Path>) {
macro_rules! canonicalize_path {
($path:expr) => {{
let path = if $path.is_relative() {
manifest_file_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.boot.grub_mkrescue = canonicalize_optional_path!(self.boot.grub_mkrescue);
self.boot.ovmf = canonicalize_optional_path!(self.boot.ovmf);
self.qemu.path = canonicalize_optional_path!(self.qemu.path);
for drive_file in &mut self.qemu.drive_files {
drive_file.path = canonicalize_path!(&drive_file.path);
}
}
} }
/// The osdk manifest from configuration file `OSDK.toml`. /// The osdk manifest from configuration file `OSDK.toml`.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone)]
pub struct TomlManifest { pub struct TomlManifest {
/// Command line arguments for guest kernel pub project: Project,
#[serde(default)] cfg_map: BTreeMap<Cfg, CfgArgs>,
pub kcmd_args: Vec<String>,
#[serde(default)]
pub init_args: Vec<String>,
/// The path of initramfs
pub initramfs: Option<PathBuf>,
#[serde(default)]
pub boot: Boot,
#[serde(default)]
pub qemu: CfgQemu,
} }
fn check_args(arg_name: &str, args: &[String]) { impl TomlManifest {
for arg in args { /// Get the action manifest given the architecture and the schema from the command line arguments.
if arg.as_str() == "--" { ///
error_msg!("`{}` cannot have `--` as argument", arg_name); /// 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 _); 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

@ -5,54 +5,47 @@
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`, //! 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. //! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
pub mod boot; pub mod action;
pub mod cfg; pub mod cfg;
pub mod manifest; pub mod manifest;
pub mod qemu; pub mod qemu;
pub mod unix_args;
use action::ActionSettings;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
use std::{fs, path::PathBuf, process}; use std::{fs, path::PathBuf, process};
use indexmap::{IndexMap, IndexSet}; use self::manifest::{OsdkManifest, TomlManifest};
use which::which;
use self::{
boot::BootLoader,
manifest::{OsdkManifest, TomlManifest},
};
use crate::{ use crate::{
arch::{get_default_arch, Arch}, arch::{get_default_arch, Arch},
cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs}, cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs},
error::Errno, error::Errno,
error_msg, error_msg,
util::get_cargo_metadata, util::get_cargo_metadata,
warn_msg,
}; };
fn get_final_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
let mut manifest = load_osdk_manifest(cargo_args, osdk_args.select.as_ref());
apply_cli_args(&mut manifest, osdk_args);
try_fill_system_configs(&mut manifest);
manifest
}
/// Configurations for build subcommand /// Configurations for build subcommand
#[derive(Debug)] #[derive(Debug)]
pub struct BuildConfig { pub struct BuildConfig {
pub arch: Arch, pub arch: Arch,
pub manifest: OsdkManifest, pub settings: ActionSettings,
pub cargo_args: CargoArgs, pub cargo_args: CargoArgs,
} }
impl BuildConfig { impl BuildConfig {
pub fn parse(args: &BuildArgs) -> Self { pub fn parse(args: &BuildArgs) -> Self {
let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args); 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 { Self {
arch, arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args), settings: manifest.run.unwrap(),
cargo_args, cargo_args,
} }
} }
@ -62,18 +55,22 @@ impl BuildConfig {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RunConfig { pub struct RunConfig {
pub arch: Arch, pub arch: Arch,
pub manifest: OsdkManifest, pub settings: ActionSettings,
pub cargo_args: CargoArgs, pub cargo_args: CargoArgs,
pub gdb_server_args: GdbServerArgs, pub gdb_server_args: GdbServerArgs,
} }
impl RunConfig { impl RunConfig {
pub fn parse(args: &RunArgs) -> Self { pub fn parse(args: &RunArgs) -> Self {
let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args); 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 { Self {
arch, arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args), settings: manifest.run.unwrap(),
cargo_args, cargo_args,
gdb_server_args: args.gdb_server_args.clone(), gdb_server_args: args.gdb_server_args.clone(),
} }
@ -99,27 +96,32 @@ impl DebugConfig {
#[derive(Debug)] #[derive(Debug)]
pub struct TestConfig { pub struct TestConfig {
pub arch: Arch, pub arch: Arch,
pub manifest: OsdkManifest, pub settings: ActionSettings,
pub cargo_args: CargoArgs, pub cargo_args: CargoArgs,
pub test_name: Option<String>, pub test_name: Option<String>,
} }
impl TestConfig { impl TestConfig {
pub fn parse(args: &TestArgs) -> Self { pub fn parse(args: &TestArgs) -> Self {
let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
let cargo_args = parse_cargo_args(&args.cargo_args); 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 { Self {
arch, arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args), settings: test,
cargo_args, cargo_args,
test_name: args.test_name.clone(), test_name: args.test_name.clone(),
} }
} }
} }
/// FIXME: I guess OSDK manifest is definitely NOT per workspace. It's per crate. When you cannot
/// find a manifest per crate, find it in the upper levels.
/// I don't bother to do it now, just fix the relpaths.
fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest { fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
let feature_strings = get_feature_strings(cargo_args); let feature_strings = get_feature_strings(cargo_args);
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap(); let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap();
@ -130,8 +132,13 @@ fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManif
.as_str() .as_str()
.unwrap(), .unwrap(),
); );
let manifest_path = workspace_root.join("OSDK.toml");
// 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 { let Ok(contents) = fs::read_to_string(&manifest_path) else {
error_msg!( error_msg!(
"Cannot read file {}", "Cannot read file {}",
@ -139,25 +146,27 @@ fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManif
); );
process::exit(Errno::GetMetadata as _); process::exit(Errno::GetMetadata as _);
}; };
(contents, manifest_path)
};
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| { 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!( error_msg!(
"Cannot parse TOML file, {}:\n{}:\n {}", "Cannot parse TOML file, {}. {}:{:?}:\n {}",
err.message(), err.message(),
manifest_path.to_string_lossy().to_string(), manifest_path.to_string_lossy().to_string(),
&contents[err.span().unwrap()], span,
&contents[wider_span],
); );
process::exit(Errno::ParseMetadata as _); process::exit(Errno::ParseMetadata as _);
}); });
let mut osdk_manifest = OsdkManifest::from_toml_manifest( let osdk_manifest = toml_manifest.get_osdk_manifest(
toml_manifest, workspace_root,
osdk_args osdk_args.arch.unwrap_or_else(get_default_arch),
.arch osdk_args.schema.as_ref().map(|s| s.to_string()),
.as_ref()
.map(|s| s.to_string()),
osdk_args.select.as_ref().map(|s| s.to_string()),
); );
osdk_manifest.check_canonicalize_all_paths(workspace_root);
osdk_manifest osdk_manifest
} }
@ -188,195 +197,10 @@ fn parse_cargo_args(cargo_args: &CargoArgs) -> CargoArgs {
} }
} }
pub fn get_feature_strings(cargo_args: &CargoArgs) -> Vec<String> { fn get_feature_strings(cargo_args: &CargoArgs) -> Vec<String> {
cargo_args cargo_args
.features .features
.iter() .iter()
.map(|feature| format!("--features={}", feature)) .map(|feature| format!("--features={}", feature))
.collect() .collect()
} }
pub fn try_fill_system_configs(manifest: &mut OsdkManifest) {
if manifest.qemu.path.is_none() {
if let Ok(path) = which("qemu-system-x86_64") {
trace!("system qemu path: {:?}", path);
manifest.qemu.path = Some(path);
} else {
warn_msg!("Cannot find qemu-system-x86_64 in your system. ")
}
}
if manifest.boot.grub_mkrescue.is_none() && manifest.boot.loader == BootLoader::Grub {
if let Ok(path) = which("grub-mkrescue") {
trace!("system grub-mkrescue path: {:?}", path);
manifest.boot.grub_mkrescue = Some(path);
} else {
warn_msg!("Cannot find grub-mkrescue in your system.")
}
}
}
pub fn apply_cli_args(manifest: &mut OsdkManifest, args: &OsdkArgs) {
let mut init_args = split_kcmd_args(&mut manifest.kcmd_args);
apply_kv_array(&mut manifest.kcmd_args, &args.kcmd_args, "=", &[]);
init_args.append(&mut args.init_args.clone());
manifest.kcmd_args.push("--".to_string());
for init_arg in init_args {
for seperated_arg in init_arg.split(' ') {
manifest.kcmd_args.push(seperated_arg.to_string());
}
}
apply_option(&mut manifest.initramfs, &args.initramfs);
apply_option(&mut manifest.boot.ovmf, &args.boot_ovmf);
apply_option(&mut manifest.boot.grub_mkrescue, &args.boot_grub_mkrescue);
apply_item(&mut manifest.boot.loader, &args.boot_loader);
apply_item(&mut manifest.boot.protocol, &args.boot_protocol);
apply_option(&mut manifest.qemu.path, &args.qemu_path);
apply_item(&mut manifest.qemu.machine, &args.qemu_machine);
// check qemu_args
for arg in manifest.qemu.args.iter() {
qemu::check_qemu_arg(arg);
}
for arg in args.qemu_args.iter() {
qemu::check_qemu_arg(arg);
}
apply_kv_array(
&mut manifest.qemu.args,
&args.qemu_args,
" ",
qemu::MULTI_VALUE_KEYS,
);
}
fn apply_item<'a, T: From<&'a str> + Clone>(item: &mut T, arg: &Option<T>) {
let Some(arg) = arg.clone() else {
return;
};
*item = arg;
}
fn apply_option<'a, T: From<&'a str> + Clone>(item: &mut Option<T>, arg: &Option<T>) {
let Some(arg) = arg.clone() else {
return;
};
*item = Some(arg);
}
pub fn apply_kv_array(
array: &mut Vec<String>,
args: &Vec<String>,
seperator: &str,
multi_value_keys: &[&str],
) {
let multi_value_keys = {
let mut inferred_keys = infer_multi_value_keys(array, seperator);
for key in multi_value_keys {
inferred_keys.insert(key.to_string());
}
inferred_keys
};
debug!("multi value keys: {:?}", multi_value_keys);
// We use IndexMap to keep key orders
let mut key_strings = IndexMap::new();
let mut multi_value_key_strings: IndexMap<String, Vec<String>> = IndexMap::new();
for item in array.drain(..) {
// Each key-value string has two patterns:
// 1. Seperated by separator: key value / key=value
if let Some(key) = get_key(&item, seperator) {
if multi_value_keys.contains(&key) {
if let Some(v) = multi_value_key_strings.get_mut(&key) {
v.push(item);
} else {
let v = vec![item];
multi_value_key_strings.insert(key, v);
}
continue;
}
key_strings.insert(key, item);
continue;
}
// 2. Only key, no value
key_strings.insert(item.clone(), item);
}
for arg in args {
if let Some(key) = get_key(arg, seperator) {
if multi_value_keys.contains(&key) {
if let Some(v) = multi_value_key_strings.get_mut(&key) {
v.push(arg.to_owned());
} else {
let v = vec![arg.to_owned()];
multi_value_key_strings.insert(key, v);
}
continue;
}
key_strings.insert(key, arg.to_owned());
continue;
}
key_strings.insert(arg.to_owned(), arg.to_owned());
}
*array = key_strings.into_iter().map(|(_, value)| value).collect();
for (_, mut values) in multi_value_key_strings {
array.append(&mut values);
}
}
fn infer_multi_value_keys(array: &Vec<String>, seperator: &str) -> IndexSet<String> {
let mut multi_val_keys = IndexSet::new();
let mut occured_keys = IndexSet::new();
for item in array {
let Some(key) = get_key(item, seperator) else {
continue;
};
if occured_keys.contains(&key) {
multi_val_keys.insert(key);
} else {
occured_keys.insert(key);
}
}
multi_val_keys
}
pub fn get_key(item: &str, seperator: &str) -> Option<String> {
let split = item.split(seperator).collect::<Vec<_>>();
let len = split.len();
if len > 2 || len == 0 {
error_msg!("`{}` is an invalid argument.", item);
process::exit(Errno::ParseMetadata as _);
}
if len == 1 {
return None;
}
let key = split.first().unwrap();
Some(key.to_string())
}
fn split_kcmd_args(kcmd_args: &mut Vec<String>) -> Vec<String> {
let seperator = "--";
let index = kcmd_args.iter().position(|item| item.as_str() == seperator);
let Some(index) = index else {
return Vec::new();
};
let mut init_args = kcmd_args.split_off(index);
init_args.remove(0);
init_args
}

View File

@ -1,181 +1,39 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::{collections::BTreeMap, fmt, path::PathBuf, process}; //! A module about QEMU arguments.
use serde::{ use std::process;
de::{self, Visitor},
Deserialize, Deserializer, use super::unix_args::{apply_kv_array, get_key};
};
use super::cfg::Cfg;
use super::get_key;
use crate::{error::Errno, error_msg}; use crate::{error::Errno, error_msg};
/// Arguments for creating bootdev image and how to boot with vmm. pub fn apply_qemu_args_addition(target: &mut Vec<String>, args: &Vec<String>) {
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] // check qemu_args
pub struct Qemu { for arg in target.iter() {
/// The additional arguments for running qemu, except `-cpu` and `-machine`. check_qemu_arg(arg);
#[serde(default)]
pub args: Vec<String>,
/// The additional drive files
#[serde(default)]
pub drive_files: Vec<DriveFile>,
/// The `-machine` argument for running qemu.
#[serde(default)]
pub machine: QemuMachine,
/// The path of qemu.
#[serde(default)]
pub path: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DriveFile {
#[serde(default)]
pub path: PathBuf,
#[serde(default)]
pub append: String,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct CfgQemu {
pub default: Qemu,
pub cfg_map: Option<BTreeMap<Cfg, Qemu>>,
}
impl CfgQemu {
pub fn new(default: Qemu, cfg_map: Option<BTreeMap<Cfg, Qemu>>) -> Self {
Self { default, cfg_map }
} }
} for arg in args.iter() {
check_qemu_arg(arg);
impl<'de> Deserialize<'de> for CfgQemu {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
Path,
Args,
Machine,
DriveFiles,
Cfg(Cfg),
} }
impl<'de> Deserialize<'de> for Field { apply_kv_array(target, args, " ", MULTI_VALUE_KEYS);
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`path`, `args`, `machine`, `drive_files` or cfg")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match v {
"args" => Ok(Field::Args),
"machine" => Ok(Field::Machine),
"path" => Ok(Field::Path),
"drive_files" => Ok(Field::DriveFiles),
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 CfgQemuVisitor;
impl<'de> Visitor<'de> for CfgQemuVisitor {
type Value = CfgQemu;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct CfgQemu")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut default = Qemu::default();
let mut cfg_map = BTreeMap::<Cfg, Qemu>::new();
while let Some(key) = map.next_key()? {
match key {
Field::Args => {
default.args = map.next_value()?;
}
Field::Machine => {
default.machine = map.next_value()?;
}
Field::Path => {
default.path = map.next_value()?;
}
Field::DriveFiles => {
default.drive_files = map.next_value()?;
}
Field::Cfg(cfg) => {
let qemu_args = map.next_value()?;
cfg_map.insert(cfg, qemu_args);
}
}
}
Ok(CfgQemu::new(default, Some(cfg_map)))
}
}
deserializer.deserialize_struct("CfgQemu", &["default", "cfg"], CfgQemuVisitor)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum QemuMachine {
Microvm,
#[default]
Q35,
Virt,
}
impl<'a> From<&'a str> for QemuMachine {
fn from(value: &'a str) -> Self {
match value {
"microvm" => Self::Microvm,
"q35" => Self::Q35,
"virt" => Self::Virt,
_ => {
error_msg!("{} is not a valid option for `qemu.machine`", value);
process::exit(Errno::ParseMetadata as _);
}
}
}
} }
// Below are keys in qemu arguments. The key list is not complete. // Below are keys in qemu arguments. The key list is not complete.
/// Keys with multiple values /// Keys with multiple values
pub const MULTI_VALUE_KEYS: &[&str] = &["-device", "-chardev", "-object", "-netdev", "-drive"]; const MULTI_VALUE_KEYS: &[&str] = &[
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
];
/// Keys with only single value /// Keys with only single value
pub const SINGLE_VALUE_KEYS: &[&str] = &["-m", "-serial", "-monitor", "-display"]; const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
/// Keys with no value /// Keys with no value
pub const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"]; const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
/// Keys are not allowed to set in configuration files and command line /// Keys are not allowed to set in configuration files and command line
pub const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-cpu", "-machine", "-kernel", "-initrd", "-cdrom"]; const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-initrd"];
pub fn check_qemu_arg(arg: &str) { fn check_qemu_arg(arg: &str) {
let key = if let Some(key) = get_key(arg, " ") { let key = if let Some(key) = get_key(arg, " ") {
key key
} else { } else {

View File

@ -1,16 +0,0 @@
[qemu]
args = [
"-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"
]
[qemu.'cfg(arch="x86_64", select="intel_tdx")']
[qemu.'cfg(select="iommu")']
args = [
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,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 intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]

View File

@ -1,20 +1,110 @@
kcmd_args = ["init=/bin/busybox", "path=/usr/local/bin"] [project]
init_args = ["sh", "-l"] type = "kernel"
[boot] [run]
loader = "grub" kcmd_args = [
protocol = "multiboot2" "SHELL=/bin/sh",
grub-mkrescue = "/usr/bin/grub-mkrescue" "LOGNAME=root",
ovmf = "/usr/bin/ovmf" "HOME=/",
"USER=root",
[qemu] "PATH=/bin:/benchmark",
machine = "q35" "init=/usr/bin/busybox",
args = [ ]
"-enable-kvm", init_args = ["sh", "-l"]
"-m 2G", initramfs = "/usr/bin/bash"
"-s /tmp/aster.sock", boot_protocol = "multiboot2"
"--no-reboot", bootloader = "grub"
"-nographic", ovmf = "/usr/bin/bash"
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", opensbi = "/usr/bin/bash"
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", 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,176 +1,43 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::*; use super::*;
use crate::config_manager::cfg::Cfg;
#[test] #[test]
fn split_kcmd_args_test() { fn deserialize_toml_manifest() {
let mut kcmd_args = ["init=/bin/sh", "--", "sh", "-l"]
.iter()
.map(ToString::to_string)
.collect();
let init_args = split_kcmd_args(&mut kcmd_args);
let expected_kcmd_args: Vec<_> = ["init=/bin/sh"].iter().map(ToString::to_string).collect();
assert_eq!(kcmd_args, expected_kcmd_args);
let expecetd_init_args: Vec<_> = ["sh", "-l"].iter().map(ToString::to_string).collect();
assert_eq!(init_args, expecetd_init_args);
let mut kcmd_args = ["init=/bin/sh", "--"]
.iter()
.map(ToString::to_string)
.collect();
let init_args = split_kcmd_args(&mut kcmd_args);
let expected_kcmd_args: Vec<_> = ["init=/bin/sh"].iter().map(ToString::to_string).collect();
assert_eq!(kcmd_args, expected_kcmd_args);
let expecetd_init_args: Vec<String> = Vec::new();
assert_eq!(init_args, expecetd_init_args);
let mut kcmd_args = ["init=/bin/sh", "shell=/bin/sh"]
.iter()
.map(ToString::to_string)
.collect();
let init_args = split_kcmd_args(&mut kcmd_args);
let expected_kcmd_args: Vec<_> = ["init=/bin/sh", "shell=/bin/sh"]
.iter()
.map(ToString::to_string)
.collect();
assert_eq!(kcmd_args, expected_kcmd_args);
let expecetd_init_args: Vec<String> = Vec::new();
assert_eq!(init_args, expecetd_init_args);
}
#[test]
fn get_key_test() {
let string1 = "init=/bin/init";
let key = get_key(string1, "=").unwrap();
assert_eq!(key.as_str(), "init");
let string2 = "-m 2G";
let key = get_key(string2, " ").unwrap();
assert_eq!(key.as_str(), "-m");
let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off";
let key = get_key(string3, " ").unwrap();
assert_eq!(key.as_str(), "-device");
let string4 = "-device";
assert!(get_key(string4, " ").is_none());
}
#[test]
fn apply_kv_array_test() {
let qemu_args = &[
"-enable-kvm",
"-m 2G",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
];
let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"];
let expected = &[
"-enable-kvm",
"-m 100G",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device ioh3420,id=pcie.0,chassis=1",
];
let mut array = qemu_args.iter().map(ToString::to_string).collect();
let args = args.iter().map(ToString::to_string).collect();
apply_kv_array(&mut array, &args, " ", &["-device"]);
let expected: Vec<_> = expected.iter().map(ToString::to_string).collect();
assert_eq!(expected, array);
}
#[test]
fn test_cfg_from_str() {
let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]);
let cfg1 = Cfg::from_str(" cfg(arch = \"x86_64\", select=\"foo\", )").unwrap();
let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",select=\"foo\")").unwrap();
let cfg3 = Cfg::from_str(" cfg( arch=\"x86_64\", select=\"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"), ("select", "foo")]);
let cfg_string = cfg.to_string();
let cfg_back = Cfg::from_str(&cfg_string).unwrap();
assert_eq!(cfg_string, "cfg(arch=\"x86_64\", select=\"foo\")");
assert_eq!(cfg, cfg_back);
}
#[test]
fn deserialize_osdk_manifest() {
let content = include_str!("OSDK.toml.empty");
let osdk_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(osdk_manifest == TomlManifest::default());
let content = include_str!("OSDK.toml.full"); let content = include_str!("OSDK.toml.full");
let osdk_manifest: TomlManifest = toml::from_str(content).unwrap(); let toml_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(osdk_manifest.boot.grub_mkrescue.unwrap() == PathBuf::from("/usr/bin/grub-mkrescue")); assert!(toml_manifest.project.type_ == manifest::ProjectType::Kernel);
}
#[test]
fn serialize_osdk_manifest() {
let manifest = TomlManifest::default();
let contents = toml::to_string(&manifest).unwrap();
fs::write("OSDK.toml", contents).unwrap();
fs::remove_file("OSDK.toml").unwrap();
}
#[test]
fn deserialize_conditional_osdk_manifest() {
let content = include_str!("OSDK.toml.conditional");
let manifest: TomlManifest = toml::from_str(content).unwrap();
println!("manifest = {:?}", manifest);
} }
#[test] #[test]
fn conditional_manifest() { fn conditional_manifest() {
let toml_manifest: TomlManifest = { let toml_manifest: TomlManifest = {
let content = include_str!("OSDK.toml.conditional"); let content = include_str!("OSDK.toml.full");
toml::from_str(content).unwrap() toml::from_str(content).unwrap()
}; };
let arch = crate::arch::Arch::X86_64;
assert!(toml_manifest.qemu.cfg_map.is_some()); // Default schema
assert!(toml_manifest let schema: Option<String> = None;
.qemu let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
.cfg_map assert!(manifest.run.unwrap().qemu_args.contains(&String::from(
.as_ref() "-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
.unwrap()
.contains_key(&Cfg::from([("arch", "x86_64"), ("select", "intel_tdx")])));
assert!(toml_manifest
.qemu
.cfg_map
.as_ref()
.unwrap()
.contains_key(&Cfg::from([("select", "iommu")])));
// Default selection
let arch = None;
let selection: Option<String> = None;
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection);
assert!(manifest.qemu.args.contains(&String::from(
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"
))); )));
// Iommu // Iommu
let arch = None; let schema: Option<String> = Some("iommu".to_owned());
let selection: Option<String> = Some("iommu".to_owned()); let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection);
assert!(manifest assert!(manifest
.qemu .run
.args .unwrap()
.qemu_args
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1"))); .contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
// Tdx // Tdx
let arch = Some("x86_64".to_owned()); let schema: Option<String> = Some("intel_tdx".to_owned());
let selection: Option<String> = Some("intel_tdx".to_owned()); let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection); assert_eq!(
assert!(manifest.qemu.args.is_empty()); manifest.run.unwrap().qemu_exe.unwrap(),
PathBuf::from("/usr/bin/bash")
);
} }

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: MPL-2.0
//! This module contains utilities for manipulating common Unix command-line arguments.
use std::process;
use indexmap::{IndexMap, IndexSet};
use crate::{error::Errno, error_msg};
/// Apply key-value pairs to an array of strings.
///
/// The provided arguments will be appended to the array if the key is not already present or if the key is a multi-value key.
/// Otherwise, the value will be updated.
pub fn apply_kv_array(
array: &mut Vec<String>,
args: &Vec<String>,
seperator: &str,
multi_value_keys: &[&str],
) {
let multi_value_keys = {
let mut inferred_keys = infer_multi_value_keys(array, seperator);
for key in multi_value_keys {
inferred_keys.insert(key.to_string());
}
inferred_keys
};
debug!("multi value keys: {:?}", multi_value_keys);
// We use IndexMap to keep key orders
let mut key_strings = IndexMap::new();
let mut multi_value_key_strings: IndexMap<String, Vec<String>> = IndexMap::new();
for item in array.drain(..) {
// Each key-value string has two patterns:
// 1. Seperated by separator: key value / key=value
if let Some(key) = get_key(&item, seperator) {
if multi_value_keys.contains(&key) {
if let Some(v) = multi_value_key_strings.get_mut(&key) {
v.push(item);
} else {
let v = vec![item];
multi_value_key_strings.insert(key, v);
}
continue;
}
key_strings.insert(key, item);
continue;
}
// 2. Only key, no value
key_strings.insert(item.clone(), item);
}
for arg in args {
if let Some(key) = get_key(arg, seperator) {
if multi_value_keys.contains(&key) {
if let Some(v) = multi_value_key_strings.get_mut(&key) {
v.push(arg.to_owned());
} else {
let v = vec![arg.to_owned()];
multi_value_key_strings.insert(key, v);
}
continue;
}
key_strings.insert(key, arg.to_owned());
continue;
}
key_strings.insert(arg.to_owned(), arg.to_owned());
}
*array = key_strings.into_iter().map(|(_, value)| value).collect();
for (_, mut values) in multi_value_key_strings {
array.append(&mut values);
}
}
fn infer_multi_value_keys(array: &Vec<String>, seperator: &str) -> IndexSet<String> {
let mut multi_val_keys = IndexSet::new();
let mut occured_keys = IndexSet::new();
for item in array {
let Some(key) = get_key(item, seperator) else {
continue;
};
if occured_keys.contains(&key) {
multi_val_keys.insert(key);
} else {
occured_keys.insert(key);
}
}
multi_val_keys
}
pub fn get_key(item: &str, seperator: &str) -> Option<String> {
let split = item.split(seperator).collect::<Vec<_>>();
let len = split.len();
if len > 2 || len == 0 {
error_msg!("`{}` is an invalid argument.", item);
process::exit(Errno::ParseMetadata as _);
}
if len == 1 {
return None;
}
let key = split.first().unwrap();
Some(key.to_string())
}
#[cfg(test)]
pub mod test {
use super::*;
#[test]
fn test_get_key() {
let string1 = "init=/bin/init";
let key = get_key(string1, "=").unwrap();
assert_eq!(key.as_str(), "init");
let string2 = "-m 2G";
let key = get_key(string2, " ").unwrap();
assert_eq!(key.as_str(), "-m");
let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off";
let key = get_key(string3, " ").unwrap();
assert_eq!(key.as_str(), "-device");
let string4 = "-device";
assert!(get_key(string4, " ").is_none());
}
#[test]
fn test_apply_kv_array() {
let qemu_args = &[
"-enable-kvm",
"-m 2G",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
];
let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"];
let expected = &[
"-enable-kvm",
"-m 100G",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device ioh3420,id=pcie.0,chassis=1",
];
let mut array = qemu_args.iter().map(ToString::to_string).collect();
let args = args.iter().map(ToString::to_string).collect();
apply_kv_array(&mut array, &args, " ", &["-device"]);
let expected: Vec<_> = expected.iter().map(ToString::to_string).collect();
assert_eq!(expected, array);
}
}

View File

@ -14,7 +14,7 @@ use quote::ToTokens;
/// FIXME: We should publish the asterinas crates to a public registry /// FIXME: We should publish the asterinas crates to a public registry
/// and use the published version in the generated Cargo.toml. /// and use the published version in the generated Cargo.toml.
pub const ASTER_GIT_LINK: &str = "https://github.com/asterinas/asterinas"; pub const ASTER_GIT_LINK: &str = "https://github.com/asterinas/asterinas";
pub const ASTER_GIT_REV: &str = "d390365"; pub const ASTER_GIT_REV: &str = "1069006";
pub fn aster_crate_dep(crate_name: &str) -> String { pub fn aster_crate_dep(crate_name: &str) -> String {
format!( format!(
"{} = {{ git = \"{}\", rev = \"{}\" }}", "{} = {{ git = \"{}\", rev = \"{}\" }}",

View File

@ -14,7 +14,7 @@ fn create_kernel_in_workspace() {
remove_dir_all(WORKSPACE_NAME).unwrap(); remove_dir_all(WORKSPACE_NAME).unwrap();
} }
create_workspace(WORKSPACE_NAME, &[KERNEL_NAME]); create_workspace(WORKSPACE_NAME, &[KERNEL_NAME]);
let mut cargo_osdk = cargo_osdk(["new", "--kernel", KERNEL_NAME]); let mut cargo_osdk = cargo_osdk(["new", "--type", "kernel", KERNEL_NAME]);
cargo_osdk.current_dir(WORKSPACE_NAME); cargo_osdk.current_dir(WORKSPACE_NAME);
let output = cargo_osdk.output().unwrap(); let output = cargo_osdk.output().unwrap();
assert_success(&output); assert_success(&output);
@ -51,7 +51,7 @@ fn create_two_crates_in_workspace() {
add_member_to_workspace(WORKSPACE_NAME, KERNEL_NAME); add_member_to_workspace(WORKSPACE_NAME, KERNEL_NAME);
// Create kernel crate // Create kernel crate
let mut command = cargo_osdk(["new", "--kernel", KERNEL_NAME]); let mut command = cargo_osdk(["new", "--type", "kernel", KERNEL_NAME]);
command.current_dir(WORKSPACE_NAME); command.current_dir(WORKSPACE_NAME);
let output = command.output().unwrap(); let output = command.output().unwrap();
assert_success(&output); assert_success(&output);

View File

@ -15,7 +15,7 @@ fn create_a_kernel_project() {
fs::remove_dir_all(&kernel_path).unwrap(); fs::remove_dir_all(&kernel_path).unwrap();
} }
cargo_osdk(&["new", "--kernel", kernel]) cargo_osdk(&["new", "--type", "kernel", kernel])
.current_dir(workdir) .current_dir(workdir)
.unwrap(); .unwrap();

View File

@ -15,7 +15,7 @@ fn create_and_run_kernel() {
fs::remove_dir_all(&os_dir).unwrap(); fs::remove_dir_all(&os_dir).unwrap();
} }
let mut command = cargo_osdk(&["new", "--kernel", os_name]); let mut command = cargo_osdk(&["new", "--type", "kernel", os_name]);
command.current_dir(work_dir); command.current_dir(work_dir);
command.ok().unwrap(); command.ok().unwrap();

View File

@ -29,7 +29,9 @@ fn work_in_workspace() {
// Create a kernel project and a library project // Create a kernel project and a library project
let kernel = "myos"; let kernel = "myos";
let module = "mymodule"; let module = "mymodule";
cargo_osdk(&["new", "--kernel", kernel]).ok().unwrap(); cargo_osdk(&["new", "--type", "kernel", kernel])
.ok()
.unwrap();
cargo_osdk(&["new", module]).ok().unwrap(); cargo_osdk(&["new", module]).ok().unwrap();
// Add a test function to mymodule/src/lib.rs // Add a test function to mymodule/src/lib.rs

View File

@ -7,5 +7,4 @@
mod cli; mod cli;
mod commands; mod commands;
mod examples_in_book; mod examples_in_book;
mod integration;
mod util; mod util;

View File

@ -1,16 +0,0 @@
[qemu]
args = [
"-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"
]
[qemu.'cfg(select="intel_tdx")']
[qemu.'cfg(select="iommu")']
args = [
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,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 intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]

View File

@ -1,78 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
#![allow(unused)]
use std::{
fs,
path::{Path, PathBuf},
};
use crate::util::{assert_success, cargo_osdk, create_workspace};
#[test]
fn build_with_default_manifest() {
let workspace = "/tmp/workspace_foo";
if Path::new(workspace).exists() {
fs::remove_dir_all(workspace).unwrap();
}
let kernel_name: &str = "foo_os";
create_workspace(workspace, &[kernel_name]);
create_osdk_kernel(kernel_name, workspace);
cargo_osdk_build(PathBuf::from(workspace).join(kernel_name), &[]);
fs::remove_dir_all(workspace).unwrap();
}
#[test]
fn build_with_conditional_manifest() {
let workspace = "/tmp/workspace_bar";
if Path::new(workspace).exists() {
fs::remove_dir_all(workspace).unwrap();
}
let kernel_name: &str = "bar_os";
create_workspace(workspace, &[kernel_name]);
create_osdk_kernel_with_features(kernel_name, &["intel_tdx", "iommu"], workspace);
let contents = include_str!("OSDK.toml.conditional");
let path = PathBuf::from(workspace).join("OSDK.toml");
fs::write(path, contents).unwrap();
cargo_osdk_build(
PathBuf::from(workspace).join(kernel_name),
&["--profile", "release", "--features", "iommu"],
);
fs::remove_dir_all(workspace).unwrap();
}
fn create_osdk_kernel(name: &str, current_dir: &str) {
let output = cargo_osdk(&["new", "--kernel", name])
.current_dir(current_dir)
.output()
.unwrap();
assert_success(&output);
}
fn create_osdk_kernel_with_features(name: &str, features: &[&str], current_dir: &str) {
create_osdk_kernel(name, current_dir);
let manifest_path = PathBuf::from(current_dir).join(name).join("Cargo.toml");
let contents = fs::read_to_string(&manifest_path).unwrap();
let mut manifest: toml::Table = toml::from_str(&contents).unwrap();
let mut features_table = toml::Table::new();
for feature in features {
features_table.insert(feature.to_string(), toml::Value::Array(Vec::new()));
}
manifest.insert("features".to_string(), toml::Value::Table(features_table));
fs::write(&manifest_path, manifest.to_string()).unwrap();
}
fn cargo_osdk_build<P: AsRef<Path>>(current_dir: P, args: &[&str]) {
let mut command = cargo_osdk(&["build"]);
command.args(args);
command.current_dir(current_dir);
let output = command.output().unwrap();
assert_success(&output);
}

View File

@ -59,10 +59,6 @@ pub fn create_workspace(workspace_name: &str, members: &[&str]) {
let content = table.to_string(); let content = table.to_string();
fs::write(manefest_path, content).unwrap(); fs::write(manefest_path, content).unwrap();
// Create OSDK.toml
let osdk_manifest_path = PathBuf::from(workspace_name).join("OSDK.toml");
fs::write(osdk_manifest_path, "").unwrap();
// Create rust-toolchain.toml which is synced with the Asterinas' toolchain // Create rust-toolchain.toml which is synced with the Asterinas' toolchain
let rust_toolchain_path = PathBuf::from(workspace_name).join("rust-toolchain.toml"); let rust_toolchain_path = PathBuf::from(workspace_name).join("rust-toolchain.toml");
let content = include_str!("../../../rust-toolchain.toml"); let content = include_str!("../../../rust-toolchain.toml");