14
14
15
15
mod flags;
16
16
mod options;
17
+ mod output;
18
+ mod rustc;
17
19
mod util;
18
20
19
21
use 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;
21
27
22
28
use crate :: options:: options;
23
29
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
+
24
54
fn main ( ) {
25
55
let opts = match options ( ) {
26
56
Err ( err) => panic ! ( "process wrapper error: {}" , err) ,
27
57
Ok ( v) => v,
28
58
} ;
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 )
52
61
. args ( opts. child_arguments )
53
62
. env_clear ( )
54
63
. envs ( opts. child_environment )
55
- . stdout ( stdout )
56
- . stderr ( stderr )
57
- . status ( )
64
+ . stdout ( Stdio :: piped ( ) )
65
+ . stderr ( Stdio :: piped ( ) )
66
+ . spawn ( )
58
67
. expect ( "process wrapper error: failed to spawn child process" ) ;
59
68
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 {
61
137
if let Some ( tf) = opts. touch_file {
62
138
OpenOptions :: new ( )
63
139
. create ( true )
@@ -75,5 +151,5 @@ fn main() {
75
151
}
76
152
}
77
153
78
- exit ( status . code ( ) . unwrap ( ) )
154
+ exit ( code)
79
155
}
0 commit comments