Supporting running OSDK commands in workspace root

This commit is contained in:
Jianfeng Jiang 2024-03-06 09:40:48 +00:00 committed by Tate, Hongliang Tian
parent c7b7e2473f
commit aaf101a53e
8 changed files with 167 additions and 29 deletions

View File

@ -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:

View File

@ -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);
}
```

6
osdk/Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -1,9 +1,6 @@
#![no_std]
#![forbid(unsafe_code)]
#[macro_use]
extern crate ktest;
use aster_frame::prelude::*;
#[aster_main]

View File

@ -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 = [

View File

@ -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<String> {
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 "<crate_name> <crate_version> (path+file://<crate_path>)"
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()
}

View File

@ -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 "<crate_name> <crate_version> (path+file://<crate_path>)"
let default_member = default_members[0]
.as_str()
.unwrap()
.split(' ')
.collect::<Vec<&str>>();
let default_member = default_member.split(' ').collect::<Vec<&str>>();
let name = default_member[0].to_string();
let version = default_member[1].to_string();
let path = default_member[2]