mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-09 13:26:48 +00:00
Overhaul OSDK
This commit is contained in:
parent
735d7b7b11
commit
33c53dcf2b
30
Makefile
30
Makefile
@ -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
|
||||
|
61
OSDK.toml
61
OSDK.toml
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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
72
osdk/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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("-initrd").arg(initramfs);
|
||||
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");
|
||||
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);
|
||||
None => {
|
||||
error_msg!("Bootloader is required for QEMU booting");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
}
|
||||
};
|
||||
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();
|
||||
|
@ -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>,
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
18
osdk/src/commands/new/kernel.OSDK.toml.template
Normal file
18
osdk/src/commands/new/kernel.OSDK.toml.template
Normal 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",
|
||||
]
|
17
osdk/src/commands/new/lib.OSDK.toml.template
Normal file
17
osdk/src/commands/new/lib.OSDK.toml.template
Normal 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",
|
||||
]
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
};
|
||||
|
||||
|
@ -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(),
|
||||
};
|
||||
|
131
osdk/src/config_manager/action.rs
Normal file
131
osdk/src/config_manager/action.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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,34 +132,41 @@ fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManif
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
);
|
||||
let manifest_path = workspace_root.join("OSDK.toml");
|
||||
|
||||
let Ok(contents) = fs::read_to_string(&manifest_path) else {
|
||||
error_msg!(
|
||||
"Cannot read file {}",
|
||||
manifest_path.to_string_lossy().to_string()
|
||||
);
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
// Search for OSDK.toml in the current directory. If not, dive into the workspace root.
|
||||
let manifest_path = PathBuf::from("OSDK.toml");
|
||||
let (contents, manifest_path) = if let Ok(contents) = fs::read_to_string("OSDK.toml") {
|
||||
(contents, manifest_path)
|
||||
} else {
|
||||
let manifest_path = workspace_root.join("OSDK.toml");
|
||||
let Ok(contents) = fs::read_to_string(&manifest_path) else {
|
||||
error_msg!(
|
||||
"Cannot read file {}",
|
||||
manifest_path.to_string_lossy().to_string()
|
||||
);
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
};
|
||||
(contents, manifest_path)
|
||||
};
|
||||
|
||||
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
|
||||
let span = err.span().unwrap();
|
||||
let wider_span =
|
||||
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
|
||||
error_msg!(
|
||||
"Cannot parse TOML file, {}:\n{}:\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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
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)
|
||||
for arg in args.iter() {
|
||||
check_qemu_arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
@ -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",
|
||||
]
|
@ -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",
|
||||
]
|
@ -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")
|
||||
);
|
||||
}
|
||||
|
165
osdk/src/config_manager/unix_args.rs
Normal file
165
osdk/src/config_manager/unix_args.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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 = \"{}\" }}",
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -7,5 +7,4 @@
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod examples_in_book;
|
||||
mod integration;
|
||||
mod util;
|
@ -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",
|
||||
]
|
@ -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);
|
||||
}
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user