From bae401f88a45f4347cf0f12992ae376967a0a0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 18 Jun 2026 15:31:16 +0800 Subject: [PATCH] feat(ostool): expose prepared runtime board APIs --- ostool/src/board/mod.rs | 13 ++- ostool/src/build/mod.rs | 115 +++++++++++++++++++++- ostool/tests/ui/pass_module_level_apis.rs | 13 +++ 3 files changed, 134 insertions(+), 7 deletions(-) diff --git a/ostool/src/board/mod.rs b/ostool/src/board/mod.rs index 70866b5c..4cc1513b 100644 --- a/ostool/src/board/mod.rs +++ b/ostool/src/board/mod.rs @@ -241,8 +241,7 @@ pub async fn run_board( ) -> anyhow::Result<()> { crate::build::prepare_runtime_artifacts(invocation, build_config, build_config_path, false) .await?; - let scope = invocation.variable_scope()?; - run_prepared_board(invocation, board_config, options, &scope).await + run_prepared_board(invocation, board_config, options).await } /// Builds a Cargo artifact and runs it on a remote board. @@ -267,16 +266,20 @@ pub async fn cargo_run_board( .await } -pub(crate) async fn run_prepared_board( +/// Runs already prepared runtime artifacts on a remote board. +/// +/// The invocation must have runtime artifacts prepared by a previous build or by +/// `ostool::build::prepare_runtime_artifact`. +pub async fn run_prepared_board( invocation: &mut Invocation, board_config: &BoardRunConfig, options: RunBoardOptions, - scope: &VariableScope, ) -> anyhow::Result<()> { + let scope = invocation.variable_scope()?; let global_config = load_board_global_config_with_notice()?; let mut board_config = board_config.clone(); board_config.apply_overrides( - scope, + &scope, options.board_type.as_deref(), options.server.as_deref(), options.port, diff --git a/ostool/src/build/mod.rs b/ostool/src/build/mod.rs index 9b1650ad..577d63bf 100644 --- a/ostool/src/build/mod.rs +++ b/ostool/src/build/mod.rs @@ -85,6 +85,39 @@ impl From<&CargoBuildOutcome> for CargoBuildOutput { } } +/// Input for preparing runtime artifacts from an already built ELF. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimeArtifactInput { + elf_path: PathBuf, + to_bin: bool, + cargo_artifact_dir: Option, + strip_elf: bool, +} + +impl RuntimeArtifactInput { + /// Creates a runtime artifact input from an ELF path. + pub fn new(elf_path: impl Into, to_bin: bool) -> Self { + Self { + elf_path: elf_path.into(), + to_bin, + cargo_artifact_dir: None, + strip_elf: false, + } + } + + /// Associates the input ELF with the Cargo artifact directory that produced it. + pub fn with_cargo_artifact_dir(mut self, cargo_artifact_dir: impl Into) -> Self { + self.cargo_artifact_dir = Some(cargo_artifact_dir.into()); + self + } + + /// Copies the input ELF into a stripped runtime `.elf` before preparing outputs. + pub fn strip_elf(mut self, strip_elf: bool) -> Self { + self.strip_elf = strip_elf; + self + } +} + /// Parameters for running a built Cargo artifact in QEMU. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct CargoQemuRunnerArgs { @@ -283,6 +316,31 @@ pub(crate) fn build_custom(invocation: &mut Invocation, config: &Custom) -> anyh Ok(()) } +/// Prepares runtime ELF/BIN outputs from an already built artifact. +/// +/// This is useful when a caller builds with [`cargo_build`], modifies the returned +/// ELF in place, and then wants QEMU, U-Boot, or board runners to consume the +/// updated artifact without rebuilding it. +pub fn prepare_runtime_artifact( + invocation: &mut Invocation, + input: RuntimeArtifactInput, +) -> anyhow::Result<()> { + let process_context = invocation.process_context()?; + let prepared = prepare_runtime_artifact_outputs( + &process_context, + RuntimeArtifactOptions { + elf_path: input.elf_path, + to_bin: input.to_bin, + bin_dir: invocation.bin_dir(), + debug: invocation.options().debug(), + cargo_artifact_dir: input.cargo_artifact_dir, + strip_elf: input.strip_elf, + }, + )?; + invocation.apply_prepared_runtime_artifacts(prepared); + Ok(()) +} + /// Builds the project using Cargo and returns the executable artifact selected from Cargo output. /// /// `config_path` is the optional `.build.toml` source path for `config`. @@ -470,8 +528,9 @@ mod tests { }; use super::{ - CargoBuildOutput, CargoSelector, activate_build_config, activate_build_context, - apply_cargo_build_outcome, build_with_config, + CargoBuildOutput, CargoSelector, RuntimeArtifactInput, activate_build_config, + activate_build_context, apply_cargo_build_outcome, build_with_config, + prepare_runtime_artifact, }; #[test] @@ -552,6 +611,58 @@ mod tests { assert_eq!(output.cargo_artifact_dir(), cargo_artifact_dir.as_path()); } + #[test] + fn prepare_runtime_artifact_records_external_artifact_state() { + let temp = tempfile::tempdir().unwrap(); + fs::write( + temp.path().join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::create_dir_all(temp.path().join("src")).unwrap(); + fs::write(temp.path().join("src/main.rs"), "fn main() {}\n").unwrap(); + + let cargo_artifact_dir = temp.path().join("target/aarch64/debug"); + fs::create_dir_all(&cargo_artifact_dir).unwrap(); + let elf_path = cargo_artifact_dir.join("kernel"); + fs::copy(std::env::current_exe().unwrap(), &elf_path).unwrap(); + + let mut invocation = Invocation::new(InvocationOptions::new( + Some(temp.path().to_path_buf()), + None, + None, + false, + )) + .unwrap(); + + prepare_runtime_artifact( + &mut invocation, + RuntimeArtifactInput::new(&elf_path, false) + .with_cargo_artifact_dir(cargo_artifact_dir.clone()), + ) + .unwrap(); + + let expected_elf = elf_path.canonicalize().unwrap(); + assert_eq!( + invocation.runtime_artifacts().elf(), + Some(expected_elf.as_path()) + ); + assert!(invocation.runtime_artifacts().bin().is_none()); + assert_eq!( + invocation.runtime_artifacts().cargo_artifact_dir(), + Some(cargo_artifact_dir.as_path()) + ); + assert_eq!( + invocation.runtime_artifacts().cargo_source_artifact_dir(), + Some(cargo_artifact_dir.as_path()) + ); + assert_eq!( + invocation.runtime_artifacts().cargo_source_elf(), + Some(expected_elf.as_path()) + ); + assert!(invocation.runtime_arch().is_some()); + } + #[tokio::test] async fn custom_build_only_does_not_prepare_runtime_artifacts() { let temp = tempfile::tempdir().unwrap(); diff --git a/ostool/tests/ui/pass_module_level_apis.rs b/ostool/tests/ui/pass_module_level_apis.rs index 947472c9..ca480a5d 100644 --- a/ostool/tests/ui/pass_module_level_apis.rs +++ b/ostool/tests/ui/pass_module_level_apis.rs @@ -4,6 +4,7 @@ use ostool::{ board::{self, config::BoardRunConfig}, build::{ self, CargoBuildOutput, CargoQemuRunnerArgs, CargoRunnerKind, CargoUbootRunnerArgs, + RuntimeArtifactInput, config::{BuildConfig, BuildSystem, Cargo, Custom}, }, invocation::{Invocation, InvocationOptions}, @@ -60,6 +61,12 @@ fn main() { let _ = build::build_with_config(&mut invocation, &custom_build, None).await; let _: anyhow::Result = build::cargo_build(&mut invocation, &cargo, None).await; + let _ = build::prepare_runtime_artifact( + &mut invocation, + RuntimeArtifactInput::new("target/kernel", true) + .with_cargo_artifact_dir("target/aarch64/debug") + .strip_elf(false), + ); let _ = build::cargo_run(&mut invocation, &cargo, None, &qemu_runner).await; let _ = build::cargo_run(&mut invocation, &cargo, None, &uboot_runner).await; @@ -112,5 +119,11 @@ fn main() { board::RunBoardOptions::default(), ) .await; + let _ = board::run_prepared_board( + &mut invocation, + &board_config, + board::RunBoardOptions::default(), + ) + .await; }; }