mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-09 05:16:47 +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)
|
- name: Boot Test (MicroVM)
|
||||||
id: 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)
|
- name: Boot Test (Linux Legacy 32-bit Boot Protocol)
|
||||||
id: boot_test_linux_legacy32
|
id: boot_test_linux_legacy32
|
||||||
@ -74,7 +74,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Syscall Test at Ext2 (MicroVM)
|
- name: Syscall Test at Ext2 (MicroVM)
|
||||||
id: syscall_test_at_ext2
|
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
|
- name: Syscall Test at Exfat
|
||||||
id: syscall_test_at_exfat_linux
|
id: syscall_test_at_exfat_linux
|
||||||
@ -82,4 +82,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Regression Test (MicroVM)
|
- name: Regression Test (MicroVM)
|
||||||
id: regression_test_linux
|
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
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# Project-wide options.
|
# Global options.
|
||||||
ARCH ?= x86_64
|
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
|
# The Makefile provides a way to run arbitrary tests in the kernel
|
||||||
# mode using the kernel command line.
|
# mode using the kernel command line.
|
||||||
# Here are the options for the auto test feature.
|
# Here are the options for the auto test feature.
|
||||||
AUTO_TEST ?= none
|
AUTO_TEST ?= none
|
||||||
BOOT_LOADER ?= grub
|
|
||||||
BOOT_PROTOCOL ?= multiboot2
|
|
||||||
BUILD_SYSCALL_TEST ?= 0
|
|
||||||
ENABLE_KVM ?= 1
|
|
||||||
EXTRA_BLOCKLISTS_DIRS ?= ""
|
EXTRA_BLOCKLISTS_DIRS ?= ""
|
||||||
INTEL_TDX ?= 0
|
|
||||||
SCHEMA ?= ""
|
|
||||||
RELEASE_MODE ?= 0
|
|
||||||
SKIP_GRUB_MENU ?= 1
|
|
||||||
SYSCALL_TEST_DIR ?= /tmp
|
SYSCALL_TEST_DIR ?= /tmp
|
||||||
# End of auto test features.
|
# End of auto test features.
|
||||||
|
|
||||||
CARGO_OSDK := ~/.cargo/bin/cargo-osdk
|
CARGO_OSDK := ~/.cargo/bin/cargo-osdk
|
||||||
|
|
||||||
CARGO_OSDK_ARGS := --arch=$(ARCH)
|
CARGO_OSDK_ARGS := --target-arch=$(ARCH)
|
||||||
|
|
||||||
ifeq ($(AUTO_TEST), syscall)
|
ifeq ($(AUTO_TEST), syscall)
|
||||||
BUILD_SYSCALL_TEST := 1
|
BUILD_SYSCALL_TEST := 1
|
||||||
CARGO_OSDK_ARGS += --kcmd_args+="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
|
CARGO_OSDK_ARGS += --kcmd-args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
|
||||||
CARGO_OSDK_ARGS += --kcmd_args+="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
|
CARGO_OSDK_ARGS += --kcmd-args="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)"
|
||||||
CARGO_OSDK_ARGS += --init_args+="/opt/syscall_test/run_syscall_test.sh"
|
CARGO_OSDK_ARGS += --init-args="/opt/syscall_test/run_syscall_test.sh"
|
||||||
else ifeq ($(AUTO_TEST), regression)
|
else ifeq ($(AUTO_TEST), regression)
|
||||||
CARGO_OSDK_ARGS += --init_args+="/regression/run_regression_test.sh"
|
CARGO_OSDK_ARGS += --init-args="/regression/run_regression_test.sh"
|
||||||
else ifeq ($(AUTO_TEST), boot)
|
else ifeq ($(AUTO_TEST), boot)
|
||||||
CARGO_OSDK_ARGS += --init_args+="/regression/boot_hello.sh"
|
CARGO_OSDK_ARGS += --init-args="/regression/boot_hello.sh"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(RELEASE_MODE), 1)
|
ifeq ($(RELEASE_MODE), 1)
|
||||||
@ -43,21 +42,26 @@ ifeq ($(INTEL_TDX), 1)
|
|||||||
CARGO_OSDK_ARGS += --features intel_tdx
|
CARGO_OSDK_ARGS += --features intel_tdx
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CARGO_OSDK_ARGS += --bootloader="$(BOOT_LOADER)"
|
ifneq ($(SCHEME), "")
|
||||||
CARGO_OSDK_ARGS += --boot_protocol="$(BOOT_PROTOCOL)"
|
CARGO_OSDK_ARGS += --scheme $(SCHEME)
|
||||||
|
else
|
||||||
ifneq ($(SCHEMA), "")
|
CARGO_OSDK_ARGS += --boot-method="$(BOOT_METHOD)"
|
||||||
CARGO_OSDK_ARGS += --schema $(SCHEMA)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# To test the linux-efi-handover64 boot protocol, we need to use Debian's
|
# To test the linux-efi-handover64 boot protocol, we need to use Debian's
|
||||||
# GRUB release, which is installed in /usr/bin in our Docker image.
|
# GRUB release, which is installed in /usr/bin in our Docker image.
|
||||||
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
|
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
|
||||||
CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue
|
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
|
endif
|
||||||
|
|
||||||
ifeq ($(ENABLE_KVM), 1)
|
ifeq ($(ENABLE_KVM), 1)
|
||||||
CARGO_OSDK_ARGS += --qemu_args+="--enable-kvm"
|
CARGO_OSDK_ARGS += --qemu-args="--enable-kvm"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Pass make variables to all subdirectory makes
|
# Pass make variables to all subdirectory makes
|
||||||
@ -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)
|
(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
|
@rm /tmp/all_crates /tmp/combined_crates
|
||||||
@for dir in $(NON_OSDK_CRATES); do \
|
@for dir in $(NON_OSDK_CRATES); do \
|
||||||
|
echo "Checking $$dir"; \
|
||||||
(cd $$dir && cargo clippy -- -D warnings) || exit 1; \
|
(cd $$dir && cargo clippy -- -D warnings) || exit 1; \
|
||||||
done
|
done
|
||||||
@for dir in $(OSDK_CRATES); do \
|
@for dir in $(OSDK_CRATES); do \
|
||||||
|
echo "Checking $$dir"; \
|
||||||
(cd $$dir && cargo osdk clippy -- -- -D warnings) || exit 1; \
|
(cd $$dir && cargo osdk clippy -- -- -D warnings) || exit 1; \
|
||||||
done
|
done
|
||||||
@make --no-print-directory -C regression check
|
@make --no-print-directory -C regression check
|
||||||
|
178
OSDK.toml
178
OSDK.toml
@ -1,7 +1,19 @@
|
|||||||
[project]
|
vars = [
|
||||||
type = "kernel"
|
["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]
|
[run]
|
||||||
|
vars = [
|
||||||
|
["OVMF_PATH", "/usr/share/OVMF"],
|
||||||
|
]
|
||||||
|
|
||||||
|
[run.boot]
|
||||||
kcmd_args = [
|
kcmd_args = [
|
||||||
"SHELL=/bin/sh",
|
"SHELL=/bin/sh",
|
||||||
"LOGNAME=root",
|
"LOGNAME=root",
|
||||||
@ -12,117 +24,67 @@ kcmd_args = [
|
|||||||
]
|
]
|
||||||
init_args = ["sh", "-l"]
|
init_args = ["sh", "-l"]
|
||||||
initramfs = "regression/build/initramfs.cpio.gz"
|
initramfs = "regression/build/initramfs.cpio.gz"
|
||||||
boot_protocol = "multiboot2"
|
|
||||||
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]
|
[test]
|
||||||
boot_protocol = "multiboot"
|
boot.method = "qemu-direct"
|
||||||
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",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
[grub]
|
||||||
|
protocol = "multiboot2"
|
||||||
|
|
||||||
['cfg(arch="x86_64", schema="iommu")'.run]
|
[qemu]
|
||||||
drive_files = [
|
args = "$(./tools/qemu_args.sh)"
|
||||||
["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",
|
|
||||||
]
|
|
||||||
|
|
||||||
['cfg(arch="x86_64", schema="microvm")'.run]
|
[scheme."microvm"]
|
||||||
bootloader = "qemu"
|
boot.method = "qemu-direct"
|
||||||
drive_files = [
|
vars = [
|
||||||
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
|
["MICROVM", "true"],
|
||||||
["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",
|
|
||||||
]
|
]
|
||||||
|
qemu.args = "$(./tools/qemu_args.sh)"
|
||||||
|
|
||||||
['cfg(arch="riscv64")'.run]
|
[scheme."iommu"]
|
||||||
qemu_args = [
|
supported_archs = ["x86_64"]
|
||||||
"-machine virt",
|
vars = [
|
||||||
"--no-reboot",
|
["IOMMU_DEV_EXTRA", ",iommu_platform=on,ats=on"],
|
||||||
"-m 2G",
|
["IOMMU_EXTRA_ARGS", """\
|
||||||
"-nographic",
|
-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",
|
"-m 2G",
|
||||||
]
|
]
|
||||||
|
|
||||||
['cfg(arch="x86_64", schema=microvm)'.run] # <12>
|
['cfg(arch="x86_64", scheme=microvm)'.run] # <12>
|
||||||
bootloader = "qemu"
|
bootloader = "qemu"
|
||||||
qemu_args = [ # <10>
|
qemu_args = [ # <10>
|
||||||
"-machine microvm,rtc=on",
|
"-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
|
Cfg is an advanced feature to create multiple profiles for
|
||||||
the same actions under different scenarios. Currently we
|
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
|
The key `arch` has a fixed set of values which is aligned
|
||||||
with the CLI `--arch` argument. If an action has no specified
|
with the CLI `--arch` argument. If an action has no specified
|
||||||
arch, it matches all the architectures. The key `schema` allows
|
arch, it matches all the architectures. The key `scheme` allows
|
||||||
user-defined values and can be selected by the `--schema` CLI
|
user-defined values and can be selected by the `--scheme` CLI
|
||||||
argument. The key `schema` can be used to create special settings
|
argument. The key `scheme` can be used to create special settings
|
||||||
(especially special QEMU configurations). If a cfg action is
|
(especially special QEMU configurations). If a cfg action is
|
||||||
matched, unspecified and required arguments will be inherited
|
matched, unspecified and required arguments will be inherited
|
||||||
from the action that has no cfg (i.e. the default action setting).
|
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",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"shlex",
|
||||||
"syn",
|
"syn",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
@ -470,6 +471,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -19,6 +19,7 @@ quote = "1.0.35"
|
|||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
shlex = "1.3.0"
|
||||||
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
|
syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] }
|
||||||
toml = { version = "0.8.8", features = ["preserve_order"] }
|
toml = { version = "0.8.8", features = ["preserve_order"] }
|
||||||
|
|
||||||
|
@ -12,8 +12,11 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
/// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>
|
/// <https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Arch {
|
pub enum Arch {
|
||||||
|
#[serde(rename = "aarch64")]
|
||||||
Aarch64,
|
Aarch64,
|
||||||
|
#[serde(rename = "riscv64")]
|
||||||
X86_64,
|
X86_64,
|
||||||
|
#[serde(rename = "x86_64")]
|
||||||
RiscV64,
|
RiscV64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +36,19 @@ impl ValueEnum for Arch {
|
|||||||
|
|
||||||
impl Arch {
|
impl Arch {
|
||||||
/// Get the target triple for the architecture.
|
/// Get the target triple for the architecture.
|
||||||
pub fn triple(&self) -> String {
|
pub fn triple(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Arch::Aarch64 => "aarch64-unknown-none".to_owned(),
|
Arch::Aarch64 => "aarch64-unknown-none",
|
||||||
Arch::RiscV64 => "riscv64gc-unknown-none-elf".to_owned(),
|
Arch::RiscV64 => "riscv64gc-unknown-none-elf",
|
||||||
Arch::X86_64 => "x86_64-unknown-none".to_owned(),
|
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 bin::AsterBin;
|
||||||
use file::{BundleFile, Initramfs};
|
use file::{BundleFile, Initramfs};
|
||||||
use std::process;
|
use std::process;
|
||||||
use vm_image::AsterVmImage;
|
use vm_image::{AsterVmImage, AsterVmImageType};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -16,11 +16,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::Arch,
|
config::{
|
||||||
cli::CargoArgs,
|
scheme::{ActionChoice, BootMethod},
|
||||||
config_manager::{
|
Config,
|
||||||
action::{ActionSettings, Bootloader},
|
|
||||||
RunConfig,
|
|
||||||
},
|
},
|
||||||
error::Errno,
|
error::Errno,
|
||||||
error_msg,
|
error_msg,
|
||||||
@ -42,16 +40,20 @@ pub struct BundleManifest {
|
|||||||
pub initramfs: Option<Initramfs>,
|
pub initramfs: Option<Initramfs>,
|
||||||
pub aster_bin: Option<AsterBin>,
|
pub aster_bin: Option<AsterBin>,
|
||||||
pub vm_image: Option<AsterVmImage>,
|
pub vm_image: Option<AsterVmImage>,
|
||||||
pub settings: ActionSettings,
|
pub config: Config,
|
||||||
pub cargo_args: CargoArgs,
|
pub action: ActionChoice,
|
||||||
pub last_modified: SystemTime,
|
pub last_modified: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bundle {
|
impl Bundle {
|
||||||
/// This function creates a new `Bundle` without adding any files.
|
/// This function creates a new `Bundle` without adding any files.
|
||||||
pub fn new(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();
|
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() {
|
if !initramfs.exists() {
|
||||||
error_msg!("initramfs file not found: {}", initramfs.display());
|
error_msg!("initramfs file not found: {}", initramfs.display());
|
||||||
process::exit(Errno::BuildCrate as _);
|
process::exit(Errno::BuildCrate as _);
|
||||||
@ -65,8 +67,8 @@ impl Bundle {
|
|||||||
initramfs,
|
initramfs,
|
||||||
aster_bin: None,
|
aster_bin: None,
|
||||||
vm_image: None,
|
vm_image: None,
|
||||||
settings,
|
config: config.clone(),
|
||||||
cargo_args,
|
action,
|
||||||
last_modified: SystemTime::now(),
|
last_modified: SystemTime::now(),
|
||||||
},
|
},
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
@ -109,105 +111,139 @@ impl Bundle {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
|
pub fn can_run_with_config(&self, config: &Config, action: ActionChoice) -> Result<(), String> {
|
||||||
// Compare the manifest with the run configuration.
|
// If built for testing, better not to run it. Vice versa.
|
||||||
// TODO: This pairwise comparison will result in some false negatives. We may
|
if self.manifest.action != action {
|
||||||
// fix it by pondering upon each fields with more care.
|
return Err(format!(
|
||||||
if self.manifest.settings != config.settings
|
"The bundle is built for {:?}",
|
||||||
|| self.manifest.cargo_args != config.cargo_args
|
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.
|
// 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)) => {
|
(Some(initramfs), Some(initramfs_path)) => {
|
||||||
let config_initramfs = Initramfs::new(initramfs_path);
|
let config_initramfs = Initramfs::new(initramfs_path);
|
||||||
if initramfs.sha256sum() != config_initramfs.sha256sum() {
|
if initramfs.sha256sum() != config_initramfs.sha256sum() {
|
||||||
return false;
|
return Err(initramfs_err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
_ => {
|
_ => {
|
||||||
return false;
|
return Err(initramfs_err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
true
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_modified_time(&self) -> SystemTime {
|
pub fn last_modified_time(&self) -> SystemTime {
|
||||||
self.manifest.last_modified
|
self.manifest.last_modified
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, config: &RunConfig) {
|
pub fn run(&self, config: &Config, action: ActionChoice) {
|
||||||
if !self.can_run_with_config(config) {
|
match self.can_run_with_config(config, action) {
|
||||||
error_msg!("The bundle is not compatible with the run configuration");
|
Ok(()) => {}
|
||||||
std::process::exit(Errno::RunBundle as _);
|
Err(msg) => {
|
||||||
}
|
error_msg!("{}", msg);
|
||||||
let mut qemu_cmd = Command::new(config.settings.qemu_exe.clone().unwrap_or_else(|| {
|
std::process::exit(Errno::RunBundle as _);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match config.settings.bootloader {
|
let action = match action {
|
||||||
Some(Bootloader::Qemu) => {
|
ActionChoice::Run => &config.run,
|
||||||
let Some(ref aster_bin) = self.manifest.aster_bin else {
|
ActionChoice::Test => &config.test,
|
||||||
error_msg!("Kernel ELF binary is required for direct QEMU booting");
|
};
|
||||||
std::process::exit(Errno::RunBundle as _);
|
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
|
qemu_cmd
|
||||||
.arg("-kernel")
|
.arg("-kernel")
|
||||||
.arg(self.path.join(aster_bin.path()));
|
.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);
|
qemu_cmd.arg("-initrd").arg(initramfs);
|
||||||
} else {
|
} else {
|
||||||
info!("No initramfs specified");
|
info!("No initramfs specified");
|
||||||
};
|
};
|
||||||
qemu_cmd
|
qemu_cmd.arg("-append").arg(action.boot.kcmdline.join(" "));
|
||||||
.arg("-append")
|
|
||||||
.arg(config.settings.combined_kcmd_args().join(" "));
|
|
||||||
}
|
}
|
||||||
Some(Bootloader::Grub) => {
|
BootMethod::GrubRescueIso => {
|
||||||
let Some(ref vm_image) = self.manifest.vm_image else {
|
let vm_image = self.manifest.vm_image.as_ref().unwrap();
|
||||||
error_msg!("VM image is required for QEMU booting");
|
assert!(matches!(vm_image.typ(), AsterVmImageType::GrubIso(_)));
|
||||||
std::process::exit(Errno::RunBundle as _);
|
|
||||||
};
|
|
||||||
qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path()));
|
qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path()));
|
||||||
if let Some(ovmf) = &config.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 => {
|
BootMethod::GrubQcow2 => {
|
||||||
error_msg!("Bootloader is required for QEMU booting");
|
let vm_image = self.manifest.vm_image.as_ref().unwrap();
|
||||||
std::process::exit(Errno::RunBundle as _);
|
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 {
|
info!("Running QEMU: {:#?}", qemu_cmd);
|
||||||
qemu_cmd.arg("-drive").arg(format!(
|
|
||||||
"file={},{}",
|
|
||||||
drive_file.path.display(),
|
|
||||||
drive_file.append,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let exit_status = qemu_cmd.status().unwrap();
|
let exit_status = qemu_cmd.status().unwrap();
|
||||||
if !exit_status.success() {
|
if !exit_status.success() {
|
||||||
|
@ -18,7 +18,7 @@ pub struct AsterVmImage {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum AsterVmImageType {
|
pub enum AsterVmImageType {
|
||||||
GrubIso(AsterGrubIsoImageMeta),
|
GrubIso(AsterGrubIsoImageMeta),
|
||||||
// TODO: add more vm image types such as qcow2, etc.
|
Qcow2(AsterQcow2ImageMeta),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@ -26,6 +26,11 @@ pub struct AsterGrubIsoImageMeta {
|
|||||||
pub grub_version: String,
|
pub grub_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct AsterQcow2ImageMeta {
|
||||||
|
pub grub_version: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl BundleFile for AsterVmImage {
|
impl BundleFile for AsterVmImage {
|
||||||
fn path(&self) -> &PathBuf {
|
fn path(&self) -> &PathBuf {
|
||||||
&self.path
|
&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.
|
/// 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 {
|
pub fn move_to(self, base: impl AsRef<Path>) -> Self {
|
||||||
let file_name = self.path.file_name().unwrap();
|
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_build_command, execute_debug_command, execute_forwarded_command,
|
||||||
execute_new_command, execute_run_command, execute_test_command,
|
execute_new_command, execute_run_command, execute_test_command,
|
||||||
},
|
},
|
||||||
config_manager::{
|
config::{
|
||||||
action::{BootProtocol, Bootloader},
|
manifest::{ProjectType, TomlManifest},
|
||||||
manifest::ProjectType,
|
scheme::{BootMethod, BootProtocol},
|
||||||
BuildConfig, DebugConfig, RunConfig, TestConfig,
|
Config,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let osdk_subcommand = match Cli::parse() {
|
let (osdk_subcommand, common_args) = match Cli::parse() {
|
||||||
Cli {
|
Cli {
|
||||||
cargo_subcommand: CargoSubcommand::Osdk(osdk_subcommand),
|
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 {
|
match &osdk_subcommand {
|
||||||
OsdkSubcommand::New(args) => execute_new_command(args),
|
OsdkSubcommand::New(args) => execute_new_command(args),
|
||||||
OsdkSubcommand::Build(build_args) => {
|
OsdkSubcommand::Build(build_args) => {
|
||||||
let build_config = BuildConfig::parse(build_args);
|
execute_build_command(&load_config(), build_args);
|
||||||
execute_build_command(&build_config);
|
|
||||||
}
|
}
|
||||||
OsdkSubcommand::Run(run_args) => {
|
OsdkSubcommand::Run(run_args) => {
|
||||||
let run_config = RunConfig::parse(run_args);
|
execute_run_command(&load_config(), &run_args.gdb_server_args);
|
||||||
execute_run_command(&run_config);
|
|
||||||
}
|
}
|
||||||
OsdkSubcommand::Debug(debug_args) => {
|
OsdkSubcommand::Debug(debug_args) => {
|
||||||
let debug_config = DebugConfig::parse(debug_args);
|
execute_debug_command(&load_config().run.build.profile, debug_args);
|
||||||
execute_debug_command(&debug_config);
|
|
||||||
}
|
}
|
||||||
OsdkSubcommand::Test(test_args) => {
|
OsdkSubcommand::Test(test_args) => {
|
||||||
let test_config = TestConfig::parse(test_args);
|
execute_test_command(&load_config(), test_args);
|
||||||
execute_test_command(&test_config);
|
|
||||||
}
|
}
|
||||||
OsdkSubcommand::Check(args) => execute_forwarded_command("check", &args.args),
|
OsdkSubcommand::Check(args) => execute_forwarded_command("check", &args.args),
|
||||||
OsdkSubcommand::Clippy(args) => execute_forwarded_command("clippy", &args.args),
|
OsdkSubcommand::Clippy(args) => execute_forwarded_command("clippy", &args.args),
|
||||||
@ -54,6 +57,8 @@ pub fn main() {
|
|||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
cargo_subcommand: CargoSubcommand,
|
cargo_subcommand: CargoSubcommand,
|
||||||
|
#[command(flatten)]
|
||||||
|
common_args: CommonArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@ -136,18 +141,23 @@ impl NewArgs {
|
|||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct BuildArgs {
|
pub struct BuildArgs {
|
||||||
#[command(flatten)]
|
#[arg(
|
||||||
pub cargo_args: CargoArgs,
|
long = "for-test",
|
||||||
#[command(flatten)]
|
help = "Build for running unit tests",
|
||||||
pub osdk_args: OsdkArgs,
|
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)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct RunArgs {
|
pub struct RunArgs {
|
||||||
#[command(flatten)]
|
|
||||||
pub cargo_args: CargoArgs,
|
|
||||||
#[command(flatten)]
|
|
||||||
pub osdk_args: OsdkArgs,
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub gdb_server_args: GdbServerArgs,
|
pub gdb_server_args: GdbServerArgs,
|
||||||
}
|
}
|
||||||
@ -181,10 +191,6 @@ pub struct GdbServerArgs {
|
|||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct DebugArgs {
|
pub struct DebugArgs {
|
||||||
#[command(flatten)]
|
|
||||||
pub cargo_args: CargoArgs,
|
|
||||||
#[command(flatten)]
|
|
||||||
pub osdk_args: OsdkArgs,
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Specify the address of the remote target",
|
help = "Specify the address of the remote target",
|
||||||
@ -195,15 +201,11 @@ pub struct DebugArgs {
|
|||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct TestArgs {
|
pub struct TestArgs {
|
||||||
#[command(flatten)]
|
|
||||||
pub cargo_args: CargoArgs,
|
|
||||||
#[arg(
|
#[arg(
|
||||||
name = "TESTNAME",
|
name = "TESTNAME",
|
||||||
help = "Only run tests containing this string in their names"
|
help = "Only run tests containing this string in their names"
|
||||||
)]
|
)]
|
||||||
pub test_name: Option<String>,
|
pub test_name: Option<String>,
|
||||||
#[command(flatten)]
|
|
||||||
pub osdk_args: OsdkArgs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
@ -211,79 +213,120 @@ pub struct CargoArgs {
|
|||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "The Cargo build profile (built-in candidates are 'dev', 'release', 'test' and 'bench')",
|
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(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Build artifacts in release mode",
|
help = "Build artifacts in release mode",
|
||||||
conflicts_with = "profile"
|
conflicts_with = "profile",
|
||||||
|
global = true
|
||||||
)]
|
)]
|
||||||
pub release: bool,
|
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>,
|
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)]
|
#[derive(Debug, Args)]
|
||||||
pub struct OsdkArgs {
|
pub struct CommonArgs {
|
||||||
#[arg(long, value_name = "ARCH", help = "The architecture to build for")]
|
#[command(flatten)]
|
||||||
pub arch: Option<Arch>,
|
pub build_args: CargoArgs,
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "schema",
|
long = "linux-x86-legacy-boot",
|
||||||
help = "Select the specific configuration schema provided in the OSDK manifest",
|
help = "Enable legacy 32-bit boot support for the Linux x86 boot protocol",
|
||||||
value_name = "SCHEMA"
|
global = true
|
||||||
)]
|
)]
|
||||||
pub schema: Option<String>,
|
pub linux_x86_legacy_boot: bool,
|
||||||
#[arg(
|
#[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,
|
require_equals = true,
|
||||||
help = "Extra or overriding command line arguments for guest kernel",
|
help = "Extra or overriding command line arguments for guest kernel",
|
||||||
value_name = "ARGS"
|
value_name = "ARGS",
|
||||||
|
global = true
|
||||||
)]
|
)]
|
||||||
pub kcmd_args: Vec<String>,
|
pub kcmd_args: Vec<String>,
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "init_args+",
|
long = "init-args",
|
||||||
require_equals = true,
|
require_equals = true,
|
||||||
help = "Extra command line arguments for init process",
|
help = "Extra command line arguments for init process",
|
||||||
value_name = "ARGS"
|
value_name = "ARGS",
|
||||||
|
global = true
|
||||||
)]
|
)]
|
||||||
pub init_args: Vec<String>,
|
pub init_args: Vec<String>,
|
||||||
#[arg(long, help = "Path of initramfs", value_name = "PATH")]
|
#[arg(long, help = "Path of initramfs", value_name = "PATH", global = true)]
|
||||||
pub initramfs: Option<PathBuf>,
|
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(
|
#[arg(
|
||||||
long = "bootloader",
|
long = "boot-method",
|
||||||
help = "Loader for booting the kernel",
|
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(
|
#[arg(
|
||||||
long = "grub-mkrescue",
|
long = "grub-mkrescue",
|
||||||
help = "Path of grub-mkrescue",
|
help = "Path of grub-mkrescue",
|
||||||
value_name = "PATH"
|
value_name = "PATH",
|
||||||
|
global = true
|
||||||
)]
|
)]
|
||||||
pub grub_mkrescue: Option<PathBuf>,
|
pub grub_mkrescue: Option<PathBuf>,
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "boot_protocol",
|
long = "grub-boot-protocol",
|
||||||
help = "Protocol for booting the kernel",
|
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(
|
#[arg(
|
||||||
long = "qemu_exe",
|
long = "qemu-exe",
|
||||||
help = "The QEMU executable file",
|
help = "The QEMU executable file",
|
||||||
value_name = "FILE"
|
value_name = "FILE",
|
||||||
|
global = true
|
||||||
)]
|
)]
|
||||||
pub qemu_exe: Option<PathBuf>,
|
pub qemu_exe: Option<PathBuf>,
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "qemu_args+",
|
long = "qemu-args",
|
||||||
require_equals = true,
|
require_equals = true,
|
||||||
help = "Extra arguments or overriding arguments for running QEMU",
|
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},
|
bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta},
|
||||||
file::BundleFile,
|
file::BundleFile,
|
||||||
},
|
},
|
||||||
config_manager::action::BootProtocol,
|
|
||||||
util::get_current_crate_info,
|
util::get_current_crate_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,13 +21,13 @@ pub fn make_install_bzimage(
|
|||||||
install_dir: impl AsRef<Path>,
|
install_dir: impl AsRef<Path>,
|
||||||
target_dir: impl AsRef<Path>,
|
target_dir: impl AsRef<Path>,
|
||||||
aster_elf: &AsterBin,
|
aster_elf: &AsterBin,
|
||||||
protocol: &BootProtocol,
|
linux_x86_legacy_boot: bool,
|
||||||
) -> AsterBin {
|
) -> AsterBin {
|
||||||
let target_name = get_current_crate_info().name;
|
let target_name = get_current_crate_info().name;
|
||||||
let image_type = match protocol {
|
let image_type = if linux_x86_legacy_boot {
|
||||||
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
|
BzImageType::Legacy32
|
||||||
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
|
} else {
|
||||||
_ => unreachable!(),
|
BzImageType::Efi64
|
||||||
};
|
};
|
||||||
let setup_bin = {
|
let setup_bin = {
|
||||||
let setup_install_dir = target_dir.as_ref();
|
let setup_install_dir = target_dir.as_ref();
|
||||||
@ -60,9 +59,9 @@ pub fn make_install_bzimage(
|
|||||||
AsterBin::new(
|
AsterBin::new(
|
||||||
&install_path,
|
&install_path,
|
||||||
AsterBinType::BzImage(AsterBzImageMeta {
|
AsterBinType::BzImage(AsterBzImageMeta {
|
||||||
support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32),
|
support_legacy32_boot: linux_x86_legacy_boot,
|
||||||
support_efi_boot: false,
|
support_efi_boot: false,
|
||||||
support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64),
|
support_efi_handover: !linux_x86_legacy_boot,
|
||||||
}),
|
}),
|
||||||
aster_elf.version().clone(),
|
aster_elf.version().clone(),
|
||||||
aster_elf.stripped(),
|
aster_elf.stripped(),
|
||||||
|
@ -12,7 +12,10 @@ use crate::{
|
|||||||
file::BundleFile,
|
file::BundleFile,
|
||||||
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
|
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
|
||||||
},
|
},
|
||||||
config_manager::{action::BootProtocol, BuildConfig},
|
config::{
|
||||||
|
scheme::{ActionChoice, BootProtocol},
|
||||||
|
Config,
|
||||||
|
},
|
||||||
util::get_current_crate_info,
|
util::get_current_crate_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,11 +23,16 @@ pub fn create_bootdev_image(
|
|||||||
target_dir: impl AsRef<Path>,
|
target_dir: impl AsRef<Path>,
|
||||||
aster_bin: &AsterBin,
|
aster_bin: &AsterBin,
|
||||||
initramfs_path: Option<impl AsRef<Path>>,
|
initramfs_path: Option<impl AsRef<Path>>,
|
||||||
config: &BuildConfig,
|
config: &Config,
|
||||||
|
action: ActionChoice,
|
||||||
) -> AsterVmImage {
|
) -> AsterVmImage {
|
||||||
let target_name = get_current_crate_info().name;
|
let target_name = get_current_crate_info().name;
|
||||||
let iso_root = &target_dir.as_ref().join("iso_root");
|
let iso_root = &target_dir.as_ref().join("iso_root");
|
||||||
let protocol = &config.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.
|
// Clear or make the iso dir.
|
||||||
if iso_root.exists() {
|
if iso_root.exists() {
|
||||||
@ -43,12 +51,12 @@ pub fn create_bootdev_image(
|
|||||||
|
|
||||||
// Make the kernel image and place it in the boot directory.
|
// Make the kernel image and place it in the boot directory.
|
||||||
match protocol {
|
match protocol {
|
||||||
Some(BootProtocol::LinuxLegacy32) | Some(BootProtocol::LinuxEfiHandover64) => {
|
BootProtocol::Linux => {
|
||||||
make_install_bzimage(
|
make_install_bzimage(
|
||||||
iso_root.join("boot"),
|
iso_root.join("boot"),
|
||||||
&target_dir,
|
&target_dir,
|
||||||
aster_bin,
|
aster_bin,
|
||||||
&protocol.clone().unwrap(),
|
action.build.linux_x86_legacy_boot,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -65,22 +73,17 @@ pub fn create_bootdev_image(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
let grub_cfg = generate_grub_cfg(
|
let grub_cfg = generate_grub_cfg(
|
||||||
&config.settings.combined_kcmd_args().join(" "),
|
&action.boot.kcmdline.join(" "),
|
||||||
true,
|
!action.grub.display_grub_menu,
|
||||||
initramfs_in_image,
|
initramfs_in_image,
|
||||||
&protocol.clone().unwrap_or(BootProtocol::Multiboot2),
|
protocol,
|
||||||
);
|
);
|
||||||
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
|
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
|
||||||
fs::write(grub_cfg_path, grub_cfg).unwrap();
|
fs::write(grub_cfg_path, grub_cfg).unwrap();
|
||||||
|
|
||||||
// Make the boot device CDROM image using `grub-mkrescue`.
|
// Make the boot device CDROM image using `grub-mkrescue`.
|
||||||
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
|
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
|
||||||
let grub_mkrescue_bin = &config
|
let mut grub_mkrescue_cmd = std::process::Command::new(action.grub.grub_mkrescue.as_os_str());
|
||||||
.settings
|
|
||||||
.grub_mkrescue
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| PathBuf::from("grub-mkrescue"));
|
|
||||||
let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
|
|
||||||
grub_mkrescue_cmd
|
grub_mkrescue_cmd
|
||||||
.arg(iso_root.as_os_str())
|
.arg(iso_root.as_os_str())
|
||||||
.arg("-o")
|
.arg("-o")
|
||||||
@ -92,7 +95,7 @@ pub fn create_bootdev_image(
|
|||||||
AsterVmImage::new(
|
AsterVmImage::new(
|
||||||
iso_path,
|
iso_path,
|
||||||
AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
|
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(),
|
aster_bin.version().clone(),
|
||||||
)
|
)
|
||||||
@ -115,7 +118,7 @@ fn generate_grub_cfg(
|
|||||||
"#GRUB_TIMEOUT_STYLE#",
|
"#GRUB_TIMEOUT_STYLE#",
|
||||||
if skip_grub_menu { "hidden" } else { "menu" },
|
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.
|
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
|
||||||
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
|
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
|
||||||
// Replace the grub commands according to the protocol selected.
|
// Replace the grub commands according to the protocol selected.
|
||||||
@ -147,7 +150,7 @@ fn generate_grub_cfg(
|
|||||||
"".to_owned()
|
"".to_owned()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => grub_cfg
|
BootProtocol::Linux => grub_cfg
|
||||||
.replace("#GRUB_CMD_KERNEL#", "linux")
|
.replace("#GRUB_CMD_KERNEL#", "linux")
|
||||||
.replace("#KERNEL#", &aster_bin_path_on_device)
|
.replace("#KERNEL#", &aster_bin_path_on_device)
|
||||||
.replace(
|
.replace(
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
mod bin;
|
mod bin;
|
||||||
mod grub;
|
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;
|
use bin::strip_elf_for_qemu;
|
||||||
|
|
||||||
@ -15,39 +20,53 @@ use crate::{
|
|||||||
bin::{AsterBin, AsterBinType, AsterElfMeta},
|
bin::{AsterBin, AsterBinType, AsterElfMeta},
|
||||||
Bundle,
|
Bundle,
|
||||||
},
|
},
|
||||||
cli::CargoArgs,
|
cli::BuildArgs,
|
||||||
config_manager::{action::Bootloader, BuildConfig},
|
config::{
|
||||||
|
scheme::{ActionChoice, BootMethod},
|
||||||
|
Config,
|
||||||
|
},
|
||||||
error::Errno,
|
error::Errno,
|
||||||
error_msg,
|
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) {
|
pub fn execute_build_command(config: &Config, build_args: &BuildArgs) {
|
||||||
let ws_target_directory = get_target_directory();
|
let cargo_target_directory = get_target_directory();
|
||||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
let osdk_output_directory = build_args
|
||||||
if !osdk_target_directory.exists() {
|
.output
|
||||||
std::fs::create_dir_all(&osdk_target_directory).unwrap();
|
.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 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,
|
bundle_path,
|
||||||
&osdk_target_directory,
|
&osdk_output_directory,
|
||||||
&ws_target_directory,
|
&cargo_target_directory,
|
||||||
config,
|
config,
|
||||||
|
action,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_base_and_build(
|
pub fn create_base_and_cached_build(
|
||||||
bundle_path: impl AsRef<Path>,
|
bundle_path: impl AsRef<Path>,
|
||||||
osdk_target_directory: impl AsRef<Path>,
|
osdk_output_directory: impl AsRef<Path>,
|
||||||
cargo_target_directory: impl AsRef<Path>,
|
cargo_target_directory: impl AsRef<Path>,
|
||||||
config: &BuildConfig,
|
config: &Config,
|
||||||
|
action: ActionChoice,
|
||||||
rustflags: &[&str],
|
rustflags: &[&str],
|
||||||
) -> Bundle {
|
) -> 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(
|
new_base_crate(
|
||||||
&base_crate_path,
|
&base_crate_path,
|
||||||
&get_current_crate_info().name,
|
&get_current_crate_info().name,
|
||||||
@ -55,55 +74,108 @@ pub fn create_base_and_build(
|
|||||||
);
|
);
|
||||||
let original_dir = std::env::current_dir().unwrap();
|
let original_dir = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&base_crate_path).unwrap();
|
std::env::set_current_dir(&base_crate_path).unwrap();
|
||||||
let bundle = do_build(
|
let bundle = do_cached_build(
|
||||||
&bundle_path,
|
&bundle_path,
|
||||||
&osdk_target_directory,
|
&osdk_output_directory,
|
||||||
&cargo_target_directory,
|
&cargo_target_directory,
|
||||||
config,
|
config,
|
||||||
|
action,
|
||||||
rustflags,
|
rustflags,
|
||||||
);
|
);
|
||||||
std::env::set_current_dir(original_dir).unwrap();
|
std::env::set_current_dir(original_dir).unwrap();
|
||||||
bundle
|
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(
|
pub fn do_build(
|
||||||
bundle_path: impl AsRef<Path>,
|
bundle_path: impl AsRef<Path>,
|
||||||
osdk_target_directory: impl AsRef<Path>,
|
osdk_output_directory: impl AsRef<Path>,
|
||||||
cargo_target_directory: impl AsRef<Path>,
|
cargo_target_directory: impl AsRef<Path>,
|
||||||
config: &BuildConfig,
|
config: &Config,
|
||||||
|
action: ActionChoice,
|
||||||
rustflags: &[&str],
|
rustflags: &[&str],
|
||||||
) -> Bundle {
|
) -> Bundle {
|
||||||
if bundle_path.as_ref().exists() {
|
if bundle_path.as_ref().exists() {
|
||||||
std::fs::remove_dir_all(&bundle_path).unwrap();
|
std::fs::remove_dir_all(&bundle_path).unwrap();
|
||||||
}
|
}
|
||||||
let mut bundle = Bundle::new(
|
let mut bundle = Bundle::new(&bundle_path, config, action);
|
||||||
&bundle_path,
|
|
||||||
config.settings.clone(),
|
|
||||||
config.cargo_args.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Building kernel ELF");
|
info!("Building kernel ELF");
|
||||||
let aster_elf = build_kernel_elf(
|
let aster_elf = build_kernel_elf(
|
||||||
&config.arch,
|
&config.target_arch,
|
||||||
&config.cargo_args,
|
&config.build.profile,
|
||||||
|
&config.build.features[..],
|
||||||
&cargo_target_directory,
|
&cargo_target_directory,
|
||||||
rustflags,
|
rustflags,
|
||||||
);
|
);
|
||||||
|
|
||||||
if matches!(config.settings.bootloader, Some(Bootloader::Qemu)) {
|
let boot = match action {
|
||||||
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
|
ActionChoice::Run => &config.run.boot,
|
||||||
bundle.consume_aster_bin(stripped_elf);
|
ActionChoice::Test => &config.test.boot,
|
||||||
}
|
};
|
||||||
|
|
||||||
if matches!(config.settings.bootloader, Some(Bootloader::Grub)) {
|
match boot.method {
|
||||||
info!("Building boot device image");
|
BootMethod::GrubRescueIso => {
|
||||||
let bootdev_image = grub::create_bootdev_image(
|
info!("Building boot device image");
|
||||||
&osdk_target_directory,
|
let bootdev_image = grub::create_bootdev_image(
|
||||||
&aster_elf,
|
&osdk_output_directory,
|
||||||
config.settings.initramfs.as_ref(),
|
&aster_elf,
|
||||||
config,
|
boot.initramfs.as_ref(),
|
||||||
);
|
config,
|
||||||
bundle.consume_vm_image(bootdev_image);
|
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
|
bundle
|
||||||
@ -111,7 +183,8 @@ pub fn do_build(
|
|||||||
|
|
||||||
fn build_kernel_elf(
|
fn build_kernel_elf(
|
||||||
arch: &Arch,
|
arch: &Arch,
|
||||||
cargo_args: &CargoArgs,
|
profile: &str,
|
||||||
|
features: &[String],
|
||||||
cargo_target_directory: impl AsRef<Path>,
|
cargo_target_directory: impl AsRef<Path>,
|
||||||
rustflags: &[&str],
|
rustflags: &[&str],
|
||||||
) -> AsterBin {
|
) -> AsterBin {
|
||||||
@ -135,12 +208,13 @@ fn build_kernel_elf(
|
|||||||
command.env_remove("RUSTUP_TOOLCHAIN");
|
command.env_remove("RUSTUP_TOOLCHAIN");
|
||||||
command.env("RUSTFLAGS", rustflags.join(" "));
|
command.env("RUSTFLAGS", rustflags.join(" "));
|
||||||
command.arg("build");
|
command.arg("build");
|
||||||
|
command.arg("--features").arg(features.join(" "));
|
||||||
command.arg("--target").arg(&target_os_string);
|
command.arg("--target").arg(&target_os_string);
|
||||||
command
|
command
|
||||||
.arg("--target-dir")
|
.arg("--target-dir")
|
||||||
.arg(cargo_target_directory.as_ref());
|
.arg(cargo_target_directory.as_ref());
|
||||||
command.args(COMMON_CARGO_ARGS);
|
command.args(COMMON_CARGO_ARGS);
|
||||||
command.arg("--profile=".to_string() + &cargo_args.profile);
|
command.arg("--profile=".to_string() + profile);
|
||||||
let status = command.status().unwrap();
|
let status = command.status().unwrap();
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
error_msg!("Cargo build failed");
|
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 = 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")
|
aster_bin_path.join("debug")
|
||||||
} else {
|
} else {
|
||||||
aster_bin_path.join(&cargo_args.profile)
|
aster_bin_path.join(profile)
|
||||||
}
|
}
|
||||||
.join(get_current_crate_info().name);
|
.join(get_current_crate_info().name);
|
||||||
|
|
||||||
@ -167,3 +241,21 @@ fn build_kernel_elf(
|
|||||||
false,
|
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
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::commands::util::{bin_file_name, profile_adapter};
|
use crate::commands::util::bin_file_name;
|
||||||
use crate::config_manager::DebugConfig;
|
|
||||||
|
|
||||||
use crate::util::get_target_directory;
|
use crate::{cli::DebugArgs, util::get_target_directory};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn execute_debug_command(config: &DebugConfig) {
|
pub fn execute_debug_command(profile: &String, args: &DebugArgs) {
|
||||||
let DebugConfig { cargo_args, remote } = config;
|
let remote = &args.remote;
|
||||||
|
|
||||||
let profile = profile_adapter(&cargo_args.profile);
|
|
||||||
let file_path = get_target_directory()
|
let file_path = get_target_directory()
|
||||||
.join("x86_64-unknown-none")
|
.join("x86_64-unknown-none")
|
||||||
.join(profile)
|
.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.
|
/// Execute the forwarded cargo command with args containing the subcommand and its arguments.
|
||||||
pub fn execute_forwarded_command(subcommand: &str, args: &Vec<String>) -> ! {
|
pub fn execute_forwarded_command(subcommand: &str, args: &Vec<String>) -> ! {
|
||||||
let mut cargo = util::cargo();
|
let mut cargo = util::cargo();
|
||||||
cargo
|
cargo.arg(subcommand).args(util::COMMON_CARGO_ARGS);
|
||||||
.arg(subcommand)
|
if !args.contains(&"--target".to_owned()) {
|
||||||
.args(util::COMMON_CARGO_ARGS)
|
cargo.arg("--target").arg(get_default_arch().triple());
|
||||||
.arg("--target")
|
}
|
||||||
.arg(get_default_arch().triple())
|
cargo.args(args);
|
||||||
.args(args);
|
|
||||||
let status = cargo.status().expect("Failed to execute cargo");
|
let status = cargo.status().expect("Failed to execute cargo");
|
||||||
std::process::exit(status.code().unwrap_or(1));
|
std::process::exit(status.code().unwrap_or(1));
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
[project]
|
project_type = "kernel"
|
||||||
type = "kernel"
|
|
||||||
|
|
||||||
[run]
|
vars = [
|
||||||
bootloader = "grub"
|
["OVMF_PATH", "/usr/share/OVMF"],
|
||||||
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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[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]
|
project_type = "lib"
|
||||||
type = "library"
|
|
||||||
|
|
||||||
[test]
|
[boot]
|
||||||
bootloader = "qemu"
|
method = "qemu-direct"
|
||||||
qemu_args = [
|
|
||||||
"-machine q35,kernel-irqchip=split",
|
[qemu]
|
||||||
"-cpu Icelake-Server,+x2apic",
|
args = """\
|
||||||
"--no-reboot",
|
-machine q35,kernel-irqchip=split \
|
||||||
"-m 2G",
|
-cpu Icelake-Server,+x2apic \
|
||||||
"-nographic",
|
--no-reboot \
|
||||||
"-serial chardev:mux",
|
-m 2G \
|
||||||
"-monitor chardev:mux",
|
-smp 1 \
|
||||||
"-chardev stdio,id=mux,mux=on,signal=off",
|
-nographic \
|
||||||
"-display none",
|
-serial chardev:mux \
|
||||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
-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::{
|
use crate::{
|
||||||
cli::NewArgs,
|
cli::NewArgs,
|
||||||
config_manager::manifest::ProjectType,
|
config::manifest::ProjectType,
|
||||||
error::Errno,
|
error::Errno,
|
||||||
error_msg,
|
error_msg,
|
||||||
util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata},
|
util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata},
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use std::{
|
use super::{build::create_base_and_cached_build, util::DEFAULT_TARGET_RELPATH};
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{build::create_base_and_build, util::DEFAULT_TARGET_RELPATH};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
cli::GdbServerArgs,
|
||||||
config_manager::{BuildConfig, RunConfig},
|
config::{scheme::ActionChoice, Config},
|
||||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
util::{get_current_crate_info, get_target_directory},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn execute_run_command(config: &RunConfig) {
|
pub fn execute_run_command(config: &Config, gdb_server_args: &GdbServerArgs) {
|
||||||
if config.gdb_server_args.is_gdb_enabled {
|
if gdb_server_args.is_gdb_enabled {
|
||||||
use std::env;
|
use std::env;
|
||||||
env::set_var(
|
env::set_var(
|
||||||
"RUSTFLAGS",
|
"RUSTFLAGS",
|
||||||
@ -21,111 +16,48 @@ pub fn execute_run_command(config: &RunConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ws_target_directory = get_target_directory();
|
let cargo_target_directory = get_target_directory();
|
||||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||||
let target_name = get_current_crate_info().name;
|
let target_name = get_current_crate_info().name;
|
||||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
|
||||||
let existing_bundle = Bundle::load(&default_bundle_directory);
|
|
||||||
|
|
||||||
let config = RunConfig {
|
let mut config = config.clone();
|
||||||
settings: {
|
if gdb_server_args.is_gdb_enabled {
|
||||||
if config.gdb_server_args.is_gdb_enabled {
|
let qemu_gdb_args = {
|
||||||
let qemu_gdb_args: Vec<_> = {
|
let gdb_stub_addr = gdb_server_args.gdb_server_addr.as_str();
|
||||||
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
|
match gdb::stub_type_of(gdb_stub_addr) {
|
||||||
match gdb::stub_type_of(gdb_stub_addr) {
|
gdb::StubAddrType::Unix => {
|
||||||
gdb::StubAddrType::Unix => {
|
format!(
|
||||||
let chardev = format!(
|
" -chardev socket,path={},server=on,wait=off,id=gdb0 -gdb chardev:gdb0 -S",
|
||||||
"-chardev socket,path={},server=on,wait=off,id=gdb0",
|
gdb_stub_addr
|
||||||
gdb_stub_addr
|
)
|
||||||
);
|
}
|
||||||
let stub = "-gdb chardev:gdb0".to_owned();
|
gdb::StubAddrType::Tcp => {
|
||||||
vec![chardev, stub, "-S".into()]
|
format!(
|
||||||
}
|
" -gdb tcp:{} -S",
|
||||||
gdb::StubAddrType::Tcp => {
|
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
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 {
|
let default_bundle_directory = osdk_output_directory.join(target_name);
|
||||||
arch: config.arch,
|
let bundle = create_base_and_cached_build(
|
||||||
settings: config.settings.clone(),
|
|
||||||
cargo_args: config.cargo_args.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let bundle = create_base_and_build(
|
|
||||||
default_bundle_directory,
|
default_bundle_directory,
|
||||||
&osdk_target_directory,
|
&osdk_output_directory,
|
||||||
&ws_target_directory,
|
&cargo_target_directory,
|
||||||
&required_build_config,
|
&config,
|
||||||
|
ActionChoice::Run,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
bundle.run(&config);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
bundle.run(&config, ActionChoice::Run);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod gdb {
|
mod gdb {
|
||||||
|
@ -2,38 +2,38 @@
|
|||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use super::{build::do_build, util::DEFAULT_TARGET_RELPATH};
|
use super::{build::do_cached_build, util::DEFAULT_TARGET_RELPATH};
|
||||||
use crate::{
|
use crate::{
|
||||||
base_crate::new_base_crate,
|
base_crate::new_base_crate,
|
||||||
cli::GdbServerArgs,
|
cli::TestArgs,
|
||||||
config_manager::{BuildConfig, RunConfig, TestConfig},
|
config::{scheme::ActionChoice, Config},
|
||||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
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();
|
let crates = get_workspace_default_members();
|
||||||
for crate_path in crates {
|
for crate_path in crates {
|
||||||
std::env::set_current_dir(crate_path).unwrap();
|
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 current_crate = get_current_crate_info();
|
||||||
let ws_target_directory = get_target_directory();
|
let cargo_target_directory = get_target_directory();
|
||||||
let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH);
|
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
|
||||||
let target_crate_dir = osdk_target_directory.join("base");
|
let target_crate_dir = osdk_output_directory.join("base");
|
||||||
new_base_crate(&target_crate_dir, ¤t_crate.name, ¤t_crate.path);
|
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 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),
|
Some(name) => format!(r#"Some(&["{}"])"#, name),
|
||||||
None => r#"None"#.to_string(),
|
None => r#"None"#.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ktest_crate_whitelist = vec![current_crate.name];
|
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());
|
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
|
// Build the kernel with the given base crate
|
||||||
let target_name = get_current_crate_info().name;
|
let target_name = get_current_crate_info().name;
|
||||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
let default_bundle_directory = osdk_output_directory.join(target_name);
|
||||||
let required_build_config = BuildConfig {
|
|
||||||
arch: config.arch,
|
|
||||||
settings: config.settings.clone(),
|
|
||||||
cargo_args: config.cargo_args.clone(),
|
|
||||||
};
|
|
||||||
let original_dir = std::env::current_dir().unwrap();
|
let original_dir = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&target_crate_dir).unwrap();
|
std::env::set_current_dir(&target_crate_dir).unwrap();
|
||||||
let bundle = do_build(
|
let bundle = do_cached_build(
|
||||||
default_bundle_directory,
|
default_bundle_directory,
|
||||||
&osdk_target_directory,
|
&osdk_output_directory,
|
||||||
&ws_target_directory,
|
&cargo_target_directory,
|
||||||
&required_build_config,
|
config,
|
||||||
|
ActionChoice::Test,
|
||||||
&["--cfg ktest"],
|
&["--cfg ktest"],
|
||||||
);
|
);
|
||||||
std::env::remove_var("RUSTFLAGS");
|
std::env::remove_var("RUSTFLAGS");
|
||||||
std::env::set_current_dir(original_dir).unwrap();
|
std::env::set_current_dir(original_dir).unwrap();
|
||||||
|
|
||||||
let required_run_config = RunConfig {
|
bundle.run(config, ActionChoice::Test);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_workspace_default_members() -> Vec<String> {
|
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 bundle;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config_manager;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use crate::util::*;
|
|||||||
fn cli_help_message() {
|
fn cli_help_message() {
|
||||||
let output = cargo_osdk(&["-h"]).output().unwrap();
|
let output = cargo_osdk(&["-h"]).output().unwrap();
|
||||||
assert_success(&output);
|
assert_success(&output);
|
||||||
assert_stdout_contains_msg(&output, "cargo osdk <COMMAND>");
|
assert_stdout_contains_msg(&output, "cargo osdk [OPTIONS] <COMMAND>");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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