Skip to content

Commit 8cd5f37

Browse files
authored
feat(ostool): expose prepared runtime board APIs (#143)
1 parent 2d8fcaf commit 8cd5f37

3 files changed

Lines changed: 134 additions & 7 deletions

File tree

ostool/src/board/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,7 @@ pub async fn run_board(
241241
) -> anyhow::Result<()> {
242242
crate::build::prepare_runtime_artifacts(invocation, build_config, build_config_path, false)
243243
.await?;
244-
let scope = invocation.variable_scope()?;
245-
run_prepared_board(invocation, board_config, options, &scope).await
244+
run_prepared_board(invocation, board_config, options).await
246245
}
247246

248247
/// Builds a Cargo artifact and runs it on a remote board.
@@ -267,16 +266,20 @@ pub async fn cargo_run_board(
267266
.await
268267
}
269268

270-
pub(crate) async fn run_prepared_board(
269+
/// Runs already prepared runtime artifacts on a remote board.
270+
///
271+
/// The invocation must have runtime artifacts prepared by a previous build or by
272+
/// `ostool::build::prepare_runtime_artifact`.
273+
pub async fn run_prepared_board(
271274
invocation: &mut Invocation,
272275
board_config: &BoardRunConfig,
273276
options: RunBoardOptions,
274-
scope: &VariableScope,
275277
) -> anyhow::Result<()> {
278+
let scope = invocation.variable_scope()?;
276279
let global_config = load_board_global_config_with_notice()?;
277280
let mut board_config = board_config.clone();
278281
board_config.apply_overrides(
279-
scope,
282+
&scope,
280283
options.board_type.as_deref(),
281284
options.server.as_deref(),
282285
options.port,

ostool/src/build/mod.rs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ impl From<&CargoBuildOutcome> for CargoBuildOutput {
8585
}
8686
}
8787

88+
/// Input for preparing runtime artifacts from an already built ELF.
89+
#[derive(Debug, Clone, PartialEq, Eq)]
90+
pub struct RuntimeArtifactInput {
91+
elf_path: PathBuf,
92+
to_bin: bool,
93+
cargo_artifact_dir: Option<PathBuf>,
94+
strip_elf: bool,
95+
}
96+
97+
impl RuntimeArtifactInput {
98+
/// Creates a runtime artifact input from an ELF path.
99+
pub fn new(elf_path: impl Into<PathBuf>, to_bin: bool) -> Self {
100+
Self {
101+
elf_path: elf_path.into(),
102+
to_bin,
103+
cargo_artifact_dir: None,
104+
strip_elf: false,
105+
}
106+
}
107+
108+
/// Associates the input ELF with the Cargo artifact directory that produced it.
109+
pub fn with_cargo_artifact_dir(mut self, cargo_artifact_dir: impl Into<PathBuf>) -> Self {
110+
self.cargo_artifact_dir = Some(cargo_artifact_dir.into());
111+
self
112+
}
113+
114+
/// Copies the input ELF into a stripped runtime `.elf` before preparing outputs.
115+
pub fn strip_elf(mut self, strip_elf: bool) -> Self {
116+
self.strip_elf = strip_elf;
117+
self
118+
}
119+
}
120+
88121
/// Parameters for running a built Cargo artifact in QEMU.
89122
#[derive(Debug, Clone, Default, PartialEq, Eq)]
90123
pub struct CargoQemuRunnerArgs {
@@ -283,6 +316,31 @@ pub(crate) fn build_custom(invocation: &mut Invocation, config: &Custom) -> anyh
283316
Ok(())
284317
}
285318

319+
/// Prepares runtime ELF/BIN outputs from an already built artifact.
320+
///
321+
/// This is useful when a caller builds with [`cargo_build`], modifies the returned
322+
/// ELF in place, and then wants QEMU, U-Boot, or board runners to consume the
323+
/// updated artifact without rebuilding it.
324+
pub fn prepare_runtime_artifact(
325+
invocation: &mut Invocation,
326+
input: RuntimeArtifactInput,
327+
) -> anyhow::Result<()> {
328+
let process_context = invocation.process_context()?;
329+
let prepared = prepare_runtime_artifact_outputs(
330+
&process_context,
331+
RuntimeArtifactOptions {
332+
elf_path: input.elf_path,
333+
to_bin: input.to_bin,
334+
bin_dir: invocation.bin_dir(),
335+
debug: invocation.options().debug(),
336+
cargo_artifact_dir: input.cargo_artifact_dir,
337+
strip_elf: input.strip_elf,
338+
},
339+
)?;
340+
invocation.apply_prepared_runtime_artifacts(prepared);
341+
Ok(())
342+
}
343+
286344
/// Builds the project using Cargo and returns the executable artifact selected from Cargo output.
287345
///
288346
/// `config_path` is the optional `.build.toml` source path for `config`.
@@ -470,8 +528,9 @@ mod tests {
470528
};
471529

472530
use super::{
473-
CargoBuildOutput, CargoSelector, activate_build_config, activate_build_context,
474-
apply_cargo_build_outcome, build_with_config,
531+
CargoBuildOutput, CargoSelector, RuntimeArtifactInput, activate_build_config,
532+
activate_build_context, apply_cargo_build_outcome, build_with_config,
533+
prepare_runtime_artifact,
475534
};
476535

477536
#[test]
@@ -552,6 +611,58 @@ mod tests {
552611
assert_eq!(output.cargo_artifact_dir(), cargo_artifact_dir.as_path());
553612
}
554613

614+
#[test]
615+
fn prepare_runtime_artifact_records_external_artifact_state() {
616+
let temp = tempfile::tempdir().unwrap();
617+
fs::write(
618+
temp.path().join("Cargo.toml"),
619+
"[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
620+
)
621+
.unwrap();
622+
fs::create_dir_all(temp.path().join("src")).unwrap();
623+
fs::write(temp.path().join("src/main.rs"), "fn main() {}\n").unwrap();
624+
625+
let cargo_artifact_dir = temp.path().join("target/aarch64/debug");
626+
fs::create_dir_all(&cargo_artifact_dir).unwrap();
627+
let elf_path = cargo_artifact_dir.join("kernel");
628+
fs::copy(std::env::current_exe().unwrap(), &elf_path).unwrap();
629+
630+
let mut invocation = Invocation::new(InvocationOptions::new(
631+
Some(temp.path().to_path_buf()),
632+
None,
633+
None,
634+
false,
635+
))
636+
.unwrap();
637+
638+
prepare_runtime_artifact(
639+
&mut invocation,
640+
RuntimeArtifactInput::new(&elf_path, false)
641+
.with_cargo_artifact_dir(cargo_artifact_dir.clone()),
642+
)
643+
.unwrap();
644+
645+
let expected_elf = elf_path.canonicalize().unwrap();
646+
assert_eq!(
647+
invocation.runtime_artifacts().elf(),
648+
Some(expected_elf.as_path())
649+
);
650+
assert!(invocation.runtime_artifacts().bin().is_none());
651+
assert_eq!(
652+
invocation.runtime_artifacts().cargo_artifact_dir(),
653+
Some(cargo_artifact_dir.as_path())
654+
);
655+
assert_eq!(
656+
invocation.runtime_artifacts().cargo_source_artifact_dir(),
657+
Some(cargo_artifact_dir.as_path())
658+
);
659+
assert_eq!(
660+
invocation.runtime_artifacts().cargo_source_elf(),
661+
Some(expected_elf.as_path())
662+
);
663+
assert!(invocation.runtime_arch().is_some());
664+
}
665+
555666
#[tokio::test]
556667
async fn custom_build_only_does_not_prepare_runtime_artifacts() {
557668
let temp = tempfile::tempdir().unwrap();

ostool/tests/ui/pass_module_level_apis.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use ostool::{
44
board::{self, config::BoardRunConfig},
55
build::{
66
self, CargoBuildOutput, CargoQemuRunnerArgs, CargoRunnerKind, CargoUbootRunnerArgs,
7+
RuntimeArtifactInput,
78
config::{BuildConfig, BuildSystem, Cargo, Custom},
89
},
910
invocation::{Invocation, InvocationOptions},
@@ -60,6 +61,12 @@ fn main() {
6061
let _ = build::build_with_config(&mut invocation, &custom_build, None).await;
6162
let _: anyhow::Result<CargoBuildOutput> =
6263
build::cargo_build(&mut invocation, &cargo, None).await;
64+
let _ = build::prepare_runtime_artifact(
65+
&mut invocation,
66+
RuntimeArtifactInput::new("target/kernel", true)
67+
.with_cargo_artifact_dir("target/aarch64/debug")
68+
.strip_elf(false),
69+
);
6370
let _ = build::cargo_run(&mut invocation, &cargo, None, &qemu_runner).await;
6471
let _ = build::cargo_run(&mut invocation, &cargo, None, &uboot_runner).await;
6572

@@ -112,5 +119,11 @@ fn main() {
112119
board::RunBoardOptions::default(),
113120
)
114121
.await;
122+
let _ = board::run_prepared_board(
123+
&mut invocation,
124+
&board_config,
125+
board::RunBoardOptions::default(),
126+
)
127+
.await;
115128
};
116129
}

0 commit comments

Comments
 (0)