1 // Copyright 2020 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 mod flags;
16 mod options;
17 mod output;
18 mod rustc;
19 mod util;
20
21 use std::fmt;
22 use std::fs::{copy, OpenOptions};
23 use std::io;
24 use std::process::{exit, Command, ExitStatus, Stdio};
25
26 use crate::options::options;
27 use crate::output::{process_output, LineOutput};
28
29 #[cfg(windows)]
status_code(status: ExitStatus, was_killed: bool) -> i3230 fn status_code(status: ExitStatus, was_killed: bool) -> i32 {
31 // On windows, there's no good way to know if the process was killed by a signal.
32 // If we killed the process, we override the code to signal success.
33 if was_killed {
34 0
35 } else {
36 status.code().unwrap_or(1)
37 }
38 }
39
40 #[cfg(not(windows))]
status_code(status: ExitStatus, was_killed: bool) -> i3241 fn status_code(status: ExitStatus, was_killed: bool) -> i32 {
42 // On unix, if code is None it means that the process was killed by a signal.
43 // https://doc.rust-lang.org/std/process/struct.ExitStatus.html#method.success
44 match status.code() {
45 Some(code) => code,
46 // If we killed the process, we expect None here
47 None if was_killed => 0,
48 // Otherwise it's some unexpected signal
49 None => 1,
50 }
51 }
52
53 #[derive(Debug)]
54 struct ProcessWrapperError(String);
55
56 impl fmt::Display for ProcessWrapperError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 write!(f, "process wrapper error: {}", self.0)
59 }
60 }
61
62 impl std::error::Error for ProcessWrapperError {}
63
main() -> Result<(), ProcessWrapperError>64 fn main() -> Result<(), ProcessWrapperError> {
65 let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?;
66
67 let mut child = Command::new(opts.executable)
68 .args(opts.child_arguments)
69 .env_clear()
70 .envs(opts.child_environment)
71 .stdout(if let Some(stdout_file) = opts.stdout_file {
72 OpenOptions::new()
73 .create(true)
74 .truncate(true)
75 .write(true)
76 .open(stdout_file)
77 .map_err(|e| ProcessWrapperError(format!("unable to open stdout file: {}", e)))?
78 .into()
79 } else {
80 Stdio::inherit()
81 })
82 .stderr(Stdio::piped())
83 .spawn()
84 .map_err(|e| ProcessWrapperError(format!("failed to spawn child process: {}", e)))?;
85
86 let mut stderr: Box<dyn io::Write> = if let Some(stderr_file) = opts.stderr_file {
87 Box::new(
88 OpenOptions::new()
89 .create(true)
90 .truncate(true)
91 .write(true)
92 .open(stderr_file)
93 .map_err(|e| ProcessWrapperError(format!("unable to open stderr file: {}", e)))?,
94 )
95 } else {
96 Box::new(io::stderr())
97 };
98
99 let mut child_stderr = child.stderr.take().ok_or(ProcessWrapperError(
100 "unable to get child stderr".to_string(),
101 ))?;
102
103 let mut output_file: Option<std::fs::File> = if let Some(output_file_name) = opts.output_file {
104 Some(
105 OpenOptions::new()
106 .create(true)
107 .truncate(true)
108 .write(true)
109 .open(output_file_name)
110 .map_err(|e| ProcessWrapperError(format!("Unable to open output_file: {}", e)))?,
111 )
112 } else {
113 None
114 };
115
116 let mut was_killed = false;
117 let result = if let Some(format) = opts.rustc_output_format {
118 let quit_on_rmeta = opts.rustc_quit_on_rmeta;
119 // Process json rustc output and kill the subprocess when we get a signal
120 // that we emitted a metadata file.
121 let mut me = false;
122 let metadata_emitted = &mut me;
123 let result = process_output(
124 &mut child_stderr,
125 stderr.as_mut(),
126 output_file.as_mut(),
127 move |line| {
128 if quit_on_rmeta {
129 rustc::stop_on_rmeta_completion(line, format, metadata_emitted)
130 } else {
131 rustc::process_json(line, format)
132 }
133 },
134 );
135 if me {
136 // If recv returns Ok(), a signal was sent in this channel so we should terminate the child process.
137 // We can safely ignore the Result from kill() as we don't care if the process already terminated.
138 let _ = child.kill();
139 was_killed = true;
140 }
141 result
142 } else {
143 // Process output normally by forwarding stderr
144 process_output(
145 &mut child_stderr,
146 stderr.as_mut(),
147 output_file.as_mut(),
148 move |line| Ok(LineOutput::Message(line)),
149 )
150 };
151 result.map_err(|e| ProcessWrapperError(format!("failed to process stderr: {}", e)))?;
152
153 let status = child
154 .wait()
155 .map_err(|e| ProcessWrapperError(format!("failed to wait for child process: {}", e)))?;
156 // If the child process is rustc and is killed after metadata generation, that's also a success.
157 let code = status_code(status, was_killed);
158 let success = code == 0;
159 if success {
160 if let Some(tf) = opts.touch_file {
161 OpenOptions::new()
162 .create(true)
163 .truncate(true)
164 .write(true)
165 .open(tf)
166 .map_err(|e| ProcessWrapperError(format!("failed to create touch file: {}", e)))?;
167 }
168 if let Some((copy_source, copy_dest)) = opts.copy_output {
169 copy(©_source, ©_dest).map_err(|e| {
170 ProcessWrapperError(format!(
171 "failed to copy {} into {}: {}",
172 copy_source, copy_dest, e
173 ))
174 })?;
175 }
176 }
177
178 exit(code)
179 }
180