@@ -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 ) ]
90123pub 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]\n name = \" kernel\" \n version = \" 0.1.0\" \n edition = \" 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 ( ) ;
0 commit comments