1414
1515mod flags;
1616mod options;
17+ mod output;
18+ mod rustc;
1719mod util;
1820
1921use std:: fs:: { copy, OpenOptions } ;
20- use std:: process:: { exit, Command , Stdio } ;
22+ use std:: io;
23+ use std:: process:: { exit, Command , ExitStatus , Stdio } ;
24+ use std:: sync:: mpsc:: sync_channel;
25+
26+ use output:: process_output;
2127
2228use crate :: options:: options;
2329
30+ #[ cfg( windows) ]
31+ fn status_code ( status : ExitStatus , was_killed : bool ) -> i32 {
32+ // On windows, there's no good way to know if the process was killed by a signal.
33+ // If we killed the process, we override the code to signal success.
34+ if was_killed {
35+ 0
36+ } else {
37+ status. code ( ) . unwrap_or ( 1 )
38+ }
39+ }
40+
41+ #[ cfg( not( windows) ) ]
42+ fn status_code ( status : ExitStatus , was_killed : bool ) -> i32 {
43+ // On unix, if code is None it means that the process was killed by a signal.
44+ // https://doc.rust-lang.org/std/process/struct.ExitStatus.html#method.success
45+ match status. code ( ) {
46+ Some ( code) => code,
47+ // If we killed the process, we expect None here
48+ None if was_killed => 0 ,
49+ // Otherwise it's some unexpected signal
50+ None => 1 ,
51+ }
52+ }
53+
2454fn main ( ) {
2555 let opts = match options ( ) {
2656 Err ( err) => panic ! ( "process wrapper error: {}" , err) ,
2757 Ok ( v) => v,
2858 } ;
29- let stdout = if let Some ( stdout_file) = opts. stdout_file {
30- OpenOptions :: new ( )
31- . create ( true )
32- . truncate ( true )
33- . write ( true )
34- . open ( stdout_file)
35- . expect ( "process wrapper error: unable to open stdout file" )
36- . into ( )
37- } else {
38- Stdio :: inherit ( )
39- } ;
40- let stderr = if let Some ( stderr_file) = opts. stderr_file {
41- OpenOptions :: new ( )
42- . create ( true )
43- . truncate ( true )
44- . write ( true )
45- . open ( stderr_file)
46- . expect ( "process wrapper error: unable to open stderr file" )
47- . into ( )
48- } else {
49- Stdio :: inherit ( )
50- } ;
51- let status = Command :: new ( opts. executable )
59+
60+ let mut child = Command :: new ( opts. executable )
5261 . args ( opts. child_arguments )
5362 . env_clear ( )
5463 . envs ( opts. child_environment )
55- . stdout ( stdout )
56- . stderr ( stderr )
57- . status ( )
64+ . stdout ( Stdio :: piped ( ) )
65+ . stderr ( Stdio :: piped ( ) )
66+ . spawn ( )
5867 . expect ( "process wrapper error: failed to spawn child process" ) ;
5968
60- if status. success ( ) {
69+ let stdout: Box < dyn io:: Write + Send > = if let Some ( stdout_file) = opts. stdout_file {
70+ Box :: new (
71+ OpenOptions :: new ( )
72+ . create ( true )
73+ . truncate ( true )
74+ . write ( true )
75+ . open ( stdout_file)
76+ . expect ( "process wrapper error: unable to open stdout file" ) ,
77+ )
78+ } else {
79+ Box :: new ( io:: stdout ( ) )
80+ } ;
81+ let stderr: Box < dyn io:: Write + Send > = if let Some ( stderr_file) = opts. stderr_file {
82+ Box :: new (
83+ OpenOptions :: new ( )
84+ . create ( true )
85+ . truncate ( true )
86+ . write ( true )
87+ . open ( stderr_file)
88+ . expect ( "process wrapper error: unable to open stderr file" ) ,
89+ )
90+ } else {
91+ Box :: new ( io:: stderr ( ) )
92+ } ;
93+
94+ let child_stdout = Box :: new ( child. stdout . take ( ) . unwrap ( ) ) ;
95+ let child_stderr = Box :: new ( child. stderr . take ( ) . unwrap ( ) ) ;
96+
97+ let was_killed = if !opts. rustc_quit_on_rmeta {
98+ // Process output normally by forwarding stdout and stderr
99+ let stdout_thread = process_output ( child_stdout, stdout, Some ) ;
100+ let stderr_thread = process_output ( child_stderr, stderr, Some ) ;
101+ stdout_thread. join ( ) . unwrap ( ) . unwrap ( ) ;
102+ stderr_thread. join ( ) . unwrap ( ) . unwrap ( ) ;
103+ false
104+ } else {
105+ let mut was_killed = false ;
106+ let format = opts. rustc_output_format ;
107+ // Process json rustc output and kill the subprocess when we get a signal
108+ // that we emitted a metadata file.
109+ // This receiver will block until a corresponding send happens.
110+ let ( stop_sender_stdout, stop) = sync_channel ( 0 ) ;
111+ let stop_sender_stderr = stop_sender_stdout. clone ( ) ;
112+ let stdout_thread = process_output ( child_stdout, stdout, move |line| {
113+ rustc:: process_message ( line, format, & stop_sender_stdout)
114+ } ) ;
115+ let stderr_thread = process_output ( child_stderr, stderr, move |line| {
116+ rustc:: process_message ( line, format, & stop_sender_stderr)
117+ } ) ;
118+ if stop. recv ( ) . is_ok ( ) {
119+ // If recv returns Ok(), a signal was sent in this channel so we should terminate the child process.
120+ // We can safely ignore the Result from kill() as we don't care if the process already terminated.
121+ let _ = child. kill ( ) ;
122+ was_killed = true ;
123+ }
124+
125+ stdout_thread. join ( ) . unwrap ( ) . unwrap ( ) ;
126+ stderr_thread. join ( ) . unwrap ( ) . unwrap ( ) ;
127+ was_killed
128+ } ;
129+
130+ let status = child
131+ . wait ( )
132+ . expect ( "process wrapper error: failed to wait for child process" ) ;
133+ // If the child process is rustc and is killed after metadata generation, that's also a success.
134+ let code = status_code ( status, was_killed) ;
135+ let success = code == 0 ;
136+ if success {
61137 if let Some ( tf) = opts. touch_file {
62138 OpenOptions :: new ( )
63139 . create ( true )
@@ -75,5 +151,5 @@ fn main() {
75151 }
76152 }
77153
78- exit ( status . code ( ) . unwrap ( ) )
154+ exit ( code)
79155}
0 commit comments