66
77use std:: {
88 collections:: HashMap ,
9+ io:: BufReader ,
910 path:: { Path , PathBuf } ,
11+ process:: Stdio ,
1012} ;
1113
14+ use anyhow:: { Context , anyhow, bail} ;
15+ use cargo_metadata:: { Message , PackageId } ;
1216use colored:: Colorize ;
1317
14- use crate :: { build:: config:: Cargo , ctx:: AppContext , utils:: Command } ;
18+ use crate :: {
19+ build:: { config:: Cargo , someboot} ,
20+ ctx:: AppContext ,
21+ utils:: { Command , PathResultExt } ,
22+ } ;
1523
1624/// A builder for constructing and executing Cargo commands.
1725///
@@ -35,6 +43,8 @@ pub struct CargoBuilder<'a> {
3543 extra_args : Vec < String > ,
3644 extra_envs : HashMap < String , String > ,
3745 skip_objcopy : bool ,
46+ resolve_artifact_from_json : bool ,
47+ resolved_elf_path : Option < PathBuf > ,
3848 config_path : Option < PathBuf > ,
3949}
4050
@@ -54,6 +64,8 @@ impl<'a> CargoBuilder<'a> {
5464 extra_args : Vec :: new ( ) ,
5565 extra_envs : HashMap :: new ( ) ,
5666 skip_objcopy : false ,
67+ resolve_artifact_from_json : false ,
68+ resolved_elf_path : None ,
5769 config_path,
5870 }
5971 }
@@ -73,6 +85,8 @@ impl<'a> CargoBuilder<'a> {
7385 extra_args : Vec :: new ( ) ,
7486 extra_envs : HashMap :: new ( ) ,
7587 skip_objcopy : true ,
88+ resolve_artifact_from_json : false ,
89+ resolved_elf_path : None ,
7690 config_path,
7791 }
7892 }
@@ -130,6 +144,12 @@ impl<'a> CargoBuilder<'a> {
130144 self
131145 }
132146
147+ /// Enables artifact path resolution from Cargo JSON messages.
148+ pub fn resolve_artifact_from_json ( mut self , enable : bool ) -> Self {
149+ self . resolve_artifact_from_json = enable;
150+ self
151+ }
152+
133153 /// Executes the configured Cargo command.
134154 ///
135155 /// This runs pre-build commands, executes Cargo, handles output artifacts,
@@ -162,11 +182,78 @@ impl<'a> CargoBuilder<'a> {
162182 }
163183
164184 async fn run_cargo ( & mut self ) -> anyhow:: Result < ( ) > {
185+ if self . resolve_artifact_from_json {
186+ return self . run_cargo_and_resolve_artifact ( ) . await ;
187+ }
188+
165189 let mut cmd = self . build_cargo_command ( ) . await ?;
166190 cmd. run ( ) ?;
167191 Ok ( ( ) )
168192 }
169193
194+ async fn run_cargo_and_resolve_artifact ( & mut self ) -> anyhow:: Result < ( ) > {
195+ let ( target_pkg_id, default_run) = self . target_package_info ( ) ?;
196+ let mut cmd = self . build_cargo_command ( ) . await ?;
197+
198+ cmd. stdout ( Stdio :: piped ( ) ) ;
199+ cmd. stderr ( Stdio :: inherit ( ) ) ;
200+ cmd. print_cmd ( ) ;
201+
202+ let mut child = cmd
203+ . spawn ( )
204+ . context ( "failed to spawn cargo build command for artifact resolution" ) ?;
205+
206+ let stdout = child
207+ . stdout
208+ . take ( )
209+ . ok_or_else ( || anyhow ! ( "failed to capture cargo stdout for message parsing" ) ) ?;
210+ let reader = BufReader :: new ( stdout) ;
211+
212+ let mut executable_artifacts: Vec < ( String , PathBuf ) > = Vec :: new ( ) ;
213+ for message in Message :: parse_stream ( reader) {
214+ let message = message. context ( "failed to parse cargo JSON message stream" ) ?;
215+ match message {
216+ Message :: CompilerArtifact ( artifact) => {
217+ if artifact. package_id == target_pkg_id
218+ && artifact. target . is_bin ( )
219+ && let Some ( executable) = artifact. executable
220+ {
221+ executable_artifacts
222+ . push ( ( artifact. target . name , executable. into_std_path_buf ( ) ) ) ;
223+ }
224+ }
225+ Message :: CompilerMessage ( msg) => {
226+ if let Some ( rendered) = msg. message . rendered {
227+ eprint ! ( "{rendered}" ) ;
228+ }
229+ }
230+ Message :: TextLine ( line) => {
231+ println ! ( "{line}" ) ;
232+ }
233+ _ => { }
234+ }
235+ }
236+
237+ let status = child
238+ . wait ( )
239+ . context ( "failed waiting for cargo build process" ) ?;
240+ if !status. success ( ) {
241+ bail ! ( "failed with status: {status}" ) ;
242+ }
243+
244+ let resolved = self . pick_executable_artifact ( & executable_artifacts, default_run. as_deref ( ) ) ;
245+ let Some ( resolved) = resolved else {
246+ bail ! (
247+ "no executable artifact found for package '{}' and target '{}'; please check system.Cargo.package/system.Cargo.target" ,
248+ self . config. package,
249+ self . config. target
250+ ) ;
251+ } ;
252+
253+ self . resolved_elf_path = Some ( resolved) ;
254+ Ok ( ( ) )
255+ }
256+
170257 async fn build_cargo_command ( & mut self ) -> anyhow:: Result < Command > {
171258 let mut cmd = self . ctx . command ( "cargo" ) ;
172259
@@ -212,11 +299,32 @@ impl<'a> CargoBuilder<'a> {
212299 cmd. arg ( arg) ;
213300 }
214301
302+ // Auto-detected args from someboot/build-info.toml
303+ let workspace_manifest = self . ctx . paths . workspace . join ( "Cargo.toml" ) ;
304+ if workspace_manifest. exists ( ) {
305+ let detected_args =
306+ someboot:: detect_build_config ( & workspace_manifest, & self . config . target )
307+ . with_context ( || {
308+ format ! (
309+ "failed to detect someboot build config from {}" ,
310+ workspace_manifest. display( )
311+ )
312+ } ) ?;
313+ for arg in detected_args {
314+ cmd. arg ( arg) ;
315+ }
316+ }
317+
215318 // Release mode
216319 if !self . ctx . debug {
217320 cmd. arg ( "--release" ) ;
218321 }
219322
323+ if self . resolve_artifact_from_json {
324+ cmd. arg ( "--message-format" ) ;
325+ cmd. arg ( "json-render-diagnostics" ) ;
326+ }
327+
220328 // Extra args
221329 for arg in & self . extra_args {
222330 cmd. arg ( arg) ;
@@ -230,14 +338,18 @@ impl<'a> CargoBuilder<'a> {
230338 }
231339
232340 async fn handle_output ( & mut self ) -> anyhow:: Result < ( ) > {
233- let target_dir = self . ctx . paths . build_dir ( ) ;
341+ let elf_path = if let Some ( path) = & self . resolved_elf_path {
342+ path. clone ( )
343+ } else {
344+ let target_dir = self . ctx . paths . build_dir ( ) ;
234345
235- let elf_path = target_dir
236- . join ( & self . config . target )
237- . join ( if self . ctx . debug { "debug" } else { "release" } )
238- . join ( & self . config . package ) ;
346+ target_dir
347+ . join ( & self . config . target )
348+ . join ( if self . ctx . debug { "debug" } else { "release" } )
349+ . join ( & self . config . package )
350+ } ;
239351
240- self . ctx . set_elf_path ( elf_path) . await ;
352+ self . ctx . set_elf_path ( elf_path) . await ? ;
241353
242354 if self . config . to_bin && !self . skip_objcopy {
243355 self . ctx . objcopy_output_bin ( ) ?;
@@ -253,6 +365,43 @@ impl<'a> CargoBuilder<'a> {
253365 Ok ( ( ) )
254366 }
255367
368+ fn target_package_info ( & self ) -> anyhow:: Result < ( PackageId , Option < String > ) > {
369+ let metadata = self . ctx . metadata ( ) ?;
370+ let Some ( package) = metadata
371+ . packages
372+ . iter ( )
373+ . find ( |pkg| pkg. name == self . config . package )
374+ else {
375+ bail ! (
376+ "package '{}' not found in cargo metadata under {}" ,
377+ self . config. package,
378+ self . ctx. paths. manifest. display( )
379+ ) ;
380+ } ;
381+ Ok ( ( package. id . clone ( ) , package. default_run . clone ( ) ) )
382+ }
383+
384+ fn pick_executable_artifact (
385+ & self ,
386+ executable_artifacts : & [ ( String , PathBuf ) ] ,
387+ default_run : Option < & str > ,
388+ ) -> Option < PathBuf > {
389+ executable_artifacts
390+ . iter ( )
391+ . rev ( )
392+ . find ( |( name, _) | name == & self . config . package )
393+ . or_else ( || {
394+ default_run. and_then ( |default_bin| {
395+ executable_artifacts
396+ . iter ( )
397+ . rev ( )
398+ . find ( |( name, _) | name == default_bin)
399+ } )
400+ } )
401+ . or_else ( || executable_artifacts. last ( ) )
402+ . map ( |( _, path) | path. clone ( ) )
403+ }
404+
256405 fn build_features ( & self ) -> Vec < String > {
257406 let mut features = self . config . features . clone ( ) ;
258407 if let Some ( log_level) = self . log_level_feature ( ) {
@@ -310,7 +459,12 @@ impl<'a> CargoBuilder<'a> {
310459 if let Some ( ref config_path) = self . config_path {
311460 let combined = config_path
312461 . parent ( )
313- . ok_or_else ( || anyhow:: anyhow!( "Invalid config path" ) ) ?
462+ . ok_or_else ( || {
463+ anyhow:: anyhow!(
464+ "invalid config path without parent: {}" ,
465+ config_path. display( )
466+ )
467+ } ) ?
314468 . join ( extra) ;
315469 Ok ( Some ( combined) )
316470 } else {
@@ -397,7 +551,8 @@ impl<'a> CargoBuilder<'a> {
397551 // Write to temp file
398552 tokio:: fs:: write ( & target_path, content)
399553 . await
400- . map_err ( |e| anyhow:: anyhow!( "Failed to write to temp file: {}" , e) ) ?;
554+ . with_path ( "failed to write downloaded cargo config" , & target_path)
555+ . with_context ( || format ! ( "while downloading cargo config from {url}" ) ) ?;
401556
402557 println ! ( "Config downloaded to: {}" , target_path. display( ) ) ;
403558
0 commit comments