diff --git a/.cargo/config.toml b/.cargo/config.toml index 4bbc3d91ea..43dd848697 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,7 @@ incompatible-rust-versions = "allow" [alias] arceos = "run -p tg-xtask -- arceos" +axloader = "run -p tg-xtask -- axloader" axvisor = "run -p tg-xtask -- axvisor" starry = "run -p tg-xtask -- starry" xtask = "run -p tg-xtask --" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2b521b208..8835f5de3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,6 +155,7 @@ jobs: - "Cargo.toml" - "Cargo.lock" - "rust-toolchain.toml" + - "bootloader/axloader/**" - "components/**" - "drivers/**" - "os/**" @@ -624,8 +625,8 @@ jobs: echo "ERROR: installed OVMF firmware image was not found under /usr/share." >&2 exit 1 fi - export AXVISOR_X86_64_UEFI_FIRMWARE="${UEFI_FIRMWARE}" - cargo axvisor loader test --target x86_64-unknown-uefi --http-smoke + export AXLOADER_X86_64_UEFI_FIRMWARE="${UEFI_FIRMWARE}" + cargo axloader test qemu --target x86_64-unknown-uefi cache_key: "" container_image: "" limit_to_owner: rcore-os diff --git a/bootloader/axloader/README.md b/bootloader/axloader/README.md index abecd07693..589102e444 100644 --- a/bootloader/axloader/README.md +++ b/bootloader/axloader/README.md @@ -127,8 +127,8 @@ board-specific files such as AxVisor board configs, remote board configs, and VM configs are expected to come from the board-flow work that lands separately. ```bash -cargo axvisor loader build -cargo axvisor loader test +cargo axloader build +cargo axloader test qemu ``` Once a board-flow configuration is available, the host side is responsible for diff --git a/scripts/axbuild/src/axvisor/loader.rs b/scripts/axbuild/src/axloader/mod.rs similarity index 79% rename from scripts/axbuild/src/axvisor/loader.rs rename to scripts/axbuild/src/axloader/mod.rs index 37a3272eee..52a47c9cf9 100644 --- a/scripts/axbuild/src/axvisor/loader.rs +++ b/scripts/axbuild/src/axloader/mod.rs @@ -3,7 +3,7 @@ use std::{ io::{Read, Write}, net::TcpListener, path::{Path, PathBuf}, - process::{Child, Command, Stdio}, + process::{Child, Command as StdCommand, Stdio}, sync::{ Arc, atomic::{AtomicBool, Ordering}, @@ -14,7 +14,7 @@ use std::{ }; use anyhow::{Context, bail}; -use clap::Args; +use clap::{Args, Subcommand}; use crate::support::process::ProcessExt; @@ -23,6 +23,7 @@ const AXLOADER_BIN: &str = "axloader"; const DEFAULT_UEFI_TARGET: &str = "x86_64-unknown-uefi"; const HTTP_SMOKE_TIMEOUT: Duration = Duration::from_secs(120); const QEMU_HOST_GATEWAY: &str = "10.0.2.2"; +const LEGACY_X86_64_UEFI_FIRMWARE_ENV: &str = "AXVISOR_X86_64_UEFI_FIRMWARE"; #[derive(Clone, Copy)] struct LoaderSmokeTarget { @@ -57,12 +58,48 @@ pub struct ArgsBuild { #[derive(Args, Debug, Clone, PartialEq, Eq)] pub struct ArgsTest { + #[command(subcommand)] + pub command: TestCommand, +} + +#[derive(Subcommand, Debug, Clone, PartialEq, Eq)] +pub enum TestCommand { + /// Run axloader host checks and QEMU HTTP smoke test + Qemu(ArgsTestQemu), +} + +#[derive(Args, Debug, Clone, PartialEq, Eq)] +pub struct ArgsTestQemu { #[arg(long, default_value = DEFAULT_UEFI_TARGET)] pub target: String, +} - /// Run axloader in QEMU and verify HTTP kernel transfer. - #[arg(long)] - pub http_smoke: bool, +/// Axloader host-side commands +#[derive(Subcommand)] +pub enum Command { + /// Build axloader + Build(ArgsBuild), + /// Run axloader test suites + Test(ArgsTest), +} + +pub struct Axloader { + workspace_root: PathBuf, +} + +impl Axloader { + pub fn new() -> anyhow::Result { + Ok(Self { + workspace_root: crate::context::workspace_root_path()?, + }) + } + + pub async fn execute(&mut self, command: Command) -> anyhow::Result<()> { + match command { + Command::Build(args) => build(&self.workspace_root, args), + Command::Test(args) => test(&self.workspace_root, args), + } + } } pub fn build(workspace_root: &Path, args: ArgsBuild) -> anyhow::Result<()> { @@ -70,6 +107,12 @@ pub fn build(workspace_root: &Path, args: ArgsBuild) -> anyhow::Result<()> { } pub fn test(workspace_root: &Path, args: ArgsTest) -> anyhow::Result<()> { + match args.command { + TestCommand::Qemu(args) => test_qemu(workspace_root, args), + } +} + +fn test_qemu(workspace_root: &Path, args: ArgsTestQemu) -> anyhow::Result<()> { run_cargo( workspace_root, ["test", "-p", AXLOADER_PACKAGE, "--all-targets"], @@ -88,11 +131,7 @@ pub fn test(workspace_root: &Path, args: ArgsTest) -> anyhow::Result<()> { ); result?; - if args.http_smoke { - run_http_smoke_test(workspace_root, &args.target)?; - } - - Ok(()) + run_http_smoke_test(workspace_root, &args.target) } fn run_loader_build(workspace_root: &Path, target: &str, release: bool) -> anyhow::Result<()> { @@ -115,7 +154,7 @@ fn run_cargo<'a>( workspace_root: &Path, args: impl IntoIterator, ) -> anyhow::Result<()> { - let mut command = Command::new("cargo"); + let mut command = StdCommand::new("cargo"); command.current_dir(workspace_root).args(args); command.exec() } @@ -235,7 +274,7 @@ fn smoke_target(target: &str) -> anyhow::Result { cargo_target: "x86_64-unknown-uefi", arch: "x86_64", efi_output_file: "BOOTX64.EFI", - firmware_env: "AXVISOR_X86_64_UEFI_FIRMWARE", + firmware_env: "AXLOADER_X86_64_UEFI_FIRMWARE", firmware_candidates: X86_64_UEFI_FIRMWARE_CANDIDATES, qemu_program: "qemu-system-x86_64", qemu_args: x86_64_qemu_args, @@ -252,6 +291,14 @@ fn find_uefi_firmware(target: LoaderSmokeTarget) -> anyhow::Result { return Ok(path); } } + if target.firmware_env == "AXLOADER_X86_64_UEFI_FIRMWARE" + && let Some(path) = std::env::var_os(LEGACY_X86_64_UEFI_FIRMWARE_ENV) + { + let path = PathBuf::from(path); + if path.is_file() { + return Ok(path); + } + } for candidate in target.firmware_candidates { let path = PathBuf::from(candidate); @@ -272,7 +319,7 @@ fn spawn_axloader_qemu( firmware: &Path, esp_dir: &Path, ) -> anyhow::Result { - Command::new(target.qemu_program) + StdCommand::new(target.qemu_program) .args((target.qemu_args)(firmware, esp_dir)) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -461,3 +508,70 @@ fn put_u32(image: &mut [u8], offset: usize, value: u32) { fn put_u64(image: &mut [u8], offset: usize, value: u64) { image[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); } + +#[cfg(test)] +mod tests { + use clap::Parser; + + use super::*; + + #[derive(Parser)] + struct Cli { + #[command(subcommand)] + command: Command, + } + + #[test] + fn command_parses_build_default_target() { + let cli = Cli::try_parse_from(["axloader", "build"]).unwrap(); + + match cli.command { + Command::Build(args) => { + assert_eq!(args.target, "x86_64-unknown-uefi"); + assert!(!args.release); + assert!(!args.debug); + } + _ => panic!("expected build command"), + } + } + + #[test] + fn command_parses_build_debug() { + let cli = Cli::try_parse_from(["axloader", "build", "--debug"]).unwrap(); + + match cli.command { + Command::Build(args) => { + assert_eq!(args.target, "x86_64-unknown-uefi"); + assert!(!args.release); + assert!(args.debug); + } + _ => panic!("expected build command"), + } + } + + #[test] + fn command_parses_test_qemu() { + let cli = Cli::try_parse_from([ + "axloader", + "test", + "qemu", + "--target", + "x86_64-unknown-uefi", + ]) + .unwrap(); + + match cli.command { + Command::Test(args) => match args.command { + TestCommand::Qemu(args) => { + assert_eq!(args.target, "x86_64-unknown-uefi"); + } + }, + _ => panic!("expected test command"), + } + } + + #[test] + fn command_rejects_legacy_http_smoke_flag() { + assert!(Cli::try_parse_from(["axloader", "test", "qemu", "--http-smoke"]).is_err()); + } +} diff --git a/scripts/axbuild/src/axvisor/mod.rs b/scripts/axbuild/src/axvisor/mod.rs index 11647b531f..14e5375662 100644 --- a/scripts/axbuild/src/axvisor/mod.rs +++ b/scripts/axbuild/src/axvisor/mod.rs @@ -13,7 +13,6 @@ use crate::context::{ pub mod board; pub mod build; pub mod config; -pub mod loader; pub mod rootfs; pub mod test; @@ -26,8 +25,6 @@ pub enum Command { Qemu(ArgsQemu), /// Build and run Axvisor on a remote board Board(ArgsBoard), - /// Build and test the Axvisor UEFI HTTP loader - Loader(ArgsLoader), /// Run Axvisor test suites Test(ArgsTest), /// Build and run Axvisor with U-Boot @@ -102,24 +99,6 @@ pub struct ArgsBoard { pub port: Option, } -#[derive(Args)] -#[command(args_conflicts_with_subcommands = true)] -pub struct ArgsLoader { - #[command(subcommand)] - pub command: Option, - - #[command(flatten)] - pub build: loader::ArgsBuild, -} - -#[derive(Subcommand)] -pub enum LoaderCommand { - /// Build axloader - Build(loader::ArgsBuild), - /// Run axloader host tests and UEFI target checks - Test(loader::ArgsTest), -} - #[derive(Args)] pub struct ArgsDefconfig { pub board: String, @@ -268,7 +247,6 @@ impl Axvisor { Command::Qemu(args) => self.qemu(args).await, Command::Uboot(args) => self.uboot(args).await, Command::Board(args) => self.board(args).await, - Command::Loader(args) => self.loader(args), Command::Defconfig(args) => self.defconfig(args), Command::Config(args) => self.config(args), Command::Test(args) => self.test(args).await, @@ -317,14 +295,6 @@ impl Axvisor { .await } - fn loader(&mut self, args: ArgsLoader) -> anyhow::Result<()> { - match args.command { - Some(LoaderCommand::Build(args)) => loader::build(self.app.workspace_root(), args), - Some(LoaderCommand::Test(args)) => loader::test(self.app.workspace_root(), args), - None => loader::build(self.app.workspace_root(), args.build), - } - } - fn defconfig(&mut self, args: ArgsDefconfig) -> anyhow::Result<()> { let workspace_root = self.app.workspace_root().to_path_buf(); let axvisor_dir = self @@ -671,100 +641,6 @@ mod tests { } } - #[test] - fn command_parses_loader_default_build() { - #[derive(Parser)] - struct Cli { - #[command(subcommand)] - command: Command, - } - - let cli = - Cli::try_parse_from(["axvisor", "loader", "--target", "x86_64-unknown-uefi"]).unwrap(); - - match cli.command { - Command::Loader(args) => { - assert!(args.command.is_none()); - assert_eq!(args.build.target, "x86_64-unknown-uefi"); - assert!(!args.build.debug); - } - _ => panic!("expected loader command"), - } - } - - #[test] - fn command_parses_loader_build() { - #[derive(Parser)] - struct Cli { - #[command(subcommand)] - command: Command, - } - - let cli = Cli::try_parse_from(["axvisor", "loader", "build", "--debug"]).unwrap(); - - match cli.command { - Command::Loader(args) => match args.command { - Some(LoaderCommand::Build(build)) => { - assert_eq!(build.target, "x86_64-unknown-uefi"); - assert!(build.debug); - } - _ => panic!("expected loader build command"), - }, - _ => panic!("expected loader command"), - } - } - - #[test] - fn command_parses_loader_test() { - #[derive(Parser)] - struct Cli { - #[command(subcommand)] - command: Command, - } - - let cli = Cli::try_parse_from([ - "axvisor", - "loader", - "test", - "--target", - "x86_64-unknown-uefi", - ]) - .unwrap(); - - match cli.command { - Command::Loader(args) => match args.command { - Some(LoaderCommand::Test(test)) => { - assert_eq!(test.target, "x86_64-unknown-uefi"); - assert!(!test.http_smoke); - } - _ => panic!("expected loader test command"), - }, - _ => panic!("expected loader command"), - } - } - - #[test] - fn command_parses_loader_test_http_smoke() { - #[derive(Parser)] - struct Cli { - #[command(subcommand)] - command: Command, - } - - let cli = Cli::try_parse_from(["axvisor", "loader", "test", "--http-smoke"]).unwrap(); - - match cli.command { - Command::Loader(args) => match args.command { - Some(LoaderCommand::Test(test)) => { - assert_eq!(test.target, "x86_64-unknown-uefi"); - assert!(test.http_smoke); - } - _ => panic!("expected loader test command"), - }, - _ => panic!("expected loader command"), - } - } - #[test] fn command_parses_build_and_qemu() { let build_config = "os/axvisor/.build.toml"; diff --git a/scripts/axbuild/src/lib.rs b/scripts/axbuild/src/lib.rs index 3e5e65583d..327c1f2e9d 100644 --- a/scripts/axbuild/src/lib.rs +++ b/scripts/axbuild/src/lib.rs @@ -3,9 +3,10 @@ use clap::{Args, Parser, Subcommand}; -use crate::{arceos::ArceOS, axvisor::Axvisor, starry::Starry}; +use crate::{arceos::ArceOS, axloader::Axloader, axvisor::Axvisor, starry::Starry}; pub mod arceos; +pub mod axloader; pub mod axvisor; mod backtrace; mod board; @@ -77,6 +78,11 @@ enum Commands { #[command(subcommand)] command: axvisor::Command, }, + /// Axloader host-side commands + Axloader { + #[command(subcommand)] + command: axloader::Command, + }, /// ArceOS build commands Arceos { #[command(subcommand)] @@ -107,6 +113,7 @@ async fn run_root_cli(cli: Cli) -> anyhow::Result<()> { Commands::Backtrace { command } => backtrace::execute(command), Commands::Image(args) => image::run(args).await, Commands::Axvisor { command } => Axvisor::new()?.execute(command).await, + Commands::Axloader { command } => Axloader::new()?.execute(command).await, Commands::Arceos { command } => ArceOS::new()?.execute(command).await, Commands::Starry { command } => { ensure_aic8800_firmware().await?;