@@ -5,22 +5,37 @@ use std::future::Future;
55use std:: io:: { Read , Write } ;
66use std:: process:: Command ;
77use std:: process:: ExitStatus ;
8+ use std:: sync:: { Arc , Mutex } ;
89use std:: thread;
910
11+ struct CmdRunnerOptions < F > {
12+ on_process_spawned : Option < F > ,
13+ capture_stdout : bool ,
14+ }
15+
16+ impl < F > Default for CmdRunnerOptions < F > {
17+ fn default ( ) -> Self {
18+ Self {
19+ on_process_spawned : None ,
20+ capture_stdout : false ,
21+ }
22+ }
23+ }
24+
1025/// Run a command and log its output to stdout and stderr
1126///
1227/// # Arguments
1328/// - `cmd`: The command to run.
14- /// - `cb `: A callback function that takes the process ID and returns a result.
29+ /// - `options `: Configuration options for the runner (e.g. capture output, run callback)
1530///
1631/// # Returns
17- ///
18- /// The exit status of the command.
19- ///
20- pub async fn run_command_with_log_pipe_and_callback < F , Fut > (
32+ /// A tuple containing:
33+ /// - `ExitStatus`: The exit status of the executed command
34+ /// - `Option<String>`: Captured stdout if `capture_stdout` was true, otherwise None
35+ async fn run_command_with_log_pipe_and_options < F , Fut > (
2136 mut cmd : Command ,
22- cb : F ,
23- ) -> Result < ExitStatus >
37+ options : CmdRunnerOptions < F > ,
38+ ) -> Result < ( ExitStatus , Option < String > ) >
2439where
2540 F : FnOnce ( u32 ) -> Fut ,
2641 Fut : Future < Output = anyhow:: Result < ( ) > > ,
2944 mut reader : impl Read ,
3045 mut writer : impl Write ,
3146 log_prefix : Option < & str > ,
47+ captured_output : Option < Arc < Mutex < Vec < u8 > > > > ,
3248 ) -> Result < ( ) > {
3349 let prefix = log_prefix. unwrap_or ( "" ) ;
3450 let mut buffer = [ 0 ; 1024 ] ;
3753 if bytes_read == 0 {
3854 break ;
3955 }
56+
57+ if let Some ( ref capture) = captured_output {
58+ let mut output = capture. lock ( ) . unwrap ( ) ;
59+ output. extend_from_slice ( & buffer[ ..bytes_read] ) ;
60+ }
61+
4062 suspend_progress_bar ( || {
4163 writer. write_all ( & buffer[ ..bytes_read] ) . unwrap ( ) ;
4264 trace ! (
@@ -57,19 +79,76 @@ where
5779 . context ( "failed to spawn the process" ) ?;
5880 let stdout = process. stdout . take ( ) . expect ( "unable to get stdout" ) ;
5981 let stderr = process. stderr . take ( ) . expect ( "unable to get stderr" ) ;
60- thread:: spawn ( move || {
61- log_tee ( stdout, std:: io:: stdout ( ) , None ) . unwrap ( ) ;
62- } ) ;
6382
64- thread:: spawn ( move || {
65- log_tee ( stderr, std:: io:: stderr ( ) , Some ( "[stderr]" ) ) . unwrap ( ) ;
66- } ) ;
83+ let captured_stdout = if options. capture_stdout {
84+ Some ( Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) )
85+ } else {
86+ None
87+ } ;
88+ let ( stdout_handle, stderr_handle) = {
89+ let stdout_capture = captured_stdout. clone ( ) ;
90+ let stdout_handle = thread:: spawn ( move || {
91+ log_tee ( stdout, std:: io:: stdout ( ) , None , stdout_capture) . unwrap ( ) ;
92+ } ) ;
93+ let stderr_handle = thread:: spawn ( move || {
94+ log_tee ( stderr, std:: io:: stderr ( ) , Some ( "[stderr]" ) , None ) . unwrap ( ) ;
95+ } ) ;
6796
68- cb ( process. id ( ) ) . await ?;
97+ ( stdout_handle, stderr_handle)
98+ } ;
6999
70- process. wait ( ) . context ( "failed to wait for the process" )
100+ if let Some ( cb) = options. on_process_spawned {
101+ cb ( process. id ( ) ) . await ?;
102+ }
103+
104+ let exit_status = process. wait ( ) . context ( "failed to wait for the process" ) ?;
105+ let _ = ( stdout_handle. join ( ) . unwrap ( ) , stderr_handle. join ( ) . unwrap ( ) ) ;
106+
107+ let stdout_output = captured_stdout
108+ . map ( |capture| String :: from_utf8_lossy ( & capture. lock ( ) . unwrap ( ) ) . to_string ( ) ) ;
109+ Ok ( ( exit_status, stdout_output) )
110+ }
111+
112+ pub async fn run_command_with_log_pipe_and_callback < F , Fut > (
113+ cmd : Command ,
114+ cb : F ,
115+ ) -> Result < ( ExitStatus , Option < String > ) >
116+ where
117+ F : FnOnce ( u32 ) -> Fut ,
118+ Fut : Future < Output = anyhow:: Result < ( ) > > ,
119+ {
120+ run_command_with_log_pipe_and_options (
121+ cmd,
122+ CmdRunnerOptions {
123+ on_process_spawned : Some ( cb) ,
124+ capture_stdout : false ,
125+ } ,
126+ )
127+ . await
71128}
72129
73130pub async fn run_command_with_log_pipe ( cmd : Command ) -> Result < ExitStatus > {
74- run_command_with_log_pipe_and_callback ( cmd, async |_| Ok ( ( ) ) ) . await
131+ let ( exit_status, _) = run_command_with_log_pipe_and_options (
132+ cmd,
133+ CmdRunnerOptions :: < fn ( u32 ) -> futures:: future:: Ready < anyhow:: Result < ( ) > > > {
134+ on_process_spawned : None ,
135+ capture_stdout : false ,
136+ } ,
137+ )
138+ . await ?;
139+ Ok ( exit_status)
140+ }
141+
142+ pub async fn run_command_with_log_pipe_capture_stdout (
143+ cmd : Command ,
144+ ) -> Result < ( ExitStatus , String ) > {
145+ let ( exit_status, stdout) = run_command_with_log_pipe_and_options (
146+ cmd,
147+ CmdRunnerOptions :: < fn ( u32 ) -> futures:: future:: Ready < anyhow:: Result < ( ) > > > {
148+ on_process_spawned : None ,
149+ capture_stdout : true ,
150+ } ,
151+ )
152+ . await ?;
153+ Ok ( ( exit_status, stdout. unwrap_or_default ( ) ) )
75154}
0 commit comments