diff --git a/Makefile b/Makefile index 1797dfdf6..25a16df79 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,7 @@ initramfs: .PHONY: build build: initramfs $(CARGO_OSDK) - @cd kernel && cargo osdk build $(CARGO_OSDK_ARGS) + cargo osdk build $(CARGO_OSDK_ARGS) .PHONY: tools tools: @@ -130,7 +130,7 @@ tools: .PHONY: run run: build - @cd kernel && cargo osdk run $(CARGO_OSDK_ARGS) + @cargo osdk run $(CARGO_OSDK_ARGS) .PHONY: test test: diff --git a/docs/src/osdk/guide/work-in-workspace.md b/docs/src/osdk/guide/work-in-workspace.md index 70484ff6b..79bc942ab 100644 --- a/docs/src/osdk/guide/work-in-workspace.md +++ b/docs/src/osdk/guide/work-in-workspace.md @@ -49,6 +49,9 @@ myworkspace/ └── lib.rs ``` +At present, OSDK mandates that there must be only one kernel project +within a workspace. + In addition to the two projects, OSDK will also generate `OSDK.toml` and `rust-toolchain.toml` at the root of the workspace. @@ -78,8 +81,8 @@ This function will call the function from `mymodule`: ```rust #[aster_main] fn kernel_main() { - let avail_mem_as_mb = mymodule::available_memory() / 1_000_000; - println!("The available memory is {} MB", avail_mem_as_mb); + let avail_mem_as_mb = mymodule::available_memory() / 1_000_000; + println!("The available memory is {} MB", avail_mem_as_mb); } ``` diff --git a/osdk/Cargo.lock b/osdk/Cargo.lock index 4478918e6..cefcc85e6 100644 --- a/osdk/Cargo.lock +++ b/osdk/Cargo.lock @@ -137,10 +137,12 @@ dependencies = [ "lazy_static", "linux-bzimage-builder", "log", + "quote", "regex", "serde", "serde_json", "sha2", + "syn", "toml", "which", ] @@ -534,9 +536,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "2.0.50" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", diff --git a/osdk/Cargo.toml b/osdk/Cargo.toml index 330ebcbd8..fe4a5bdbe 100644 --- a/osdk/Cargo.toml +++ b/osdk/Cargo.toml @@ -15,10 +15,12 @@ indexmap = "2.2.1" lazy_static = "1.4.0" linux-bzimage-builder = { path = "../framework/libs/linux-bzimage/builder" } log = "0.4.20" +quote = "1.0.35" regex = "1.10.3" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" sha2 = "0.10.8" +syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] } toml = { version = "0.8.8", features = ["preserve_order"] } which = "6.0.0" diff --git a/osdk/src/commands/new/kernel.template b/osdk/src/commands/new/kernel.template index b99e6cae2..6d46e4638 100644 --- a/osdk/src/commands/new/kernel.template +++ b/osdk/src/commands/new/kernel.template @@ -1,9 +1,6 @@ #![no_std] #![forbid(unsafe_code)] -#[macro_use] -extern crate ktest; - use aster_frame::prelude::*; #[aster_main] diff --git a/osdk/src/commands/new/mod.rs b/osdk/src/commands/new/mod.rs index 02e486ab5..c23a9e6d7 100644 --- a/osdk/src/commands/new/mod.rs +++ b/osdk/src/commands/new/mod.rs @@ -1,12 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use std::{ - ffi::OsStr, - fs, - path::{Path, PathBuf}, - process, - str::FromStr, -}; +use std::{fs, path::PathBuf, process, str::FromStr}; use crate::{ cli::NewArgs, @@ -20,6 +14,7 @@ pub fn execute_new_command(args: &NewArgs) { 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); + exclude_osdk_base(&cargo_metadata); if args.kernel { write_kernel_template(&cargo_metadata, &args.crate_name); } else { @@ -50,15 +45,48 @@ fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &st 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 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(); } +// Add `target/osdk/base` to `exclude` array of the workspace manifest +fn exclude_osdk_base(metadata: &serde_json::Value) { + let osdk_base_path = "target/osdk/base"; + + let workspace_manifest_path = { + let workspace_root = metadata.get("workspace_root").unwrap().as_str().unwrap(); + format!("{}/Cargo.toml", workspace_root) + }; + + let content = fs::read_to_string(&workspace_manifest_path).unwrap(); + let mut manifest_toml: toml::Table = toml::from_str(&content).unwrap(); + + if let Some(workspace) = manifest_toml.get_mut("workspace") { + let workspace = workspace.as_table_mut().unwrap(); + + if let Some(exclude) = workspace.get_mut("exclude") { + let exclude = exclude.as_array_mut().unwrap(); + if exclude.contains(&toml::Value::String(osdk_base_path.to_string())) { + return; + } + + exclude.push(toml::Value::String(osdk_base_path.to_string())); + } else { + let exclude = vec![toml::Value::String(osdk_base_path.to_string())]; + workspace.insert("exclude".to_string(), toml::Value::Array(exclude)); + } + } else { + let exclude = toml::Table::from_str(r#"exclude = ["target/osdk/base"]"#).unwrap(); + manifest_toml.insert("workspace".to_string(), toml::Value::Table(exclude)); + } + + let content = toml::to_string(&manifest_toml).unwrap(); + fs::write(workspace_manifest_path, content).unwrap(); +} + fn create_osdk_manifest(cargo_metadata: &serde_json::Value) { let osdk_manifest_path = { let workspace_root = get_workspace_root(cargo_metadata); @@ -78,6 +106,7 @@ fn create_osdk_manifest(cargo_metadata: &serde_json::Value) { r#" [boot] ovmf = "/usr/share/OVMF" +protocol = "multiboot" [qemu] machine = "q35" args = [ diff --git a/osdk/src/commands/test.rs b/osdk/src/commands/test.rs index 299883ab9..ea531b4ce 100644 --- a/osdk/src/commands/test.rs +++ b/osdk/src/commands/test.rs @@ -6,10 +6,18 @@ use super::{build::do_build, util::DEFAULT_TARGET_RELPATH}; use crate::{ base_crate::new_base_crate, config_manager::{BuildConfig, RunConfig, TestConfig}, - util::{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) { + let crates = get_workspace_default_members(); + for crate_path in crates { + std::env::set_current_dir(crate_path).unwrap(); + test_current_crate(config); + } +} + +pub fn test_current_crate(config: &TestConfig) { let current_crate = get_current_crate_info(); let ws_target_directory = get_target_directory(); let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH); @@ -69,3 +77,23 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); bundle.run(&required_run_config); } + +fn get_workspace_default_members() -> Vec { + let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap(); + let default_members = metadata + .get("workspace_default_members") + .unwrap() + .as_array() + .unwrap(); + default_members + .iter() + .map(|value| { + // The default member is in the form of " (path+file://)" + let default_member = value.as_str().unwrap(); + let path = default_member.split(" ").nth(2).unwrap(); + path.trim_start_matches("(path+file://") + .trim_end_matches(')') + .to_string() + }) + .collect() +} diff --git a/osdk/src/util.rs b/osdk/src/util.rs index 6ea311e1e..440b837a0 100644 --- a/osdk/src/util.rs +++ b/osdk/src/util.rs @@ -2,16 +2,19 @@ use std::{ ffi::OsStr, + fs, path::{Path, PathBuf}, process::Command, }; use crate::{error::Errno, error_msg}; +use quote::ToTokens; + /// FIXME: We should publish the asterinas crates to a public registry /// and use the published version in the generated Cargo.toml. pub const ASTER_GIT_LINK: &str = "https://github.com/asterinas/asterinas"; -pub const ASTER_GIT_REV: &str = "7d0ea99"; +pub const ASTER_GIT_REV: &str = "437ab80"; pub fn aster_crate_dep(crate_name: &str) -> String { format!( "{} = {{ git = \"{}\", rev = \"{}\" }}", @@ -78,16 +81,90 @@ pub struct CrateInfo { pub path: String, } +/// Retrieve the default member in the workspace. +/// +/// If there is only one kernel crate, return that crate; +/// If there are multiple kernel crates or no kernel crates in the workspace, +/// this function will exit with an error. +/// +/// A crate is considered a kernel crate if it utilizes the `aster_main` macro. +fn get_default_member(metadata: &serde_json::Value) -> &str { + let default_members = metadata + .get("workspace_default_members") + .unwrap() + .as_array() + .unwrap(); + + if default_members.len() == 1 { + return default_members[0].as_str().unwrap(); + } + + let packages: Vec<_> = { + let packages = metadata.get("packages").unwrap().as_array().unwrap(); + + packages + .iter() + .filter(|package| { + let id = package.get("id").unwrap(); + if !default_members.contains(&id) { + return false; + } + + let src_path = { + let targets = package.get("targets").unwrap().as_array().unwrap(); + if targets.len() != 1 { + return false; + } + targets[0].get("src_path").unwrap().as_str().unwrap() + }; + + let file = { + let content = fs::read_to_string(src_path).unwrap(); + syn::parse_file(&content).unwrap() + }; + + contains_aster_main_macro(&file) + }) + .collect() + }; + + if packages.len() == 0 { + error_msg!("OSDK requires there's at least one kernel package. Please navigate to the kernel package directory or the workspace root and run the command."); + std::process::exit(Errno::BuildCrate as _); + } + + if packages.len() >= 2 { + error_msg!("OSDK requires there's at most one kernel package in the workspace. Please navigate to the kernel package directory and run the command."); + std::process::exit(Errno::BuildCrate as _); + } + + packages[0].get("id").unwrap().as_str().unwrap() +} + +fn contains_aster_main_macro(file: &syn::File) -> bool { + for item in &file.items { + let syn::Item::Fn(item_fn) = item else { + continue; + }; + + for attr in &item_fn.attrs { + let attr = format!("{}", attr.to_token_stream()); + if attr.as_str() == "# [aster_main]" { + return true; + } + } + } + + false +} + 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); + + let default_member = get_default_member(&metadata); + // The default member string here is in the form of " (path+file://)" - let default_member = default_members[0] - .as_str() - .unwrap() - .split(' ') - .collect::>(); + let default_member = default_member.split(' ').collect::>(); let name = default_member[0].to_string(); let version = default_member[1].to_string(); let path = default_member[2]