use std::path::{Path, PathBuf};
use clap::{Args, Subcommand};
use ostool::{
board::{RunBoardOptions, config::BoardRunConfig},
build::config::Cargo,
};
use crate::{
axvisor::context::AxvisorContext,
context::{
AppContext, AxvisorCliArgs, AxvisorRequestPaths, ResolvedAxvisorRequest,
SnapshotPersistence,
},
};
pub mod board;
pub mod build;
pub mod config;
pub mod context;
pub mod image;
pub mod rootfs;
pub mod test;
#[derive(Subcommand)]
pub enum Command {
Build(ArgsBuild),
Qemu(ArgsQemu),
Board(ArgsBoard),
Test(ArgsTest),
Uboot(ArgsUboot),
Defconfig(ArgsDefconfig),
Config(ArgsConfig),
Image(image::Args),
}
#[derive(Args, Clone)]
pub struct ArgsBuild {
#[arg(short, long)]
pub config: Option<PathBuf>,
#[arg(long)]
pub arch: Option<String>,
#[arg(short, long)]
pub target: Option<String>,
#[arg(long = "plat_dyn", alias = "plat-dyn")]
pub plat_dyn: Option<bool>,
#[arg(long, value_name = "CPUS")]
pub smp: Option<usize>,
#[arg(long)]
pub debug: bool,
#[arg(long)]
pub vmconfigs: Vec<PathBuf>,
}
#[derive(Args)]
pub struct ArgsQemu {
#[command(flatten)]
pub build: ArgsBuild,
#[arg(long)]
pub qemu_config: Option<PathBuf>,
#[arg(long, value_name = "IMAGE")]
pub rootfs: Option<PathBuf>,
}
#[derive(Args)]
pub struct ArgsUboot {
#[command(flatten)]
pub build: ArgsBuild,
#[arg(long)]
pub uboot_config: Option<PathBuf>,
}
#[derive(Args)]
pub struct ArgsBoard {
#[command(flatten)]
pub build: ArgsBuild,
#[arg(long = "board-config")]
pub board_config: Option<PathBuf>,
#[arg(short = 'b', long)]
pub board_type: Option<String>,
#[arg(long)]
pub server: Option<String>,
#[arg(long)]
pub port: Option<u16>,
}
#[derive(Args)]
pub struct ArgsDefconfig {
pub board: String,
}
#[derive(Args)]
pub struct ArgsConfig {
#[command(subcommand)]
pub command: ConfigCommand,
}
#[derive(Args)]
pub struct ArgsTest {
#[command(subcommand)]
pub command: TestCommand,
}
#[derive(Subcommand)]
pub enum TestCommand {
Qemu(ArgsTestQemu),
Uboot(ArgsTestUboot),
Board(ArgsTestBoard),
}
#[derive(Args, Debug, Clone)]
pub struct ArgsTestQemu {
#[arg(
long,
value_name = "ARCH",
required_unless_present_any = ["target", "list"],
help = "Axvisor architecture to test"
)]
pub arch: Option<String>,
#[arg(
short = 't',
long,
value_name = "TARGET",
required_unless_present_any = ["arch", "list"],
help = "Axvisor target triple to test"
)]
pub target: Option<String>,
#[arg(
short = 'g',
long = "test-group",
value_name = "GROUP",
help = "Run Axvisor QEMU test cases from one test group"
)]
pub test_group: Option<String>,
#[arg(
short = 'c',
long = "test-case",
value_name = "CASE",
help = "Run only one Axvisor QEMU test case"
)]
pub test_case: Option<String>,
#[arg(short = 'l', long, help = "List discovered Axvisor QEMU test cases")]
pub list: bool,
}
#[derive(Args, Debug, Clone)]
pub struct ArgsTestUboot {
#[arg(short = 'b', long, value_name = "BOARD")]
pub board: String,
#[arg(long, default_value = "linux", value_name = "GUEST")]
pub guest: String,
#[arg(long)]
pub uboot_config: Option<PathBuf>,
}
#[derive(Args, Debug, Clone, Default)]
pub struct ArgsTestBoard {
#[arg(
short = 'g',
long = "test-group",
value_name = "GROUP",
help = "Run Axvisor board test cases from one test group"
)]
pub test_group: Option<String>,
#[arg(
short = 'c',
long = "test-case",
value_name = "CASE",
help = "Run only one Axvisor board test case"
)]
pub test_case: Option<String>,
#[arg(
long,
value_name = "BOARD",
help = "Run all Axvisor board test cases for one board"
)]
pub board: Option<String>,
#[arg(short = 'b', long = "board-type", value_name = "BOARD_TYPE")]
pub board_type: Option<String>,
#[arg(long)]
pub server: Option<String>,
#[arg(long)]
pub port: Option<u16>,
#[arg(short = 'l', long, help = "List discovered Axvisor board test cases")]
pub list: bool,
}
#[derive(Subcommand)]
pub enum ConfigCommand {
Ls,
}
pub struct Axvisor {
app: AppContext,
ctx: AxvisorContext,
}
impl From<&ArgsBuild> for AxvisorCliArgs {
fn from(args: &ArgsBuild) -> Self {
Self {
config: args.config.clone(),
arch: args.arch.clone(),
target: args.target.clone(),
plat_dyn: args.plat_dyn,
smp: args.smp,
debug: args.debug,
vmconfigs: args.vmconfigs.clone(),
}
}
}
impl Axvisor {
pub fn new() -> anyhow::Result<Self> {
let app = AppContext::new()?;
let ctx = AxvisorContext::new()?;
Ok(Self { app, ctx })
}
pub async fn execute(&mut self, command: Command) -> anyhow::Result<()> {
match command {
Command::Build(args) => self.build(args).await,
Command::Qemu(args) => self.qemu(args).await,
Command::Uboot(args) => self.uboot(args).await,
Command::Board(args) => self.board(args).await,
Command::Defconfig(args) => self.defconfig(args),
Command::Config(args) => self.config(args),
Command::Image(args) => self.image(args).await,
Command::Test(args) => self.test(args).await,
}
}
async fn build(&mut self, args: ArgsBuild) -> anyhow::Result<()> {
let request =
self.prepare_request((&args).into(), None, None, SnapshotPersistence::Store)?;
self.run_build_request(request).await
}
async fn qemu(&mut self, args: ArgsQemu) -> anyhow::Result<()> {
rootfs::qemu(self, args).await
}
async fn uboot(&mut self, args: ArgsUboot) -> anyhow::Result<()> {
let request = self.prepare_request(
(&args.build).into(),
None,
args.uboot_config,
SnapshotPersistence::Store,
)?;
self.run_uboot_request(request).await
}
async fn board(&mut self, args: ArgsBoard) -> anyhow::Result<()> {
let request =
self.prepare_request((&args.build).into(), None, None, SnapshotPersistence::Store)?;
self.app.set_debug_mode(request.debug)?;
let cargo = build::load_cargo_config(&request)?;
let board_config = self
.load_board_config(&cargo, args.board_config.as_deref())
.await?;
self.app
.board(
cargo,
request.build_info_path,
board_config,
RunBoardOptions {
board_type: args.board_type,
server: args.server,
port: args.port,
},
)
.await
}
fn defconfig(&mut self, args: ArgsDefconfig) -> anyhow::Result<()> {
let workspace_root = self.app.workspace_root().to_path_buf();
let axvisor_dir = self
.app
.workspace_member_dir(build::AXVISOR_PACKAGE)?
.to_path_buf();
let path = config::write_defconfig(&workspace_root, &axvisor_dir, &args.board)?;
println!("Generated {} for board {}", path.display(), args.board);
Ok(())
}
fn config(&mut self, args: ArgsConfig) -> anyhow::Result<()> {
match args.command {
ConfigCommand::Ls => {
for board in config::available_board_names(
self.app.workspace_member_dir(build::AXVISOR_PACKAGE)?,
)? {
println!("{board}");
}
}
}
Ok(())
}
async fn image(&self, args: image::Args) -> anyhow::Result<()> {
image::run(args, &self.ctx).await
}
async fn test(&mut self, args: ArgsTest) -> anyhow::Result<()> {
test::test(self, args).await
}
pub(super) fn prepare_request(
&mut self,
args: AxvisorCliArgs,
qemu_config: Option<PathBuf>,
uboot_config: Option<PathBuf>,
persistence: SnapshotPersistence,
) -> anyhow::Result<ResolvedAxvisorRequest> {
let axvisor_dir = self
.app
.workspace_member_dir(build::AXVISOR_PACKAGE)?
.to_path_buf();
let (request, snapshot) = self.app.prepare_axvisor_request(
args,
AxvisorRequestPaths {
package: build::AXVISOR_PACKAGE.to_string(),
axvisor_dir,
load_config_target: build::load_target_from_build_config,
resolve_build_info_path: build::resolve_build_info_path,
},
qemu_config,
uboot_config,
)?;
if persistence.should_store() {
self.app.store_axvisor_snapshot(&snapshot)?;
}
Ok(request)
}
async fn load_uboot_config(
&mut self,
request: &ResolvedAxvisorRequest,
cargo: &Cargo,
) -> anyhow::Result<Option<ostool::run::uboot::UbootConfig>> {
match request.uboot_config.as_deref() {
Some(path) => self
.app
.tool_mut()
.read_uboot_config_from_path_for_cargo(cargo, path)
.await
.map(Some),
None => Ok(None),
}
}
async fn load_board_config(
&mut self,
cargo: &Cargo,
board_config_path: Option<&Path>,
) -> anyhow::Result<BoardRunConfig> {
match board_config_path {
Some(path) => {
self.app
.tool_mut()
.read_board_run_config_from_path_for_cargo(cargo, path)
.await
}
None => {
let workspace_root = self.app.workspace_root().to_path_buf();
self.app
.tool_mut()
.ensure_board_run_config_in_dir_for_cargo(cargo, &workspace_root)
.await
}
}
}
async fn run_build_request(&mut self, request: ResolvedAxvisorRequest) -> anyhow::Result<()> {
self.app.set_debug_mode(request.debug)?;
let cargo = build::load_cargo_config(&request)?;
self.app.build(cargo, request.build_info_path).await
}
async fn run_uboot_request(&mut self, request: ResolvedAxvisorRequest) -> anyhow::Result<()> {
self.app.set_debug_mode(request.debug)?;
let cargo = build::load_cargo_config(&request)?;
let uboot = self.load_uboot_config(&request, &cargo).await?;
self.app.uboot(cargo, request.build_info_path, uboot).await
}
}
fn default_qemu_config_template_path(axvisor_dir: &Path, arch: &str) -> PathBuf {
axvisor_dir.join(format!("configs/qemu/qemu-{arch}.toml"))
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::*;
use crate::context::{resolve_workspace_member_dir, workspace_root_path};
#[test]
fn context_resolves_workspace_root() {
let ctx = AxvisorContext::new().unwrap();
assert_eq!(
ctx.workspace_root(),
workspace_root_path().unwrap().as_path()
);
assert_eq!(
ctx.axvisor_dir(),
resolve_workspace_member_dir(crate::axvisor::build::AXVISOR_PACKAGE)
.unwrap()
.as_path()
);
}
#[test]
fn default_qemu_template_path_uses_axvisor_script_location() {
let path = default_qemu_config_template_path(Path::new("os/axvisor"), "aarch64");
assert_eq!(
path,
PathBuf::from("os/axvisor/configs/qemu/qemu-aarch64.toml")
);
}
#[test]
fn command_parses_uboot() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from([
"axvisor",
"uboot",
"--arch",
"aarch64",
"--uboot-config",
"uboot.toml",
"--vmconfigs",
"tmp/vm1.toml",
])
.unwrap();
match cli.command {
Command::Uboot(args) => {
assert_eq!(args.build.arch.as_deref(), Some("aarch64"));
assert_eq!(args.uboot_config, Some(PathBuf::from("uboot.toml")));
assert_eq!(args.build.vmconfigs, vec![PathBuf::from("tmp/vm1.toml")]);
}
_ => panic!("expected uboot command"),
}
}
#[test]
fn command_parses_board() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from([
"axvisor",
"board",
"--arch",
"aarch64",
"--board-config",
"remote.board.toml",
"-b",
"rk3568",
"--server",
"10.0.0.2",
"--port",
"9000",
"--vmconfigs",
"tmp/vm1.toml",
])
.unwrap();
match cli.command {
Command::Board(args) => {
assert_eq!(args.build.arch.as_deref(), Some("aarch64"));
assert_eq!(args.board_config, Some(PathBuf::from("remote.board.toml")));
assert_eq!(args.board_type.as_deref(), Some("rk3568"));
assert_eq!(args.server.as_deref(), Some("10.0.0.2"));
assert_eq!(args.port, Some(9000));
assert_eq!(args.build.vmconfigs, vec![PathBuf::from("tmp/vm1.toml")]);
}
_ => panic!("expected board command"),
}
}
#[test]
fn command_parses_test_qemu() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from(["axvisor", "test", "qemu", "--arch", "aarch64"]).unwrap();
match cli.command {
Command::Test(args) => match args.command {
TestCommand::Qemu(args) => {
assert_eq!(args.arch.as_deref(), Some("aarch64"));
assert_eq!(args.target, None);
assert_eq!(args.test_group, None);
assert_eq!(args.test_case, None);
}
_ => panic!("expected qemu test command"),
},
_ => panic!("expected test command"),
}
}
#[test]
fn command_parses_test_qemu_target() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli =
Cli::try_parse_from(["axvisor", "test", "qemu", "--target", "x86_64-unknown-none"])
.unwrap();
match cli.command {
Command::Test(args) => match args.command {
TestCommand::Qemu(args) => {
assert_eq!(args.arch, None);
assert_eq!(args.target.as_deref(), Some("x86_64-unknown-none"));
assert_eq!(args.test_group, None);
}
_ => panic!("expected qemu test command"),
},
_ => panic!("expected test command"),
}
}
#[test]
fn command_parses_test_qemu_case_filter() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from([
"axvisor", "test", "qemu", "--arch", "aarch64", "-g", "normal", "-c", "smoke",
])
.unwrap();
match cli.command {
Command::Test(args) => match args.command {
TestCommand::Qemu(args) => {
assert_eq!(args.arch.as_deref(), Some("aarch64"));
assert_eq!(args.test_group.as_deref(), Some("normal"));
assert_eq!(args.test_case.as_deref(), Some("smoke"));
}
_ => panic!("expected qemu test command"),
},
_ => panic!("expected test command"),
}
}
#[test]
fn command_parses_test_uboot() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from([
"axvisor",
"test",
"uboot",
"-b",
"roc-rk3568-pc",
"--guest",
"arceos",
"--uboot-config",
"uboot.toml",
])
.unwrap();
match cli.command {
Command::Test(args) => match args.command {
TestCommand::Uboot(args) => {
assert_eq!(args.board, "roc-rk3568-pc");
assert_eq!(args.guest, "arceos");
assert_eq!(args.uboot_config, Some(PathBuf::from("uboot.toml")));
}
_ => panic!("expected uboot test command"),
},
_ => panic!("expected test command"),
}
}
#[test]
fn command_parses_test_board() {
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let cli = Cli::try_parse_from([
"axvisor",
"test",
"board",
"-g",
"normal",
"-c",
"smoke",
"--board",
"phytiumpi-linux",
"-b",
"Phytiumpi",
"--server",
"10.0.0.2",
"--port",
"9000",
])
.unwrap();
match cli.command {
Command::Test(args) => match args.command {
TestCommand::Board(args) => {
assert_eq!(args.test_group.as_deref(), Some("normal"));
assert_eq!(args.test_case.as_deref(), Some("smoke"));
assert_eq!(args.board.as_deref(), Some("phytiumpi-linux"));
assert_eq!(args.board_type.as_deref(), Some("Phytiumpi"));
assert_eq!(args.server.as_deref(), Some("10.0.0.2"));
assert_eq!(args.port, Some(9000));
}
_ => panic!("expected board test command"),
},
_ => panic!("expected test command"),
}
}
#[test]
fn command_parses_build_and_qemu() {
let build_config = "os/axvisor/.build.toml";
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
let build_cli = Cli::try_parse_from([
"axvisor",
"build",
"--config",
build_config,
"--arch",
"aarch64",
"--vmconfigs",
"tmp/vm1.toml",
])
.unwrap();
match build_cli.command {
Command::Build(args) => {
assert_eq!(args.config, Some(PathBuf::from(build_config)));
assert_eq!(args.arch.as_deref(), Some("aarch64"));
assert_eq!(args.vmconfigs, vec![PathBuf::from("tmp/vm1.toml")]);
}
_ => panic!("expected build command"),
}
let qemu_cli = Cli::try_parse_from([
"axvisor",
"qemu",
"--config",
build_config,
"--arch",
"aarch64",
"--qemu-config",
"configs/qemu.toml",
"--vmconfigs",
"tmp/vm1.toml",
"--vmconfigs",
"tmp/vm2.toml",
])
.unwrap();
match qemu_cli.command {
Command::Qemu(args) => {
assert_eq!(args.build.config, Some(PathBuf::from(build_config)));
assert_eq!(args.build.arch.as_deref(), Some("aarch64"));
assert_eq!(args.qemu_config, Some(PathBuf::from("configs/qemu.toml")));
assert_eq!(
args.build.vmconfigs,
vec![PathBuf::from("tmp/vm1.toml"), PathBuf::from("tmp/vm2.toml")]
);
}
_ => panic!("expected qemu command"),
}
}
}