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

View File

@ -1,3 +1,7 @@
[project]
type = "kernel"
[run]
kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
@ -8,20 +12,16 @@ kcmd_args = [
]
init_args = ["sh", "-l"]
initramfs = "regression/build/initramfs.cpio.gz"
[boot]
protocol = "multiboot2"
loader = "grub"
boot_protocol = "multiboot2"
bootloader = "grub"
ovmf = "/root/ovmf/release"
opensbi = "/root/opensbi-virt-firmware"
[qemu.'cfg(arch="x86_64")']
machine = "q35"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
args = [
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
@ -40,13 +40,36 @@ args = [
"-device virtconsole,chardev=mux",
]
[qemu.'cfg(arch="x86_64", select="iommu")']
machine = "q35"
[test]
boot_protocol = "multiboot"
bootloader = "qemu"
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
['cfg(arch="x86_64", schema="iommu")'.run]
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
args = [
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
@ -67,13 +90,15 @@ args = [
"-device ioh3420,id=pcie.0,chassis=1",
]
[qemu.'cfg(arch="x86_64", select="microvm")']
machine = "microvm"
['cfg(arch="x86_64", schema="microvm")'.run]
bootloader = "qemu"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
]
args = [
qemu_args = [
"-machine microvm,rtc=on",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
@ -94,9 +119,9 @@ args = [
"-device virtconsole,chardev=mux",
]
[qemu.'cfg(arch="riscv64")']
machine = "virt"
args = [
['cfg(arch="riscv64")'.run]
qemu_args = [
"-machine virt",
"--no-reboot",
"-m 2G",
"-nographic",

View File

@ -13,7 +13,7 @@ Creating a new kernel project is simple.
You only need to execute the following command:
```bash
cargo osdk new --kernel myos
cargo osdk new --type kernel myos
```
## 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:
```bash
cargo osdk new --kernel myos && cd myos
cargo osdk new --type kernel myos && cd myos
```
## 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:
```bash
cargo osdk new --kernel myos
cargo osdk new --type kernel myos
cargo osdk new mymodule
```

View File

@ -17,7 +17,7 @@ cargo osdk new [OPTIONS] <name>
## Options
`--kernel`:
`--type kernel`:
Use the kernel template.
If this option is not set,
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`:
```bash
cargo osdk new --kernel myos
cargo osdk new --type kernel myos
```
- 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"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -143,7 +137,6 @@ dependencies = [
"sha2",
"syn",
"toml",
"which",
]
[[package]]
@ -239,12 +232,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "env_filter"
version = "0.1.0"
@ -274,16 +261,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "generic-array"
version = "0.14.7"
@ -306,15 +283,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "humantime"
version = "2.1.0"
@ -353,18 +321,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
name = "linux-bzimage-builder"
version = "0.1.0"
dependencies = [
"bitflags 1.3.2",
"bitflags",
"bytemuck",
"serde",
"xmas-elf",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
version = "0.4.20"
@ -377,12 +339,6 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "predicates"
version = "3.1.0"
@ -457,19 +413,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ryu"
version = "1.0.17"
@ -618,19 +561,6 @@ dependencies = [
"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]]
name = "windows-sys"
version = "0.52.0"

View File

@ -21,7 +21,6 @@ serde_json = "1.0.111"
sha2 = "0.10.8"
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
toml = { version = "0.8.8", features = ["preserve_order"] }
which = "6.0.0"
[dev-dependencies]
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
```bash
cargo osdk new --kernel my-first-os
cargo osdk new --type kernel my-first-os
```
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
/// of Cargo:
/// <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 {
Aarch64,
X86_64,
@ -41,7 +41,7 @@ impl Arch {
}
}
pub fn to_str(&self) -> &'static str {
pub fn to_str(self) -> &'static str {
match self {
Arch::Aarch64 => "aarch64",
Arch::RiscV64 => "riscv64",

View File

@ -6,6 +6,7 @@ pub mod vm_image;
use bin::AsterBin;
use file::{BundleFile, Initramfs};
use std::process;
use vm_image::AsterVmImage;
use std::{
@ -15,10 +16,10 @@ use std::{
};
use crate::{
arch::Arch,
cli::CargoArgs,
config_manager::{
boot::Boot,
qemu::{Qemu, QemuMachine},
action::{ActionSettings, Bootloader},
RunConfig,
},
error::Errno,
@ -38,34 +39,33 @@ pub struct Bundle {
/// The osdk bundle artifact manifest that stores as `bundle.toml`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleManifest {
pub kcmd_args: Vec<String>,
pub initramfs: Option<Initramfs>,
pub aster_bin: Option<AsterBin>,
pub vm_image: Option<AsterVmImage>,
pub boot: Boot,
pub qemu: Qemu,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub last_modified: SystemTime,
}
impl Bundle {
/// This function creates a new `Bundle` without adding any files.
pub fn new(
path: impl AsRef<Path>,
kcmd_args: Vec<String>,
boot: Boot,
qemu: Qemu,
cargo_args: CargoArgs,
) -> Self {
pub fn new(path: impl AsRef<Path>, settings: ActionSettings, cargo_args: CargoArgs) -> Self {
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 {
manifest: BundleManifest {
kcmd_args,
initramfs: None,
initramfs,
aster_bin: None,
vm_image: None,
boot,
qemu,
settings,
cargo_args,
last_modified: SystemTime::now(),
},
@ -113,16 +113,14 @@ impl Bundle {
// Compare the manifest with the run configuration.
// TODO: This pairwise comparison will result in some false negatives. We may
// fix it by pondering upon each fields with more care.
if self.manifest.kcmd_args != config.manifest.kcmd_args
|| self.manifest.boot != config.manifest.boot
|| self.manifest.qemu != config.manifest.qemu
if self.manifest.settings != config.settings
|| self.manifest.cargo_args != config.cargo_args
{
return false;
}
// Compare the initramfs.
match (&self.manifest.initramfs, &config.manifest.initramfs) {
match (&self.manifest.initramfs, &config.settings.initramfs) {
(Some(initramfs), Some(initramfs_path)) => {
let config_initramfs = Initramfs::new(initramfs_path);
if initramfs.sha256sum() != config_initramfs.sha256sum() {
@ -147,42 +145,46 @@ impl Bundle {
error_msg!("The bundle is not compatible with the run configuration");
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
// is a dirty hack to make it work. Anything like space in the paths will
// break this.
for arg in &config.manifest.qemu.args {
for arg in &config.settings.qemu_args {
for part in arg.split_whitespace() {
qemu_cmd.arg(part);
}
}
match config.manifest.qemu.machine {
QemuMachine::Microvm => {
qemu_cmd.arg("-machine").arg("microvm,rtc=on");
match config.settings.bootloader {
Some(Bootloader::Qemu) => {
let Some(ref aster_bin) = self.manifest.aster_bin else {
error_msg!("Kernel ELF binary is required for Microvm");
error_msg!("Kernel ELF binary is required for direct QEMU booting");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd
.arg("-kernel")
.arg(self.path.join(aster_bin.path()));
let Some(ref initramfs) = config.manifest.initramfs else {
error_msg!("Initramfs is required for Microvm");
std::process::exit(Errno::RunBundle as _);
};
if let Some(ref initramfs) = config.settings.initramfs {
qemu_cmd.arg("-initrd").arg(initramfs);
} else {
info!("No initramfs specified");
};
qemu_cmd
.arg("-append")
.arg(config.manifest.kcmd_args.join(" "));
.arg(config.settings.combined_kcmd_args().join(" "));
}
QemuMachine::Q35 => {
qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split");
Some(Bootloader::Grub) => {
let Some(ref vm_image) = self.manifest.vm_image else {
error_msg!("VM image is required for QEMU booting");
std::process::exit(Errno::RunBundle as _);
};
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!(
"if=pflash,format=raw,unit=0,readonly=on,file={}",
ovmf.join("OVMF_CODE.fd").display()
@ -193,31 +195,13 @@ impl Bundle {
));
}
}
QemuMachine::Virt => {
qemu_cmd.arg("-machine").arg("virt");
let Some(ref vm_image) = self.manifest.vm_image else {
error_msg!("VM image is required for QEMU booting");
None => {
error_msg!("Bootloader is required for QEMU booting");
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!(
"file={},{}",
drive_file.path.display(),
@ -256,15 +240,6 @@ impl Bundle {
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) {
self.manifest.last_modified = SystemTime::now();
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,
},
config_manager::{
boot::{BootLoader, BootProtocol},
qemu::QemuMachine,
action::{BootProtocol, Bootloader},
manifest::ProjectType,
BuildConfig, DebugConfig, RunConfig, TestConfig,
},
};
@ -96,8 +96,13 @@ pub struct ForwardedArguments {
#[derive(Debug, Parser)]
pub struct NewArgs {
#[arg(long, default_value = "false", help = "Use the kernel template")]
pub kernel: bool,
#[arg(
long = "type",
short = 't',
default_value = "library",
help = "The type of the project to create"
)]
pub type_: ProjectType,
#[arg(name = "name", required = true)]
pub crate_name: String,
}
@ -189,7 +194,7 @@ pub struct CargoArgs {
conflicts_with = "profile"
)]
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>,
}
@ -198,57 +203,60 @@ pub struct OsdkArgs {
#[arg(long, value_name = "ARCH", help = "The architecture to build for")]
pub arch: Option<Arch>,
#[arg(
long = "select",
help = "Select the specific configuration provided in the OSDK manifest",
value_name = "SELECTION"
long = "schema",
help = "Select the specific configuration schema provided in the OSDK manifest",
value_name = "SCHEMA"
)]
pub select: Option<String>,
pub schema: Option<String>,
#[arg(
long = "kcmd_args",
help = "Command line arguments for guest kernel",
long = "kcmd_args+",
require_equals = true,
help = "Extra or overriding command line arguments for guest kernel",
value_name = "ARGS"
)]
pub kcmd_args: Vec<String>,
#[arg(
long = "init_args",
help = "Command line arguments for init process",
long = "init_args+",
require_equals = true,
help = "Extra command line arguments for init process",
value_name = "ARGS"
)]
pub init_args: Vec<String>,
#[arg(long, help = "Path of initramfs", value_name = "PATH")]
pub initramfs: Option<PathBuf>,
#[arg(long = "boot.ovmf", help = "Path of OVMF", value_name = "PATH")]
pub boot_ovmf: Option<PathBuf>,
#[arg(long = "ovmf", help = "Path of OVMF", value_name = "PATH")]
pub ovmf: Option<PathBuf>,
#[arg(long = "opensbi", help = "Path of OpenSBI", value_name = "PATH")]
pub opensbi: Option<PathBuf>,
#[arg(
long = "boot.loader",
long = "bootloader",
help = "Loader for booting the kernel",
value_name = "LOADER"
value_name = "BOOTLOADER"
)]
pub boot_loader: Option<BootLoader>,
pub bootloader: Option<Bootloader>,
#[arg(
long = "boot.grub-mkrescue",
long = "grub-mkrescue",
help = "Path of grub-mkrescue",
value_name = "PATH"
)]
pub boot_grub_mkrescue: Option<PathBuf>,
pub grub_mkrescue: Option<PathBuf>,
#[arg(
long = "boot.protocol",
long = "boot_protocol",
help = "Protocol for booting the kernel",
value_name = "PROTOCOL"
value_name = "BOOT_PROTOCOL"
)]
pub boot_protocol: Option<BootProtocol>,
#[arg(long = "qemu.path", help = "Path of QEMU", value_name = "PATH")]
pub qemu_path: Option<PathBuf>,
#[arg(
long = "qemu.machine",
help = "QEMU machine type",
value_name = "MACHINE"
long = "qemu_exe",
help = "The QEMU executable file",
value_name = "FILE"
)]
pub qemu_machine: Option<QemuMachine>,
pub qemu_exe: Option<PathBuf>,
#[arg(
long = "qemu.args",
help = "Arguments for running QEMU",
long = "qemu_args+",
require_equals = true,
help = "Extra arguments or overriding arguments for running QEMU",
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},
file::BundleFile,
},
config_manager::boot::BootProtocol,
config_manager::action::BootProtocol,
util::get_current_crate_info,
};
@ -153,7 +153,6 @@ fn install_setup_with_arch(
cmd.arg("install").arg("linux-bzimage-setup");
cmd.arg("--force");
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("--rev").arg(crate::util::ASTER_GIT_REV);
cmd.arg("--target").arg(match arch {

View File

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

View File

@ -13,11 +13,10 @@ use crate::{
base_crate::new_base_crate,
bundle::{
bin::{AsterBin, AsterBinType, AsterElfMeta},
file::Initramfs,
Bundle,
},
cli::CargoArgs,
config_manager::{qemu::QemuMachine, BuildConfig},
config_manager::{action::Bootloader, BuildConfig},
error::Errno,
error_msg,
util::{get_current_crate_info, get_target_directory},
@ -79,20 +78,10 @@ pub fn do_build(
}
let mut bundle = Bundle::new(
&bundle_path,
config.manifest.kcmd_args.clone(),
config.manifest.boot.clone(),
config.manifest.qemu.clone(),
config.settings.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");
let aster_elf = build_kernel_elf(
&config.arch,
@ -101,20 +90,17 @@ pub fn do_build(
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);
bundle.consume_aster_bin(stripped_elf);
}
// TODO: A boot device is required if we use GRUB. Actually you can boot
// a multiboot kernel with Q35 machine directly without a bootloader.
// We are currently ignoring this case.
if matches!(config.manifest.qemu.machine, QemuMachine::Q35) {
if matches!(config.settings.bootloader, Some(Bootloader::Grub)) {
info!("Building boot device image");
let bootdev_image = grub::create_bootdev_image(
&osdk_target_directory,
&aster_elf,
config.manifest.initramfs.as_ref(),
config.settings.initramfs.as_ref(),
config,
);
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 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![
&env_rustflags,
&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::{
cli::NewArgs,
config_manager::manifest::ProjectType,
error::Errno,
error_msg,
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);
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap();
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);
if args.kernel {
write_kernel_template(&cargo_metadata, &args.crate_name);
} else {
write_library_template(&cargo_metadata, &args.crate_name);
}
write_src_template(&cargo_metadata, &args.crate_name, &args.type_);
add_rust_toolchain(&cargo_metadata);
}
@ -83,7 +80,7 @@ fn exclude_osdk_base(metadata: &serde_json::Value) {
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 workspace_root = get_workspace_root(cargo_metadata);
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
// FIXME: we need ovmf for grub-efi, the user may not have it.
// The apt OVMF repo installs to `/usr/share/OVMF`
fs::write(
osdk_manifest_path,
r#"
[boot]
ovmf = "/usr/share/OVMF"
protocol = "multiboot"
[qemu]
machine = "q35"
args = [
"--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",
]
"#,
)
.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();
let contents = match type_ {
ProjectType::Kernel => {
include_str!("kernel.OSDK.toml.template")
}
ProjectType::Library => {
include_str!("lib.OSDK.toml.template")
}
ProjectType::Module => {
todo!()
}
};
fs::write(osdk_manifest_path, contents).unwrap();
}
/// 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 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();
}

View File

@ -28,7 +28,7 @@ pub fn execute_run_command(config: &RunConfig) {
let existing_bundle = Bundle::load(&default_bundle_directory);
let config = RunConfig {
manifest: {
settings: {
if config.gdb_server_args.is_gdb_enabled {
let qemu_gdb_args: Vec<_> = {
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
@ -55,14 +55,14 @@ pub fn execute_run_command(config: &RunConfig) {
let qemu_gdb_args: Vec<_> = qemu_gdb_args
.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())
.collect();
let mut manifest = config.manifest.clone();
manifest.qemu.args.extend(qemu_gdb_args);
manifest
let mut settings = config.settings.clone();
settings.qemu_args.extend(qemu_gdb_args);
settings
} else {
config.manifest.clone()
config.settings.clone()
}
},
..config.clone()
@ -95,8 +95,8 @@ pub fn execute_run_command(config: &RunConfig) {
}
let required_build_config = BuildConfig {
arch: config.arch.clone(),
manifest: config.manifest.clone(),
arch: config.arch,
settings: config.settings.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 default_bundle_directory = osdk_target_directory.join(target_name);
let required_build_config = BuildConfig {
arch: config.arch.clone(),
manifest: config.manifest.clone(),
arch: config.arch,
settings: config.settings.clone(),
cargo_args: config.cargo_args.clone(),
};
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();
let required_run_config = RunConfig {
arch: config.arch.clone(),
manifest: required_build_config.manifest.clone(),
arch: config.arch,
settings: required_build_config.settings.clone(),
cargo_args: required_build_config.cargo_args.clone(),
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")])`.
/// Making things easier for testing.
/// This allows literal constructions like `Cfg::from([("arch", "foo"), ("schema", "bar")])`.
impl<K, V, const N: usize> From<[(K, V); N]> for Cfg
where
K: Into<String>,
@ -50,7 +49,7 @@ where
}
impl Cfg {
pub fn new() -> Self {
pub fn empty() -> Self {
Self(BTreeMap::new())
}
@ -77,17 +76,8 @@ impl Cfg {
Ok(Self(cfg))
}
pub fn check_allowed(&self, allowed_keys: &[&str]) -> bool {
for (k, _) in self.0.iter() {
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);
pub fn map(&self) -> &BTreeMap<String, String> {
&self.0
}
}
@ -110,10 +100,10 @@ mod test {
#[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();
let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", schema=\"foo\", )").unwrap();
let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",schema=\"foo\")").unwrap();
let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", schema=\"foo\" )").unwrap();
assert_eq!(cfg, cfg1);
assert_eq!(cfg, cfg2);
assert_eq!(cfg, cfg3);
@ -121,17 +111,17 @@ mod test {
#[test]
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_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);
}
#[test]
fn test_bad_cfg_strings() {
Cfg::from_str("cfg(,,,,arch=\"x86_64 \", select=\"foo\")").is_err();
Cfg::from_str("cfg(arch=\"x86_64\", select=\"foo\"").is_err();
Cfg::from_str("cfg(arch=x86_64,,, select=\"foo\") ").is_err();
assert!(Cfg::from_str("fg(,,,,arch=\"x86_64 \", schema=\"foo\")").is_err());
assert!(Cfg::from_str("cfg(arch=\"x86_64\", schema=\"foo\"").is_err());
assert!(Cfg::from_str("cfgarch=x86_64,,, schema=\"foo\") ").is_err());
}
}

View File

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

View File

@ -5,54 +5,47 @@
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`,
//! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
pub mod boot;
pub mod action;
pub mod cfg;
pub mod manifest;
pub mod qemu;
pub mod unix_args;
use action::ActionSettings;
#[cfg(test)]
mod test;
use std::{fs, path::PathBuf, process};
use indexmap::{IndexMap, IndexSet};
use which::which;
use self::{
boot::BootLoader,
manifest::{OsdkManifest, TomlManifest},
};
use self::manifest::{OsdkManifest, TomlManifest};
use crate::{
arch::{get_default_arch, Arch},
cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs},
error::Errno,
error_msg,
util::get_cargo_metadata,
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
#[derive(Debug)]
pub struct BuildConfig {
pub arch: Arch,
pub manifest: OsdkManifest,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
}
impl BuildConfig {
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 mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
if let Some(run) = manifest.run.as_mut() {
run.apply_cli_args(&args.osdk_args);
}
Self {
arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args),
settings: manifest.run.unwrap(),
cargo_args,
}
}
@ -62,18 +55,22 @@ impl BuildConfig {
#[derive(Debug, Clone)]
pub struct RunConfig {
pub arch: Arch,
pub manifest: OsdkManifest,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub gdb_server_args: GdbServerArgs,
}
impl RunConfig {
pub fn parse(args: &RunArgs) -> Self {
let arch = args.osdk_args.arch.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 mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
if let Some(run) = manifest.run.as_mut() {
run.apply_cli_args(&args.osdk_args);
}
Self {
arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args),
settings: manifest.run.unwrap(),
cargo_args,
gdb_server_args: args.gdb_server_args.clone(),
}
@ -99,27 +96,32 @@ impl DebugConfig {
#[derive(Debug)]
pub struct TestConfig {
pub arch: Arch,
pub manifest: OsdkManifest,
pub settings: ActionSettings,
pub cargo_args: CargoArgs,
pub test_name: Option<String>,
}
impl TestConfig {
pub fn parse(args: &TestArgs) -> Self {
let arch = args.osdk_args.arch.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 manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
// Use run settings if test settings are not provided
let mut test = if let Some(test) = manifest.test {
test
} else {
manifest.run.unwrap()
};
test.apply_cli_args(&args.osdk_args);
Self {
arch,
manifest: get_final_manifest(&cargo_args, &args.osdk_args),
settings: test,
cargo_args,
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 {
let feature_strings = get_feature_strings(cargo_args);
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()
.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 {
error_msg!(
"Cannot read file {}",
@ -139,25 +146,27 @@ fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManif
);
process::exit(Errno::GetMetadata as _);
};
(contents, manifest_path)
};
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
let span = err.span().unwrap();
let wider_span =
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
error_msg!(
"Cannot parse TOML file, {}:\n{}:\n {}",
"Cannot parse TOML file, {}. {}:{:?}:\n {}",
err.message(),
manifest_path.to_string_lossy().to_string(),
&contents[err.span().unwrap()],
span,
&contents[wider_span],
);
process::exit(Errno::ParseMetadata as _);
});
let mut osdk_manifest = OsdkManifest::from_toml_manifest(
toml_manifest,
osdk_args
.arch
.as_ref()
.map(|s| s.to_string()),
osdk_args.select.as_ref().map(|s| s.to_string()),
let osdk_manifest = toml_manifest.get_osdk_manifest(
workspace_root,
osdk_args.arch.unwrap_or_else(get_default_arch),
osdk_args.schema.as_ref().map(|s| s.to_string()),
);
osdk_manifest.check_canonicalize_all_paths(workspace_root);
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
.features
.iter()
.map(|feature| format!("--features={}", feature))
.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
use std::{collections::BTreeMap, fmt, path::PathBuf, process};
//! A module about QEMU arguments.
use serde::{
de::{self, Visitor},
Deserialize, Deserializer,
};
use std::process;
use super::unix_args::{apply_kv_array, get_key};
use super::cfg::Cfg;
use super::get_key;
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)]
pub struct Qemu {
/// The additional arguments for running qemu, except `-cpu` and `-machine`.
#[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 }
pub fn apply_qemu_args_addition(target: &mut Vec<String>, args: &Vec<String>) {
// check qemu_args
for arg in target.iter() {
check_qemu_arg(arg);
}
}
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),
for arg in args.iter() {
check_qemu_arg(arg);
}
impl<'de> Deserialize<'de> for Field {
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 _);
}
}
}
apply_kv_array(target, args, " ", MULTI_VALUE_KEYS);
}
// Below are keys in qemu arguments. The key list is not complete.
/// 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
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
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
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, " ") {
key
} 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"]
init_args = ["sh", "-l"]
[project]
type = "kernel"
[boot]
loader = "grub"
protocol = "multiboot2"
grub-mkrescue = "/usr/bin/grub-mkrescue"
ovmf = "/usr/bin/ovmf"
[qemu]
machine = "q35"
args = [
"-enable-kvm",
"-m 2G",
"-s /tmp/aster.sock",
"--no-reboot",
"-nographic",
"-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",
[run]
kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
"HOME=/",
"USER=root",
"PATH=/bin:/benchmark",
"init=/usr/bin/busybox",
]
init_args = ["sh", "-l"]
initramfs = "/usr/bin/bash"
boot_protocol = "multiboot2"
bootloader = "grub"
ovmf = "/usr/bin/bash"
opensbi = "/usr/bin/bash"
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
['cfg(arch="x86_64", schema="iommu")'.run]
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine q35,kernel-irqchip=split",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtconsole,chardev=mux",
"-device intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]
['cfg(arch="x86_64", schema="microvm")'.run]
bootloader = "qemu"
drive_files = [
["/usr/bin/bash", "if=none,format=raw,id=x0"],
["/usr/bin/bash", "if=none,format=raw,id=x1"],
]
qemu_args = [
"-machine microvm,rtc=on",
"-cpu Icelake-Server,+x2apic",
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-nodefaults",
"-no-user-config",
"-device virtio-blk-device,drive=x0,serial=vext2",
"-device virtio-blk-device,drive=x1,serial=vexfat",
"-device virtio-keyboard-device",
"-device virtio-net-device,netdev=net01",
"-device virtio-serial-device",
"-device virtconsole,chardev=mux",
]
['cfg(schema="intel_tdx")'.run]
qemu_exe = "/usr/bin/bash"
['cfg(arch="riscv64")'.run]
qemu_args = [
"-machine virt",
"--no-reboot",
"-m 2G",
"-nographic",
]

View File

@ -1,176 +1,43 @@
// SPDX-License-Identifier: MPL-2.0
use super::*;
use crate::config_manager::cfg::Cfg;
#[test]
fn split_kcmd_args_test() {
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());
fn deserialize_toml_manifest() {
let content = include_str!("OSDK.toml.full");
let osdk_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(osdk_manifest.boot.grub_mkrescue.unwrap() == PathBuf::from("/usr/bin/grub-mkrescue"));
}
#[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);
let toml_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(toml_manifest.project.type_ == manifest::ProjectType::Kernel);
}
#[test]
fn conditional_manifest() {
let toml_manifest: TomlManifest = {
let content = include_str!("OSDK.toml.conditional");
let content = include_str!("OSDK.toml.full");
toml::from_str(content).unwrap()
};
let arch = crate::arch::Arch::X86_64;
assert!(toml_manifest.qemu.cfg_map.is_some());
assert!(toml_manifest
.qemu
.cfg_map
.as_ref()
.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"
// Default schema
let schema: Option<String> = None;
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert!(manifest.run.unwrap().qemu_args.contains(&String::from(
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
)));
// Iommu
let arch = None;
let selection: Option<String> = Some("iommu".to_owned());
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection);
let schema: Option<String> = Some("iommu".to_owned());
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert!(manifest
.qemu
.args
.run
.unwrap()
.qemu_args
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
// Tdx
let arch = Some("x86_64".to_owned());
let selection: Option<String> = Some("intel_tdx".to_owned());
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection);
assert!(manifest.qemu.args.is_empty());
let schema: Option<String> = Some("intel_tdx".to_owned());
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
assert_eq!(
manifest.run.unwrap().qemu_exe.unwrap(),
PathBuf::from("/usr/bin/bash")
);
}

View File

@ -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
/// 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_REV: &str = "d390365";
pub const ASTER_GIT_REV: &str = "1069006";
pub fn aster_crate_dep(crate_name: &str) -> String {
format!(
"{} = {{ git = \"{}\", rev = \"{}\" }}",

View File

@ -14,7 +14,7 @@ fn create_kernel_in_workspace() {
remove_dir_all(WORKSPACE_NAME).unwrap();
}
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);
let output = cargo_osdk.output().unwrap();
assert_success(&output);
@ -51,7 +51,7 @@ fn create_two_crates_in_workspace() {
add_member_to_workspace(WORKSPACE_NAME, KERNEL_NAME);
// 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);
let output = command.output().unwrap();
assert_success(&output);

View File

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

View File

@ -15,7 +15,7 @@ fn create_and_run_kernel() {
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.ok().unwrap();

View File

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

View File

@ -7,5 +7,4 @@
mod cli;
mod commands;
mod examples_in_book;
mod integration;
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();
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
let rust_toolchain_path = PathBuf::from(workspace_name).join("rust-toolchain.toml");
let content = include_str!("../../../rust-toolchain.toml");