xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/process_wrapper/main.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
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(&copy_source, &copy_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