mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-28 11:53:24 +00:00
Implement OSDK functionalities and opt-in OSDK for asterinas
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bc9bce9dea
commit
f97d0f1260
12
osdk/src/base_crate/main.rs.template
Normal file
12
osdk/src/base_crate/main.rs.template
Normal file
@ -0,0 +1,12 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate #TARGET_NAME#;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
extern "Rust" {
|
||||
pub fn __aster_panic_handler(info: &core::panic::PanicInfo) -> !;
|
||||
}
|
||||
unsafe { __aster_panic_handler(info); }
|
||||
}
|
108
osdk/src/base_crate/mod.rs
Normal file
108
osdk/src/base_crate/mod.rs
Normal file
@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! The base crate is the OSDK generated crate that is ultimately built by cargo.
|
||||
//! It will depend on the kernel crate.
|
||||
//!
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, process};
|
||||
|
||||
use crate::error::Errno;
|
||||
use crate::error_msg;
|
||||
|
||||
pub fn new_base_crate(
|
||||
base_crate_path: impl AsRef<Path>,
|
||||
dep_crate_name: &str,
|
||||
dep_crate_path: impl AsRef<Path>,
|
||||
) {
|
||||
if base_crate_path.as_ref().exists() {
|
||||
std::fs::remove_dir_all(&base_crate_path).unwrap();
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("new").arg("--bin").arg(base_crate_path.as_ref());
|
||||
cmd.arg("--vcs").arg("none");
|
||||
|
||||
if !cmd.status().unwrap().success() {
|
||||
error_msg!(
|
||||
"Failed to create base crate at: {:#?}",
|
||||
base_crate_path.as_ref()
|
||||
);
|
||||
process::exit(Errno::CreateBaseCrate as _);
|
||||
}
|
||||
|
||||
// Set the current directory to the target osdk directory
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(&base_crate_path).unwrap();
|
||||
|
||||
// Add linker.ld file
|
||||
let linker_ld = include_str!("x86_64-custom.ld.template");
|
||||
fs::write("x86_64-custom.ld", linker_ld).unwrap();
|
||||
|
||||
// Add target json file
|
||||
let target_json = include_str!("x86_64-custom.json.template");
|
||||
fs::write("x86_64-custom.json", target_json).unwrap();
|
||||
|
||||
// Overrite the main.rs file
|
||||
let main_rs = include_str!("main.rs.template");
|
||||
// Replace all occurence of `#TARGET_NAME#` with the `dep_crate_name`
|
||||
let main_rs = main_rs.replace("#TARGET_NAME#", &dep_crate_name.replace("-", "_"));
|
||||
fs::write("src/main.rs", main_rs).unwrap();
|
||||
|
||||
// Add dependencies to the Cargo.toml
|
||||
add_manifest_dependency(dep_crate_name, dep_crate_path);
|
||||
|
||||
// Copy the manifest configurations from the target crate to the base crate
|
||||
copy_manifest_configurations(base_crate_path);
|
||||
|
||||
// Get back to the original directory
|
||||
std::env::set_current_dir(&original_dir).unwrap();
|
||||
}
|
||||
|
||||
fn add_manifest_dependency(crate_name: &str, crate_path: impl AsRef<Path>) {
|
||||
let mainfest_path = "Cargo.toml";
|
||||
|
||||
let mut manifest: toml::Table = {
|
||||
let content = fs::read_to_string(mainfest_path).unwrap();
|
||||
toml::from_str(&content).unwrap()
|
||||
};
|
||||
|
||||
let dependencies = manifest.get_mut("dependencies").unwrap();
|
||||
|
||||
let dep = toml::Table::from_str(&format!(
|
||||
"{} = {{ path = \"{}\"}}",
|
||||
crate_name,
|
||||
crate_path.as_ref().display()
|
||||
))
|
||||
.unwrap();
|
||||
dependencies.as_table_mut().unwrap().extend(dep);
|
||||
|
||||
let content = toml::to_string(&manifest).unwrap();
|
||||
fs::write(mainfest_path, content).unwrap();
|
||||
}
|
||||
|
||||
fn copy_manifest_configurations(target_crate_path: impl AsRef<Path>) {
|
||||
let target_manifest_path = target_crate_path.as_ref().join("Cargo.toml");
|
||||
let manifest_path = "Cargo.toml";
|
||||
|
||||
let target_manifest: toml::Table = {
|
||||
let content = fs::read_to_string(target_manifest_path).unwrap();
|
||||
toml::from_str(&content).unwrap()
|
||||
};
|
||||
|
||||
let mut manifest: toml::Table = {
|
||||
let content = fs::read_to_string(manifest_path).unwrap();
|
||||
toml::from_str(&content).unwrap()
|
||||
};
|
||||
|
||||
// Copy the profile configurations
|
||||
let profile = target_manifest.get("profile");
|
||||
if let Some(profile) = profile {
|
||||
manifest.insert("profile".to_string(), profile.clone());
|
||||
}
|
||||
|
||||
let content = toml::to_string(&manifest).unwrap();
|
||||
fs::write(manifest_path, content).unwrap();
|
||||
}
|
@ -11,6 +11,11 @@
|
||||
"executables": true,
|
||||
"linker-flavor": "ld.lld",
|
||||
"linker": "rust-lld",
|
||||
"pre-link-args": {
|
||||
"ld.lld": [
|
||||
"--script=x86_64-custom.ld"
|
||||
]
|
||||
},
|
||||
"disable-redzone": true,
|
||||
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float"
|
||||
}
|
74
osdk/src/base_crate/x86_64-custom.ld.template
Normal file
74
osdk/src/base_crate/x86_64-custom.ld.template
Normal file
@ -0,0 +1,74 @@
|
||||
ENTRY(__multiboot_boot)
|
||||
OUTPUT_ARCH(i386:x86-64)
|
||||
OUTPUT_FORMAT(elf64-x86-64)
|
||||
|
||||
KERNEL_LMA = 0x8000000;
|
||||
LINUX_32_ENTRY = 0x8001000;
|
||||
KERNEL_VMA = 0xffffffff80000000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = KERNEL_LMA;
|
||||
|
||||
__kernel_start = .;
|
||||
|
||||
.multiboot_header : { KEEP(*(.multiboot_header)) }
|
||||
.multiboot2_header : { KEEP(*(.multiboot2_header)) }
|
||||
|
||||
. = LINUX_32_ENTRY;
|
||||
|
||||
.boot : { KEEP(*(.boot)) }
|
||||
|
||||
. += KERNEL_VMA;
|
||||
|
||||
.text : AT(ADDR(.text) - KERNEL_VMA) {
|
||||
*(.text .text.*)
|
||||
PROVIDE(__etext = .);
|
||||
}
|
||||
.rodata : AT(ADDR(.rodata) - KERNEL_VMA) { *(.rodata .rodata.*) }
|
||||
|
||||
.eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - KERNEL_VMA) {
|
||||
KEEP(*(.eh_frame_hdr .eh_frame_hdr.*))
|
||||
}
|
||||
. = ALIGN(8);
|
||||
.eh_frame : AT(ADDR(.eh_frame) - KERNEL_VMA) {
|
||||
PROVIDE(__eh_frame = .);
|
||||
KEEP(*(.eh_frame .eh_frame.*))
|
||||
}
|
||||
|
||||
.gcc_except_table : AT(ADDR(.gcc_except_table) - KERNEL_VMA) { *(.gcc_except_table .gcc_except_table.*) }
|
||||
|
||||
.data.rel.ro : AT(ADDR(.data.rel.ro) - KERNEL_VMA) { *(.data.rel.ro .data.rel.ro.*) }
|
||||
.dynamic : AT(ADDR(.dynamic) - KERNEL_VMA) { *(.dynamic) }
|
||||
|
||||
.init_array : AT(ADDR(.init_array) - KERNEL_VMA) {
|
||||
__sinit_array = .;
|
||||
KEEP(*(SORT(.init_array .init_array.*)))
|
||||
__einit_array = .;
|
||||
}
|
||||
|
||||
.got : AT(ADDR(.got) - KERNEL_VMA) { *(.got .got.*) }
|
||||
.got.plt : AT(ADDR(.got.plt) - KERNEL_VMA) { *(.got.plt .got.plt.*) }
|
||||
|
||||
. = DATA_SEGMENT_RELRO_END(0, .);
|
||||
|
||||
.data : AT(ADDR(.data) - KERNEL_VMA) { *(.data .data.*) }
|
||||
.bss : AT(ADDR(.bss) - KERNEL_VMA) {
|
||||
__bss = .;
|
||||
*(.bss .bss.*) *(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
.ktest_array : AT(ADDR(.ktest_array) - KERNEL_VMA) {
|
||||
__ktest_array = .;
|
||||
KEEP(*(SORT(.ktest_array)))
|
||||
__ktest_array_end = .;
|
||||
}
|
||||
|
||||
.tdata : AT(ADDR(.tdata) - KERNEL_VMA) { *(.tdata .tdata.*) }
|
||||
.tbss : AT(ADDR(.tbss) - KERNEL_VMA) { *(.tbss .tbss.*) }
|
||||
|
||||
. = DATA_SEGMENT_END(.);
|
||||
|
||||
__kernel_end = . - KERNEL_VMA;
|
||||
}
|
33
osdk/src/bin.rs
Normal file
33
osdk/src/bin.rs
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterBin {
|
||||
pub path: PathBuf,
|
||||
pub typ: AsterBinType,
|
||||
pub version: String,
|
||||
pub sha256sum: String,
|
||||
pub stripped: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AsterBinType {
|
||||
Elf(AsterElfMeta),
|
||||
BzImage(AsterBzImageMeta),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterElfMeta {
|
||||
pub has_linux_header: bool,
|
||||
pub has_pvh_header: bool,
|
||||
pub has_multiboot_header: bool,
|
||||
pub has_multiboot2_header: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterBzImageMeta {
|
||||
pub support_legacy32_boot: bool,
|
||||
pub support_efi_boot: bool,
|
||||
pub support_efi_handover: bool,
|
||||
}
|
191
osdk/src/bundle.rs
Normal file
191
osdk/src/bundle.rs
Normal file
@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::bin::AsterBin;
|
||||
use crate::cli::CargoArgs;
|
||||
use crate::config_manager::{
|
||||
boot::Boot,
|
||||
qemu::{Qemu, QemuMachine},
|
||||
RunConfig,
|
||||
};
|
||||
use crate::vm_image::AsterVmImage;
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
/// The osdk bundle artifact that stores as `bundle` directory.
|
||||
///
|
||||
/// This `Bundle` struct is used to track a bundle on a filesystem. Every modification to the bundle
|
||||
/// would result in file system writes. But the bundle will not be removed from the file system when
|
||||
/// the `Bundle` is dropped.
|
||||
pub struct Bundle {
|
||||
manifest: BundleManifest,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
pub fn new(manifest: BundleManifest, path: impl AsRef<Path>) -> Self {
|
||||
std::fs::create_dir_all(path.as_ref()).unwrap();
|
||||
let created = Self {
|
||||
manifest,
|
||||
path: path.as_ref().to_path_buf(),
|
||||
};
|
||||
created.write_manifest_content();
|
||||
created
|
||||
}
|
||||
|
||||
// FIXME: the load function should be used when implementing build cache, but it is not
|
||||
// implemented yet.
|
||||
#[allow(dead_code)]
|
||||
pub fn load(path: impl AsRef<Path>) -> Self {
|
||||
let manifest_file_path = path.as_ref().join("bundle.toml");
|
||||
let manifest_file_content = std::fs::read_to_string(&manifest_file_path).unwrap();
|
||||
let manifest: BundleManifest = toml::from_str(&manifest_file_content).unwrap();
|
||||
// TODO: check integrity of the loaded bundle.
|
||||
Self {
|
||||
manifest,
|
||||
path: path.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
|
||||
// TODO: This pairwise comparison will result in some false negatives. We may
|
||||
// fix it by pondering upon each fields with more care.
|
||||
self.manifest.kcmd_args == config.manifest.kcmd_args
|
||||
&& self.manifest.initramfs == config.manifest.initramfs
|
||||
&& self.manifest.boot == config.manifest.boot
|
||||
&& self.manifest.qemu == config.manifest.qemu
|
||||
&& self.manifest.cargo_args == config.cargo_args
|
||||
}
|
||||
|
||||
pub fn run(&self, config: &RunConfig) {
|
||||
if !self.can_run_with_config(config) {
|
||||
error_msg!("The bundle is not compatible with the run configuration");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
}
|
||||
let mut qemu_cmd = Command::new(config.manifest.qemu.path.clone().unwrap());
|
||||
// FIXME: Arguments like "-m 2G" sould be separated into "-m" and "2G". This
|
||||
// is a dirty hack to make it work. Anything like space in the paths will
|
||||
// break this.
|
||||
for arg in &config.manifest.qemu.args {
|
||||
for part in arg.split_whitespace() {
|
||||
qemu_cmd.arg(part);
|
||||
}
|
||||
}
|
||||
match config.manifest.qemu.machine {
|
||||
QemuMachine::Microvm => {
|
||||
qemu_cmd.arg("-machine").arg("microvm");
|
||||
let Some(ref aster_bin) = self.manifest.aster_bin else {
|
||||
error_msg!("Kernel ELF binary is required for Microvm");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
qemu_cmd.arg("-kernel").arg(self.path.join(&aster_bin.path));
|
||||
let Some(ref initramfs) = config.manifest.initramfs else {
|
||||
error_msg!("Initramfs is required for Microvm");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
qemu_cmd.arg("-initrd").arg(initramfs);
|
||||
qemu_cmd
|
||||
.arg("-append")
|
||||
.arg(config.manifest.kcmd_args.join(" "));
|
||||
}
|
||||
QemuMachine::Q35 => {
|
||||
qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split");
|
||||
let Some(ref vm_image) = self.manifest.vm_image else {
|
||||
error_msg!("VM image is required for QEMU booting");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
qemu_cmd.arg("-cdrom").arg(self.path.join(&vm_image.path));
|
||||
if let Some(ovmf) = &config.manifest.boot.ovmf {
|
||||
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()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic");
|
||||
|
||||
for drive_file in &config.manifest.qemu.drive_files {
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"file={},{}",
|
||||
drive_file.path.display(),
|
||||
drive_file.append,
|
||||
));
|
||||
}
|
||||
|
||||
let exit_status = qemu_cmd.status().unwrap();
|
||||
if !exit_status.success() {
|
||||
// FIXME: Exit code manipulation is not needed when using non-x86 QEMU
|
||||
let qemu_exit_code = exit_status.code().unwrap();
|
||||
let kernel_exit_code = qemu_exit_code >> 1;
|
||||
match kernel_exit_code {
|
||||
0x10 /*aster_frame::QemuExitCode::Success*/ => { std::process::exit(0); },
|
||||
0x20 /*aster_frame::QemuExitCode::Failed*/ => { std::process::exit(1); },
|
||||
_ /* unknown, e.g., a triple fault */ => { std::process::exit(2) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_vm_image(&mut self, vm_image: &AsterVmImage) {
|
||||
if self.manifest.vm_image.is_some() {
|
||||
panic!("vm_image already exists");
|
||||
}
|
||||
let file_name = vm_image.path.file_name().unwrap();
|
||||
let copied_path = self.path.join(file_name);
|
||||
std::fs::copy(&vm_image.path, &copied_path).unwrap();
|
||||
self.manifest.vm_image = Some(AsterVmImage {
|
||||
path: file_name.into(),
|
||||
typ: vm_image.typ.clone(),
|
||||
aster_version: vm_image.aster_version.clone(),
|
||||
sha256sum: vm_image.sha256sum.clone(),
|
||||
});
|
||||
self.write_manifest_content();
|
||||
}
|
||||
|
||||
pub fn add_aster_bin(&mut self, aster_bin: &AsterBin) {
|
||||
if self.manifest.aster_bin.is_some() {
|
||||
panic!("aster_bin already exists");
|
||||
}
|
||||
let file_name = aster_bin.path.file_name().unwrap();
|
||||
let copied_path = self.path.join(file_name);
|
||||
std::fs::copy(&aster_bin.path, &copied_path).unwrap();
|
||||
self.manifest.aster_bin = Some(AsterBin {
|
||||
path: file_name.into(),
|
||||
typ: aster_bin.typ.clone(),
|
||||
version: aster_bin.version.clone(),
|
||||
sha256sum: aster_bin.sha256sum.clone(),
|
||||
stripped: aster_bin.stripped.clone(),
|
||||
});
|
||||
self.write_manifest_content();
|
||||
}
|
||||
|
||||
fn write_manifest_content(&self) {
|
||||
let manifest_file_content = toml::to_string(&self.manifest).unwrap();
|
||||
let manifest_file_path = self.path.join("bundle.toml");
|
||||
std::fs::write(&manifest_file_path, manifest_file_content).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// The osdk bundle artifact manifest that stores as `bundle.toml`.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BundleManifest {
|
||||
#[serde(default)]
|
||||
pub kcmd_args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub initramfs: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub aster_bin: Option<AsterBin>,
|
||||
#[serde(default)]
|
||||
pub vm_image: Option<AsterVmImage>,
|
||||
#[serde(default)]
|
||||
pub boot: Boot,
|
||||
#[serde(default)]
|
||||
pub qemu: Qemu,
|
||||
#[serde(default)]
|
||||
pub cargo_args: CargoArgs,
|
||||
}
|
@ -5,7 +5,10 @@ use std::path::PathBuf;
|
||||
use clap::{crate_version, Args, Parser};
|
||||
|
||||
use crate::{
|
||||
commands::{execute_check_command, execute_clippy_command, execute_new_command},
|
||||
commands::{
|
||||
execute_build_command, execute_check_command, execute_clippy_command, execute_new_command,
|
||||
execute_run_command, execute_test_command,
|
||||
},
|
||||
config_manager::{
|
||||
boot::{BootLoader, BootProtocol},
|
||||
qemu::QemuMachine,
|
||||
@ -24,21 +27,15 @@ pub fn main() {
|
||||
OsdkSubcommand::New(args) => execute_new_command(args),
|
||||
OsdkSubcommand::Build(build_args) => {
|
||||
let build_config = BuildConfig::parse(build_args);
|
||||
println!("{:?}", build_config);
|
||||
// TODO: execute_build_command(build_config);
|
||||
// todo!("execute build command");
|
||||
execute_build_command(&build_config);
|
||||
}
|
||||
OsdkSubcommand::Run(run_args) => {
|
||||
let run_config = RunConfig::parse(run_args);
|
||||
println!("{:?}", run_config);
|
||||
// TODO: execute_run_command(run_config);
|
||||
// todo!("execute run command");
|
||||
execute_run_command(&run_config);
|
||||
}
|
||||
OsdkSubcommand::Test(test_args) => {
|
||||
let test_config = TestConfig::parse(test_args);
|
||||
println!("{:?}", test_config);
|
||||
// TODO: execute_test_command(test_config);
|
||||
// todo!("execute test command");
|
||||
execute_test_command(&test_config);
|
||||
}
|
||||
OsdkSubcommand::Check => execute_check_command(),
|
||||
OsdkSubcommand::Clippy => execute_clippy_command(),
|
||||
@ -114,20 +111,26 @@ pub struct TestArgs {
|
||||
pub osdk_args: OsdkArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, Default)]
|
||||
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CargoArgs {
|
||||
#[arg(
|
||||
long,
|
||||
help = "Build artifacts in release mode",
|
||||
default_value = "false"
|
||||
help = "The Cargo build profile (built-in candidates are 'debug', 'release' and 'dev')",
|
||||
default_value = "dev"
|
||||
)]
|
||||
pub release: bool,
|
||||
pub profile: String,
|
||||
#[arg(long, value_name = "FEATURES", help = "List of features to activate")]
|
||||
pub features: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct OsdkArgs {
|
||||
#[arg(
|
||||
long = "select",
|
||||
help = "Select the specific configuration provided in the OSDK manifest",
|
||||
value_name = "SELECTION"
|
||||
)]
|
||||
pub select: Option<String>,
|
||||
#[arg(
|
||||
long = "kcmd_args",
|
||||
help = "Command line arguments for guest kernel",
|
||||
|
102
osdk/src/commands/build/bin.rs
Normal file
102
osdk/src/commands/build/bin.rs
Normal file
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use linux_bzimage_builder::{make_bzimage, BzImageType};
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Seek, SeekFrom, Write},
|
||||
};
|
||||
|
||||
use crate::bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta};
|
||||
use crate::config_manager::boot::BootProtocol;
|
||||
use crate::utils::get_current_crate_info;
|
||||
|
||||
pub fn make_install_bzimage(
|
||||
install_dir: impl AsRef<Path>,
|
||||
aster_elf: &AsterBin,
|
||||
protocol: &BootProtocol,
|
||||
) -> AsterBin {
|
||||
let target_name = get_current_crate_info().name;
|
||||
let image_type = match protocol {
|
||||
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
|
||||
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Make the `bzImage`-compatible kernel image and place it in the boot directory.
|
||||
let install_path = install_dir.as_ref().join(&target_name);
|
||||
info!("Building bzImage");
|
||||
make_bzimage(&install_path, image_type, &aster_elf.path);
|
||||
|
||||
AsterBin {
|
||||
path: install_path,
|
||||
typ: AsterBinType::BzImage(AsterBzImageMeta {
|
||||
support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32),
|
||||
support_efi_boot: false,
|
||||
support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64),
|
||||
}),
|
||||
version: aster_elf.version.clone(),
|
||||
sha256sum: "TODO".to_string(),
|
||||
stripped: aster_elf.stripped,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_elf_for_qemu(install_dir: impl AsRef<Path>, elf: &AsterBin) -> AsterBin {
|
||||
let stripped_elf_path = {
|
||||
let elf_name = elf.path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
install_dir.as_ref().join(elf_name + ".stripped.elf")
|
||||
};
|
||||
|
||||
// We use rust-strip to reduce the kernel image size.
|
||||
let status = Command::new("rust-strip")
|
||||
.arg(&elf.path)
|
||||
.arg("-o")
|
||||
.arg(stripped_elf_path.as_os_str())
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
panic!("Failed to strip kernel elf.");
|
||||
}
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
std::io::ErrorKind::NotFound => panic!(
|
||||
"`rust-strip` command not found. Please
|
||||
try `cargo install cargo-binutils` and then rerun."
|
||||
),
|
||||
_ => panic!("Strip kernel elf failed, err:{:#?}", err),
|
||||
},
|
||||
}
|
||||
|
||||
// Because QEMU denies a x86_64 multiboot ELF file (GRUB2 accept it, btw),
|
||||
// modify `em_machine` to pretend to be an x86 (32-bit) ELF image,
|
||||
//
|
||||
// https://github.com/qemu/qemu/blob/950c4e6c94b15cd0d8b63891dddd7a8dbf458e6a/hw/i386/multiboot.c#L197
|
||||
// Set EM_386 (0x0003) to em_machine.
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&stripped_elf_path)
|
||||
.unwrap();
|
||||
|
||||
let bytes: [u8; 2] = [0x03, 0x00];
|
||||
|
||||
file.seek(SeekFrom::Start(18)).unwrap();
|
||||
file.write_all(&bytes).unwrap();
|
||||
file.flush().unwrap();
|
||||
|
||||
AsterBin {
|
||||
path: stripped_elf_path,
|
||||
typ: AsterBinType::Elf(AsterElfMeta {
|
||||
has_linux_header: false,
|
||||
has_pvh_header: false,
|
||||
has_multiboot_header: true,
|
||||
has_multiboot2_header: true,
|
||||
}),
|
||||
version: elf.version.clone(),
|
||||
sha256sum: "TODO".to_string(),
|
||||
stripped: true,
|
||||
}
|
||||
}
|
14
osdk/src/commands/build/grub.cfg.template
Normal file
14
osdk/src/commands/build/grub.cfg.template
Normal file
@ -0,0 +1,14 @@
|
||||
# This template file is used by the runner script to generate the acutal grub.cfg
|
||||
|
||||
# AUTOMATICALLY GENERATED FILE, DO NOT EDIT IF YOU KNOW WHAT YOU ARE DOING
|
||||
|
||||
# set debug=linux,efi,linuxefi
|
||||
|
||||
set timeout_style=#GRUB_TIMEOUT_STYLE#
|
||||
set timeout=#GRUB_TIMEOUT#
|
||||
|
||||
menuentry 'asterinas' {
|
||||
#GRUB_CMD_KERNEL# #KERNEL# #KERNEL_COMMAND_LINE#
|
||||
#GRUB_CMD_INITRAMFS#
|
||||
boot
|
||||
}
|
157
osdk/src/commands/build/grub.rs
Normal file
157
osdk/src/commands/build/grub.rs
Normal file
@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::bin::AsterBin;
|
||||
use crate::config_manager::{boot::BootProtocol, BuildConfig};
|
||||
use crate::utils::get_current_crate_info;
|
||||
use crate::vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType};
|
||||
|
||||
use super::bin::make_install_bzimage;
|
||||
|
||||
pub fn create_bootdev_image(
|
||||
target_dir: impl AsRef<Path>,
|
||||
aster_bin: &AsterBin,
|
||||
initramfs_path: Option<impl AsRef<Path>>,
|
||||
config: &BuildConfig,
|
||||
) -> AsterVmImage {
|
||||
let target_name = get_current_crate_info().name;
|
||||
let iso_root = &target_dir.as_ref().join("iso_root");
|
||||
let protocol = &config.manifest.boot.protocol;
|
||||
|
||||
// Clear or make the iso dir.
|
||||
if iso_root.exists() {
|
||||
fs::remove_dir_all(&iso_root).unwrap();
|
||||
}
|
||||
fs::create_dir_all(iso_root.join("boot").join("grub")).unwrap();
|
||||
|
||||
// Copy the initramfs to the boot directory.
|
||||
if let Some(init_path) = &initramfs_path {
|
||||
fs::copy(
|
||||
init_path.as_ref().to_str().unwrap(),
|
||||
iso_root.join("boot").join("initramfs.cpio.gz"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Make the kernel image and place it in the boot directory.
|
||||
match protocol {
|
||||
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => {
|
||||
make_install_bzimage(&iso_root.join("boot"), aster_bin, protocol);
|
||||
}
|
||||
BootProtocol::Multiboot | BootProtocol::Multiboot2 => {
|
||||
// Copy the kernel image to the boot directory.
|
||||
let target_path = iso_root.join("boot").join(&target_name);
|
||||
fs::copy(&aster_bin.path, &target_path).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
// Write the grub.cfg file
|
||||
let initramfs_in_image = if initramfs_path.is_some() {
|
||||
Some("/boot/initramfs.cpio.gz".to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let grub_cfg = generate_grub_cfg(
|
||||
&config.manifest.kcmd_args.join(" "),
|
||||
true,
|
||||
initramfs_in_image,
|
||||
protocol,
|
||||
);
|
||||
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
|
||||
fs::write(&grub_cfg_path, grub_cfg).unwrap();
|
||||
|
||||
// Make the boot device CDROM image using `grub-mkrescue`.
|
||||
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
|
||||
let grub_mkrescue_bin = &config.manifest.boot.grub_mkrescue.clone().unwrap();
|
||||
let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
|
||||
grub_mkrescue_cmd
|
||||
.arg(iso_root.as_os_str())
|
||||
.arg("-o")
|
||||
.arg(iso_path);
|
||||
if !grub_mkrescue_cmd.status().unwrap().success() {
|
||||
panic!("Failed to run {:#?}.", grub_mkrescue_cmd);
|
||||
}
|
||||
|
||||
AsterVmImage {
|
||||
path: iso_path.clone(),
|
||||
typ: AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
|
||||
grub_version: get_grub_mkrescue_version(grub_mkrescue_bin),
|
||||
}),
|
||||
aster_version: aster_bin.version.clone(),
|
||||
sha256sum: "TODO".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_grub_cfg(
|
||||
kcmdline: &str,
|
||||
skip_grub_menu: bool,
|
||||
initramfs_path: Option<String>,
|
||||
protocol: &BootProtocol,
|
||||
) -> String {
|
||||
let target_name = get_current_crate_info().name;
|
||||
let grub_cfg = include_str!("grub.cfg.template").to_string();
|
||||
|
||||
// Delete the first two lines that notes the file a template file.
|
||||
let grub_cfg = grub_cfg.lines().skip(2).collect::<Vec<&str>>().join("\n");
|
||||
// Set the timout style and timeout.
|
||||
let grub_cfg = grub_cfg
|
||||
.replace(
|
||||
"#GRUB_TIMEOUT_STYLE#",
|
||||
if skip_grub_menu { "hidden" } else { "menu" },
|
||||
)
|
||||
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" });
|
||||
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
|
||||
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
|
||||
// Replace the grub commands according to the protocol selected.
|
||||
let aster_bin_path_on_device = PathBuf::from("/boot")
|
||||
.join(&target_name)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
let grub_cfg = match protocol {
|
||||
BootProtocol::Multiboot => grub_cfg
|
||||
.replace("#GRUB_CMD_KERNEL#", "multiboot")
|
||||
.replace("#KERNEL#", &aster_bin_path_on_device)
|
||||
.replace(
|
||||
"#GRUB_CMD_INITRAMFS#",
|
||||
&if let Some(p) = &initramfs_path {
|
||||
"module --nounzip ".to_owned() + p
|
||||
} else {
|
||||
"".to_owned()
|
||||
},
|
||||
),
|
||||
BootProtocol::Multiboot2 => grub_cfg
|
||||
.replace("#GRUB_CMD_KERNEL#", "multiboot2")
|
||||
.replace("#KERNEL#", &aster_bin_path_on_device)
|
||||
.replace(
|
||||
"#GRUB_CMD_INITRAMFS#",
|
||||
&if let Some(p) = &initramfs_path {
|
||||
"module2 --nounzip ".to_owned() + p
|
||||
} else {
|
||||
"".to_owned()
|
||||
},
|
||||
),
|
||||
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => grub_cfg
|
||||
.replace("#GRUB_CMD_KERNEL#", "linux")
|
||||
.replace("#KERNEL#", &aster_bin_path_on_device)
|
||||
.replace(
|
||||
"#GRUB_CMD_INITRAMFS#",
|
||||
&if let Some(p) = &initramfs_path {
|
||||
"initrd ".to_owned() + p
|
||||
} else {
|
||||
"".to_owned()
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
grub_cfg
|
||||
}
|
||||
|
||||
fn get_grub_mkrescue_version(grub_mkrescue: &PathBuf) -> String {
|
||||
let mut cmd = std::process::Command::new(grub_mkrescue);
|
||||
cmd.arg("--version");
|
||||
let output = cmd.output().unwrap();
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
}
|
134
osdk/src/commands/build/mod.rs
Normal file
134
osdk/src/commands/build/mod.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
mod bin;
|
||||
mod grub;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bin::strip_elf_for_qemu;
|
||||
|
||||
use crate::base_crate::new_base_crate;
|
||||
use crate::bin::{AsterBin, AsterBinType, AsterElfMeta};
|
||||
use crate::bundle::{Bundle, BundleManifest};
|
||||
use crate::cli::CargoArgs;
|
||||
use crate::config_manager::{qemu::QemuMachine, BuildConfig};
|
||||
use crate::utils::{get_current_crate_info, get_target_directory};
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
use super::utils::{cargo, COMMON_CARGO_ARGS, DEFAULT_TARGET_RELPATH};
|
||||
|
||||
pub fn execute_build_command(config: &BuildConfig) {
|
||||
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
|
||||
if !osdk_target_directory.exists() {
|
||||
std::fs::create_dir_all(&osdk_target_directory).unwrap();
|
||||
}
|
||||
let target_info = get_current_crate_info();
|
||||
let bundle_path = osdk_target_directory.join(&target_info.name);
|
||||
|
||||
let _bundle = create_base_and_build(&bundle_path, &osdk_target_directory, &config);
|
||||
}
|
||||
|
||||
pub fn create_base_and_build(
|
||||
bundle_path: impl AsRef<Path>,
|
||||
osdk_target_directory: impl AsRef<Path>,
|
||||
config: &BuildConfig,
|
||||
) -> Bundle {
|
||||
let base_crate_path = osdk_target_directory.as_ref().join("base");
|
||||
new_base_crate(
|
||||
&base_crate_path,
|
||||
&get_current_crate_info().name,
|
||||
&get_current_crate_info().path,
|
||||
);
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(&base_crate_path).unwrap();
|
||||
let bundle = do_build(&bundle_path, &osdk_target_directory, &config);
|
||||
std::env::set_current_dir(&original_dir).unwrap();
|
||||
bundle
|
||||
}
|
||||
|
||||
pub fn do_build(
|
||||
bundle_path: impl AsRef<Path>,
|
||||
osdk_target_directory: impl AsRef<Path>,
|
||||
config: &BuildConfig,
|
||||
) -> Bundle {
|
||||
if let Some(ref initramfs) = config.manifest.initramfs {
|
||||
if !initramfs.exists() {
|
||||
error_msg!("initramfs file not found: {}", initramfs.display());
|
||||
process::exit(Errno::BuildCrate as _);
|
||||
}
|
||||
};
|
||||
let mut bundle = Bundle::new(
|
||||
BundleManifest {
|
||||
kcmd_args: config.manifest.kcmd_args.clone(),
|
||||
initramfs: config.manifest.initramfs.clone(),
|
||||
aster_bin: None,
|
||||
vm_image: None,
|
||||
boot: config.manifest.boot.clone(),
|
||||
qemu: config.manifest.qemu.clone(),
|
||||
cargo_args: config.cargo_args.clone(),
|
||||
},
|
||||
&bundle_path,
|
||||
);
|
||||
info!("Building kernel ELF");
|
||||
let aster_elf = build_kernel_elf(&config.cargo_args);
|
||||
|
||||
if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) {
|
||||
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
|
||||
bundle.add_aster_bin(&stripped_elf);
|
||||
}
|
||||
|
||||
// TODO: A boot device is required if we use GRUB. Actually you can boot
|
||||
// a multiboot kernel with Q35 machine directly without a bootloader.
|
||||
// We are currently ignoring this case.
|
||||
if matches!(config.manifest.qemu.machine, QemuMachine::Q35) {
|
||||
info!("Building boot device image");
|
||||
let bootdev_image = grub::create_bootdev_image(
|
||||
&osdk_target_directory,
|
||||
&aster_elf,
|
||||
config.manifest.initramfs.as_ref(),
|
||||
&config,
|
||||
);
|
||||
bundle.add_vm_image(&bootdev_image);
|
||||
}
|
||||
|
||||
bundle
|
||||
}
|
||||
|
||||
fn build_kernel_elf(args: &CargoArgs) -> AsterBin {
|
||||
let target_directory = get_target_directory();
|
||||
let target_json_path = PathBuf::from_str("x86_64-custom.json").unwrap();
|
||||
|
||||
let mut command = cargo();
|
||||
command.arg("build").arg("--target").arg(&target_json_path);
|
||||
command.args(COMMON_CARGO_ARGS);
|
||||
command.arg("--profile=".to_string() + &args.profile);
|
||||
let status = command.status().unwrap();
|
||||
if !status.success() {
|
||||
error_msg!("Cargo build failed");
|
||||
process::exit(Errno::ExecuteCommand as _);
|
||||
}
|
||||
|
||||
let aster_bin_path = PathBuf::from(target_directory)
|
||||
.join(target_json_path.file_stem().unwrap().to_str().unwrap());
|
||||
let aster_bin_path = if args.profile == "dev" {
|
||||
aster_bin_path.join("debug")
|
||||
} else {
|
||||
aster_bin_path.join(&args.profile)
|
||||
}
|
||||
.join(get_current_crate_info().name);
|
||||
|
||||
AsterBin {
|
||||
path: aster_bin_path,
|
||||
typ: AsterBinType::Elf(AsterElfMeta {
|
||||
has_linux_header: false,
|
||||
has_pvh_header: false,
|
||||
has_multiboot_header: true,
|
||||
has_multiboot2_header: true,
|
||||
}),
|
||||
version: get_current_crate_info().version,
|
||||
sha256sum: "TODO".to_string(),
|
||||
stripped: false,
|
||||
}
|
||||
}
|
0
osdk/src/commands/build/x86_64-i386_pm-none.json
Normal file
0
osdk/src/commands/build/x86_64-i386_pm-none.json
Normal file
@ -2,17 +2,17 @@
|
||||
|
||||
use std::process;
|
||||
|
||||
use crate::commands::utils::create_target_json;
|
||||
use crate::error::Errno;
|
||||
use crate::error_msg;
|
||||
|
||||
use super::utils::{cargo, COMMON_CARGO_ARGS};
|
||||
use crate::{
|
||||
commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
|
||||
};
|
||||
|
||||
pub fn execute_check_command() {
|
||||
let target_json_path = {
|
||||
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>);
|
||||
let target_directory = metadata.get("target_directory").unwrap().as_str().unwrap();
|
||||
create_target_json(target_directory)
|
||||
};
|
||||
let target_json_path = create_target_json();
|
||||
|
||||
let mut command = cargo();
|
||||
command.arg("check").arg("--target").arg(target_json_path);
|
||||
|
@ -4,19 +4,15 @@ use std::process;
|
||||
|
||||
use super::utils::{cargo, COMMON_CARGO_ARGS};
|
||||
use crate::{
|
||||
commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
|
||||
error_msg, commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
|
||||
};
|
||||
|
||||
pub fn execute_clippy_command() {
|
||||
let target_json_path = {
|
||||
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>);
|
||||
let target_directory = metadata.get("target_directory").unwrap().as_str().unwrap();
|
||||
create_target_json(target_directory)
|
||||
};
|
||||
let target_json_path = create_target_json();
|
||||
|
||||
let mut command = cargo();
|
||||
command.arg("clippy").arg("-h");
|
||||
info!("[Running] cargo clippy -h");
|
||||
info!("Running `cargo clippy -h`");
|
||||
let output = command.output().unwrap();
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
@ -28,6 +24,7 @@ pub fn execute_clippy_command() {
|
||||
let mut command = cargo();
|
||||
command.arg("clippy").arg("--target").arg(target_json_path);
|
||||
command.args(COMMON_CARGO_ARGS);
|
||||
// TODO: Add support for custom clippy args using OSDK commandline rather than hardcode it.
|
||||
command.args(["--", "-D", "warnings"]);
|
||||
let status = command.status().unwrap();
|
||||
if !status.success() {
|
||||
|
@ -2,11 +2,15 @@
|
||||
|
||||
//! This module contains subcommands of cargo-osdk.
|
||||
|
||||
mod build;
|
||||
mod check;
|
||||
mod clippy;
|
||||
mod new;
|
||||
mod run;
|
||||
mod test;
|
||||
mod utils;
|
||||
|
||||
pub use self::{
|
||||
check::execute_check_command, clippy::execute_clippy_command, new::execute_new_command,
|
||||
run::execute_run_command, test::execute_test_command,
|
||||
};
|
||||
|
22
osdk/src/commands/new/kernel.template
Normal file
22
osdk/src/commands/new/kernel.template
Normal file
@ -0,0 +1,22 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate ktest;
|
||||
|
||||
use aster_frame::prelude::*;
|
||||
|
||||
#[aster_main]
|
||||
fn kernel_main() -> ! {
|
||||
println!("Hello world from guest kernel!");
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[cfg(ktest)]
|
||||
mod test {
|
||||
#[ktest]
|
||||
fn trivial_test() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
#[macro_use]
|
||||
extern crate ktest;
|
||||
extern crate aster_frame;
|
||||
|
||||
#[cfg(ktest)]
|
||||
mod tests {
|
||||
#[ktest]
|
||||
@ -7,4 +11,4 @@ mod tests {
|
||||
let memory_regions = aster_frame::boot::memory_regions();
|
||||
assert!(!memory_regions.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,18 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{fs, path::PathBuf, process, str::FromStr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::{fs, process};
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use crate::{
|
||||
cli::NewArgs,
|
||||
error::Errno,
|
||||
error_msg,
|
||||
utils::{cargo_new_lib, get_cargo_metadata, ASTER_FRAME_DEP},
|
||||
};
|
||||
use crate::cli::NewArgs;
|
||||
use crate::error::Errno;
|
||||
use crate::error_msg;
|
||||
use crate::utils::{cargo_new_lib, get_cargo_metadata, ASTER_FRAME_DEP, KTEST_DEP};
|
||||
|
||||
pub fn execute_new_command(args: &NewArgs) {
|
||||
cargo_new_lib(&args.crate_name);
|
||||
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>);
|
||||
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap();
|
||||
add_manifest_dependencies(&cargo_metadata, &args.crate_name);
|
||||
create_osdk_manifest(&cargo_metadata);
|
||||
if args.kernel {
|
||||
@ -22,6 +23,14 @@ pub fn execute_new_command(args: &NewArgs) {
|
||||
add_rust_toolchain(&cargo_metadata);
|
||||
}
|
||||
|
||||
/// OSDK assumes that the toolchain used by the kernel should be same same as the toolchain
|
||||
/// specified in the asterinas workspace.
|
||||
macro_rules! aster_rust_toolchain {
|
||||
() => {
|
||||
include_str!("../../../../rust-toolchain.toml")
|
||||
};
|
||||
}
|
||||
|
||||
fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &str) {
|
||||
let mainfest_path = get_manifest_path(cargo_metadata, crate_name);
|
||||
|
||||
@ -34,7 +43,15 @@ fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &st
|
||||
|
||||
let aster_frame_dep = toml::Table::from_str(ASTER_FRAME_DEP).unwrap();
|
||||
dependencies.as_table_mut().unwrap().extend(aster_frame_dep);
|
||||
let ktest_dep = toml::Table::from_str(KTEST_DEP).unwrap();
|
||||
dependencies.as_table_mut().unwrap().extend(ktest_dep);
|
||||
|
||||
// If we created a workspace by `osdk new`, we should exclude the `base` crate from the workspace.
|
||||
if get_cargo_metadata::<&Path, &OsStr>(None, None).is_none() {
|
||||
let exclude = toml::Table::from_str(r#"exclude = ["target/osdk/base"]"#).unwrap();
|
||||
manifest.insert("workspace".to_string(), toml::Value::Table(exclude));
|
||||
}
|
||||
|
||||
let content = toml::to_string(&manifest).unwrap();
|
||||
fs::write(mainfest_path, content).unwrap();
|
||||
}
|
||||
@ -51,20 +68,37 @@ fn create_osdk_manifest(cargo_metadata: &serde_json::Value) {
|
||||
}
|
||||
|
||||
// Create `OSDK.toml` for the workspace
|
||||
fs::write(osdk_manifest_path, "").unwrap();
|
||||
// FIXME: we need ovmf for grub-efi, the user may not have it.
|
||||
// The apt OVMF repo installs to `/usr/share/OVMF`
|
||||
fs::write(osdk_manifest_path, r#"
|
||||
[boot]
|
||||
ovmf = "/usr/share/OVMF"
|
||||
[qemu]
|
||||
machine = "q35"
|
||||
args = [
|
||||
"--no-reboot",
|
||||
"-m 2G",
|
||||
"-nographic",
|
||||
"-serial chardev:mux",
|
||||
"-monitor chardev:mux",
|
||||
"-chardev stdio,id=mux,mux=on,signal=off",
|
||||
"-display none",
|
||||
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||
]
|
||||
"#).unwrap();
|
||||
}
|
||||
|
||||
/// Write the default content of `src/kernel.rs`, with contents in provided template.
|
||||
fn write_kernel_template(cargo_metadata: &serde_json::Value, crate_name: &str) {
|
||||
let src_path = get_src_path(cargo_metadata, crate_name);
|
||||
let contents = include_str!("template/kernel.template");
|
||||
let contents = include_str!("kernel.template");
|
||||
fs::write(src_path, contents).unwrap();
|
||||
}
|
||||
|
||||
/// Write the default content of `src/lib.rs`, with contents in provided template.
|
||||
fn write_library_template(cargo_metadata: &serde_json::Value, crate_name: &str) {
|
||||
let src_path = get_src_path(cargo_metadata, crate_name);
|
||||
let contents = include_str!("template/lib.template");
|
||||
let contents = include_str!("lib.template");
|
||||
fs::write(src_path, contents).unwrap();
|
||||
}
|
||||
|
||||
@ -83,7 +117,7 @@ fn add_rust_toolchain(cargo_metadata: &serde_json::Value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contents = include_str!("template/rust-toolchain.toml.template");
|
||||
let contents = aster_rust_toolchain!();
|
||||
fs::write(rust_toolchain_path, contents).unwrap();
|
||||
}
|
||||
|
||||
@ -134,7 +168,7 @@ fn get_package_metadata<'a>(
|
||||
|
||||
fn check_rust_toolchain(toolchain: &toml::Table) {
|
||||
let expected = {
|
||||
let contents = include_str!("template/rust-toolchain.toml.template");
|
||||
let contents = aster_rust_toolchain!();
|
||||
toml::Table::from_str(contents).unwrap()
|
||||
};
|
||||
|
27
osdk/src/commands/run.rs
Normal file
27
osdk/src/commands/run.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::config_manager::{BuildConfig, RunConfig};
|
||||
use crate::utils::{get_current_crate_info, get_target_directory};
|
||||
|
||||
use super::build::create_base_and_build;
|
||||
use super::utils::DEFAULT_TARGET_RELPATH;
|
||||
|
||||
pub fn execute_run_command(config: &RunConfig) {
|
||||
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
|
||||
let target_name = get_current_crate_info().name;
|
||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
||||
|
||||
let required_build_config = BuildConfig {
|
||||
manifest: config.manifest.clone(),
|
||||
cargo_args: config.cargo_args.clone(),
|
||||
};
|
||||
|
||||
// TODO: Check if the bundle is already built and compatible with the run configuration.
|
||||
let bundle = create_base_and_build(
|
||||
&default_bundle_directory,
|
||||
&osdk_target_directory,
|
||||
&required_build_config,
|
||||
);
|
||||
|
||||
bundle.run(&config);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aster_frame::prelude::*;
|
||||
|
||||
#[aster_main]
|
||||
fn kernel_main() {
|
||||
println!("Hello world from guest kernel!");
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2023-12-01"
|
||||
components = ["rust-src", "rustc-dev", "llvm-tools-preview"]
|
70
osdk/src/commands/test.rs
Normal file
70
osdk/src/commands/test.rs
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::fs;
|
||||
|
||||
use crate::base_crate::new_base_crate;
|
||||
use crate::config_manager::{BuildConfig, RunConfig, TestConfig};
|
||||
use crate::utils::{get_current_crate_info, get_target_directory};
|
||||
|
||||
use super::build::do_build;
|
||||
use super::utils::DEFAULT_TARGET_RELPATH;
|
||||
|
||||
pub fn execute_test_command(config: &TestConfig) {
|
||||
let current_crate = get_current_crate_info();
|
||||
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
|
||||
let target_crate_dir = osdk_target_directory.join("base");
|
||||
new_base_crate(&target_crate_dir, ¤t_crate.name, ¤t_crate.path);
|
||||
|
||||
let main_rs_path = target_crate_dir.join("src").join("main.rs");
|
||||
|
||||
let ktest_test_whitelist = match &config.test_name {
|
||||
Some(name) => format!(r#"Some(&["{}"])"#, name),
|
||||
None => format!(r#"None"#),
|
||||
};
|
||||
|
||||
let mut ktest_crate_whitelist = vec![current_crate.name];
|
||||
if let Some(name) = &config.test_name {
|
||||
ktest_crate_whitelist.push(name.clone());
|
||||
}
|
||||
|
||||
let ktest_static_var = format!(
|
||||
r#"
|
||||
#[no_mangle]
|
||||
pub static KTEST_TEST_WHITELIST: Option<&[&str]> = {};
|
||||
#[no_mangle]
|
||||
pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
|
||||
"#,
|
||||
ktest_test_whitelist, ktest_crate_whitelist,
|
||||
);
|
||||
|
||||
// Append the ktest static variable to the main.rs file
|
||||
let mut main_rs_content = fs::read_to_string(&main_rs_path).unwrap();
|
||||
main_rs_content.push_str(&ktest_static_var);
|
||||
fs::write(&main_rs_path, main_rs_content).unwrap();
|
||||
|
||||
// Build the kernel with the given base crate
|
||||
let target_name = get_current_crate_info().name;
|
||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
||||
let required_build_config = BuildConfig {
|
||||
manifest: config.manifest.clone(),
|
||||
cargo_args: config.cargo_args.clone(),
|
||||
};
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(&target_crate_dir).unwrap();
|
||||
// Add `--cfg ktest` to RUSTFLAGS
|
||||
std::env::set_var("RUSTFLAGS", "--cfg ktest");
|
||||
let bundle = do_build(
|
||||
&default_bundle_directory,
|
||||
&osdk_target_directory,
|
||||
&required_build_config,
|
||||
);
|
||||
std::env::remove_var("RUSTFLAGS");
|
||||
std::env::set_current_dir(&original_dir).unwrap();
|
||||
|
||||
let required_run_config = RunConfig {
|
||||
manifest: required_build_config.manifest.clone(),
|
||||
cargo_args: required_build_config.cargo_args.clone(),
|
||||
};
|
||||
|
||||
bundle.run(&required_run_config);
|
||||
}
|
@ -6,17 +6,21 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use crate::utils::get_target_directory;
|
||||
|
||||
pub const COMMON_CARGO_ARGS: &[&str] = &[
|
||||
"-Zbuild-std=core,alloc,compiler_builtins",
|
||||
"-Zbuild-std-features=compiler-builtins-mem",
|
||||
];
|
||||
|
||||
pub const DEFAULT_TARGET_RELPATH: &str = "osdk";
|
||||
|
||||
pub fn cargo() -> Command {
|
||||
Command::new("cargo")
|
||||
}
|
||||
|
||||
pub fn create_target_json(target_directory: impl AsRef<Path>) -> PathBuf {
|
||||
let target_osdk_dir = PathBuf::from(target_directory.as_ref()).join("osdk");
|
||||
pub fn create_target_json() -> PathBuf {
|
||||
let target_osdk_dir = get_target_directory().join(DEFAULT_TARGET_RELPATH);
|
||||
fs::create_dir_all(&target_osdk_dir).unwrap();
|
||||
|
||||
let target_json_path = target_osdk_dir.join("x86_64-custom.json");
|
||||
@ -24,7 +28,7 @@ pub fn create_target_json(target_directory: impl AsRef<Path>) -> PathBuf {
|
||||
return target_json_path;
|
||||
}
|
||||
|
||||
let contents = include_str!("template/x86_64-custom.json.template");
|
||||
let contents = include_str!("../base_crate/x86_64-custom.json.template");
|
||||
fs::write(&target_json_path, contents).unwrap();
|
||||
|
||||
target_json_path
|
||||
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{path::PathBuf, process};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
@ -12,7 +13,7 @@ use super::{
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
/// The osdk manifest from configuration file and command line arguments.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OsdkManifest {
|
||||
pub kcmd_args: Vec<String>,
|
||||
pub initramfs: Option<PathBuf>,
|
||||
@ -21,7 +22,10 @@ pub struct OsdkManifest {
|
||||
}
|
||||
|
||||
impl OsdkManifest {
|
||||
pub fn from_toml_manifest<S: AsRef<str>>(toml_manifest: TomlManifest, features: &[S]) -> Self {
|
||||
pub fn from_toml_manifest<S: AsRef<str>>(
|
||||
toml_manifest: TomlManifest,
|
||||
selection: Option<S>,
|
||||
) -> Self {
|
||||
let TomlManifest {
|
||||
mut kcmd_args,
|
||||
mut init_args,
|
||||
@ -46,26 +50,26 @@ impl OsdkManifest {
|
||||
|
||||
let mut qemu_args = None;
|
||||
|
||||
let mut feature_enabled_args: Vec<_> = cfg
|
||||
.into_iter()
|
||||
.filter_map(|(cfg, args)| {
|
||||
if features
|
||||
.iter()
|
||||
.any(|feature| cfg.contains(feature.as_ref()))
|
||||
{
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut selected_args: Vec<_> = if let Some(sel) = selection {
|
||||
cfg.into_iter()
|
||||
.filter_map(|(cfg, args)| {
|
||||
if cfg.contains(sel.as_ref()) {
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
if feature_enabled_args.len() > 1 {
|
||||
error_msg!("Multiple features are conflict");
|
||||
if selected_args.len() > 1 {
|
||||
error_msg!("Multiple selections are not allowed");
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
} else if feature_enabled_args.len() == 1 {
|
||||
qemu_args = Some(feature_enabled_args.remove(0));
|
||||
} else if feature_enabled_args.is_empty() {
|
||||
} else if selected_args.len() == 1 {
|
||||
qemu_args = Some(selected_args.remove(0));
|
||||
} else if selected_args.is_empty() {
|
||||
qemu_args = Some(default);
|
||||
}
|
||||
|
||||
@ -82,6 +86,38 @@ impl OsdkManifest {
|
||||
qemu: qemu_args.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_canonicalize_all_paths(&mut self, manifest_file_dir: impl AsRef<Path>) {
|
||||
macro_rules! canonicalize_path {
|
||||
($path:expr) => {{
|
||||
let path = if $path.is_relative() {
|
||||
manifest_file_dir.as_ref().join($path)
|
||||
} else {
|
||||
$path.clone()
|
||||
};
|
||||
path.canonicalize().unwrap_or_else(|_| {
|
||||
error_msg!("File specified but not found: {:#?}", path);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
})
|
||||
}};
|
||||
}
|
||||
macro_rules! canonicalize_optional_path {
|
||||
($path:expr) => {
|
||||
if let Some(path_inner) = &$path {
|
||||
Some(canonicalize_path!(path_inner))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
self.initramfs = canonicalize_optional_path!(self.initramfs);
|
||||
self.boot.grub_mkrescue = canonicalize_optional_path!(self.boot.grub_mkrescue);
|
||||
self.boot.ovmf = canonicalize_optional_path!(self.boot.ovmf);
|
||||
self.qemu.path = canonicalize_optional_path!(self.qemu.path);
|
||||
for drive_file in &mut self.qemu.drive_files {
|
||||
drive_file.path = canonicalize_path!(&drive_file.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The osdk manifest from configuration file `OSDK.toml`.
|
||||
@ -111,12 +147,12 @@ fn check_args(arg_name: &str, args: &[String]) {
|
||||
|
||||
/// Check cfg that is in the form that we can accept
|
||||
fn check_cfg(cfg: &str) {
|
||||
if FEATURE_REGEX.captures(cfg).is_none() {
|
||||
error_msg!("{} is not allowed to used after `qemu` in `OSDK.toml`. Currently we only allowed cfg like `cfg(feature=\"foo\")`", cfg);
|
||||
if SELECT_REGEX.captures(cfg).is_none() {
|
||||
error_msg!("{} is not allowed to used after `qemu` in `OSDK.toml`. Currently we only allow cfgs like `cfg(select=\"foo\")`", cfg);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref FEATURE_REGEX: Regex = Regex::new(r#"cfg\(feature="(?P<feature>\w+)"\)"#).unwrap();
|
||||
pub static ref SELECT_REGEX: Regex = Regex::new(r#"cfg\(select="(?P<select>\w+)"\)"#).unwrap();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ pub struct BuildConfig {
|
||||
impl BuildConfig {
|
||||
pub fn parse(args: &BuildArgs) -> Self {
|
||||
let cargo_args = split_features(&args.cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
|
||||
apply_cli_args(&mut manifest, &args.osdk_args);
|
||||
try_fill_system_configs(&mut manifest);
|
||||
Self {
|
||||
@ -56,7 +56,7 @@ pub struct RunConfig {
|
||||
impl RunConfig {
|
||||
pub fn parse(args: &RunArgs) -> Self {
|
||||
let cargo_args = split_features(&args.cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
|
||||
apply_cli_args(&mut manifest, &args.osdk_args);
|
||||
try_fill_system_configs(&mut manifest);
|
||||
Self {
|
||||
@ -77,7 +77,7 @@ pub struct TestConfig {
|
||||
impl TestConfig {
|
||||
pub fn parse(args: &TestArgs) -> Self {
|
||||
let cargo_args = split_features(&args.cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args);
|
||||
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
|
||||
apply_cli_args(&mut manifest, &args.osdk_args);
|
||||
try_fill_system_configs(&mut manifest);
|
||||
Self {
|
||||
@ -88,17 +88,20 @@ impl TestConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_osdk_manifest(cargo_args: &CargoArgs) -> OsdkManifest {
|
||||
let manifest_path = {
|
||||
let feature_strings = get_feature_strings(cargo_args);
|
||||
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings));
|
||||
let workspace_root = cargo_metadata
|
||||
/// FIXME: I guess OSDK manifest is definitely NOT per workspace. It's per crate. When you cannot
|
||||
/// find a manifest per crate, find it in the upper levels.
|
||||
/// I don't bother to do it now, just fix the relpaths.
|
||||
fn load_osdk_manifest<S: AsRef<str>>(cargo_args: &CargoArgs, selection: Option<S>) -> 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();
|
||||
PathBuf::from(workspace_root).join("OSDK.toml")
|
||||
};
|
||||
.unwrap(),
|
||||
);
|
||||
let manifest_path = workspace_root.join("OSDK.toml");
|
||||
|
||||
let Ok(contents) = fs::read_to_string(&manifest_path) else {
|
||||
error_msg!(
|
||||
@ -108,8 +111,18 @@ fn load_osdk_manifest(cargo_args: &CargoArgs) -> OsdkManifest {
|
||||
process::exit(Errno::GetMetadata as _);
|
||||
};
|
||||
|
||||
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap();
|
||||
OsdkManifest::from_toml_manifest(toml_manifest, &cargo_args.features)
|
||||
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
|
||||
error_msg!(
|
||||
"Cannot parse TOML file, {}:\n{}:\n {}",
|
||||
err.message(),
|
||||
manifest_path.to_string_lossy().to_string(),
|
||||
&contents[err.span().unwrap()],
|
||||
);
|
||||
process::exit(Errno::ParseMetadata as _);
|
||||
});
|
||||
let mut osdk_manifest = OsdkManifest::from_toml_manifest(toml_manifest, selection);
|
||||
osdk_manifest.check_canonicalize_all_paths(workspace_root);
|
||||
osdk_manifest
|
||||
}
|
||||
|
||||
/// Split `features` in `cargo_args` to ensure each string contains exactly one feature.
|
||||
@ -126,7 +139,7 @@ fn split_features(cargo_args: &CargoArgs) -> CargoArgs {
|
||||
}
|
||||
|
||||
CargoArgs {
|
||||
release: cargo_args.release,
|
||||
profile: cargo_args.profile.clone(),
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,25 @@ pub struct Qemu {
|
||||
/// The additional arguments for running qemu, except `-cpu` and `-machine`.
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
/// The additional drive files
|
||||
#[serde(default)]
|
||||
pub drive_files: Vec<DriveFile>,
|
||||
/// The `-machine` argument for running qemu.
|
||||
#[serde(default)]
|
||||
pub machine: QemuMachine,
|
||||
/// The path of qemu.
|
||||
#[serde(default)]
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DriveFile {
|
||||
#[serde(default)]
|
||||
pub path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub append: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct CfgQemu {
|
||||
pub default: Qemu,
|
||||
@ -44,6 +56,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
|
||||
Path,
|
||||
Args,
|
||||
Machine,
|
||||
DriveFiles,
|
||||
Cfg(String),
|
||||
}
|
||||
|
||||
@ -58,7 +71,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
|
||||
type Value = Field;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("`path`, `args`, `machine` or cfg")
|
||||
formatter.write_str("`path`, `args`, `machine`, `drive_files` or cfg")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
@ -69,6 +82,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
|
||||
"args" => Ok(Field::Args),
|
||||
"machine" => Ok(Field::Machine),
|
||||
"path" => Ok(Field::Path),
|
||||
"drive_files" => Ok(Field::DriveFiles),
|
||||
v => Ok(Field::Cfg(v.to_string())),
|
||||
}
|
||||
}
|
||||
@ -105,6 +119,9 @@ impl<'de> Deserialize<'de> for CfgQemu {
|
||||
Field::Path => {
|
||||
default.path = map.next_value()?;
|
||||
}
|
||||
Field::DriveFiles => {
|
||||
default.drive_files = map.next_value()?;
|
||||
}
|
||||
Field::Cfg(cfg) => {
|
||||
let qemu_args = map.next_value()?;
|
||||
cfgs.insert(cfg, qemu_args);
|
||||
|
@ -8,6 +8,9 @@ pub enum Errno {
|
||||
AddRustToolchain = 3,
|
||||
ParseMetadata = 4,
|
||||
ExecuteCommand = 5,
|
||||
BuildCrate = 6,
|
||||
RunBundle = 7,
|
||||
CreateBaseCrate = 8,
|
||||
}
|
||||
|
||||
/// Print error message to console
|
||||
|
@ -7,6 +7,9 @@ extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
mod base_crate;
|
||||
mod bin;
|
||||
mod bundle;
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod config_manager;
|
||||
@ -14,6 +17,7 @@ mod error;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod utils;
|
||||
mod vm_image;
|
||||
|
||||
fn main() {
|
||||
// init logger
|
||||
|
@ -1,17 +1,13 @@
|
||||
initramfs="./build/initramfs.cpio.gz"
|
||||
|
||||
[qemu]
|
||||
path = "/usr/bin/qemu-system-x86_64"
|
||||
args = ["-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
|
||||
args = [
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
|
||||
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off"
|
||||
]
|
||||
|
||||
[qemu.'cfg(feature="intel_tdx")']
|
||||
path = "/usr/local/sbin/qemu-kvm"
|
||||
[qemu.'cfg(select="intel_tdx")']
|
||||
|
||||
[qemu.'cfg(feature="iommu")']
|
||||
path = "/usr/bin/qemu-system-x86_64"
|
||||
[qemu.'cfg(select="iommu")']
|
||||
args = [
|
||||
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
|
||||
|
@ -1,6 +1,5 @@
|
||||
kcmd_args = ["init=/bin/busybox", "path=/usr/local/bin"]
|
||||
init_args = ["sh", "-l"]
|
||||
initramfs="./build/initramfs.cpio.gz"
|
||||
|
||||
[boot]
|
||||
loader = "grub"
|
||||
@ -9,7 +8,6 @@ grub-mkrescue = "/usr/bin/grub-mkrescue"
|
||||
ovmf = "/usr/bin/ovmf"
|
||||
|
||||
[qemu]
|
||||
path = "/usr/bin/qemu-system-x86_64"
|
||||
machine = "q35"
|
||||
args = [
|
||||
"-enable-kvm",
|
||||
|
@ -23,9 +23,6 @@ fn deserialize_osdk_manifest() {
|
||||
let content = include_str!("OSDK.toml.full");
|
||||
let osdk_manifest: TomlManifest = toml::from_str(content).unwrap();
|
||||
assert!(osdk_manifest.boot.grub_mkrescue.unwrap() == PathBuf::from("/usr/bin/grub-mkrescue"));
|
||||
assert!(
|
||||
osdk_manifest.qemu.default.path.unwrap() == PathBuf::from("/usr/bin/qemu-system-x86_64")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -65,7 +62,7 @@ fn load_manifest_conditional() {
|
||||
fs::write(path, contents).unwrap();
|
||||
|
||||
let cargo_args = CargoArgs {
|
||||
release: true,
|
||||
profile: "release".to_string(),
|
||||
features: vec![String::from("iommu")],
|
||||
};
|
||||
cargo_osdk_build(PathBuf::from(workspace).join(kernel_name), &cargo_args);
|
||||
@ -118,51 +115,39 @@ fn conditional_manifest() {
|
||||
.cfg
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains_key(&String::from("cfg(feature=\"intel_tdx\")")));
|
||||
.contains_key(&String::from("cfg(select=\"intel_tdx\")")));
|
||||
assert!(toml_manifest
|
||||
.qemu
|
||||
.cfg
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains_key(&String::from("cfg(feature=\"iommu\")")));
|
||||
.contains_key(&String::from("cfg(select=\"iommu\")")));
|
||||
|
||||
// No features
|
||||
let features: &[&str] = &[];
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
|
||||
assert_eq!(
|
||||
manifest.qemu.path,
|
||||
Some(PathBuf::from("/usr/bin/qemu-system-x86_64"))
|
||||
);
|
||||
// Default selection
|
||||
let selection: Option<&str> = None;
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
|
||||
assert!(manifest.qemu.args.contains(&String::from(
|
||||
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"
|
||||
)));
|
||||
|
||||
// Iommu features
|
||||
let features: &[&str] = &["iommu"];
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
|
||||
assert_eq!(
|
||||
manifest.qemu.path,
|
||||
Some(PathBuf::from("/usr/bin/qemu-system-x86_64"))
|
||||
);
|
||||
// Iommu
|
||||
let selection: Option<&str> = Some("iommu");
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
|
||||
assert!(manifest
|
||||
.qemu
|
||||
.args
|
||||
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
|
||||
|
||||
// Tdx features
|
||||
let features: &[&str] = &["intel_tdx"];
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
|
||||
assert_eq!(
|
||||
manifest.qemu.path,
|
||||
Some(PathBuf::from("/usr/local/sbin/qemu-kvm"))
|
||||
);
|
||||
// Tdx
|
||||
let selection: Option<&str> = Some("intel_tdx");
|
||||
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
|
||||
assert!(manifest.qemu.args.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_feature() {
|
||||
let text = "cfg(feature=\"abc123_\")";
|
||||
let captures = FEATURE_REGEX.captures(text).unwrap();
|
||||
let feature = captures.name("feature").unwrap().as_str();
|
||||
assert_eq!(feature, "abc123_");
|
||||
fn extract_selection() {
|
||||
let text = "cfg(select=\"abc123_\")";
|
||||
let captures = SELECT_REGEX.captures(text).unwrap();
|
||||
let selection = captures.name("select").unwrap().as_str();
|
||||
assert_eq!(selection, "abc123_");
|
||||
}
|
||||
|
@ -19,7 +19,12 @@ pub fn cargo_osdk<T: AsRef<OsStr>, I: IntoIterator<Item = T>>(args: I) -> Comman
|
||||
}
|
||||
|
||||
pub fn assert_success(output: &Output) {
|
||||
assert!(output.status.success());
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command output {:#?} seems failed, stderr:\n {}",
|
||||
output,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn assert_stdout_contains_msg(output: &Output, msg: &str) {
|
||||
@ -37,7 +42,11 @@ pub fn create_workspace(workspace_name: &str, members: &[&str]) {
|
||||
.iter()
|
||||
.map(|member| toml::Value::String(member.to_string()))
|
||||
.collect();
|
||||
|
||||
let exclude = toml::Value::Array(vec![toml::Value::String("target/osdk/base".to_string())]);
|
||||
|
||||
table.insert("members".to_string(), toml::Value::Array(members));
|
||||
table.insert("exclude".to_string(), exclude);
|
||||
table
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{ffi::OsStr, path::Path, process::Command};
|
||||
use std::{ffi::OsStr, path::{Path, PathBuf}, process::Command};
|
||||
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
// FIXME: Crates belonging to Asterinas require a different dependency format. The dependency
|
||||
// should be specified using a relative path instead of a URL.
|
||||
// TODO: The dependency should be corrected when this branch is merged.
|
||||
pub const ASTER_FRAME_DEP: &str =
|
||||
"aster-frame = { git = \"https://github.com/asterinas/asterinas\", rev = \"f2f991b\" }";
|
||||
"aster-frame = { git = \"https://github.com/junyang-zh/asterinas\", branch = \"osdk\" }";
|
||||
pub const KTEST_DEP: &str =
|
||||
"ktest = { git = \"https://github.com/junyang-zh/asterinas\", branch = \"osdk\" }";
|
||||
|
||||
fn cargo() -> Command {
|
||||
Command::new("cargo")
|
||||
@ -24,10 +27,13 @@ pub fn cargo_new_lib(crate_name: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the Cargo metadata parsed from the standard output
|
||||
/// of the invocation of Cargo. Return `None` if the command
|
||||
/// fails or the `current_dir` is not in a Cargo workspace.
|
||||
pub fn get_cargo_metadata<S1: AsRef<Path>, S2: AsRef<OsStr>>(
|
||||
current_dir: Option<S1>,
|
||||
cargo_args: Option<&[S2]>,
|
||||
) -> serde_json::Value {
|
||||
) -> Option<serde_json::Value> {
|
||||
let mut command = cargo();
|
||||
command.args(["metadata", "--no-deps", "--format-version", "1"]);
|
||||
|
||||
@ -42,13 +48,48 @@ pub fn get_cargo_metadata<S1: AsRef<Path>, S2: AsRef<OsStr>>(
|
||||
let output = command.output().unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
eprintln!("{}", &stderr);
|
||||
|
||||
error_msg!("Failed to get metadata for newly created crate");
|
||||
std::process::exit(Errno::GetMetadata as _);
|
||||
return None;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
serde_json::from_str(&stdout).unwrap()
|
||||
Some(serde_json::from_str(&stdout).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_target_directory() -> PathBuf {
|
||||
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
|
||||
metadata
|
||||
.get("target_directory")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub struct CrateInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub fn get_current_crate_info() -> CrateInfo {
|
||||
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
|
||||
let default_members = metadata.get("workspace_default_members").unwrap();
|
||||
assert_eq!(default_members.as_array().unwrap().len(), 1);
|
||||
// The default member string here is in the form of "<crate_name> <crate_version> (path+file://<crate_path>)"
|
||||
let default_member = default_members[0]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.split(" ")
|
||||
.collect::<Vec<&str>>();
|
||||
let name = default_member[0].to_string();
|
||||
let version = default_member[1].to_string();
|
||||
let path = default_member[2]
|
||||
.trim_start_matches("(path+file://")
|
||||
.trim_end_matches(")")
|
||||
.to_string();
|
||||
CrateInfo {
|
||||
name,
|
||||
version,
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
22
osdk/src/vm_image.rs
Normal file
22
osdk/src/vm_image.rs
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterVmImage {
|
||||
pub path: PathBuf,
|
||||
pub typ: AsterVmImageType,
|
||||
pub aster_version: String,
|
||||
pub sha256sum: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AsterVmImageType {
|
||||
GrubIso(AsterGrubIsoImageMeta),
|
||||
// TODO: add more vm image types such as qcow2, etc.
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AsterGrubIsoImageMeta {
|
||||
pub grub_version: String,
|
||||
}
|
Reference in New Issue
Block a user