mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 12:56:48 +00:00
Implement the next OSDK
This commit is contained in:
parent
79bdbbe4f9
commit
e4c2151566
6
.github/workflows/kernel_test.yml
vendored
6
.github/workflows/kernel_test.yml
vendored
@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Boot Test (MicroVM)
|
||||
id: boot_test_microvm
|
||||
run: make run AUTO_TEST=boot ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
|
||||
run: make run AUTO_TEST=boot ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1
|
||||
|
||||
- name: Boot Test (Linux Legacy 32-bit Boot Protocol)
|
||||
id: boot_test_linux_legacy32
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
- name: Syscall Test at Ext2 (MicroVM)
|
||||
id: syscall_test_at_ext2
|
||||
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
|
||||
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1
|
||||
|
||||
- name: Syscall Test at Exfat
|
||||
id: syscall_test_at_exfat_linux
|
||||
@ -82,4 +82,4 @@ jobs:
|
||||
|
||||
- name: Regression Test (MicroVM)
|
||||
id: regression_test_linux
|
||||
run: make run AUTO_TEST=regression ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
|
||||
run: make run AUTO_TEST=regression ENABLE_KVM=0 SCHEME=microvm RELEASE_MODE=1
|
||||
|
50
Makefile
50
Makefile
@ -1,38 +1,37 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Project-wide options.
|
||||
# Global options.
|
||||
ARCH ?= x86_64
|
||||
# End of project-wide options.
|
||||
BOOT_METHOD ?= grub-rescue-iso
|
||||
BOOT_PROTOCOL ?= multiboot2
|
||||
BUILD_SYSCALL_TEST ?= 0
|
||||
ENABLE_KVM ?= 1
|
||||
INTEL_TDX ?= 0
|
||||
RELEASE_MODE ?= 0
|
||||
SCHEME ?= ""
|
||||
# End of global options.
|
||||
|
||||
# The Makefile provides a way to run arbitrary tests in the kernel
|
||||
# mode using the kernel command line.
|
||||
# Here are the options for the auto test feature.
|
||||
AUTO_TEST ?= none
|
||||
BOOT_LOADER ?= grub
|
||||
BOOT_PROTOCOL ?= multiboot2
|
||||
BUILD_SYSCALL_TEST ?= 0
|
||||
ENABLE_KVM ?= 1
|
||||
EXTRA_BLOCKLISTS_DIRS ?= ""
|
||||
INTEL_TDX ?= 0
|
||||
SCHEMA ?= ""
|
||||
RELEASE_MODE ?= 0
|
||||
SKIP_GRUB_MENU ?= 1
|
||||
SYSCALL_TEST_DIR ?= /tmp
|
||||
# End of auto test features.
|
||||
|
||||
CARGO_OSDK := ~/.cargo/bin/cargo-osdk
|
||||
|
||||
CARGO_OSDK_ARGS := --arch=$(ARCH)
|
||||
CARGO_OSDK_ARGS := --target-arch=$(ARCH)
|
||||
|
||||
ifeq ($(AUTO_TEST), syscall)
|
||||
BUILD_SYSCALL_TEST := 1
|
||||
CARGO_OSDK_ARGS += --kcmd_args+="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
|
||||
CARGO_OSDK_ARGS += --kcmd_args+="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
|
||||
CARGO_OSDK_ARGS += --init_args+="/opt/syscall_test/run_syscall_test.sh"
|
||||
CARGO_OSDK_ARGS += --kcmd-args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
|
||||
CARGO_OSDK_ARGS += --kcmd-args="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
|
||||
CARGO_OSDK_ARGS += --init-args="/opt/syscall_test/run_syscall_test.sh"
|
||||
else ifeq ($(AUTO_TEST), regression)
|
||||
CARGO_OSDK_ARGS += --init_args+="/regression/run_regression_test.sh"
|
||||
CARGO_OSDK_ARGS += --init-args="/regression/run_regression_test.sh"
|
||||
else ifeq ($(AUTO_TEST), boot)
|
||||
CARGO_OSDK_ARGS += --init_args+="/regression/boot_hello.sh"
|
||||
CARGO_OSDK_ARGS += --init-args="/regression/boot_hello.sh"
|
||||
endif
|
||||
|
||||
ifeq ($(RELEASE_MODE), 1)
|
||||
@ -43,21 +42,26 @@ ifeq ($(INTEL_TDX), 1)
|
||||
CARGO_OSDK_ARGS += --features intel_tdx
|
||||
endif
|
||||
|
||||
CARGO_OSDK_ARGS += --bootloader="$(BOOT_LOADER)"
|
||||
CARGO_OSDK_ARGS += --boot_protocol="$(BOOT_PROTOCOL)"
|
||||
|
||||
ifneq ($(SCHEMA), "")
|
||||
CARGO_OSDK_ARGS += --schema $(SCHEMA)
|
||||
ifneq ($(SCHEME), "")
|
||||
CARGO_OSDK_ARGS += --scheme $(SCHEME)
|
||||
else
|
||||
CARGO_OSDK_ARGS += --boot-method="$(BOOT_METHOD)"
|
||||
endif
|
||||
|
||||
# To test the linux-efi-handover64 boot protocol, we need to use Debian's
|
||||
# GRUB release, which is installed in /usr/bin in our Docker image.
|
||||
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
|
||||
CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
|
||||
else ifeq ($(BOOT_PROTOCOL), linux-legacy32)
|
||||
CARGO_OSDK_ARGS += --linux-x86-legacy-boot
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
|
||||
else
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol=$(BOOT_PROTOCOL)
|
||||
endif
|
||||
|
||||
ifeq ($(ENABLE_KVM), 1)
|
||||
CARGO_OSDK_ARGS += --qemu_args+="--enable-kvm"
|
||||
CARGO_OSDK_ARGS += --qemu-args="--enable-kvm"
|
||||
endif
|
||||
|
||||
# Pass make variables to all subdirectory makes
|
||||
@ -186,9 +190,11 @@ check: $(CARGO_OSDK)
|
||||
(echo "Error: STD_CRATES and NOSTD_CRATES combined is not the same as all workspace members" && exit 1)
|
||||
@rm /tmp/all_crates /tmp/combined_crates
|
||||
@for dir in $(NON_OSDK_CRATES); do \
|
||||
echo "Checking $$dir"; \
|
||||
(cd $$dir && cargo clippy -- -D warnings) || exit 1; \
|
||||
done
|
||||
@for dir in $(OSDK_CRATES); do \
|
||||
echo "Checking $$dir"; \
|
||||
(cd $$dir && cargo osdk clippy -- -- -D warnings) || exit 1; \
|
||||
done
|
||||
@make --no-print-directory -C regression check
|
||||
|
178
OSDK.toml
178
OSDK.toml
@ -1,7 +1,19 @@
|
||||
[project]
|
||||
type = "kernel"
|
||||
vars = [
|
||||
["SMP", "1"],
|
||||
["MEM", "2G"],
|
||||
["EXT2_IMG", "$OSDK_CWD/regression/build/ext2.img"],
|
||||
["EXFAT_IMG", "$OSDK_CWD/regression/build/exfat.img"],
|
||||
]
|
||||
|
||||
[boot]
|
||||
method = "grub-rescue-iso"
|
||||
|
||||
[run]
|
||||
vars = [
|
||||
["OVMF_PATH", "/usr/share/OVMF"],
|
||||
]
|
||||
|
||||
[run.boot]
|
||||
kcmd_args = [
|
||||
"SHELL=/bin/sh",
|
||||
"LOGNAME=root",
|
||||
@ -12,117 +24,67 @@ kcmd_args = [
|
||||
]
|
||||
init_args = ["sh", "-l"]
|
||||
initramfs = "regression/build/initramfs.cpio.gz"
|
||||
boot_protocol = "multiboot2"
|
||||
bootloader = "grub"
|
||||
ovmf = "/root/ovmf/release"
|
||||
drive_files = [
|
||||
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
|
||||
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtconsole,chardev=mux",
|
||||
]
|
||||
|
||||
[test]
|
||||
boot_protocol = "multiboot"
|
||||
bootloader = "qemu"
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtconsole,chardev=mux",
|
||||
]
|
||||
boot.method = "qemu-direct"
|
||||
|
||||
[grub]
|
||||
protocol = "multiboot2"
|
||||
|
||||
['cfg(arch="x86_64", schema="iommu")'.run]
|
||||
drive_files = [
|
||||
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
|
||||
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtconsole,chardev=mux",
|
||||
"-device intel-iommu,intremap=on,device-iotlb=on",
|
||||
"-device ioh3420,id=pcie.0,chassis=1",
|
||||
]
|
||||
[qemu]
|
||||
args = "$(./tools/qemu_args.sh)"
|
||||
|
||||
['cfg(arch="x86_64", schema="microvm")'.run]
|
||||
bootloader = "qemu"
|
||||
drive_files = [
|
||||
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
|
||||
["regression/build/exfat.img", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine microvm,rtc=on",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-nodefaults",
|
||||
"-no-user-config",
|
||||
"-device virtio-blk-device,drive=x0,serial=vext2",
|
||||
"-device virtio-blk-device,drive=x1,serial=vexfat",
|
||||
"-device virtio-keyboard-device",
|
||||
"-device virtio-net-device,netdev=net01",
|
||||
"-device virtio-serial-device",
|
||||
"-device virtconsole,chardev=mux",
|
||||
[scheme."microvm"]
|
||||
boot.method = "qemu-direct"
|
||||
vars = [
|
||||
["MICROVM", "true"],
|
||||
]
|
||||
qemu.args = "$(./tools/qemu_args.sh)"
|
||||
|
||||
['cfg(arch="riscv64")'.run]
|
||||
qemu_args = [
|
||||
"-machine virt",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
[scheme."iommu"]
|
||||
supported_archs = ["x86_64"]
|
||||
vars = [
|
||||
["IOMMU_DEV_EXTRA", ",iommu_platform=on,ats=on"],
|
||||
["IOMMU_EXTRA_ARGS", """\
|
||||
-device intel-iommu,intremap=on,device-iotlb=on \
|
||||
-device ioh3420,id=pcie.0,chassis=1\
|
||||
"""],
|
||||
]
|
||||
qemu.args = "$(./tools/qemu_args.sh)"
|
||||
|
||||
[scheme."tdx"]
|
||||
supported_archs = ["x86_64"]
|
||||
build.features = ["intel_tdx"]
|
||||
vars = [
|
||||
["MEM", "8G"],
|
||||
["OVMF_PATH", "~/tdx-tools/ovmf"],
|
||||
]
|
||||
boot.method = "grub-qcow2"
|
||||
grub.mkrescue_path = "~/tdx-tools/grub"
|
||||
grub.protocol = "linux"
|
||||
qemu.args = """\
|
||||
-accel kvm \
|
||||
-name process=tdxvm,debug-threads=on \
|
||||
-m $MEM \
|
||||
-smp $SMP \
|
||||
-vga none \
|
||||
-nographic \
|
||||
-monitor pty \
|
||||
-no-hpet \
|
||||
-nodefaults \
|
||||
-monitor telnet:127.0.0.1:9003,server,nowait \
|
||||
-bios $OVMF_PATH/OVMF_VARS.fd \
|
||||
-object tdx-guest,sept-ve-disable,id=tdx,quote-generation-service=vsock:2:4050 \
|
||||
-cpu host,-kvm-steal-time,pmu=off,tsc-freq=1000000000 \
|
||||
-machine q35,kernel_irqchip=split,confidential-guest-support=tdx \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on,disable-modern=off \
|
||||
-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off \
|
||||
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off \
|
||||
-drive file=fs.img,if=none,format=raw,id=x0 \
|
||||
-netdev user,id=mynet0,hostfwd=tcp::10027-:22,hostfwd=tcp::54136-:8090 \
|
||||
-chardev stdio,id=mux,mux=on,logfile=$OSDK_CWD/$(date '+%Y-%m-%dT%H%M%S').log \
|
||||
-device virtio-serial,romfile= \
|
||||
-device virtconsole,chardev=mux \
|
||||
-monitor chardev:mux \
|
||||
-serial chardev:mux \
|
||||
"""
|
@ -58,7 +58,7 @@ qemu_args = [ # <10>
|
||||
"-m 2G",
|
||||
]
|
||||
|
||||
['cfg(arch="x86_64", schema=microvm)'.run] # <12>
|
||||
['cfg(arch="x86_64", scheme=microvm)'.run] # <12>
|
||||
bootloader = "qemu"
|
||||
qemu_args = [ # <10>
|
||||
"-machine microvm,rtc=on",
|
||||
@ -169,12 +169,12 @@ which is used to create a GRUB CD_ROM.
|
||||
|
||||
Cfg is an advanced feature to create multiple profiles for
|
||||
the same actions under different scenarios. Currently we
|
||||
have two configurable keys, which are `arch` and `schema`.
|
||||
have two configurable keys, which are `arch` and `scheme`.
|
||||
The key `arch` has a fixed set of values which is aligned
|
||||
with the CLI `--arch` argument. If an action has no specified
|
||||
arch, it matches all the architectures. The key `schema` allows
|
||||
user-defined values and can be selected by the `--schema` CLI
|
||||
argument. The key `schema` can be used to create special settings
|
||||
arch, it matches all the architectures. The key `scheme` allows
|
||||
user-defined values and can be selected by the `--scheme` CLI
|
||||
argument. The key `scheme` can be used to create special settings
|
||||
(especially special QEMU configurations). If a cfg action is
|
||||
matched, unspecified and required arguments will be inherited
|
||||
from the action that has no cfg (i.e. the default action setting).
|
||||
|
7
osdk/Cargo.lock
generated
7
osdk/Cargo.lock
generated
@ -135,6 +135,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"shlex",
|
||||
"syn",
|
||||
"toml",
|
||||
]
|
||||
@ -470,6 +471,12 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.0"
|
||||
|
@ -19,6 +19,7 @@ quote = "1.0.35"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
sha2 = "0.10.8"
|
||||
shlex = "1.3.0"
|
||||
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
|
||||
toml = { version = "0.8.8", features = ["preserve_order"] }
|
||||
|
||||
|
@ -12,8 +12,11 @@ use std::fmt::{self, Display, Formatter};
|
||||
/// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Arch {
|
||||
#[serde(rename = "aarch64")]
|
||||
Aarch64,
|
||||
#[serde(rename = "riscv64")]
|
||||
X86_64,
|
||||
#[serde(rename = "x86_64")]
|
||||
RiscV64,
|
||||
}
|
||||
|
||||
@ -33,11 +36,19 @@ impl ValueEnum for Arch {
|
||||
|
||||
impl Arch {
|
||||
/// Get the target triple for the architecture.
|
||||
pub fn triple(&self) -> String {
|
||||
pub fn triple(&self) -> &'static str {
|
||||
match self {
|
||||
Arch::Aarch64 => "aarch64-unknown-none".to_owned(),
|
||||
Arch::RiscV64 => "riscv64gc-unknown-none-elf".to_owned(),
|
||||
Arch::X86_64 => "x86_64-unknown-none".to_owned(),
|
||||
Arch::Aarch64 => "aarch64-unknown-none",
|
||||
Arch::RiscV64 => "riscv64gc-unknown-none-elf",
|
||||
Arch::X86_64 => "x86_64-unknown-none",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_qemu(&self) -> &'static str {
|
||||
match self {
|
||||
Arch::Aarch64 => "qemu-system-aarch64",
|
||||
Arch::RiscV64 => "qemu-system-riscv64",
|
||||
Arch::X86_64 => "qemu-system-x86_64",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ pub mod vm_image;
|
||||
use bin::AsterBin;
|
||||
use file::{BundleFile, Initramfs};
|
||||
use std::process;
|
||||
use vm_image::AsterVmImage;
|
||||
use vm_image::{AsterVmImage, AsterVmImageType};
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@ -16,11 +16,9 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
arch::Arch,
|
||||
cli::CargoArgs,
|
||||
config_manager::{
|
||||
action::{ActionSettings, Bootloader},
|
||||
RunConfig,
|
||||
config::{
|
||||
scheme::{ActionChoice, BootMethod},
|
||||
Config,
|
||||
},
|
||||
error::Errno,
|
||||
error_msg,
|
||||
@ -42,16 +40,20 @@ pub struct BundleManifest {
|
||||
pub initramfs: Option<Initramfs>,
|
||||
pub aster_bin: Option<AsterBin>,
|
||||
pub vm_image: Option<AsterVmImage>,
|
||||
pub settings: ActionSettings,
|
||||
pub cargo_args: CargoArgs,
|
||||
pub config: Config,
|
||||
pub action: ActionChoice,
|
||||
pub last_modified: SystemTime,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
/// This function creates a new `Bundle` without adding any files.
|
||||
pub fn new(path: impl AsRef<Path>, settings: ActionSettings, cargo_args: CargoArgs) -> Self {
|
||||
pub fn new(path: impl AsRef<Path>, config: &Config, action: ActionChoice) -> Self {
|
||||
std::fs::create_dir_all(path.as_ref()).unwrap();
|
||||
let initramfs = if let Some(ref initramfs) = settings.initramfs {
|
||||
let config_initramfs = match action {
|
||||
ActionChoice::Run => config.run.boot.initramfs.as_ref(),
|
||||
ActionChoice::Test => config.test.boot.initramfs.as_ref(),
|
||||
};
|
||||
let initramfs = if let Some(ref initramfs) = config_initramfs {
|
||||
if !initramfs.exists() {
|
||||
error_msg!("initramfs file not found: {}", initramfs.display());
|
||||
process::exit(Errno::BuildCrate as _);
|
||||
@ -65,8 +67,8 @@ impl Bundle {
|
||||
initramfs,
|
||||
aster_bin: None,
|
||||
vm_image: None,
|
||||
settings,
|
||||
cargo_args,
|
||||
config: config.clone(),
|
||||
action,
|
||||
last_modified: SystemTime::now(),
|
||||
},
|
||||
path: path.as_ref().to_path_buf(),
|
||||
@ -109,105 +111,139 @@ impl Bundle {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
|
||||
// Compare the manifest with the run configuration.
|
||||
// TODO: This pairwise comparison will result in some false negatives. We may
|
||||
// fix it by pondering upon each fields with more care.
|
||||
if self.manifest.settings != config.settings
|
||||
|| self.manifest.cargo_args != config.cargo_args
|
||||
pub fn can_run_with_config(&self, config: &Config, action: ActionChoice) -> Result<(), String> {
|
||||
// If built for testing, better not to run it. Vice versa.
|
||||
if self.manifest.action != action {
|
||||
return Err(format!(
|
||||
"The bundle is built for {:?}",
|
||||
self.manifest.action
|
||||
));
|
||||
}
|
||||
|
||||
let self_action = match self.manifest.action {
|
||||
ActionChoice::Run => &self.manifest.config.run,
|
||||
ActionChoice::Test => &self.manifest.config.test,
|
||||
};
|
||||
let config_action = match action {
|
||||
ActionChoice::Run => &config.run,
|
||||
ActionChoice::Test => &config.test,
|
||||
};
|
||||
|
||||
// Compare the manifest with the run configuration except the initramfs and the boot method.
|
||||
if self_action.grub != config_action.grub
|
||||
|| self_action.qemu != config_action.qemu
|
||||
|| self_action.build != config_action.build
|
||||
|| self_action.boot.kcmdline != config_action.boot.kcmdline
|
||||
{
|
||||
return false;
|
||||
return Err("The bundle is not compatible with the run configuration".to_owned());
|
||||
}
|
||||
|
||||
// Checkout if the files on disk supports the boot method
|
||||
match config_action.boot.method {
|
||||
BootMethod::QemuDirect => {
|
||||
if self.manifest.aster_bin.is_none() {
|
||||
return Err("Kernel binary is required for direct QEMU booting".to_owned());
|
||||
};
|
||||
}
|
||||
BootMethod::GrubRescueIso => {
|
||||
let Some(ref vm_image) = self.manifest.vm_image else {
|
||||
return Err("VM image is required for QEMU booting".to_owned());
|
||||
};
|
||||
if !matches!(vm_image.typ(), AsterVmImageType::GrubIso(_)) {
|
||||
return Err("VM image in the bundle is not a Grub ISO image".to_owned());
|
||||
}
|
||||
}
|
||||
BootMethod::GrubQcow2 => {
|
||||
let Some(ref vm_image) = self.manifest.vm_image else {
|
||||
return Err("VM image is required for QEMU booting".to_owned());
|
||||
};
|
||||
if !matches!(vm_image.typ(), AsterVmImageType::Qcow2(_)) {
|
||||
return Err("VM image in the bundle is not a Qcow2 image".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the initramfs.
|
||||
match (&self.manifest.initramfs, &config.settings.initramfs) {
|
||||
let initramfs_err =
|
||||
"The initramfs in the bundle is different from the one in the run configuration"
|
||||
.to_owned();
|
||||
match (&self.manifest.initramfs, &config_action.boot.initramfs) {
|
||||
(Some(initramfs), Some(initramfs_path)) => {
|
||||
let config_initramfs = Initramfs::new(initramfs_path);
|
||||
if initramfs.sha256sum() != config_initramfs.sha256sum() {
|
||||
return false;
|
||||
return Err(initramfs_err);
|
||||
}
|
||||
}
|
||||
(None, None) => {}
|
||||
_ => {
|
||||
return false;
|
||||
return Err(initramfs_err);
|
||||
}
|
||||
};
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn last_modified_time(&self) -> SystemTime {
|
||||
self.manifest.last_modified
|
||||
}
|
||||
|
||||
pub fn run(&self, config: &RunConfig) {
|
||||
if !self.can_run_with_config(config) {
|
||||
error_msg!("The bundle is not compatible with the run configuration");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
}
|
||||
let mut qemu_cmd = Command::new(config.settings.qemu_exe.clone().unwrap_or_else(|| {
|
||||
PathBuf::from(match config.arch {
|
||||
Arch::Aarch64 => "qemu-system-aarch64",
|
||||
Arch::RiscV64 => "qemu-system-riscv64",
|
||||
Arch::X86_64 => "qemu-system-x86_64",
|
||||
})
|
||||
}));
|
||||
// FIXME: Arguments like "-m 2G" sould be separated into "-m" and "2G". This
|
||||
// is a dirty hack to make it work. Anything like space in the paths will
|
||||
// break this.
|
||||
for arg in &config.settings.qemu_args {
|
||||
for part in arg.split_whitespace() {
|
||||
qemu_cmd.arg(part);
|
||||
pub fn run(&self, config: &Config, action: ActionChoice) {
|
||||
match self.can_run_with_config(config, action) {
|
||||
Ok(()) => {}
|
||||
Err(msg) => {
|
||||
error_msg!("{}", msg);
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
}
|
||||
}
|
||||
match config.settings.bootloader {
|
||||
Some(Bootloader::Qemu) => {
|
||||
let Some(ref aster_bin) = self.manifest.aster_bin else {
|
||||
error_msg!("Kernel ELF binary is required for direct QEMU booting");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
let action = match action {
|
||||
ActionChoice::Run => &config.run,
|
||||
ActionChoice::Test => &config.test,
|
||||
};
|
||||
let mut qemu_cmd = Command::new(&action.qemu.path);
|
||||
match shlex::split(&action.qemu.args) {
|
||||
Some(v) => {
|
||||
for arg in v {
|
||||
qemu_cmd.arg(arg);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error_msg!("Failed to parse qemu args: {:#?}", &action.qemu.args);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
}
|
||||
match action.boot.method {
|
||||
BootMethod::QemuDirect => {
|
||||
let aster_bin = self.manifest.aster_bin.as_ref().unwrap();
|
||||
qemu_cmd
|
||||
.arg("-kernel")
|
||||
.arg(self.path.join(aster_bin.path()));
|
||||
if let Some(ref initramfs) = config.settings.initramfs {
|
||||
if let Some(ref initramfs) = action.boot.initramfs {
|
||||
qemu_cmd.arg("-initrd").arg(initramfs);
|
||||
} else {
|
||||
info!("No initramfs specified");
|
||||
};
|
||||
qemu_cmd
|
||||
.arg("-append")
|
||||
.arg(config.settings.combined_kcmd_args().join(" "));
|
||||
qemu_cmd.arg("-append").arg(action.boot.kcmdline.join(" "));
|
||||
}
|
||||
Some(Bootloader::Grub) => {
|
||||
let Some(ref vm_image) = self.manifest.vm_image else {
|
||||
error_msg!("VM image is required for QEMU booting");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
BootMethod::GrubRescueIso => {
|
||||
let vm_image = self.manifest.vm_image.as_ref().unwrap();
|
||||
assert!(matches!(vm_image.typ(), AsterVmImageType::GrubIso(_)));
|
||||
qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path()));
|
||||
if let Some(ovmf) = &config.settings.ovmf {
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"if=pflash,format=raw,unit=0,readonly=on,file={}",
|
||||
ovmf.join("OVMF_CODE.fd").display()
|
||||
));
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"if=pflash,format=raw,unit=1,file={}",
|
||||
ovmf.join("OVMF_VARS.fd").display()
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error_msg!("Bootloader is required for QEMU booting");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
BootMethod::GrubQcow2 => {
|
||||
let vm_image = self.manifest.vm_image.as_ref().unwrap();
|
||||
assert!(matches!(vm_image.typ(), AsterVmImageType::Qcow2(_)));
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"file={},index=0,media=disk,format=qcow2",
|
||||
self.path
|
||||
.join(vm_image.path())
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
for drive_file in &config.settings.drive_files {
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"file={},{}",
|
||||
drive_file.path.display(),
|
||||
drive_file.append,
|
||||
));
|
||||
}
|
||||
info!("Running QEMU: {:#?}", qemu_cmd);
|
||||
|
||||
let exit_status = qemu_cmd.status().unwrap();
|
||||
if !exit_status.success() {
|
||||
|
@ -18,7 +18,7 @@ pub struct AsterVmImage {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AsterVmImageType {
|
||||
GrubIso(AsterGrubIsoImageMeta),
|
||||
// TODO: add more vm image types such as qcow2, etc.
|
||||
Qcow2(AsterQcow2ImageMeta),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -26,6 +26,11 @@ pub struct AsterGrubIsoImageMeta {
|
||||
pub grub_version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterQcow2ImageMeta {
|
||||
pub grub_version: String,
|
||||
}
|
||||
|
||||
impl BundleFile for AsterVmImage {
|
||||
fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
@ -50,6 +55,10 @@ impl AsterVmImage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typ(&self) -> &AsterVmImageType {
|
||||
&self.typ
|
||||
}
|
||||
|
||||
/// Move the binary to the `base` directory and convert the path to a relative path.
|
||||
pub fn move_to(self, base: impl AsRef<Path>) -> Self {
|
||||
let file_name = self.path.file_name().unwrap();
|
||||
|
169
osdk/src/cli.rs
169
osdk/src/cli.rs
@ -10,37 +10,40 @@ use crate::{
|
||||
execute_build_command, execute_debug_command, execute_forwarded_command,
|
||||
execute_new_command, execute_run_command, execute_test_command,
|
||||
},
|
||||
config_manager::{
|
||||
action::{BootProtocol, Bootloader},
|
||||
manifest::ProjectType,
|
||||
BuildConfig, DebugConfig, RunConfig, TestConfig,
|
||||
config::{
|
||||
manifest::{ProjectType, TomlManifest},
|
||||
scheme::{BootMethod, BootProtocol},
|
||||
Config,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
let osdk_subcommand = match Cli::parse() {
|
||||
let (osdk_subcommand, common_args) = match Cli::parse() {
|
||||
Cli {
|
||||
cargo_subcommand: CargoSubcommand::Osdk(osdk_subcommand),
|
||||
} => osdk_subcommand,
|
||||
common_args,
|
||||
} => (osdk_subcommand, common_args),
|
||||
};
|
||||
|
||||
let load_config = || {
|
||||
let manifest = TomlManifest::load(&common_args.build_args.features);
|
||||
let scheme = manifest.get_scheme(common_args.scheme.as_ref());
|
||||
Config::new(scheme, &common_args)
|
||||
};
|
||||
|
||||
match &osdk_subcommand {
|
||||
OsdkSubcommand::New(args) => execute_new_command(args),
|
||||
OsdkSubcommand::Build(build_args) => {
|
||||
let build_config = BuildConfig::parse(build_args);
|
||||
execute_build_command(&build_config);
|
||||
execute_build_command(&load_config(), build_args);
|
||||
}
|
||||
OsdkSubcommand::Run(run_args) => {
|
||||
let run_config = RunConfig::parse(run_args);
|
||||
execute_run_command(&run_config);
|
||||
execute_run_command(&load_config(), &run_args.gdb_server_args);
|
||||
}
|
||||
OsdkSubcommand::Debug(debug_args) => {
|
||||
let debug_config = DebugConfig::parse(debug_args);
|
||||
execute_debug_command(&debug_config);
|
||||
execute_debug_command(&load_config().run.build.profile, debug_args);
|
||||
}
|
||||
OsdkSubcommand::Test(test_args) => {
|
||||
let test_config = TestConfig::parse(test_args);
|
||||
execute_test_command(&test_config);
|
||||
execute_test_command(&load_config(), test_args);
|
||||
}
|
||||
OsdkSubcommand::Check(args) => execute_forwarded_command("check", &args.args),
|
||||
OsdkSubcommand::Clippy(args) => execute_forwarded_command("clippy", &args.args),
|
||||
@ -54,6 +57,8 @@ pub fn main() {
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
cargo_subcommand: CargoSubcommand,
|
||||
#[command(flatten)]
|
||||
common_args: CommonArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@ -136,18 +141,23 @@ impl NewArgs {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct BuildArgs {
|
||||
#[command(flatten)]
|
||||
pub cargo_args: CargoArgs,
|
||||
#[command(flatten)]
|
||||
pub osdk_args: OsdkArgs,
|
||||
#[arg(
|
||||
long = "for-test",
|
||||
help = "Build for running unit tests",
|
||||
default_value_t
|
||||
)]
|
||||
pub for_test: bool,
|
||||
#[arg(
|
||||
long = "output",
|
||||
short = 'o',
|
||||
help = "Output directory for all generated artifacts",
|
||||
value_name = "DIR"
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct RunArgs {
|
||||
#[command(flatten)]
|
||||
pub cargo_args: CargoArgs,
|
||||
#[command(flatten)]
|
||||
pub osdk_args: OsdkArgs,
|
||||
#[command(flatten)]
|
||||
pub gdb_server_args: GdbServerArgs,
|
||||
}
|
||||
@ -181,10 +191,6 @@ pub struct GdbServerArgs {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DebugArgs {
|
||||
#[command(flatten)]
|
||||
pub cargo_args: CargoArgs,
|
||||
#[command(flatten)]
|
||||
pub osdk_args: OsdkArgs,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Specify the address of the remote target",
|
||||
@ -195,15 +201,11 @@ pub struct DebugArgs {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct TestArgs {
|
||||
#[command(flatten)]
|
||||
pub cargo_args: CargoArgs,
|
||||
#[arg(
|
||||
name = "TESTNAME",
|
||||
help = "Only run tests containing this string in their names"
|
||||
)]
|
||||
pub test_name: Option<String>,
|
||||
#[command(flatten)]
|
||||
pub osdk_args: OsdkArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@ -211,79 +213,120 @@ pub struct CargoArgs {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The Cargo build profile (built-in candidates are 'dev', 'release', 'test' and 'bench')",
|
||||
default_value = "dev",
|
||||
conflicts_with = "release"
|
||||
conflicts_with = "release",
|
||||
global = true
|
||||
)]
|
||||
pub profile: String,
|
||||
pub profile: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Build artifacts in release mode",
|
||||
conflicts_with = "profile"
|
||||
conflicts_with = "profile",
|
||||
global = true
|
||||
)]
|
||||
pub release: bool,
|
||||
#[arg(long, value_name = "FEATURES", help = "List of features to activate", value_delimiter = ',', num_args = 1..)]
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "FEATURES",
|
||||
help = "List of features to activate",
|
||||
value_delimiter = ',',
|
||||
num_args = 1..,
|
||||
global = true,
|
||||
)]
|
||||
pub features: Vec<String>,
|
||||
}
|
||||
|
||||
impl CargoArgs {
|
||||
pub fn profile(&self) -> Option<String> {
|
||||
if self.release {
|
||||
Some("release".to_owned())
|
||||
} else {
|
||||
self.profile.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct OsdkArgs {
|
||||
#[arg(long, value_name = "ARCH", help = "The architecture to build for")]
|
||||
pub arch: Option<Arch>,
|
||||
pub struct CommonArgs {
|
||||
#[command(flatten)]
|
||||
pub build_args: CargoArgs,
|
||||
#[arg(
|
||||
long = "schema",
|
||||
help = "Select the specific configuration schema provided in the OSDK manifest",
|
||||
value_name = "SCHEMA"
|
||||
long = "linux-x86-legacy-boot",
|
||||
help = "Enable legacy 32-bit boot support for the Linux x86 boot protocol",
|
||||
global = true
|
||||
)]
|
||||
pub schema: Option<String>,
|
||||
pub linux_x86_legacy_boot: bool,
|
||||
#[arg(
|
||||
long = "kcmd_args+",
|
||||
long = "target-arch",
|
||||
value_name = "ARCH",
|
||||
help = "The architecture to build for",
|
||||
global = true
|
||||
)]
|
||||
pub target_arch: Option<Arch>,
|
||||
#[arg(
|
||||
long = "scheme",
|
||||
help = "Select the specific configuration scheme provided in the OSDK manifest",
|
||||
value_name = "SCHEME",
|
||||
global = true
|
||||
)]
|
||||
pub scheme: Option<String>,
|
||||
#[arg(
|
||||
long = "kcmd-args",
|
||||
require_equals = true,
|
||||
help = "Extra or overriding command line arguments for guest kernel",
|
||||
value_name = "ARGS"
|
||||
value_name = "ARGS",
|
||||
global = true
|
||||
)]
|
||||
pub kcmd_args: Vec<String>,
|
||||
#[arg(
|
||||
long = "init_args+",
|
||||
long = "init-args",
|
||||
require_equals = true,
|
||||
help = "Extra command line arguments for init process",
|
||||
value_name = "ARGS"
|
||||
value_name = "ARGS",
|
||||
global = true
|
||||
)]
|
||||
pub init_args: Vec<String>,
|
||||
#[arg(long, help = "Path of initramfs", value_name = "PATH")]
|
||||
#[arg(long, help = "Path of initramfs", value_name = "PATH", global = true)]
|
||||
pub initramfs: Option<PathBuf>,
|
||||
#[arg(long = "ovmf", help = "Path of OVMF", value_name = "PATH")]
|
||||
pub ovmf: Option<PathBuf>,
|
||||
#[arg(long = "opensbi", help = "Path of OpenSBI", value_name = "PATH")]
|
||||
pub opensbi: Option<PathBuf>,
|
||||
#[arg(
|
||||
long = "bootloader",
|
||||
long = "boot-method",
|
||||
help = "Loader for booting the kernel",
|
||||
value_name = "BOOTLOADER"
|
||||
value_name = "BOOTMETHOD",
|
||||
global = true
|
||||
)]
|
||||
pub bootloader: Option<Bootloader>,
|
||||
pub boot_method: Option<BootMethod>,
|
||||
#[arg(
|
||||
long = "display-grub-menu",
|
||||
help = "Display the GRUB menu if booting with GRUB",
|
||||
global = true
|
||||
)]
|
||||
pub display_grub_menu: bool,
|
||||
#[arg(
|
||||
long = "grub-mkrescue",
|
||||
help = "Path of grub-mkrescue",
|
||||
value_name = "PATH"
|
||||
value_name = "PATH",
|
||||
global = true
|
||||
)]
|
||||
pub grub_mkrescue: Option<PathBuf>,
|
||||
#[arg(
|
||||
long = "boot_protocol",
|
||||
long = "grub-boot-protocol",
|
||||
help = "Protocol for booting the kernel",
|
||||
value_name = "BOOT_PROTOCOL"
|
||||
value_name = "BOOT_PROTOCOL",
|
||||
global = true
|
||||
)]
|
||||
pub boot_protocol: Option<BootProtocol>,
|
||||
pub grub_boot_protocol: Option<BootProtocol>,
|
||||
#[arg(
|
||||
long = "qemu_exe",
|
||||
long = "qemu-exe",
|
||||
help = "The QEMU executable file",
|
||||
value_name = "FILE"
|
||||
value_name = "FILE",
|
||||
global = true
|
||||
)]
|
||||
pub qemu_exe: Option<PathBuf>,
|
||||
#[arg(
|
||||
long = "qemu_args+",
|
||||
long = "qemu-args",
|
||||
require_equals = true,
|
||||
help = "Extra arguments or overriding arguments for running QEMU",
|
||||
value_name = "ARGS"
|
||||
value_name = "ARGS",
|
||||
global = true
|
||||
)]
|
||||
pub qemu_args_add: Vec<String>,
|
||||
pub qemu_args: Vec<String>,
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ use crate::{
|
||||
bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta},
|
||||
file::BundleFile,
|
||||
},
|
||||
config_manager::action::BootProtocol,
|
||||
util::get_current_crate_info,
|
||||
};
|
||||
|
||||
@ -22,13 +21,13 @@ pub fn make_install_bzimage(
|
||||
install_dir: impl AsRef<Path>,
|
||||
target_dir: impl AsRef<Path>,
|
||||
aster_elf: &AsterBin,
|
||||
protocol: &BootProtocol,
|
||||
linux_x86_legacy_boot: bool,
|
||||
) -> AsterBin {
|
||||
let target_name = get_current_crate_info().name;
|
||||
let image_type = match protocol {
|
||||
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
|
||||
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
|
||||
_ => unreachable!(),
|
||||
let image_type = if linux_x86_legacy_boot {
|
||||
BzImageType::Legacy32
|
||||
} else {
|
||||
BzImageType::Efi64
|
||||
};
|
||||
let setup_bin = {
|
||||
let setup_install_dir = target_dir.as_ref();
|
||||
@ -60,9 +59,9 @@ pub fn make_install_bzimage(
|
||||
AsterBin::new(
|
||||
&install_path,
|
||||
AsterBinType::BzImage(AsterBzImageMeta {
|
||||
support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32),
|
||||
support_legacy32_boot: linux_x86_legacy_boot,
|
||||
support_efi_boot: false,
|
||||
support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64),
|
||||
support_efi_handover: !linux_x86_legacy_boot,
|
||||
}),
|
||||
aster_elf.version().clone(),
|
||||
aster_elf.stripped(),
|
||||
|
@ -12,7 +12,10 @@ use crate::{
|
||||
file::BundleFile,
|
||||
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
|
||||
},
|
||||
config_manager::{action::BootProtocol, BuildConfig},
|
||||
config::{
|
||||
scheme::{ActionChoice, BootProtocol},
|
||||
Config,
|
||||
},
|
||||
util::get_current_crate_info,
|
||||
};
|
||||
|
||||
@ -20,11 +23,16 @@ pub fn create_bootdev_image(
|
||||
target_dir: impl AsRef<Path>,
|
||||
aster_bin: &AsterBin,
|
||||
initramfs_path: Option<impl AsRef<Path>>,
|
||||
config: &BuildConfig,
|
||||
config: &Config,
|
||||
action: ActionChoice,
|
||||
) -> AsterVmImage {
|
||||
let target_name = get_current_crate_info().name;
|
||||
let iso_root = &target_dir.as_ref().join("iso_root");
|
||||
let protocol = &config.settings.boot_protocol;
|
||||
let action = match &action {
|
||||
ActionChoice::Run => &config.run,
|
||||
ActionChoice::Test => &config.test,
|
||||
};
|
||||
let protocol = &action.grub.boot_protocol;
|
||||
|
||||
// Clear or make the iso dir.
|
||||
if iso_root.exists() {
|
||||
@ -43,12 +51,12 @@ pub fn create_bootdev_image(
|
||||
|
||||
// Make the kernel image and place it in the boot directory.
|
||||
match protocol {
|
||||
Some(BootProtocol::LinuxLegacy32) | Some(BootProtocol::LinuxEfiHandover64) => {
|
||||
BootProtocol::Linux => {
|
||||
make_install_bzimage(
|
||||
iso_root.join("boot"),
|
||||
&target_dir,
|
||||
aster_bin,
|
||||
&protocol.clone().unwrap(),
|
||||
action.build.linux_x86_legacy_boot,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
@ -65,22 +73,17 @@ pub fn create_bootdev_image(
|
||||
None
|
||||
};
|
||||
let grub_cfg = generate_grub_cfg(
|
||||
&config.settings.combined_kcmd_args().join(" "),
|
||||
true,
|
||||
&action.boot.kcmdline.join(" "),
|
||||
!action.grub.display_grub_menu,
|
||||
initramfs_in_image,
|
||||
&protocol.clone().unwrap_or(BootProtocol::Multiboot2),
|
||||
protocol,
|
||||
);
|
||||
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
|
||||
fs::write(grub_cfg_path, grub_cfg).unwrap();
|
||||
|
||||
// Make the boot device CDROM image using `grub-mkrescue`.
|
||||
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
|
||||
let grub_mkrescue_bin = &config
|
||||
.settings
|
||||
.grub_mkrescue
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from("grub-mkrescue"));
|
||||
let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
|
||||
let mut grub_mkrescue_cmd = std::process::Command::new(action.grub.grub_mkrescue.as_os_str());
|
||||
grub_mkrescue_cmd
|
||||
.arg(iso_root.as_os_str())
|
||||
.arg("-o")
|
||||
@ -92,7 +95,7 @@ pub fn create_bootdev_image(
|
||||
AsterVmImage::new(
|
||||
iso_path,
|
||||
AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
|
||||
grub_version: get_grub_mkrescue_version(grub_mkrescue_bin),
|
||||
grub_version: get_grub_mkrescue_version(&action.grub.grub_mkrescue),
|
||||
}),
|
||||
aster_bin.version().clone(),
|
||||
)
|
||||
@ -115,7 +118,7 @@ fn generate_grub_cfg(
|
||||
"#GRUB_TIMEOUT_STYLE#",
|
||||
if skip_grub_menu { "hidden" } else { "menu" },
|
||||
)
|
||||
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" });
|
||||
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "5" });
|
||||
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
|
||||
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
|
||||
// Replace the grub commands according to the protocol selected.
|
||||
@ -147,7 +150,7 @@ fn generate_grub_cfg(
|
||||
"".to_owned()
|
||||
},
|
||||
),
|
||||
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => grub_cfg
|
||||
BootProtocol::Linux => grub_cfg
|
||||
.replace("#GRUB_CMD_KERNEL#", "linux")
|
||||
.replace("#KERNEL#", &aster_bin_path_on_device)
|
||||
.replace(
|
||||
|
@ -3,7 +3,12 @@
|
||||
mod bin;
|
||||
mod grub;
|
||||
|
||||
use std::{ffi::OsString, path::Path, process};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use bin::strip_elf_for_qemu;
|
||||
|
||||
@ -15,39 +20,53 @@ use crate::{
|
||||
bin::{AsterBin, AsterBinType, AsterElfMeta},
|
||||
Bundle,
|
||||
},
|
||||
cli::CargoArgs,
|
||||
config_manager::{action::Bootloader, BuildConfig},
|
||||
cli::BuildArgs,
|
||||
config::{
|
||||
scheme::{ActionChoice, BootMethod},
|
||||
Config,
|
||||
},
|
||||
error::Errno,
|
||||
error_msg,
|
||||
util::{get_current_crate_info, get_target_directory},
|
||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
||||
};
|
||||
|
||||
pub fn execute_build_command(config: &BuildConfig) {
|
||||
let ws_target_directory = get_target_directory();
|
||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||
if !osdk_target_directory.exists() {
|
||||
std::fs::create_dir_all(&osdk_target_directory).unwrap();
|
||||
pub fn execute_build_command(config: &Config, build_args: &BuildArgs) {
|
||||
let cargo_target_directory = get_target_directory();
|
||||
let osdk_output_directory = build_args
|
||||
.output
|
||||
.clone()
|
||||
.unwrap_or(cargo_target_directory.join(DEFAULT_TARGET_RELPATH));
|
||||
if !osdk_output_directory.exists() {
|
||||
std::fs::create_dir_all(&osdk_output_directory).unwrap();
|
||||
}
|
||||
let target_info = get_current_crate_info();
|
||||
let bundle_path = osdk_target_directory.join(target_info.name);
|
||||
let bundle_path = osdk_output_directory.join(target_info.name);
|
||||
|
||||
let _bundle = create_base_and_build(
|
||||
let action = if build_args.for_test {
|
||||
ActionChoice::Test
|
||||
} else {
|
||||
ActionChoice::Run
|
||||
};
|
||||
|
||||
let _bundle = create_base_and_cached_build(
|
||||
bundle_path,
|
||||
&osdk_target_directory,
|
||||
&ws_target_directory,
|
||||
&osdk_output_directory,
|
||||
&cargo_target_directory,
|
||||
config,
|
||||
action,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn create_base_and_build(
|
||||
pub fn create_base_and_cached_build(
|
||||
bundle_path: impl AsRef<Path>,
|
||||
osdk_target_directory: impl AsRef<Path>,
|
||||
osdk_output_directory: impl AsRef<Path>,
|
||||
cargo_target_directory: impl AsRef<Path>,
|
||||
config: &BuildConfig,
|
||||
config: &Config,
|
||||
action: ActionChoice,
|
||||
rustflags: &[&str],
|
||||
) -> Bundle {
|
||||
let base_crate_path = osdk_target_directory.as_ref().join("base");
|
||||
let base_crate_path = osdk_output_directory.as_ref().join("base");
|
||||
new_base_crate(
|
||||
&base_crate_path,
|
||||
&get_current_crate_info().name,
|
||||
@ -55,55 +74,108 @@ pub fn create_base_and_build(
|
||||
);
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(&base_crate_path).unwrap();
|
||||
let bundle = do_build(
|
||||
let bundle = do_cached_build(
|
||||
&bundle_path,
|
||||
&osdk_target_directory,
|
||||
&osdk_output_directory,
|
||||
&cargo_target_directory,
|
||||
config,
|
||||
action,
|
||||
rustflags,
|
||||
);
|
||||
std::env::set_current_dir(original_dir).unwrap();
|
||||
bundle
|
||||
}
|
||||
|
||||
/// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
|
||||
pub fn do_cached_build(
|
||||
bundle_path: impl AsRef<Path>,
|
||||
osdk_output_directory: impl AsRef<Path>,
|
||||
cargo_target_directory: impl AsRef<Path>,
|
||||
config: &Config,
|
||||
action: ActionChoice,
|
||||
rustflags: &[&str],
|
||||
) -> Bundle {
|
||||
let build_a_new_one = || {
|
||||
do_build(
|
||||
&bundle_path,
|
||||
&osdk_output_directory,
|
||||
&cargo_target_directory,
|
||||
config,
|
||||
action,
|
||||
rustflags,
|
||||
)
|
||||
};
|
||||
|
||||
let existing_bundle = Bundle::load(&bundle_path);
|
||||
let Some(existing_bundle) = existing_bundle else {
|
||||
return build_a_new_one();
|
||||
};
|
||||
if existing_bundle.can_run_with_config(config, action).is_err() {
|
||||
return build_a_new_one();
|
||||
}
|
||||
let Ok(built_since) = SystemTime::now().duration_since(existing_bundle.last_modified_time())
|
||||
else {
|
||||
return build_a_new_one();
|
||||
};
|
||||
if built_since > Duration::from_secs(600) {
|
||||
return build_a_new_one();
|
||||
}
|
||||
let workspace_root = {
|
||||
let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
|
||||
PathBuf::from(meta.get("workspace_root").unwrap().as_str().unwrap())
|
||||
};
|
||||
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time() {
|
||||
return existing_bundle;
|
||||
}
|
||||
build_a_new_one()
|
||||
}
|
||||
|
||||
pub fn do_build(
|
||||
bundle_path: impl AsRef<Path>,
|
||||
osdk_target_directory: impl AsRef<Path>,
|
||||
osdk_output_directory: impl AsRef<Path>,
|
||||
cargo_target_directory: impl AsRef<Path>,
|
||||
config: &BuildConfig,
|
||||
config: &Config,
|
||||
action: ActionChoice,
|
||||
rustflags: &[&str],
|
||||
) -> Bundle {
|
||||
if bundle_path.as_ref().exists() {
|
||||
std::fs::remove_dir_all(&bundle_path).unwrap();
|
||||
}
|
||||
let mut bundle = Bundle::new(
|
||||
&bundle_path,
|
||||
config.settings.clone(),
|
||||
config.cargo_args.clone(),
|
||||
);
|
||||
let mut bundle = Bundle::new(&bundle_path, config, action);
|
||||
|
||||
info!("Building kernel ELF");
|
||||
let aster_elf = build_kernel_elf(
|
||||
&config.arch,
|
||||
&config.cargo_args,
|
||||
&config.target_arch,
|
||||
&config.build.profile,
|
||||
&config.build.features[..],
|
||||
&cargo_target_directory,
|
||||
rustflags,
|
||||
);
|
||||
|
||||
if matches!(config.settings.bootloader, Some(Bootloader::Qemu)) {
|
||||
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
|
||||
bundle.consume_aster_bin(stripped_elf);
|
||||
}
|
||||
let boot = match action {
|
||||
ActionChoice::Run => &config.run.boot,
|
||||
ActionChoice::Test => &config.test.boot,
|
||||
};
|
||||
|
||||
if matches!(config.settings.bootloader, Some(Bootloader::Grub)) {
|
||||
info!("Building boot device image");
|
||||
let bootdev_image = grub::create_bootdev_image(
|
||||
&osdk_target_directory,
|
||||
&aster_elf,
|
||||
config.settings.initramfs.as_ref(),
|
||||
config,
|
||||
);
|
||||
bundle.consume_vm_image(bootdev_image);
|
||||
match boot.method {
|
||||
BootMethod::GrubRescueIso => {
|
||||
info!("Building boot device image");
|
||||
let bootdev_image = grub::create_bootdev_image(
|
||||
&osdk_output_directory,
|
||||
&aster_elf,
|
||||
boot.initramfs.as_ref(),
|
||||
config,
|
||||
action,
|
||||
);
|
||||
bundle.consume_vm_image(bootdev_image);
|
||||
}
|
||||
BootMethod::QemuDirect => {
|
||||
let stripped_elf = strip_elf_for_qemu(&osdk_output_directory, &aster_elf);
|
||||
bundle.consume_aster_bin(stripped_elf);
|
||||
}
|
||||
BootMethod::GrubQcow2 => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
bundle
|
||||
@ -111,7 +183,8 @@ pub fn do_build(
|
||||
|
||||
fn build_kernel_elf(
|
||||
arch: &Arch,
|
||||
cargo_args: &CargoArgs,
|
||||
profile: &str,
|
||||
features: &[String],
|
||||
cargo_target_directory: impl AsRef<Path>,
|
||||
rustflags: &[&str],
|
||||
) -> AsterBin {
|
||||
@ -135,12 +208,13 @@ fn build_kernel_elf(
|
||||
command.env_remove("RUSTUP_TOOLCHAIN");
|
||||
command.env("RUSTFLAGS", rustflags.join(" "));
|
||||
command.arg("build");
|
||||
command.arg("--features").arg(features.join(" "));
|
||||
command.arg("--target").arg(&target_os_string);
|
||||
command
|
||||
.arg("--target-dir")
|
||||
.arg(cargo_target_directory.as_ref());
|
||||
command.args(COMMON_CARGO_ARGS);
|
||||
command.arg("--profile=".to_string() + &cargo_args.profile);
|
||||
command.arg("--profile=".to_string() + profile);
|
||||
let status = command.status().unwrap();
|
||||
if !status.success() {
|
||||
error_msg!("Cargo build failed");
|
||||
@ -148,10 +222,10 @@ fn build_kernel_elf(
|
||||
}
|
||||
|
||||
let aster_bin_path = cargo_target_directory.as_ref().join(&target_os_string);
|
||||
let aster_bin_path = if cargo_args.profile == "dev" {
|
||||
let aster_bin_path = if profile == "dev" {
|
||||
aster_bin_path.join("debug")
|
||||
} else {
|
||||
aster_bin_path.join(&cargo_args.profile)
|
||||
aster_bin_path.join(profile)
|
||||
}
|
||||
.join(get_current_crate_info().name);
|
||||
|
||||
@ -167,3 +241,21 @@ fn build_kernel_elf(
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
||||
let mut last_modified = SystemTime::UNIX_EPOCH;
|
||||
for entry in std::fs::read_dir(path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.file_name() == "target" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let metadata = entry.metadata().unwrap();
|
||||
if metadata.is_dir() {
|
||||
last_modified = std::cmp::max(last_modified, get_last_modified_time(&entry.path()));
|
||||
} else {
|
||||
last_modified = std::cmp::max(last_modified, metadata.modified().unwrap());
|
||||
}
|
||||
}
|
||||
last_modified
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::commands::util::{bin_file_name, profile_adapter};
|
||||
use crate::config_manager::DebugConfig;
|
||||
use crate::commands::util::bin_file_name;
|
||||
|
||||
use crate::util::get_target_directory;
|
||||
use crate::{cli::DebugArgs, util::get_target_directory};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn execute_debug_command(config: &DebugConfig) {
|
||||
let DebugConfig { cargo_args, remote } = config;
|
||||
pub fn execute_debug_command(profile: &String, args: &DebugArgs) {
|
||||
let remote = &args.remote;
|
||||
|
||||
let profile = profile_adapter(&cargo_args.profile);
|
||||
let file_path = get_target_directory()
|
||||
.join("x86_64-unknown-none")
|
||||
.join(profile)
|
||||
|
@ -19,12 +19,11 @@ use crate::arch::get_default_arch;
|
||||
/// Execute the forwarded cargo command with args containing the subcommand and its arguments.
|
||||
pub fn execute_forwarded_command(subcommand: &str, args: &Vec<String>) -> ! {
|
||||
let mut cargo = util::cargo();
|
||||
cargo
|
||||
.arg(subcommand)
|
||||
.args(util::COMMON_CARGO_ARGS)
|
||||
.arg("--target")
|
||||
.arg(get_default_arch().triple())
|
||||
.args(args);
|
||||
cargo.arg(subcommand).args(util::COMMON_CARGO_ARGS);
|
||||
if !args.contains(&"--target".to_owned()) {
|
||||
cargo.arg("--target").arg(get_default_arch().triple());
|
||||
}
|
||||
cargo.args(args);
|
||||
let status = cargo.status().expect("Failed to execute cargo");
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
|
@ -1,18 +1,25 @@
|
||||
[project]
|
||||
type = "kernel"
|
||||
project_type = "kernel"
|
||||
|
||||
[run]
|
||||
bootloader = "grub"
|
||||
ovmf = "/usr/share/OVMF"
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
vars = [
|
||||
["OVMF_PATH", "/usr/share/OVMF"],
|
||||
]
|
||||
|
||||
[boot]
|
||||
method = "grub-rescue-iso"
|
||||
|
||||
[qemu]
|
||||
args = """\
|
||||
-machine q35,kernel-irqchip=split \
|
||||
-cpu Icelake-Server,+x2apic \
|
||||
--no-reboot \
|
||||
-m 2G \
|
||||
-smp 1 \
|
||||
-nographic \
|
||||
-serial chardev:mux \
|
||||
-monitor chardev:mux \
|
||||
-chardev stdio,id=mux,mux=on,signal=off \
|
||||
-display none \
|
||||
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
|
||||
-drive if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_PATH/OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,unit=1,file=$OVMF_PATH/OVMF_VARS.fd \
|
||||
"""
|
@ -1,17 +1,19 @@
|
||||
[project]
|
||||
type = "library"
|
||||
project_type = "lib"
|
||||
|
||||
[test]
|
||||
bootloader = "qemu"
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
]
|
||||
[boot]
|
||||
method = "qemu-direct"
|
||||
|
||||
[qemu]
|
||||
args = """\
|
||||
-machine q35,kernel-irqchip=split \
|
||||
-cpu Icelake-Server,+x2apic \
|
||||
--no-reboot \
|
||||
-m 2G \
|
||||
-smp 1 \
|
||||
-nographic \
|
||||
-serial chardev:mux \
|
||||
-monitor chardev:mux \
|
||||
-chardev stdio,id=mux,mux=on,signal=off \
|
||||
-display none \
|
||||
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
|
||||
"""
|
@ -4,7 +4,7 @@ use std::{fs, path::PathBuf, process, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
cli::NewArgs,
|
||||
config_manager::manifest::ProjectType,
|
||||
config::manifest::ProjectType,
|
||||
error::Errno,
|
||||
error_msg,
|
||||
util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata},
|
||||
|
@ -1,19 +1,14 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use super::{build::create_base_and_build, util::DEFAULT_TARGET_RELPATH};
|
||||
use super::{build::create_base_and_cached_build, util::DEFAULT_TARGET_RELPATH};
|
||||
use crate::{
|
||||
bundle::Bundle,
|
||||
config_manager::{BuildConfig, RunConfig},
|
||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
||||
cli::GdbServerArgs,
|
||||
config::{scheme::ActionChoice, Config},
|
||||
util::{get_current_crate_info, get_target_directory},
|
||||
};
|
||||
|
||||
pub fn execute_run_command(config: &RunConfig) {
|
||||
if config.gdb_server_args.is_gdb_enabled {
|
||||
pub fn execute_run_command(config: &Config, gdb_server_args: &GdbServerArgs) {
|
||||
if gdb_server_args.is_gdb_enabled {
|
||||
use std::env;
|
||||
env::set_var(
|
||||
"RUSTFLAGS",
|
||||
@ -21,111 +16,48 @@ pub fn execute_run_command(config: &RunConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
let ws_target_directory = get_target_directory();
|
||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||
let cargo_target_directory = get_target_directory();
|
||||
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||
let target_name = get_current_crate_info().name;
|
||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
||||
let existing_bundle = Bundle::load(&default_bundle_directory);
|
||||
|
||||
let config = RunConfig {
|
||||
settings: {
|
||||
if config.gdb_server_args.is_gdb_enabled {
|
||||
let qemu_gdb_args: Vec<_> = {
|
||||
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
|
||||
match gdb::stub_type_of(gdb_stub_addr) {
|
||||
gdb::StubAddrType::Unix => {
|
||||
let chardev = format!(
|
||||
"-chardev socket,path={},server=on,wait=off,id=gdb0",
|
||||
gdb_stub_addr
|
||||
);
|
||||
let stub = "-gdb chardev:gdb0".to_owned();
|
||||
vec![chardev, stub, "-S".into()]
|
||||
}
|
||||
gdb::StubAddrType::Tcp => {
|
||||
vec![
|
||||
format!(
|
||||
"-gdb tcp:{}",
|
||||
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
|
||||
),
|
||||
"-S".into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let qemu_gdb_args: Vec<_> = qemu_gdb_args
|
||||
.into_iter()
|
||||
.filter(|arg| !config.settings.qemu_args.iter().any(|x| x == arg))
|
||||
.map(|x| x.to_string())
|
||||
.collect();
|
||||
let mut settings = config.settings.clone();
|
||||
settings.qemu_args.extend(qemu_gdb_args);
|
||||
settings
|
||||
} else {
|
||||
config.settings.clone()
|
||||
}
|
||||
},
|
||||
..config.clone()
|
||||
};
|
||||
let _vsc_launch_file = config.gdb_server_args.vsc_launch_file.then(|| {
|
||||
vsc::check_gdb_config(&config.gdb_server_args);
|
||||
let profile = super::util::profile_adapter(&config.cargo_args.profile);
|
||||
vsc::VscLaunchConfig::new(profile, &config.gdb_server_args.gdb_server_addr)
|
||||
});
|
||||
|
||||
// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
|
||||
if let Some(existing_bundle) = existing_bundle {
|
||||
if existing_bundle.can_run_with_config(&config) {
|
||||
if let Ok(built_since) =
|
||||
SystemTime::now().duration_since(existing_bundle.last_modified_time())
|
||||
{
|
||||
if built_since < Duration::from_secs(600) {
|
||||
let workspace_root = {
|
||||
let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
|
||||
PathBuf::from(meta.get("workspace_root").unwrap().as_str().unwrap())
|
||||
};
|
||||
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time()
|
||||
{
|
||||
existing_bundle.run(&config);
|
||||
return;
|
||||
}
|
||||
let mut config = config.clone();
|
||||
if gdb_server_args.is_gdb_enabled {
|
||||
let qemu_gdb_args = {
|
||||
let gdb_stub_addr = gdb_server_args.gdb_server_addr.as_str();
|
||||
match gdb::stub_type_of(gdb_stub_addr) {
|
||||
gdb::StubAddrType::Unix => {
|
||||
format!(
|
||||
" -chardev socket,path={},server=on,wait=off,id=gdb0 -gdb chardev:gdb0 -S",
|
||||
gdb_stub_addr
|
||||
)
|
||||
}
|
||||
gdb::StubAddrType::Tcp => {
|
||||
format!(
|
||||
" -gdb tcp:{} -S",
|
||||
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
config.run.qemu.args += &qemu_gdb_args;
|
||||
}
|
||||
let _vsc_launch_file = gdb_server_args.vsc_launch_file.then(|| {
|
||||
vsc::check_gdb_config(gdb_server_args);
|
||||
let profile = super::util::profile_adapter(&config.build.profile);
|
||||
vsc::VscLaunchConfig::new(profile, &gdb_server_args.gdb_server_addr)
|
||||
});
|
||||
|
||||
let required_build_config = BuildConfig {
|
||||
arch: config.arch,
|
||||
settings: config.settings.clone(),
|
||||
cargo_args: config.cargo_args.clone(),
|
||||
};
|
||||
|
||||
let bundle = create_base_and_build(
|
||||
let default_bundle_directory = osdk_output_directory.join(target_name);
|
||||
let bundle = create_base_and_cached_build(
|
||||
default_bundle_directory,
|
||||
&osdk_target_directory,
|
||||
&ws_target_directory,
|
||||
&required_build_config,
|
||||
&osdk_output_directory,
|
||||
&cargo_target_directory,
|
||||
&config,
|
||||
ActionChoice::Run,
|
||||
&[],
|
||||
);
|
||||
bundle.run(&config);
|
||||
}
|
||||
|
||||
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
||||
let mut last_modified = SystemTime::UNIX_EPOCH;
|
||||
for entry in std::fs::read_dir(path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.file_name() == "target" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let metadata = entry.metadata().unwrap();
|
||||
if metadata.is_dir() {
|
||||
last_modified = std::cmp::max(last_modified, get_last_modified_time(&entry.path()));
|
||||
} else {
|
||||
last_modified = std::cmp::max(last_modified, metadata.modified().unwrap());
|
||||
}
|
||||
}
|
||||
last_modified
|
||||
bundle.run(&config, ActionChoice::Run);
|
||||
}
|
||||
|
||||
mod gdb {
|
||||
|
@ -2,38 +2,38 @@
|
||||
|
||||
use std::fs;
|
||||
|
||||
use super::{build::do_build, util::DEFAULT_TARGET_RELPATH};
|
||||
use super::{build::do_cached_build, util::DEFAULT_TARGET_RELPATH};
|
||||
use crate::{
|
||||
base_crate::new_base_crate,
|
||||
cli::GdbServerArgs,
|
||||
config_manager::{BuildConfig, RunConfig, TestConfig},
|
||||
cli::TestArgs,
|
||||
config::{scheme::ActionChoice, Config},
|
||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
||||
};
|
||||
|
||||
pub fn execute_test_command(config: &TestConfig) {
|
||||
pub fn execute_test_command(config: &Config, args: &TestArgs) {
|
||||
let crates = get_workspace_default_members();
|
||||
for crate_path in crates {
|
||||
std::env::set_current_dir(crate_path).unwrap();
|
||||
test_current_crate(config);
|
||||
test_current_crate(config, args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_current_crate(config: &TestConfig) {
|
||||
pub fn test_current_crate(config: &Config, args: &TestArgs) {
|
||||
let current_crate = get_current_crate_info();
|
||||
let ws_target_directory = get_target_directory();
|
||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||
let target_crate_dir = osdk_target_directory.join("base");
|
||||
let cargo_target_directory = get_target_directory();
|
||||
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||
let target_crate_dir = osdk_output_directory.join("base");
|
||||
new_base_crate(&target_crate_dir, ¤t_crate.name, ¤t_crate.path);
|
||||
|
||||
let main_rs_path = target_crate_dir.join("src").join("main.rs");
|
||||
|
||||
let ktest_test_whitelist = match &config.test_name {
|
||||
let ktest_test_whitelist = match &args.test_name {
|
||||
Some(name) => format!(r#"Some(&["{}"])"#, name),
|
||||
None => r#"None"#.to_string(),
|
||||
};
|
||||
|
||||
let mut ktest_crate_whitelist = vec![current_crate.name];
|
||||
if let Some(name) = &config.test_name {
|
||||
if let Some(name) = &args.test_name {
|
||||
ktest_crate_whitelist.push(name.clone());
|
||||
}
|
||||
|
||||
@ -54,32 +54,21 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
|
||||
|
||||
// Build the kernel with the given base crate
|
||||
let target_name = get_current_crate_info().name;
|
||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
||||
let required_build_config = BuildConfig {
|
||||
arch: config.arch,
|
||||
settings: config.settings.clone(),
|
||||
cargo_args: config.cargo_args.clone(),
|
||||
};
|
||||
let default_bundle_directory = osdk_output_directory.join(target_name);
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(&target_crate_dir).unwrap();
|
||||
let bundle = do_build(
|
||||
let bundle = do_cached_build(
|
||||
default_bundle_directory,
|
||||
&osdk_target_directory,
|
||||
&ws_target_directory,
|
||||
&required_build_config,
|
||||
&osdk_output_directory,
|
||||
&cargo_target_directory,
|
||||
config,
|
||||
ActionChoice::Test,
|
||||
&["--cfg ktest"],
|
||||
);
|
||||
std::env::remove_var("RUSTFLAGS");
|
||||
std::env::set_current_dir(original_dir).unwrap();
|
||||
|
||||
let required_run_config = RunConfig {
|
||||
arch: config.arch,
|
||||
settings: required_build_config.settings.clone(),
|
||||
cargo_args: required_build_config.cargo_args.clone(),
|
||||
gdb_server_args: GdbServerArgs::default(),
|
||||
};
|
||||
|
||||
bundle.run(&required_run_config);
|
||||
bundle.run(config, ActionChoice::Test);
|
||||
}
|
||||
|
||||
fn get_workspace_default_members() -> Vec<String> {
|
||||
|
48
osdk/src/config/eval.rs
Normal file
48
osdk/src/config/eval.rs
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! The module implementing the evaluation feature.
|
||||
|
||||
use std::{io, process};
|
||||
|
||||
pub type Vars = Vec<(String, String)>;
|
||||
|
||||
/// This function is used to evaluate the string using the host's shell recursively
|
||||
/// in order.
|
||||
pub fn eval(vars: &Vars, s: &String) -> io::Result<String> {
|
||||
let mut vars = vars.clone();
|
||||
for i in 0..vars.len() {
|
||||
vars[i].1 = eval_with_finalized_vars(&vars[..i], &vars[i].1)?;
|
||||
}
|
||||
eval_with_finalized_vars(&vars[..], s)
|
||||
}
|
||||
|
||||
fn eval_with_finalized_vars(vars: &[(String, String)], s: &String) -> io::Result<String> {
|
||||
let env_keys: Vec<String> = std::env::vars().map(|(key, _)| key).collect();
|
||||
|
||||
let mut eval = process::Command::new("bash");
|
||||
let mut cwd = std::env::current_dir()?;
|
||||
for (key, value) in vars {
|
||||
// If the key is in the environment, we should ignore it.
|
||||
// This allows users to override with the environment variables in CLI.
|
||||
if env_keys.contains(key) {
|
||||
continue;
|
||||
}
|
||||
eval.env(key, value);
|
||||
if key == "OSDK_CWD" {
|
||||
cwd = std::path::PathBuf::from(value);
|
||||
}
|
||||
}
|
||||
eval.arg("-c");
|
||||
eval.arg(format!("echo \"{}\"", s));
|
||||
eval.current_dir(cwd);
|
||||
let output = eval.output()?;
|
||||
if !output.stderr.is_empty() {
|
||||
println!(
|
||||
"[Info] {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim_end_matches('\n')
|
||||
);
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout)
|
||||
.trim_end_matches('\n')
|
||||
.to_string())
|
||||
}
|
355
osdk/src/config/manifest.rs
Normal file
355
osdk/src/config/manifest.rs
Normal file
@ -0,0 +1,355 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt, fs,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::scheme::Scheme;
|
||||
|
||||
use crate::{error::Errno, error_msg, util::get_cargo_metadata};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OsdkMeta {
|
||||
#[serde(rename(serialize = "type", deserialize = "type"))]
|
||||
pub type_: ProjectType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ProjectType {
|
||||
Kernel,
|
||||
#[value(alias("lib"))]
|
||||
Library,
|
||||
Module,
|
||||
}
|
||||
|
||||
/// The osdk manifest from configuration file `OSDK.toml`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TomlManifest {
|
||||
pub project_type: Option<ProjectType>,
|
||||
pub default_scheme: Scheme,
|
||||
pub map: HashMap<String, Scheme>,
|
||||
}
|
||||
|
||||
impl TomlManifest {
|
||||
pub fn load(feature_strings: &Vec<String>) -> Self {
|
||||
let workspace_root = {
|
||||
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(feature_strings)).unwrap();
|
||||
PathBuf::from(
|
||||
cargo_metadata
|
||||
.get("workspace_root")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
// All the custom schemes should inherit settings from the default scheme, this is a helper.
|
||||
fn finalize(current_manifest: Option<TomlManifest>) -> TomlManifest {
|
||||
let Some(mut current_manifest) = current_manifest else {
|
||||
error_msg!(
|
||||
"Cannot find `OSDK.toml` in the current directory or the workspace root"
|
||||
);
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
};
|
||||
for scheme in current_manifest.map.values_mut() {
|
||||
scheme.inherit(¤t_manifest.default_scheme);
|
||||
}
|
||||
current_manifest
|
||||
}
|
||||
|
||||
// Search for OSDK.toml in the current directory first.
|
||||
let current_manifest_path = PathBuf::from("OSDK.toml").canonicalize().ok();
|
||||
let mut current_manifest = match ¤t_manifest_path {
|
||||
Some(path) => deserialize_toml_manifest(path),
|
||||
None => None,
|
||||
};
|
||||
// Then search in the workspace root.
|
||||
let workspace_manifest_path = workspace_root.join("OSDK.toml").canonicalize().ok();
|
||||
// The case that the current directory is also the workspace root.
|
||||
if let Some(current) = ¤t_manifest_path {
|
||||
if let Some(workspace) = &workspace_manifest_path {
|
||||
if current == workspace {
|
||||
return finalize(current_manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
let workspace_manifest = match workspace_manifest_path {
|
||||
Some(path) => deserialize_toml_manifest(path),
|
||||
None => None,
|
||||
};
|
||||
// The current manifest should inherit settings from the workspace manifest.
|
||||
if let Some(workspace_manifest) = workspace_manifest {
|
||||
if current_manifest.is_none() {
|
||||
current_manifest = Some(workspace_manifest);
|
||||
} else {
|
||||
// Inherit one scheme at a time.
|
||||
let current_manifest = current_manifest.as_mut().unwrap();
|
||||
current_manifest
|
||||
.default_scheme
|
||||
.inherit(&workspace_manifest.default_scheme);
|
||||
for (scheme_string, scheme) in workspace_manifest.map {
|
||||
let current_scheme = current_manifest
|
||||
.map
|
||||
.entry(scheme_string)
|
||||
.or_insert_with(Scheme::empty);
|
||||
current_scheme.inherit(&scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
finalize(current_manifest)
|
||||
}
|
||||
|
||||
/// Get the scheme given the scheme from the command line arguments.
|
||||
pub fn get_scheme(&self, scheme: Option<impl ToString>) -> &Scheme {
|
||||
if let Some(scheme) = scheme {
|
||||
let selected_scheme = self.map.get(&scheme.to_string());
|
||||
if selected_scheme.is_none() {
|
||||
error_msg!("Scheme `{}` not found in `OSDK.toml`", scheme.to_string());
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
selected_scheme.unwrap()
|
||||
} else {
|
||||
&self.default_scheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_toml_manifest(path: impl AsRef<Path>) -> Option<TomlManifest> {
|
||||
if !path.as_ref().exists() || !path.as_ref().is_file() {
|
||||
return None;
|
||||
}
|
||||
// Read the file content
|
||||
let contents = fs::read_to_string(&path).unwrap_or_else(|err| {
|
||||
error_msg!(
|
||||
"Cannot read file {}, {}",
|
||||
path.as_ref().to_string_lossy(),
|
||||
err,
|
||||
);
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
});
|
||||
// Parse the TOML content
|
||||
let mut manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
|
||||
let span = err.span().unwrap();
|
||||
let wider_span =
|
||||
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
|
||||
error_msg!(
|
||||
"Cannot parse TOML file, {}. {}:{:?}:\n {}",
|
||||
err.message(),
|
||||
path.as_ref().to_string_lossy(),
|
||||
span,
|
||||
&contents[wider_span],
|
||||
);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
});
|
||||
|
||||
// Preprocess the parsed manifest
|
||||
let cwd = path.as_ref().parent().unwrap();
|
||||
// Canonicalize all the path fields
|
||||
let canonicalize = |target: &mut PathBuf| {
|
||||
let last_cwd = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(cwd).unwrap();
|
||||
*target = target.canonicalize().unwrap_or_else(|err| {
|
||||
error_msg!(
|
||||
"Cannot canonicalize path `{}`: {}",
|
||||
target.to_string_lossy(),
|
||||
err,
|
||||
);
|
||||
std::env::set_current_dir(&last_cwd).unwrap();
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
});
|
||||
std::env::set_current_dir(last_cwd).unwrap();
|
||||
};
|
||||
let canonicalize_scheme = |scheme: &mut Scheme| {
|
||||
macro_rules! canonicalize_paths_in_scheme {
|
||||
($scheme:expr) => {
|
||||
if let Some(ref mut boot) = $scheme.boot {
|
||||
if let Some(ref mut initramfs) = boot.initramfs {
|
||||
canonicalize(initramfs);
|
||||
}
|
||||
}
|
||||
if let Some(ref mut qemu) = $scheme.qemu {
|
||||
if let Some(ref mut qemu_path) = qemu.path {
|
||||
canonicalize(qemu_path);
|
||||
}
|
||||
}
|
||||
if let Some(ref mut grub) = $scheme.grub {
|
||||
if let Some(ref mut grub_mkrescue_path) = grub.grub_mkrescue {
|
||||
canonicalize(grub_mkrescue_path);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
canonicalize_paths_in_scheme!(scheme);
|
||||
if let Some(ref mut run) = scheme.run {
|
||||
canonicalize_paths_in_scheme!(run);
|
||||
}
|
||||
if let Some(ref mut test) = scheme.test {
|
||||
canonicalize_paths_in_scheme!(test);
|
||||
}
|
||||
};
|
||||
canonicalize_scheme(&mut manifest.default_scheme);
|
||||
for (_, scheme) in manifest.map.iter_mut() {
|
||||
canonicalize_scheme(scheme);
|
||||
}
|
||||
// Set the magic variable `OSDK_CWD` before any variable evaluation
|
||||
let var = ("OSDK_CWD".to_owned(), cwd.to_string_lossy().to_string());
|
||||
manifest.default_scheme.vars = {
|
||||
let mut vars = vec![var.clone()];
|
||||
vars.extend(manifest.default_scheme.vars.clone());
|
||||
vars
|
||||
};
|
||||
for (_, scheme) in manifest.map.iter_mut() {
|
||||
scheme.vars = {
|
||||
let mut vars = vec![var.clone()];
|
||||
vars.extend(scheme.vars.clone());
|
||||
vars
|
||||
};
|
||||
}
|
||||
Some(manifest)
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TomlManifest {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
enum Field {
|
||||
ProjectType,
|
||||
SupportedArchs,
|
||||
Vars,
|
||||
Boot,
|
||||
Grub,
|
||||
Qemu,
|
||||
Build,
|
||||
Run,
|
||||
Test,
|
||||
Scheme,
|
||||
}
|
||||
|
||||
const EXPECTED: &[&str] = &[
|
||||
"project_type",
|
||||
"supported_archs",
|
||||
"vars",
|
||||
"boot",
|
||||
"grub",
|
||||
"qemu",
|
||||
"build",
|
||||
"run",
|
||||
"test",
|
||||
"scheme",
|
||||
];
|
||||
|
||||
impl<'de> Deserialize<'de> for Field {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct FieldVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for FieldVisitor {
|
||||
type Value = Field;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str(&EXPECTED.join(", "))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match v {
|
||||
"project_type" => Ok(Field::ProjectType),
|
||||
"supported_archs" => Ok(Field::SupportedArchs),
|
||||
"vars" => Ok(Field::Vars),
|
||||
"boot" => Ok(Field::Boot),
|
||||
"grub" => Ok(Field::Grub),
|
||||
"qemu" => Ok(Field::Qemu),
|
||||
"build" => Ok(Field::Build),
|
||||
"run" => Ok(Field::Run),
|
||||
"test" => Ok(Field::Test),
|
||||
"scheme" => Ok(Field::Scheme),
|
||||
_ => Err(de::Error::unknown_field(v, EXPECTED)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_identifier(FieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct TomlManifestVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for TomlManifestVisitor {
|
||||
type Value = TomlManifest;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Scheme")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::MapAccess<'de>,
|
||||
{
|
||||
let mut project_type = None;
|
||||
let mut default_scheme = Scheme::empty();
|
||||
let mut scheme_map = HashMap::<String, Scheme>::new();
|
||||
|
||||
macro_rules! match_and_add_option {
|
||||
($field:ident) => {{
|
||||
let value = map.next_value()?;
|
||||
if default_scheme.$field.is_some() {
|
||||
error_msg!("Duplicated field `{}`", stringify!($field));
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
default_scheme.$field = Some(value);
|
||||
}};
|
||||
}
|
||||
macro_rules! match_and_add_vec {
|
||||
($field:ident) => {{
|
||||
let value = map.next_value()?;
|
||||
if !default_scheme.$field.is_empty() {
|
||||
error_msg!("Duplicated field `{}`", stringify!($field));
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
default_scheme.$field = value;
|
||||
}};
|
||||
}
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::ProjectType => {
|
||||
let value: ProjectType = map.next_value()?;
|
||||
project_type = Some(value);
|
||||
}
|
||||
Field::SupportedArchs => match_and_add_vec!(supported_archs),
|
||||
Field::Vars => match_and_add_vec!(vars),
|
||||
Field::Boot => match_and_add_option!(boot),
|
||||
Field::Grub => match_and_add_option!(grub),
|
||||
Field::Qemu => match_and_add_option!(qemu),
|
||||
Field::Build => match_and_add_option!(build),
|
||||
Field::Run => match_and_add_option!(run),
|
||||
Field::Test => match_and_add_option!(test),
|
||||
Field::Scheme => {
|
||||
let scheme: HashMap<String, Scheme> = map.next_value()?;
|
||||
scheme_map = scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TomlManifest {
|
||||
project_type,
|
||||
default_scheme,
|
||||
map: scheme_map,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct("TomlManifest", EXPECTED, TomlManifestVisitor)
|
||||
}
|
||||
}
|
130
osdk/src/config/mod.rs
Normal file
130
osdk/src/config/mod.rs
Normal file
@ -0,0 +1,130 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module is responsible for parsing configuration files and combining them with command-line parameters
|
||||
//! to obtain the final configuration, it will also try searching system to fill valid values for specific
|
||||
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`,
|
||||
//! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
|
||||
|
||||
mod eval;
|
||||
|
||||
pub mod manifest;
|
||||
pub mod scheme;
|
||||
pub mod unix_args;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use scheme::{
|
||||
Action, ActionScheme, BootScheme, Build, BuildScheme, GrubScheme, QemuScheme, Scheme,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
arch::{get_default_arch, Arch},
|
||||
cli::CommonArgs,
|
||||
config::unix_args::apply_kv_array,
|
||||
};
|
||||
|
||||
/// The global configuration for the OSDK actions.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub target_arch: Arch,
|
||||
pub build: Build,
|
||||
pub run: Action,
|
||||
pub test: Action,
|
||||
}
|
||||
|
||||
fn apply_args_before_finalize(action_scheme: &mut ActionScheme, args: &CommonArgs) {
|
||||
if action_scheme.build.is_none() {
|
||||
action_scheme.build = Some(BuildScheme::default());
|
||||
}
|
||||
if let Some(ref mut build) = action_scheme.build {
|
||||
if let Some(profile) = &args.build_args.profile() {
|
||||
build.profile = Some(profile.clone());
|
||||
}
|
||||
build.features.extend(args.build_args.features.clone());
|
||||
if args.linux_x86_legacy_boot {
|
||||
build.linux_x86_legacy_boot = true;
|
||||
}
|
||||
}
|
||||
|
||||
if action_scheme.grub.is_none() {
|
||||
action_scheme.grub = Some(GrubScheme::default());
|
||||
}
|
||||
if let Some(ref mut grub) = action_scheme.grub {
|
||||
if let Some(grub_mkrescue) = &args.grub_mkrescue {
|
||||
grub.grub_mkrescue = Some(grub_mkrescue.clone());
|
||||
}
|
||||
if let Some(grub_boot_protocol) = args.grub_boot_protocol {
|
||||
grub.boot_protocol = Some(grub_boot_protocol);
|
||||
}
|
||||
}
|
||||
|
||||
if action_scheme.boot.is_none() {
|
||||
action_scheme.boot = Some(BootScheme::default());
|
||||
}
|
||||
if let Some(ref mut boot) = action_scheme.boot {
|
||||
apply_kv_array(&mut boot.kcmd_args, &args.kcmd_args, "=", &[]);
|
||||
for init_arg in &args.init_args {
|
||||
for seperated_arg in init_arg.split(' ') {
|
||||
boot.init_args.push(seperated_arg.to_string());
|
||||
}
|
||||
}
|
||||
if let Some(initramfs) = &args.initramfs {
|
||||
boot.initramfs = Some(initramfs.clone());
|
||||
}
|
||||
if let Some(boot_method) = args.boot_method {
|
||||
boot.method = Some(boot_method);
|
||||
}
|
||||
}
|
||||
|
||||
if action_scheme.qemu.is_none() {
|
||||
action_scheme.qemu = Some(QemuScheme::default());
|
||||
}
|
||||
if let Some(ref mut qemu) = action_scheme.qemu {
|
||||
if let Some(path) = &args.qemu_exe {
|
||||
qemu.path = Some(path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_args_after_finalize(action: &mut Action, args: &CommonArgs) {
|
||||
action.qemu.apply_qemu_args(&args.qemu_args);
|
||||
if args.display_grub_menu {
|
||||
action.grub.display_grub_menu = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(scheme: &Scheme, common_args: &CommonArgs) -> Self {
|
||||
let target_arch = common_args.target_arch.unwrap_or(get_default_arch());
|
||||
let default_scheme = ActionScheme {
|
||||
vars: scheme.vars.clone(),
|
||||
boot: scheme.boot.clone(),
|
||||
grub: scheme.grub.clone(),
|
||||
qemu: scheme.qemu.clone(),
|
||||
build: scheme.build.clone(),
|
||||
};
|
||||
let run = {
|
||||
let mut run = scheme.run.clone().unwrap_or_default();
|
||||
run.inherit(&default_scheme);
|
||||
apply_args_before_finalize(&mut run, common_args);
|
||||
let mut run = run.finalize(target_arch);
|
||||
apply_args_after_finalize(&mut run, common_args);
|
||||
run
|
||||
};
|
||||
let test = {
|
||||
let mut test = scheme.test.clone().unwrap_or_default();
|
||||
test.inherit(&default_scheme);
|
||||
apply_args_before_finalize(&mut test, common_args);
|
||||
let mut test = test.finalize(target_arch);
|
||||
apply_args_after_finalize(&mut test, common_args);
|
||||
test
|
||||
};
|
||||
Self {
|
||||
target_arch,
|
||||
build: scheme.build.clone().unwrap_or_default().finalize(),
|
||||
run,
|
||||
test,
|
||||
}
|
||||
}
|
||||
}
|
104
osdk/src/config/scheme/action.rs
Normal file
104
osdk/src/config/scheme/action.rs
Normal file
@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{inherit_optional, Boot, BootScheme, Grub, GrubScheme, Qemu, QemuScheme};
|
||||
|
||||
use crate::config::{scheme::Vars, Arch};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ActionChoice {
|
||||
Run,
|
||||
Test,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BuildScheme {
|
||||
pub profile: Option<String>,
|
||||
pub features: Vec<String>,
|
||||
/// Whether to turn on the support for the
|
||||
/// [Linux legacy x86 32-bit boot protocol](https://www.kernel.org/doc/html/v5.6/x86/boot.html)
|
||||
#[serde(default)]
|
||||
pub linux_x86_legacy_boot: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Build {
|
||||
pub profile: String,
|
||||
pub features: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub linux_x86_legacy_boot: bool,
|
||||
}
|
||||
|
||||
impl Default for Build {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
profile: "dev".to_string(),
|
||||
features: Vec::new(),
|
||||
linux_x86_legacy_boot: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuildScheme {
|
||||
pub fn inherit(&mut self, parent: &Self) {
|
||||
if parent.profile.is_some() {
|
||||
self.profile = parent.profile.clone();
|
||||
}
|
||||
self.features = {
|
||||
let mut features = parent.features.clone();
|
||||
features.extend(self.features.clone());
|
||||
features
|
||||
};
|
||||
if parent.linux_x86_legacy_boot {
|
||||
self.linux_x86_legacy_boot = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Build {
|
||||
Build {
|
||||
profile: self.profile.unwrap_or_else(|| "dev".to_string()),
|
||||
features: self.features,
|
||||
linux_x86_legacy_boot: self.linux_x86_legacy_boot,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ActionScheme {
|
||||
#[serde(default)]
|
||||
pub vars: Vars,
|
||||
pub boot: Option<BootScheme>,
|
||||
pub grub: Option<GrubScheme>,
|
||||
pub qemu: Option<QemuScheme>,
|
||||
pub build: Option<BuildScheme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
pub boot: Boot,
|
||||
pub grub: Grub,
|
||||
pub qemu: Qemu,
|
||||
pub build: Build,
|
||||
}
|
||||
|
||||
impl ActionScheme {
|
||||
pub fn inherit(&mut self, from: &Self) {
|
||||
self.vars = {
|
||||
let mut vars = from.vars.clone();
|
||||
vars.extend(self.vars.clone());
|
||||
vars
|
||||
};
|
||||
inherit_optional!(from, self, .boot);
|
||||
inherit_optional!(from, self, .grub);
|
||||
inherit_optional!(from, self, .qemu);
|
||||
inherit_optional!(from, self, .build);
|
||||
}
|
||||
|
||||
pub fn finalize(self, arch: Arch) -> Action {
|
||||
Action {
|
||||
boot: self.boot.unwrap_or_default().finalize(),
|
||||
grub: self.grub.unwrap_or_default().finalize(),
|
||||
qemu: self.qemu.unwrap_or_default().finalize(&self.vars, arch),
|
||||
build: self.build.unwrap_or_default().finalize(),
|
||||
}
|
||||
}
|
||||
}
|
80
osdk/src/config/scheme/boot.rs
Normal file
80
osdk/src/config/scheme/boot.rs
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use clap::ValueEnum;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BootScheme {
|
||||
/// Command line arguments for the guest kernel
|
||||
#[serde(default)]
|
||||
pub kcmd_args: Vec<String>,
|
||||
/// Command line arguments for the guest init process
|
||||
#[serde(default)]
|
||||
pub init_args: Vec<String>,
|
||||
/// The path of initramfs
|
||||
pub initramfs: Option<PathBuf>,
|
||||
/// The infrastructures used to boot the guest
|
||||
pub method: Option<BootMethod>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum BootMethod {
|
||||
/// Boot the kernel by making a rescue CD image.
|
||||
GrubRescueIso,
|
||||
/// Boot the kernel by making a Qcow2 image with Grub as the bootloader.
|
||||
GrubQcow2,
|
||||
/// Use the [QEMU direct boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html)
|
||||
/// to boot the kernel with QEMU's built-in Seabios and Coreboot utilites.
|
||||
QemuDirect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Boot {
|
||||
pub kcmdline: Vec<String>,
|
||||
pub initramfs: Option<PathBuf>,
|
||||
pub method: BootMethod,
|
||||
}
|
||||
|
||||
impl Default for Boot {
|
||||
fn default() -> Self {
|
||||
Boot {
|
||||
kcmdline: vec![],
|
||||
initramfs: None,
|
||||
method: BootMethod::QemuDirect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BootScheme {
|
||||
pub fn inherit(&mut self, from: &Self) {
|
||||
self.kcmd_args = {
|
||||
let mut kcmd_args = from.kcmd_args.clone();
|
||||
kcmd_args.extend(self.kcmd_args.clone());
|
||||
kcmd_args
|
||||
};
|
||||
self.init_args = {
|
||||
let mut init_args = from.init_args.clone();
|
||||
init_args.extend(self.init_args.clone());
|
||||
init_args
|
||||
};
|
||||
if self.initramfs.is_none() {
|
||||
self.initramfs = from.initramfs.clone();
|
||||
}
|
||||
if self.method.is_none() {
|
||||
self.method = from.method;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Boot {
|
||||
let mut kcmdline = self.kcmd_args;
|
||||
kcmdline.push("--".to_owned());
|
||||
kcmdline.extend(self.init_args);
|
||||
Boot {
|
||||
kcmdline,
|
||||
initramfs: self.initramfs,
|
||||
method: self.method.unwrap_or(BootMethod::QemuDirect),
|
||||
}
|
||||
}
|
||||
}
|
61
osdk/src/config/scheme/grub.rs
Normal file
61
osdk/src/config/scheme/grub.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use clap::ValueEnum;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GrubScheme {
|
||||
/// The path of `grub_mkrecue`. Only needed if `boot.method` is `grub`
|
||||
pub grub_mkrescue: Option<PathBuf>,
|
||||
/// The boot protocol specified in the GRUB configuration
|
||||
pub boot_protocol: Option<BootProtocol>,
|
||||
/// Whether to display the GRUB menu, defaults to `false`
|
||||
#[serde(default)]
|
||||
pub display_grub_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum BootProtocol {
|
||||
Linux,
|
||||
Multiboot,
|
||||
Multiboot2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Grub {
|
||||
pub grub_mkrescue: PathBuf,
|
||||
pub boot_protocol: BootProtocol,
|
||||
pub display_grub_menu: bool,
|
||||
}
|
||||
|
||||
impl Default for Grub {
|
||||
fn default() -> Self {
|
||||
Grub {
|
||||
grub_mkrescue: PathBuf::from("grub-mkrescue"),
|
||||
boot_protocol: BootProtocol::Multiboot2,
|
||||
display_grub_menu: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GrubScheme {
|
||||
pub fn inherit(&mut self, from: &Self) {
|
||||
if self.grub_mkrescue.is_none() {
|
||||
self.grub_mkrescue = from.grub_mkrescue.clone();
|
||||
}
|
||||
if self.boot_protocol.is_none() {
|
||||
self.boot_protocol = from.boot_protocol;
|
||||
}
|
||||
// `display_grub_menu` is not inherited
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Grub {
|
||||
Grub {
|
||||
grub_mkrescue: self.grub_mkrescue.unwrap_or(PathBuf::from("grub-mkrescue")),
|
||||
boot_protocol: self.boot_protocol.unwrap_or(BootProtocol::Multiboot2),
|
||||
display_grub_menu: self.display_grub_menu,
|
||||
}
|
||||
}
|
||||
}
|
73
osdk/src/config/scheme/mod.rs
Normal file
73
osdk/src/config/scheme/mod.rs
Normal file
@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::eval::Vars;
|
||||
|
||||
use crate::arch::Arch;
|
||||
|
||||
mod action;
|
||||
pub use action::*;
|
||||
mod boot;
|
||||
pub use boot::*;
|
||||
mod grub;
|
||||
pub use grub::*;
|
||||
mod qemu;
|
||||
pub use qemu::*;
|
||||
|
||||
/// All the configurable fields within a scheme.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Scheme {
|
||||
#[serde(default)]
|
||||
pub supported_archs: Vec<Arch>,
|
||||
#[serde(default)]
|
||||
pub vars: Vars,
|
||||
pub boot: Option<BootScheme>,
|
||||
pub grub: Option<GrubScheme>,
|
||||
pub qemu: Option<QemuScheme>,
|
||||
pub build: Option<BuildScheme>,
|
||||
pub run: Option<ActionScheme>,
|
||||
pub test: Option<ActionScheme>,
|
||||
}
|
||||
|
||||
macro_rules! inherit_optional {
|
||||
($from: ident, $to:ident, .$field:ident) => {
|
||||
if $from.$field.is_some() {
|
||||
if let Some($field) = &mut $to.$field {
|
||||
$field.inherit($from.$field.as_ref().unwrap());
|
||||
} else {
|
||||
$to.$field = $from.$field.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
use inherit_optional;
|
||||
|
||||
impl Scheme {
|
||||
pub fn empty() -> Self {
|
||||
Scheme {
|
||||
supported_archs: vec![],
|
||||
vars: vec![],
|
||||
boot: None,
|
||||
grub: None,
|
||||
qemu: None,
|
||||
build: None,
|
||||
run: None,
|
||||
test: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inherit(&mut self, from: &Self) {
|
||||
// Supported archs are not inherited
|
||||
|
||||
self.vars = {
|
||||
let mut vars = from.vars.clone();
|
||||
vars.extend(self.vars.clone());
|
||||
vars
|
||||
};
|
||||
inherit_optional!(from, self, .boot);
|
||||
inherit_optional!(from, self, .grub);
|
||||
inherit_optional!(from, self, .qemu);
|
||||
inherit_optional!(from, self, .build);
|
||||
inherit_optional!(from, self, .run);
|
||||
inherit_optional!(from, self, .test);
|
||||
}
|
||||
}
|
147
osdk/src/config/scheme/qemu.rs
Normal file
147
osdk/src/config/scheme/qemu.rs
Normal file
@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A module about QEMU settings and arguments.
|
||||
|
||||
use std::{path::PathBuf, process};
|
||||
|
||||
use crate::{
|
||||
arch::{get_default_arch, Arch},
|
||||
config::{
|
||||
eval::{eval, Vars},
|
||||
unix_args::{apply_kv_array, get_key},
|
||||
},
|
||||
error::Errno,
|
||||
error_msg,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct QemuScheme {
|
||||
/// The additional arguments for running QEMU, in the form of raw
|
||||
/// command line arguments.
|
||||
pub args: Option<String>,
|
||||
/// The path of qemu
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Qemu {
|
||||
pub args: String,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Qemu {
|
||||
fn default() -> Self {
|
||||
Qemu {
|
||||
args: String::new(),
|
||||
path: PathBuf::from(get_default_arch().system_qemu()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Qemu {
|
||||
pub fn apply_qemu_args(&mut self, args: &Vec<String>) {
|
||||
let target = match shlex::split(&self.args) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
error_msg!("Failed to parse qemu args: {:#?}", &self.args);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
};
|
||||
|
||||
// Join the key value arguments as a single element
|
||||
let mut joined = Vec::new();
|
||||
let mut consumed = false;
|
||||
for (first, second) in target.iter().zip(target.iter().skip(1)) {
|
||||
if consumed {
|
||||
consumed = false;
|
||||
continue;
|
||||
}
|
||||
if first.starts_with('-') && !first.starts_with("--") && !second.starts_with('-') {
|
||||
joined.push(format!("{} {}", first, second));
|
||||
consumed = true;
|
||||
} else {
|
||||
joined.push(first.clone());
|
||||
}
|
||||
}
|
||||
if !consumed {
|
||||
joined.push(target.last().unwrap().clone());
|
||||
}
|
||||
|
||||
// Check the soundness of qemu arguments
|
||||
for arg in joined.iter() {
|
||||
check_qemu_arg(arg);
|
||||
}
|
||||
for arg in joined.iter() {
|
||||
check_qemu_arg(arg);
|
||||
}
|
||||
|
||||
apply_kv_array(&mut joined, args, " ", MULTI_VALUE_KEYS);
|
||||
|
||||
self.args = joined.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
impl QemuScheme {
|
||||
pub fn inherit(&mut self, from: &Self) {
|
||||
if from.args.is_some() {
|
||||
self.args = from.args.clone();
|
||||
}
|
||||
if from.path.is_some() {
|
||||
self.path = from.path.clone();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(self, vars: &Vars, arch: Arch) -> Qemu {
|
||||
Qemu {
|
||||
args: self
|
||||
.args
|
||||
.map(|args| match eval(vars, &args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error_msg!("Failed to evaluate qemu args: {:#?}", e);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
path: self.path.unwrap_or(PathBuf::from(arch.system_qemu())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Below are checked keys in qemu arguments. The key list is non-exhaustive.
|
||||
|
||||
/// Keys with multiple values
|
||||
const MULTI_VALUE_KEYS: &[&str] = &[
|
||||
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
|
||||
];
|
||||
/// Keys with only single value
|
||||
const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
|
||||
/// Keys with no value
|
||||
const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
|
||||
/// Keys are not allowed to set in configuration files and command line
|
||||
const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-append", "-initrd"];
|
||||
|
||||
fn check_qemu_arg(arg: &str) {
|
||||
let key = if let Some(key) = get_key(arg, " ") {
|
||||
key
|
||||
} else {
|
||||
arg.to_string()
|
||||
};
|
||||
|
||||
if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
|
||||
error_msg!("`{}` is not allowed to set", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
|
||||
if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
|
||||
error_msg!("`{}` cannot have value", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
|
||||
if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
|
||||
&& key.as_str() == arg
|
||||
{
|
||||
error_msg!("`{}` should have value", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
}
|
66
osdk/src/config/test/OSDK.toml.full
Normal file
66
osdk/src/config/test/OSDK.toml.full
Normal file
@ -0,0 +1,66 @@
|
||||
project_type = "kernel"
|
||||
|
||||
supported_archs = ["x86_64"]
|
||||
vars = [
|
||||
["SMP", "1"],
|
||||
["MEM", "2G"],
|
||||
["EXT2_IMG", "$OSDK_CWD/regression/build/ext2.img"],
|
||||
["EXFAT_IMG", "$OSDK_CWD/regression/build/exfat.img"],
|
||||
]
|
||||
|
||||
[boot]
|
||||
method = "grub-rescue-iso"
|
||||
|
||||
[run]
|
||||
vars = [
|
||||
["OVMF_PATH", "/usr/share/OVMF"],
|
||||
]
|
||||
boot.kcmd_args = [
|
||||
"SHELL=/bin/sh",
|
||||
"LOGNAME=root",
|
||||
"HOME=/",
|
||||
"USER=root",
|
||||
"PATH=/bin:/benchmark",
|
||||
"init=/usr/bin/busybox",
|
||||
]
|
||||
boot.init_args = ["sh", "-l"]
|
||||
boot.initramfs = "/tmp/osdk_test_file"
|
||||
|
||||
[test]
|
||||
boot.method = "qemu-direct"
|
||||
|
||||
[grub]
|
||||
protocol = "multiboot2"
|
||||
display_grub_menu = true
|
||||
|
||||
[qemu]
|
||||
args = """\
|
||||
-machine q35 \
|
||||
-smp $SMP \
|
||||
-m $MEM \
|
||||
"""
|
||||
|
||||
[scheme."iommu"]
|
||||
supported_archs = ["x86_64"]
|
||||
vars = [
|
||||
["IOMMU_DEV_EXTRA", ",iommu_platform=on,ats=on"],
|
||||
]
|
||||
qemu.args = """\
|
||||
-device intel-iommu,intremap=on,device-iotlb=on \
|
||||
-device ioh3420,id=pcie.0,chassis=1\
|
||||
"""
|
||||
|
||||
[scheme."tdx"]
|
||||
supported_archs = ["x86_64"]
|
||||
build.features = ["intel_tdx"]
|
||||
vars = [
|
||||
["MEM", "8G"],
|
||||
["OVMF_PATH", "~/tdx-tools/ovmf"],
|
||||
]
|
||||
boot.method = "grub-qcow2"
|
||||
grub.mkrescue_path = "/tmp/osdk_test_file"
|
||||
grub.protocol = "linux"
|
||||
qemu.path = "/tmp/osdk_test_file"
|
||||
qemu.args = """\
|
||||
-name process=tdxvm,debug-threads=on \
|
||||
"""
|
60
osdk/src/config/test/mod.rs
Normal file
60
osdk/src/config/test/mod.rs
Normal file
@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize_toml_manifest() {
|
||||
let content = include_str!("OSDK.toml.full");
|
||||
let toml_manifest: manifest::TomlManifest = toml::from_str(content).unwrap();
|
||||
let type_ = toml_manifest.project_type.unwrap();
|
||||
assert!(type_ == manifest::ProjectType::Kernel);
|
||||
let vars = toml_manifest.default_scheme.vars;
|
||||
assert!(vars.contains(&("SMP".to_owned(), "1".to_owned())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_manifest() {
|
||||
let tmp_file = "/tmp/osdk_test_file";
|
||||
File::create(tmp_file).unwrap();
|
||||
|
||||
let toml_manifest: manifest::TomlManifest = {
|
||||
let content = include_str!("OSDK.toml.full");
|
||||
toml::from_str(content).unwrap()
|
||||
};
|
||||
|
||||
// Default scheme
|
||||
let scheme = toml_manifest.get_scheme(None::<String>);
|
||||
assert!(scheme
|
||||
.qemu
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.args
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&String::from("-machine q35",)));
|
||||
|
||||
// Iommu
|
||||
let scheme = toml_manifest.get_scheme(Some("iommu".to_owned()));
|
||||
assert!(scheme
|
||||
.qemu
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.args
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1",)));
|
||||
|
||||
// Tdx
|
||||
let scheme = toml_manifest.get_scheme(Some("tdx".to_owned()));
|
||||
assert_eq!(
|
||||
scheme.qemu.as_ref().unwrap().path.as_ref().unwrap(),
|
||||
&PathBuf::from(tmp_file)
|
||||
);
|
||||
|
||||
fs::remove_file(tmp_file).unwrap();
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{path::PathBuf, process};
|
||||
|
||||
use clap::ValueEnum;
|
||||
|
||||
use super::{qemu, unix_args::apply_kv_array};
|
||||
|
||||
use crate::{config_manager::OsdkArgs, error::Errno, error_msg};
|
||||
|
||||
/// The settings for an action (running or testing).
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ActionSettings {
|
||||
/// Command line arguments for the guest kernel
|
||||
#[serde(default)]
|
||||
pub kcmd_args: Vec<String>,
|
||||
/// Command line arguments for the guest init process
|
||||
#[serde(default)]
|
||||
pub init_args: Vec<String>,
|
||||
/// The path of initramfs
|
||||
pub initramfs: Option<PathBuf>,
|
||||
pub bootloader: Option<Bootloader>,
|
||||
pub boot_protocol: Option<BootProtocol>,
|
||||
/// The path of `grub_mkrecue`. Only be `Some(_)` if `loader` is `Bootloader::grub`
|
||||
pub grub_mkrescue: Option<PathBuf>,
|
||||
/// The path of OVMF binaries. Only required if `protocol` is `BootProtocol::LinuxEfiHandover64`
|
||||
pub ovmf: Option<PathBuf>,
|
||||
/// The path of OpenSBI binaries. Only required for RISC-V.
|
||||
pub opensbi: Option<PathBuf>,
|
||||
/// QEMU's available machines appended with various machine configurations
|
||||
pub qemu_machine: Option<String>,
|
||||
/// The additional arguments for running QEMU, except `-cpu` and `-machine`
|
||||
#[serde(default)]
|
||||
pub qemu_args: Vec<String>,
|
||||
/// The additional drive files attaching to QEMU
|
||||
#[serde(default)]
|
||||
pub drive_files: Vec<DriveFile>,
|
||||
/// The path of qemu
|
||||
pub qemu_exe: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Bootloader {
|
||||
Grub,
|
||||
Qemu,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum BootProtocol {
|
||||
LinuxEfiHandover64,
|
||||
LinuxLegacy32,
|
||||
Multiboot,
|
||||
Multiboot2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DriveFile {
|
||||
pub path: PathBuf,
|
||||
pub append: String,
|
||||
}
|
||||
|
||||
impl ActionSettings {
|
||||
pub fn canonicalize_paths(&mut self, cur_dir: impl AsRef<std::path::Path>) {
|
||||
macro_rules! canonicalize_path {
|
||||
($path:expr) => {{
|
||||
let path = if $path.is_relative() {
|
||||
cur_dir.as_ref().join($path)
|
||||
} else {
|
||||
$path.clone()
|
||||
};
|
||||
path.canonicalize().unwrap_or_else(|_| {
|
||||
error_msg!("File specified but not found: {:#?}", path);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
})
|
||||
}};
|
||||
}
|
||||
macro_rules! canonicalize_optional_path {
|
||||
($path:expr) => {
|
||||
if let Some(path_inner) = &$path {
|
||||
Some(canonicalize_path!(path_inner))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
self.initramfs = canonicalize_optional_path!(self.initramfs);
|
||||
self.grub_mkrescue = canonicalize_optional_path!(self.grub_mkrescue);
|
||||
self.ovmf = canonicalize_optional_path!(self.ovmf);
|
||||
self.qemu_exe = canonicalize_optional_path!(self.qemu_exe);
|
||||
self.opensbi = canonicalize_optional_path!(self.opensbi);
|
||||
for drive_file in &mut self.drive_files {
|
||||
drive_file.path = canonicalize_path!(&drive_file.path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_cli_args(&mut self, args: &OsdkArgs) {
|
||||
macro_rules! apply {
|
||||
($item:expr, $arg:expr) => {
|
||||
if let Some(arg) = $arg.clone() {
|
||||
$item = Some(arg);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
apply!(self.initramfs, &args.initramfs);
|
||||
apply!(self.ovmf, &args.ovmf);
|
||||
apply!(self.opensbi, &args.opensbi);
|
||||
apply!(self.grub_mkrescue, &args.grub_mkrescue);
|
||||
apply!(self.bootloader, &args.bootloader);
|
||||
apply!(self.boot_protocol, &args.boot_protocol);
|
||||
apply!(self.qemu_exe, &args.qemu_exe);
|
||||
|
||||
apply_kv_array(&mut self.kcmd_args, &args.kcmd_args, "=", &[]);
|
||||
for init_arg in &args.init_args {
|
||||
for seperated_arg in init_arg.split(' ') {
|
||||
self.init_args.push(seperated_arg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
qemu::apply_qemu_args_addition(&mut self.qemu_args, &args.qemu_args_add);
|
||||
}
|
||||
|
||||
pub fn combined_kcmd_args(&self) -> Vec<String> {
|
||||
let mut kcmd_args = self.kcmd_args.clone();
|
||||
kcmd_args.push("--".to_owned());
|
||||
kcmd_args.extend(self.init_args.clone());
|
||||
kcmd_args
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A module for handling configurations.
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
/// A configuration that looks like "cfg(k1=v1, k2=v2, ...)".
|
||||
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Serialize)]
|
||||
pub struct Cfg(BTreeMap<String, String>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CfgParseError(String);
|
||||
|
||||
impl fmt::Display for CfgParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Failed to parse cfg: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::StdError for CfgParseError {}
|
||||
impl serde::de::Error for CfgParseError {
|
||||
fn custom<T: fmt::Display>(msg: T) -> Self {
|
||||
Self(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl CfgParseError {
|
||||
pub fn new(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// This allows literal constructions like `Cfg::from([("arch", "foo"), ("schema", "bar")])`.
|
||||
impl<K, V, const N: usize> From<[(K, V); N]> for Cfg
|
||||
where
|
||||
K: Into<String>,
|
||||
V: Into<String>,
|
||||
{
|
||||
fn from(array: [(K, V); N]) -> Self {
|
||||
let mut cfg = BTreeMap::new();
|
||||
for (k, v) in array.into_iter() {
|
||||
cfg.insert(k.into(), v.into());
|
||||
}
|
||||
Self(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Cfg {
|
||||
pub fn empty() -> Self {
|
||||
Self(BTreeMap::new())
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Result<Self, CfgParseError> {
|
||||
let s = s.trim();
|
||||
|
||||
// Match the leading "cfg(" and trailing ")"
|
||||
if !s.starts_with("cfg(") || !s.ends_with(')') {
|
||||
return Err(CfgParseError::new(s));
|
||||
}
|
||||
let s = &s[4..s.len() - 1];
|
||||
|
||||
let mut cfg = BTreeMap::new();
|
||||
for kv in s.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
|
||||
let kv: Vec<_> = kv.split('=').collect();
|
||||
if kv.len() != 2 {
|
||||
return Err(CfgParseError::new(s));
|
||||
}
|
||||
cfg.insert(
|
||||
kv[0].trim().to_string(),
|
||||
kv[1].trim().trim_matches('\"').to_string(),
|
||||
);
|
||||
}
|
||||
Ok(Self(cfg))
|
||||
}
|
||||
|
||||
pub fn map(&self) -> &BTreeMap<String, String> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Cfg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "cfg(")?;
|
||||
for (i, (k, v)) in self.0.iter().enumerate() {
|
||||
write!(f, "{}=\"{}\"", k, v)?;
|
||||
if i != self.0.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cfg_from_str() {
|
||||
let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
|
||||
let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", schema=\"foo\", )").unwrap();
|
||||
let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",schema=\"foo\")").unwrap();
|
||||
let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", schema=\"foo\" )").unwrap();
|
||||
assert_eq!(cfg, cfg1);
|
||||
assert_eq!(cfg, cfg2);
|
||||
assert_eq!(cfg, cfg3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cfg_display() {
|
||||
let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]);
|
||||
let cfg_string = cfg.to_string();
|
||||
let cfg_back = Cfg::from_str(&cfg_string).unwrap();
|
||||
assert_eq!(cfg_string, "cfg(arch=\"x86_64\", schema=\"foo\")");
|
||||
assert_eq!(cfg, cfg_back);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_cfg_strings() {
|
||||
assert!(Cfg::from_str("fg(,,,,arch=\"x86_64 \", schema=\"foo\")").is_err());
|
||||
assert!(Cfg::from_str("cfg(arch=\"x86_64\", schema=\"foo\"").is_err());
|
||||
assert!(Cfg::from_str("cfgarch=x86_64,,, schema=\"foo\") ").is_err());
|
||||
}
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{collections::BTreeMap, fmt, path::Path, process};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::{action::ActionSettings, cfg::Cfg};
|
||||
|
||||
use crate::{config_manager::Arch, error::Errno, error_msg};
|
||||
|
||||
/// The settings for the actions summarized from the command line arguments
|
||||
/// and the configuration file `OSDK.toml`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OsdkManifest {
|
||||
pub project: Project,
|
||||
pub run: Option<ActionSettings>,
|
||||
pub test: Option<ActionSettings>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
#[serde(rename(serialize = "type", deserialize = "type"))]
|
||||
pub type_: ProjectType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ProjectType {
|
||||
Kernel,
|
||||
#[value(alias("lib"))]
|
||||
Library,
|
||||
Module,
|
||||
}
|
||||
|
||||
/// The osdk manifest from configuration file `OSDK.toml`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TomlManifest {
|
||||
pub project: Project,
|
||||
cfg_map: BTreeMap<Cfg, CfgArgs>,
|
||||
}
|
||||
|
||||
impl TomlManifest {
|
||||
/// Get the action manifest given the architecture and the schema from the command line arguments.
|
||||
///
|
||||
/// If any entry in the `OSDK.toml` manifest doesn't specify an architecture, we regard it matching
|
||||
/// all the architectures.
|
||||
pub fn get_osdk_manifest(
|
||||
&self,
|
||||
path_of_self: impl AsRef<Path>,
|
||||
arch: Arch,
|
||||
schema: Option<String>,
|
||||
) -> OsdkManifest {
|
||||
let filtered_by_arch = self.cfg_map.iter().filter(|(cfg, _)| {
|
||||
if let Some(got) = cfg.map().get("arch") {
|
||||
got == &arch.to_string()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let filtered_by_schema = if let Some(schema) = schema {
|
||||
filtered_by_arch
|
||||
.filter(|(cfg, _)| {
|
||||
if let Some(got) = cfg.map().get("schema") {
|
||||
got == &schema
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
filtered_by_arch
|
||||
.filter(|(cfg, _)| cfg == &&Cfg::empty())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let filtered = filtered_by_schema;
|
||||
if filtered.len() > 1 {
|
||||
error_msg!("Multiple entries in OSDK.toml match the given architecture and schema");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
if filtered.is_empty() {
|
||||
error_msg!("No entry in OSDK.toml matches the given architecture and schema");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
let final_cfg_args = filtered.first().unwrap().1;
|
||||
let mut run = final_cfg_args.run.clone();
|
||||
if let Some(run_inner) = &mut run {
|
||||
run_inner.canonicalize_paths(&path_of_self);
|
||||
}
|
||||
let mut test = final_cfg_args.test.clone();
|
||||
if let Some(test_inner) = &mut test {
|
||||
test_inner.canonicalize_paths(&path_of_self);
|
||||
}
|
||||
OsdkManifest {
|
||||
project: self.project.clone(),
|
||||
run,
|
||||
test,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A inner adapter for `TomlManifest` to allow the `cfg` field to be optional.
|
||||
/// The fields should be identical to `TomlManifest` except the `cfg` field.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
struct CfgArgs {
|
||||
pub run: Option<ActionSettings>,
|
||||
pub test: Option<ActionSettings>,
|
||||
}
|
||||
|
||||
impl CfgArgs {
|
||||
pub fn try_accept(&mut self, another: CfgArgs) {
|
||||
if another.run.is_some() {
|
||||
if self.run.is_some() {
|
||||
error_msg!("Duplicate `run` field in OSDK.toml");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
self.run = another.run;
|
||||
}
|
||||
if another.test.is_some() {
|
||||
if self.test.is_some() {
|
||||
error_msg!("Duplicate `test` field in OSDK.toml");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
self.test = another.test;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TomlManifest {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
enum Field {
|
||||
Project,
|
||||
Run,
|
||||
Test,
|
||||
Cfg(Cfg),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Field {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct FieldVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for FieldVisitor {
|
||||
type Value = Field;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("`project`, `run`, `test` or cfg")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match v {
|
||||
"project" => Ok(Field::Project),
|
||||
"run" => Ok(Field::Run),
|
||||
"test" => Ok(Field::Test),
|
||||
v => Ok(Field::Cfg(Cfg::from_str(v).unwrap_or_else(|e| {
|
||||
error_msg!("Error parsing cfg: {}", e);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_identifier(FieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct TomlManifestVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for TomlManifestVisitor {
|
||||
type Value = TomlManifest;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("struct TomlManifest")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::MapAccess<'de>,
|
||||
{
|
||||
let mut project: Option<Project> = None;
|
||||
let default_cfg = Cfg::empty();
|
||||
let mut cfg_map = BTreeMap::<Cfg, CfgArgs>::new();
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Project => {
|
||||
let value = map.next_value()?;
|
||||
project = Some(value);
|
||||
}
|
||||
Field::Run => {
|
||||
let value: ActionSettings = map.next_value()?;
|
||||
cfg_map
|
||||
.entry(default_cfg.clone())
|
||||
.and_modify(|v| {
|
||||
v.try_accept(CfgArgs {
|
||||
run: Some(value.clone()),
|
||||
test: None,
|
||||
})
|
||||
})
|
||||
.or_insert(CfgArgs {
|
||||
run: Some(value.clone()),
|
||||
test: None,
|
||||
});
|
||||
}
|
||||
Field::Test => {
|
||||
let value: ActionSettings = map.next_value()?;
|
||||
cfg_map
|
||||
.entry(default_cfg.clone())
|
||||
.and_modify(|v| {
|
||||
v.try_accept(CfgArgs {
|
||||
run: None,
|
||||
test: Some(value.clone()),
|
||||
})
|
||||
})
|
||||
.or_insert(CfgArgs {
|
||||
run: None,
|
||||
test: Some(value.clone()),
|
||||
});
|
||||
}
|
||||
Field::Cfg(cfg) => {
|
||||
let value: CfgArgs = map.next_value()?;
|
||||
cfg_map
|
||||
.entry(cfg)
|
||||
.and_modify(|v| v.try_accept(value.clone()))
|
||||
.or_insert(value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TomlManifest {
|
||||
project: project.unwrap_or_else(|| {
|
||||
error_msg!("`project` field is required in OSDK.toml");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}),
|
||||
cfg_map,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct(
|
||||
"TomlManifest",
|
||||
&["run", "test", "cfg"],
|
||||
TomlManifestVisitor,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module is responsible for parsing configuration files and combining them with command-line parameters
|
||||
//! to obtain the final configuration, it will also try searching system to fill valid values for specific
|
||||
//! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`,
|
||||
//! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand.
|
||||
|
||||
pub mod action;
|
||||
pub mod cfg;
|
||||
pub mod manifest;
|
||||
pub mod qemu;
|
||||
pub mod unix_args;
|
||||
|
||||
use action::ActionSettings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use std::{fs, path::PathBuf, process};
|
||||
|
||||
use self::manifest::{OsdkManifest, TomlManifest};
|
||||
use crate::{
|
||||
arch::{get_default_arch, Arch},
|
||||
cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs},
|
||||
error::Errno,
|
||||
error_msg,
|
||||
util::get_cargo_metadata,
|
||||
};
|
||||
|
||||
/// Configurations for build subcommand
|
||||
#[derive(Debug)]
|
||||
pub struct BuildConfig {
|
||||
pub arch: Arch,
|
||||
pub settings: ActionSettings,
|
||||
pub cargo_args: CargoArgs,
|
||||
}
|
||||
|
||||
impl BuildConfig {
|
||||
pub fn parse(args: &BuildArgs) -> Self {
|
||||
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
|
||||
let cargo_args = parse_cargo_args(&args.cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
|
||||
if let Some(run) = manifest.run.as_mut() {
|
||||
run.apply_cli_args(&args.osdk_args);
|
||||
}
|
||||
Self {
|
||||
arch,
|
||||
settings: manifest.run.unwrap(),
|
||||
cargo_args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configurations for run subcommand
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RunConfig {
|
||||
pub arch: Arch,
|
||||
pub settings: ActionSettings,
|
||||
pub cargo_args: CargoArgs,
|
||||
pub gdb_server_args: GdbServerArgs,
|
||||
}
|
||||
|
||||
impl RunConfig {
|
||||
pub fn parse(args: &RunArgs) -> Self {
|
||||
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
|
||||
let cargo_args = parse_cargo_args(&args.cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
|
||||
if let Some(run) = manifest.run.as_mut() {
|
||||
run.apply_cli_args(&args.osdk_args);
|
||||
}
|
||||
Self {
|
||||
arch,
|
||||
settings: manifest.run.unwrap(),
|
||||
cargo_args,
|
||||
gdb_server_args: args.gdb_server_args.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugConfig {
|
||||
pub cargo_args: CargoArgs,
|
||||
pub remote: String,
|
||||
}
|
||||
|
||||
impl DebugConfig {
|
||||
pub fn parse(args: &DebugArgs) -> Self {
|
||||
Self {
|
||||
cargo_args: parse_cargo_args(&args.cargo_args),
|
||||
remote: args.remote.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configurations for test subcommand
|
||||
#[derive(Debug)]
|
||||
pub struct TestConfig {
|
||||
pub arch: Arch,
|
||||
pub settings: ActionSettings,
|
||||
pub cargo_args: CargoArgs,
|
||||
pub test_name: Option<String>,
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
pub fn parse(args: &TestArgs) -> Self {
|
||||
let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch);
|
||||
let cargo_args = parse_cargo_args(&args.cargo_args);
|
||||
let manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args);
|
||||
// Use run settings if test settings are not provided
|
||||
let mut test = if let Some(test) = manifest.test {
|
||||
test
|
||||
} else {
|
||||
manifest.run.unwrap()
|
||||
};
|
||||
test.apply_cli_args(&args.osdk_args);
|
||||
Self {
|
||||
arch,
|
||||
settings: test,
|
||||
cargo_args,
|
||||
test_name: args.test_name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
|
||||
let feature_strings = get_feature_strings(cargo_args);
|
||||
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap();
|
||||
let workspace_root = PathBuf::from(
|
||||
cargo_metadata
|
||||
.get("workspace_root")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Search for OSDK.toml in the current directory. If not, dive into the workspace root.
|
||||
let manifest_path = PathBuf::from("OSDK.toml");
|
||||
let (contents, manifest_path) = if let Ok(contents) = fs::read_to_string("OSDK.toml") {
|
||||
(contents, manifest_path)
|
||||
} else {
|
||||
let manifest_path = workspace_root.join("OSDK.toml");
|
||||
let Ok(contents) = fs::read_to_string(&manifest_path) else {
|
||||
error_msg!(
|
||||
"Cannot read file {}",
|
||||
manifest_path.to_string_lossy().to_string()
|
||||
);
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
};
|
||||
(contents, manifest_path)
|
||||
};
|
||||
|
||||
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
|
||||
let span = err.span().unwrap();
|
||||
let wider_span =
|
||||
(span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len());
|
||||
error_msg!(
|
||||
"Cannot parse TOML file, {}. {}:{:?}:\n {}",
|
||||
err.message(),
|
||||
manifest_path.to_string_lossy().to_string(),
|
||||
span,
|
||||
&contents[wider_span],
|
||||
);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
});
|
||||
let osdk_manifest = toml_manifest.get_osdk_manifest(
|
||||
workspace_root,
|
||||
osdk_args.arch.unwrap_or_else(get_default_arch),
|
||||
osdk_args.schema.as_ref().map(|s| s.to_string()),
|
||||
);
|
||||
osdk_manifest
|
||||
}
|
||||
|
||||
/// Parse cargo args.
|
||||
/// 1. Split `features` in `cargo_args` to ensure each string contains exactly one feature.
|
||||
/// 2. Change `profile` to `release` if `--release` is set.
|
||||
fn parse_cargo_args(cargo_args: &CargoArgs) -> CargoArgs {
|
||||
let mut features = Vec::new();
|
||||
|
||||
for feature in cargo_args.features.iter() {
|
||||
for feature in feature.split(',') {
|
||||
if !feature.is_empty() {
|
||||
features.push(feature.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let profile = if cargo_args.release {
|
||||
"release".to_string()
|
||||
} else {
|
||||
cargo_args.profile.clone()
|
||||
};
|
||||
|
||||
CargoArgs {
|
||||
profile,
|
||||
release: cargo_args.release,
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_feature_strings(cargo_args: &CargoArgs) -> Vec<String> {
|
||||
cargo_args
|
||||
.features
|
||||
.iter()
|
||||
.map(|feature| format!("--features={}", feature))
|
||||
.collect()
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A module about QEMU arguments.
|
||||
|
||||
use std::process;
|
||||
|
||||
use super::unix_args::{apply_kv_array, get_key};
|
||||
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
pub fn apply_qemu_args_addition(target: &mut Vec<String>, args: &Vec<String>) {
|
||||
// check qemu_args
|
||||
for arg in target.iter() {
|
||||
check_qemu_arg(arg);
|
||||
}
|
||||
for arg in args.iter() {
|
||||
check_qemu_arg(arg);
|
||||
}
|
||||
|
||||
apply_kv_array(target, args, " ", MULTI_VALUE_KEYS);
|
||||
}
|
||||
|
||||
// Below are keys in qemu arguments. The key list is not complete.
|
||||
|
||||
/// Keys with multiple values
|
||||
const MULTI_VALUE_KEYS: &[&str] = &[
|
||||
"-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom",
|
||||
];
|
||||
/// Keys with only single value
|
||||
const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"];
|
||||
/// Keys with no value
|
||||
const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"];
|
||||
/// Keys are not allowed to set in configuration files and command line
|
||||
const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-initrd"];
|
||||
|
||||
fn check_qemu_arg(arg: &str) {
|
||||
let key = if let Some(key) = get_key(arg, " ") {
|
||||
key
|
||||
} else {
|
||||
arg.to_string()
|
||||
};
|
||||
|
||||
if NOT_ALLOWED_TO_SET_KEYS.contains(&key.as_str()) {
|
||||
error_msg!("`{}` is not allowed to set", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
|
||||
if NO_VALUE_KEYS.contains(&key.as_str()) && key.as_str() != arg {
|
||||
error_msg!("`{}` cannot have value", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
|
||||
if (SINGLE_VALUE_KEYS.contains(&key.as_str()) || MULTI_VALUE_KEYS.contains(&key.as_str()))
|
||||
&& key.as_str() == arg
|
||||
{
|
||||
error_msg!("`{}` should have value", arg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
[project]
|
||||
type = "kernel"
|
||||
|
||||
[run]
|
||||
kcmd_args = [
|
||||
"SHELL=/bin/sh",
|
||||
"LOGNAME=root",
|
||||
"HOME=/",
|
||||
"USER=root",
|
||||
"PATH=/bin:/benchmark",
|
||||
"init=/usr/bin/busybox",
|
||||
]
|
||||
init_args = ["sh", "-l"]
|
||||
initramfs = "/usr/bin/bash"
|
||||
boot_protocol = "multiboot2"
|
||||
bootloader = "grub"
|
||||
ovmf = "/usr/bin/bash"
|
||||
opensbi = "/usr/bin/bash"
|
||||
drive_files = [
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x0"],
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtconsole,chardev=mux",
|
||||
]
|
||||
|
||||
['cfg(arch="x86_64", schema="iommu")'.run]
|
||||
drive_files = [
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x0"],
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine q35,kernel-irqchip=split",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtconsole,chardev=mux",
|
||||
"-device intel-iommu,intremap=on,device-iotlb=on",
|
||||
"-device ioh3420,id=pcie.0,chassis=1",
|
||||
]
|
||||
|
||||
['cfg(arch="x86_64", schema="microvm")'.run]
|
||||
bootloader = "qemu"
|
||||
drive_files = [
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x0"],
|
||||
["/usr/bin/bash", "if=none,format=raw,id=x1"],
|
||||
]
|
||||
qemu_args = [
|
||||
"-machine microvm,rtc=on",
|
||||
"-cpu Icelake-Server,+x2apic",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
|
||||
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
|
||||
"-nodefaults",
|
||||
"-no-user-config",
|
||||
"-device virtio-blk-device,drive=x0,serial=vext2",
|
||||
"-device virtio-blk-device,drive=x1,serial=vexfat",
|
||||
"-device virtio-keyboard-device",
|
||||
"-device virtio-net-device,netdev=net01",
|
||||
"-device virtio-serial-device",
|
||||
"-device virtconsole,chardev=mux",
|
||||
]
|
||||
|
||||
['cfg(schema="intel_tdx")'.run]
|
||||
qemu_exe = "/usr/bin/bash"
|
||||
|
||||
['cfg(arch="riscv64")'.run]
|
||||
qemu_args = [
|
||||
"-machine virt",
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
]
|
@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize_toml_manifest() {
|
||||
let content = include_str!("OSDK.toml.full");
|
||||
let toml_manifest: TomlManifest = toml::from_str(content).unwrap();
|
||||
assert!(toml_manifest.project.type_ == manifest::ProjectType::Kernel);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_manifest() {
|
||||
let toml_manifest: TomlManifest = {
|
||||
let content = include_str!("OSDK.toml.full");
|
||||
toml::from_str(content).unwrap()
|
||||
};
|
||||
let arch = crate::arch::Arch::X86_64;
|
||||
|
||||
// Default schema
|
||||
let schema: Option<String> = None;
|
||||
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
|
||||
assert!(manifest.run.unwrap().qemu_args.contains(&String::from(
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off",
|
||||
)));
|
||||
|
||||
// Iommu
|
||||
let schema: Option<String> = Some("iommu".to_owned());
|
||||
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
|
||||
assert!(manifest
|
||||
.run
|
||||
.unwrap()
|
||||
.qemu_args
|
||||
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
|
||||
|
||||
// Tdx
|
||||
let schema: Option<String> = Some("intel_tdx".to_owned());
|
||||
let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema);
|
||||
assert_eq!(
|
||||
manifest.run.unwrap().qemu_exe.unwrap(),
|
||||
PathBuf::from("/usr/bin/bash")
|
||||
);
|
||||
}
|
@ -12,7 +12,7 @@ mod base_crate;
|
||||
mod bundle;
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod config_manager;
|
||||
mod config;
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
|
@ -6,7 +6,7 @@ use crate::util::*;
|
||||
fn cli_help_message() {
|
||||
let output = cargo_osdk(&["-h"]).output().unwrap();
|
||||
assert_success(&output);
|
||||
assert_stdout_contains_msg(&output, "cargo osdk <COMMAND>");
|
||||
assert_stdout_contains_msg(&output, "cargo osdk [OPTIONS] <COMMAND>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
65
tools/qemu_args.sh
Executable file
65
tools/qemu_args.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
RAND_PORT_NUM1=$(shuf -i 1024-65535 -n 1)
|
||||
RAND_PORT_NUM2=$(shuf -i 1024-65535 -n 1)
|
||||
|
||||
echo "Forwarded QEMU guest port: $RAND_PORT_NUM1->22; $RAND_PORT_NUM2->8080" 1>&2
|
||||
|
||||
COMMON_QEMU_ARGS="\
|
||||
-cpu Icelake-Server,+x2apic \
|
||||
-smp $SMP \
|
||||
-m $MEM \
|
||||
--no-reboot \
|
||||
-nographic \
|
||||
-display none \
|
||||
-serial chardev:mux \
|
||||
-monitor chardev:mux \
|
||||
-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \
|
||||
-netdev user,id=net01,hostfwd=tcp::$RAND_PORT_NUM1-:22,hostfwd=tcp::$RAND_PORT_NUM2-:8080 \
|
||||
-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap \
|
||||
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
|
||||
-drive if=none,format=raw,id=x0,file=$EXT2_IMG \
|
||||
-drive if=none,format=raw,id=x1,file=$EXFAT_IMG \
|
||||
"
|
||||
|
||||
QEMU_ARGS="\
|
||||
$COMMON_QEMU_ARGS \
|
||||
-machine q35,kernel-irqchip=split \
|
||||
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
|
||||
-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
|
||||
-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
|
||||
-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
|
||||
-device virtio-serial-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
|
||||
-device virtconsole,chardev=mux \
|
||||
$IOMMU_EXTRA_ARGS \
|
||||
"
|
||||
|
||||
MICROVM_QEMU_ARGS="\
|
||||
$COMMON_QEMU_ARGS \
|
||||
-machine microvm,rtc=on \
|
||||
-nodefaults \
|
||||
-no-user-config \
|
||||
-device virtio-blk-device,drive=x0,serial=vext2 \
|
||||
-device virtio-blk-device,drive=x1,serial=vexfat \
|
||||
-device virtio-keyboard-device \
|
||||
-device virtio-net-device,netdev=net01 \
|
||||
-device virtio-serial-device \
|
||||
-device virtconsole,chardev=mux \
|
||||
"
|
||||
|
||||
if [ "$MICROVM" ]; then
|
||||
QEMU_ARGS=$MICROVM_QEMU_ARGS
|
||||
echo $QEMU_ARGS
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$OVMF_PATH" ]; then
|
||||
QEMU_ARGS="${QEMU_ARGS}\
|
||||
-drive if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_PATH/OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,unit=1,file=$OVMF_PATH/OVMF_VARS.fd \
|
||||
"
|
||||
fi
|
||||
|
||||
echo $QEMU_ARGS
|
Loading…
x
Reference in New Issue
Block a user