From 32a6025819dfa9b44cfa68ed383f43879f197a5d Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Sun, 9 Feb 2025 17:53:02 +0800 Subject: [PATCH] Refactor the parsing of OSDK crate types --- osdk/src/cli.rs | 2 +- osdk/src/commands/build/bin.rs | 4 +- osdk/src/commands/build/grub.rs | 6 +- osdk/src/commands/build/mod.rs | 28 +++++-- osdk/src/commands/debug.rs | 4 +- osdk/src/commands/profile.rs | 4 +- osdk/src/commands/run.rs | 21 +++++- osdk/src/commands/test.rs | 31 ++------ osdk/src/commands/util.rs | 4 +- osdk/src/error.rs | 1 + osdk/src/util.rs | 127 +++++++++++++++++++------------- 11 files changed, 131 insertions(+), 101 deletions(-) diff --git a/osdk/src/cli.rs b/osdk/src/cli.rs index 0692a1a6d..e46279c84 100644 --- a/osdk/src/cli.rs +++ b/osdk/src/cli.rs @@ -303,7 +303,7 @@ impl DebugProfileOutArgs { .display() ) } else { - let crate_name = crate::util::get_current_crate_info().name; + let crate_name = crate::util::get_current_crates().remove(0).name; let time_stamp = std::time::SystemTime::now(); let time_stamp: DateTime = time_stamp.into(); let time_stamp = time_stamp.format("%H%M%S"); diff --git a/osdk/src/commands/build/bin.rs b/osdk/src/commands/build/bin.rs index d4377db72..f58369517 100644 --- a/osdk/src/commands/build/bin.rs +++ b/osdk/src/commands/build/bin.rs @@ -17,7 +17,7 @@ use crate::{ bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta}, file::BundleFile, }, - util::{get_current_crate_info, hard_link_or_copy}, + util::{get_current_crates, hard_link_or_copy}, }; pub fn make_install_bzimage( @@ -27,7 +27,7 @@ pub fn make_install_bzimage( linux_x86_legacy_boot: bool, encoding: PayloadEncoding, ) -> AsterBin { - let target_name = get_current_crate_info().name; + let target_name = get_current_crates().remove(0).name; let image_type = if linux_x86_legacy_boot { BzImageType::Legacy32 } else { diff --git a/osdk/src/commands/build/grub.rs b/osdk/src/commands/build/grub.rs index 9ad3d0081..059e4a5f1 100644 --- a/osdk/src/commands/build/grub.rs +++ b/osdk/src/commands/build/grub.rs @@ -16,7 +16,7 @@ use crate::{ scheme::{ActionChoice, BootProtocol}, Config, }, - util::{get_current_crate_info, hard_link_or_copy}, + util::{get_current_crates, hard_link_or_copy}, }; pub fn create_bootdev_image( @@ -26,7 +26,7 @@ pub fn create_bootdev_image( config: &Config, action: ActionChoice, ) -> AsterVmImage { - let target_name = get_current_crate_info().name; + let target_name = get_current_crates().remove(0).name; let iso_root = &target_dir.as_ref().join("iso_root"); let action = match &action { ActionChoice::Run => &config.run, @@ -108,7 +108,7 @@ fn generate_grub_cfg( initramfs_path: Option, protocol: &BootProtocol, ) -> String { - let target_name = get_current_crate_info().name; + let target_name = get_current_crates().remove(0).name; let grub_cfg = include_str!("grub.cfg.template").to_string(); // Delete the first two lines that notes the file a template file. diff --git a/osdk/src/commands/build/mod.rs b/osdk/src/commands/build/mod.rs index 14f684a47..b3e8cc1bd 100644 --- a/osdk/src/commands/build/mod.rs +++ b/osdk/src/commands/build/mod.rs @@ -29,7 +29,7 @@ use crate::{ }, error::Errno, error_msg, - util::{get_cargo_metadata, get_current_crate_info, get_target_directory}, + util::{get_cargo_metadata, get_current_crates, get_target_directory}, }; pub fn execute_build_command(config: &Config, build_args: &BuildArgs) { @@ -41,8 +41,22 @@ pub fn execute_build_command(config: &Config, build_args: &BuildArgs) { if !osdk_output_directory.exists() { std::fs::create_dir_all(&osdk_output_directory).unwrap(); } - let target_info = get_current_crate_info(); - let bundle_path = osdk_output_directory.join(target_info.name); + + let targets = get_current_crates(); + let mut target_info = None; + for target in targets { + if target.is_kernel_crate { + target_info = Some(target); + break; + } + } + + let target_info = target_info.unwrap_or_else(|| { + error_msg!("No kernel crate found in the current workspace"); + process::exit(Errno::NoKernelCrate as _); + }); + + let bundle_path = osdk_output_directory.join(target_info.name.clone()); let action = if build_args.for_test { ActionChoice::Test @@ -71,8 +85,8 @@ pub fn create_base_and_cached_build( let base_crate_path = osdk_output_directory.as_ref().join("base"); new_base_crate( &base_crate_path, - &get_current_crate_info().name, - get_current_crate_info().path, + &get_current_crates().remove(0).name, + get_current_crates().remove(0).path, false, ); let original_dir = std::env::current_dir().unwrap(); @@ -251,7 +265,7 @@ fn build_kernel_elf( .as_ref() .join(&target_os_string) .join(profile_name_adapter(profile)) - .join(get_current_crate_info().name); + .join(get_current_crates().remove(0).name); AsterBin::new( aster_bin_path, @@ -262,7 +276,7 @@ fn build_kernel_elf( has_multiboot_header: true, has_multiboot2_header: true, }), - get_current_crate_info().version, + get_current_crates().remove(0).version, false, ) } diff --git a/osdk/src/commands/debug.rs b/osdk/src/commands/debug.rs index a80f9c289..261e2ab71 100644 --- a/osdk/src/commands/debug.rs +++ b/osdk/src/commands/debug.rs @@ -3,7 +3,7 @@ use crate::{ cli::DebugArgs, commands::util::bin_file_name, - util::{get_current_crate_info, get_target_directory}, + util::{get_current_crates, get_target_directory}, }; use std::process::Command; @@ -12,7 +12,7 @@ pub fn execute_debug_command(_profile: &str, args: &DebugArgs) { let file_path = get_target_directory() .join("osdk") - .join(get_current_crate_info().name) + .join(get_current_crates().remove(0).name) .join(bin_file_name()); println!("Debugging {}", file_path.display()); diff --git a/osdk/src/commands/profile.rs b/osdk/src/commands/profile.rs index 587a645de..c1592f09a 100644 --- a/osdk/src/commands/profile.rs +++ b/osdk/src/commands/profile.rs @@ -13,7 +13,7 @@ use inferno::flamegraph; use crate::{ cli::{ProfileArgs, ProfileFormat}, commands::util::bin_file_name, - util::{get_current_crate_info, get_target_directory}, + util::{get_current_crates, get_target_directory}, }; use regex::Regex; use std::{collections::HashMap, fs::File, io::Write, path::PathBuf, process::Command}; @@ -45,7 +45,7 @@ fn do_parse_stack_traces(target_file: &PathBuf, args: &ProfileArgs) { fn do_collect_stack_traces(args: &ProfileArgs) { let file_path = get_target_directory() .join("osdk") - .join(get_current_crate_info().name) + .join(get_current_crates().remove(0).name) .join(bin_file_name()); let remote = &args.remote; diff --git a/osdk/src/commands/run.rs b/osdk/src/commands/run.rs index 59c542bcd..bc0e0536d 100644 --- a/osdk/src/commands/run.rs +++ b/osdk/src/commands/run.rs @@ -12,14 +12,27 @@ use crate::{ config::{scheme::ActionChoice, Config}, error::Errno, error_msg, - util::{get_current_crate_info, get_target_directory}, + util::{get_current_crates, get_target_directory}, warn_msg, }; pub fn execute_run_command(config: &Config, gdb_server_args: Option<&str>) { let cargo_target_directory = get_target_directory(); let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH); - let target_name = get_current_crate_info().name; + + let targets = get_current_crates(); + let mut target_name = None; + for target in targets { + if target.is_kernel_crate { + target_name = Some(target.name); + break; + } + } + + let target_name = target_name.unwrap_or_else(|| { + error_msg!("No kernel crate found in the current workspace"); + exit(Errno::NoKernelCrate as _); + }); let mut config = config.clone(); @@ -173,7 +186,7 @@ mod gdb { mod vsc { use crate::{ commands::util::bin_file_name, - util::{get_cargo_metadata, get_current_crate_info}, + util::{get_cargo_metadata, get_current_crates}, }; use serde_json::{from_str, Value}; use std::{ @@ -287,7 +300,7 @@ mod vsc { ) -> Result<(), std::io::Error> { let contents = include_str!("launch.json.template") .replace("#PROFILE#", profile) - .replace("#CRATE_NAME#", &get_current_crate_info().name) + .replace("#CRATE_NAME#", &get_current_crates().remove(0).name) .replace("#BIN_NAME#", &bin_file_name()) .replace( "#ADDR_PORT#", diff --git a/osdk/src/commands/test.rs b/osdk/src/commands/test.rs index bd2cb4fa6..6020ca7e9 100644 --- a/osdk/src/commands/test.rs +++ b/osdk/src/commands/test.rs @@ -9,21 +9,19 @@ use crate::{ config::{scheme::ActionChoice, Config}, error::Errno, error_msg, - util::{ - get_cargo_metadata, get_current_crate_info, get_target_directory, parse_package_id_string, - }, + util::{get_current_crates, get_target_directory}, }; pub fn execute_test_command(config: &Config, args: &TestArgs) { - let crates = get_workspace_default_members(); - for crate_path in crates { - std::env::set_current_dir(crate_path).unwrap(); + let crates = get_current_crates(); + for crate_info in crates { + std::env::set_current_dir(crate_info.path).unwrap(); test_current_crate(config, args); } } pub fn test_current_crate(config: &Config, args: &TestArgs) { - let current_crate = get_current_crate_info(); + let current_crate = get_current_crates().remove(0); let cargo_target_directory = get_target_directory(); let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH); // Use a different name for better separation and reusability of `run` and `test` @@ -87,7 +85,7 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); 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 target_name = get_current_crates().remove(0).name; let default_bundle_directory = osdk_output_directory.join(target_name); let original_dir = std::env::current_dir().unwrap(); std::env::set_current_dir(&target_crate_dir).unwrap(); @@ -104,20 +102,3 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); bundle.run(config, ActionChoice::Test); } - -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| { - let default_member = value.as_str().unwrap(); - let crate_info = parse_package_id_string(default_member); - crate_info.path - }) - .collect() -} diff --git a/osdk/src/commands/util.rs b/osdk/src/commands/util.rs index ad3683d5a..3bd6bd046 100644 --- a/osdk/src/commands/util.rs +++ b/osdk/src/commands/util.rs @@ -2,7 +2,7 @@ use std::process::Command; -use crate::util::get_current_crate_info; +use crate::util::get_current_crates; pub const COMMON_CARGO_ARGS: &[&str] = &[ "-Zbuild-std=core,alloc,compiler_builtins", @@ -23,7 +23,7 @@ pub fn profile_name_adapter(profile: &str) -> &str { } pub fn bin_file_name() -> String { - get_current_crate_info().name + "-osdk-bin" + get_current_crates().remove(0).name + "-osdk-bin" } pub(crate) fn is_tdx_enabled() -> bool { diff --git a/osdk/src/error.rs b/osdk/src/error.rs index 41260a993..b3376a0b4 100644 --- a/osdk/src/error.rs +++ b/osdk/src/error.rs @@ -12,6 +12,7 @@ pub enum Errno { BuildCrate = 7, RunBundle = 8, BadCrateName = 9, + NoKernelCrate = 10, } /// Print error message to console diff --git a/osdk/src/util.rs b/osdk/src/util.rs index 2833c8ef2..63732601d 100644 --- a/osdk/src/util.rs +++ b/osdk/src/util.rs @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 use std::{ + collections::HashMap, ffi::OsStr, fs::{self, File}, io::{BufRead, BufReader, Result, Write}, path::{Path, PathBuf}, process::Command, + sync::{LazyLock, Mutex}, }; use crate::{error::Errno, error_msg}; @@ -73,73 +75,93 @@ pub fn get_target_directory() -> PathBuf { .into() } +/// Information about an OSDK crate. +#[derive(Debug, Clone)] pub struct CrateInfo { pub name: String, pub version: String, pub path: String, + /// Whether the crate is a kernel crate. + /// + /// If a crate contains the `ostd::main` macro, it is a kernel crate (akin + /// to the binary crate in a normal Rust project, but it is a library from + /// the perspective of Cargo). + pub is_kernel_crate: bool, } -/// 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 `ostd::main` macro. -fn get_default_member(metadata: &serde_json::Value) -> &str { +/// Gets the default crates in the current directory. +pub fn get_current_crates() -> Vec { + // To avoid the overhead of parsing the Cargo metadata and reading the + // source multiple times. + static MEMOIZED: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + + let current_dir = std::env::current_dir().unwrap(); + let mut memoized = MEMOIZED.lock().unwrap(); + if let Some(crates) = memoized.get(¤t_dir) { + return crates.clone(); + } + + // If the current directory's crate info is not memoized, parse the Cargo + // metadata. + + let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap(); + let packages = metadata.get("packages").unwrap().as_array().unwrap(); + 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 mut result = Vec::new(); - let packages: Vec<_> = { - let packages = metadata.get("packages").unwrap().as_array().unwrap(); - - packages + for member in default_members { + let member_meta = packages .iter() - .filter(|package| { - let id = package.get("id").unwrap(); - if !default_members.contains(id) { + .find(|p| { + let Some(p_id) = p.get("id") else { 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_ostd_main_macro(&file) + p_id == member }) - .collect() - }; + .unwrap(); - if packages.is_empty() { - 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 _); + let is_kernel_crate = package_contains_ostd_main(member_meta); + + let parsed_id = parse_package_id_string(member.as_str().unwrap()); + + result.push(CrateInfo { + name: parsed_id.name, + version: parsed_id.version, + path: parsed_id.path, + is_kernel_crate, + }); } - 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 _); - } + memoized.insert(current_dir, result.clone()); - packages[0].get("id").unwrap().as_str().unwrap() + result } -fn contains_ostd_main_macro(file: &syn::File) -> bool { +fn package_contains_ostd_main(package: &serde_json::Value) -> bool { + 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() + }; + + file_contains_ostd_main_macro(&file) +} + +fn file_contains_ostd_main_macro(file: &syn::File) -> bool { for item in &file.items { let syn::Item::Fn(item_fn) = item else { continue; @@ -156,14 +178,13 @@ fn contains_ostd_main_macro(file: &syn::File) -> bool { false } -pub fn get_current_crate_info() -> CrateInfo { - let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap(); - - let default_member = get_default_member(&metadata); - parse_package_id_string(default_member) +struct ParsedID { + pub name: String, + pub version: String, + pub path: String, } -pub fn parse_package_id_string(package_id: &str) -> CrateInfo { +fn parse_package_id_string(package_id: &str) -> ParsedID { // Prior to 202403 (Rust 1.77.1), the package id string here is in the form of // " (path+file://)". // After that, it's @@ -173,7 +194,7 @@ pub fn parse_package_id_string(package_id: &str) -> CrateInfo { // After 1.77.1 if package_id.contains('@') { let package_id_segments = package_id.split(['#', '@']).collect::>(); - CrateInfo { + ParsedID { name: package_id_segments[1].to_string(), version: package_id_segments[2].to_string(), path: package_id_segments[0] @@ -185,7 +206,7 @@ pub fn parse_package_id_string(package_id: &str) -> CrateInfo { let path = package_id_segments[0] .trim_start_matches("path+file://") .to_string(); - CrateInfo { + ParsedID { name: PathBuf::from(path.clone()) .file_name() .unwrap() @@ -199,7 +220,7 @@ pub fn parse_package_id_string(package_id: &str) -> CrateInfo { } else { // Before 1.77.1 let default_member = package_id.split(' ').collect::>(); - CrateInfo { + ParsedID { name: default_member[0].to_string(), version: default_member[1].to_string(), path: default_member[2]