1//! Miscellaneous helpers for running commands
2
3use std::{
4 collections::hash_map,
5 ffi::OsString,
6 fmt::Display,
7 fs,
8 hash::Hasher,
9 io::{self, Read, Write},
10 path::Path,
11 process::{Child, ChildStderr, Command, Stdio},
12 sync::{
13 atomic::{AtomicBool, Ordering},
14 Arc,
15 },
16};
17
18use crate::{Error, ErrorKind, Object};
19
20#[derive(Clone, Debug)]
21pub(crate) struct CargoOutput {
22 pub(crate) metadata: bool,
23 pub(crate) warnings: bool,
24 pub(crate) debug: bool,
25 checked_dbg_var: Arc<AtomicBool>,
26}
27
28impl CargoOutput {
29 pub(crate) fn new() -> Self {
30 Self {
31 metadata: true,
32 warnings: true,
33 debug: std::env::var_os("CC_ENABLE_DEBUG_OUTPUT").is_some(),
34 checked_dbg_var: Arc::new(AtomicBool::new(false)),
35 }
36 }
37
38 pub(crate) fn print_metadata(&self, s: &dyn Display) {
39 if self.metadata {
40 println!("{}", s);
41 }
42 }
43
44 pub(crate) fn print_warning(&self, arg: &dyn Display) {
45 if self.warnings {
46 println!("cargo:warning={}", arg);
47 }
48 }
49
50 pub(crate) fn print_debug(&self, arg: &dyn Display) {
51 if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) {
52 self.checked_dbg_var.store(true, Ordering::Relaxed);
53 println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT");
54 }
55 if self.debug {
56 println!("{}", arg);
57 }
58 }
59
60 fn stdio_for_warnings(&self) -> Stdio {
61 if self.warnings {
62 Stdio::piped()
63 } else {
64 Stdio::null()
65 }
66 }
67}
68
69pub(crate) struct StderrForwarder {
70 inner: Option<(ChildStderr, Vec<u8>)>,
71 #[cfg(feature = "parallel")]
72 is_non_blocking: bool,
73 #[cfg(feature = "parallel")]
74 bytes_available_failed: bool,
75}
76
77const MIN_BUFFER_CAPACITY: usize = 100;
78
79impl StderrForwarder {
80 pub(crate) fn new(child: &mut Child) -> Self {
81 Self {
82 inner: child
83 .stderr
84 .take()
85 .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))),
86 #[cfg(feature = "parallel")]
87 is_non_blocking: false,
88 #[cfg(feature = "parallel")]
89 bytes_available_failed: false,
90 }
91 }
92
93 #[allow(clippy::uninit_vec)]
94 fn forward_available(&mut self) -> bool {
95 if let Some((stderr, buffer)) = self.inner.as_mut() {
96 loop {
97 let old_data_end = buffer.len();
98
99 // For non-blocking we check to see if there is data available, so we should try to
100 // read at least that much. For blocking, always read at least the minimum amount.
101 #[cfg(not(feature = "parallel"))]
102 let to_reserve = MIN_BUFFER_CAPACITY;
103 #[cfg(feature = "parallel")]
104 let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
105 match crate::parallel::stderr::bytes_available(stderr) {
106 #[cfg(windows)]
107 Ok(0) => return false,
108 #[cfg(unix)]
109 Ok(0) => {
110 // On Unix, depending on the implementation, we may sometimes get 0 in a
111 // loop (either there is data available or the pipe is broken), so
112 // continue with the non-blocking read anyway.
113 MIN_BUFFER_CAPACITY
114 }
115 #[cfg(windows)]
116 Err(_) => {
117 // On Windows, if we get an error then the pipe is broken, so flush
118 // the buffer and bail.
119 if !buffer.is_empty() {
120 write_warning(&buffer[..]);
121 }
122 self.inner = None;
123 return true;
124 }
125 #[cfg(unix)]
126 Err(_) => {
127 // On Unix, depending on the implementation, we may get spurious
128 // errors so make a note not to use bytes_available again and try
129 // the non-blocking read anyway.
130 self.bytes_available_failed = true;
131 MIN_BUFFER_CAPACITY
132 }
133 Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
134 }
135 } else {
136 MIN_BUFFER_CAPACITY
137 };
138 buffer.reserve(to_reserve);
139
140 // SAFETY: 1) the length is set to the capacity, so we are never using memory beyond
141 // the underlying buffer and 2) we always call `truncate` below to set the len back
142 // to the initialized data.
143 unsafe {
144 buffer.set_len(buffer.capacity());
145 }
146 match stderr.read(&mut buffer[old_data_end..]) {
147 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
148 // No data currently, yield back.
149 buffer.truncate(old_data_end);
150 return false;
151 }
152 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
153 // Interrupted, try again.
154 buffer.truncate(old_data_end);
155 }
156 Ok(0) | Err(_) => {
157 // End of stream: flush remaining data and bail.
158 if old_data_end > 0 {
159 write_warning(&buffer[..old_data_end]);
160 }
161 self.inner = None;
162 return true;
163 }
164 Ok(bytes_read) => {
165 buffer.truncate(old_data_end + bytes_read);
166 let mut consumed = 0;
167 for line in buffer.split_inclusive(|&b| b == b'\n') {
168 // Only forward complete lines, leave the rest in the buffer.
169 if let Some((b'\n', line)) = line.split_last() {
170 consumed += line.len() + 1;
171 write_warning(line);
172 }
173 }
174 buffer.drain(..consumed);
175 }
176 }
177 }
178 } else {
179 true
180 }
181 }
182
183 #[cfg(feature = "parallel")]
184 pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> {
185 assert!(!self.is_non_blocking);
186
187 #[cfg(unix)]
188 if let Some((stderr, _)) = self.inner.as_ref() {
189 crate::parallel::stderr::set_non_blocking(stderr)?;
190 }
191
192 self.is_non_blocking = true;
193 Ok(())
194 }
195
196 #[cfg(feature = "parallel")]
197 fn forward_all(&mut self) {
198 while !self.forward_available() {}
199 }
200
201 #[cfg(not(feature = "parallel"))]
202 fn forward_all(&mut self) {
203 let forward_result = self.forward_available();
204 assert!(forward_result, "Should have consumed all data");
205 }
206}
207
208fn write_warning(line: &[u8]) {
209 let stdout: Stdout = io::stdout();
210 let mut stdout: StdoutLock<'_> = stdout.lock();
211 stdout.write_all(buf:b"cargo:warning=").unwrap();
212 stdout.write_all(buf:line).unwrap();
213 stdout.write_all(buf:b"\n").unwrap();
214}
215
216fn wait_on_child(
217 cmd: &Command,
218 program: &str,
219 child: &mut Child,
220 cargo_output: &CargoOutput,
221) -> Result<(), Error> {
222 StderrForwarder::new(child).forward_all();
223
224 let status = match child.wait() {
225 Ok(s) => s,
226 Err(e) => {
227 return Err(Error::new(
228 ErrorKind::ToolExecError,
229 format!(
230 "Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
231 cmd, program, e
232 ),
233 ));
234 }
235 };
236
237 cargo_output.print_debug(&status);
238
239 if status.success() {
240 Ok(())
241 } else {
242 Err(Error::new(
243 ErrorKind::ToolExecError,
244 format!(
245 "Command {:?} with args {:?} did not execute successfully (status code {}).",
246 cmd, program, status
247 ),
248 ))
249 }
250}
251
252/// Find the destination object path for each file in the input source files,
253/// and store them in the output Object.
254pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
255 let mut objects = Vec::with_capacity(files.len());
256 for file in files {
257 let basename = file
258 .file_name()
259 .ok_or_else(|| {
260 Error::new(
261 ErrorKind::InvalidArgument,
262 "No file_name for object file path!",
263 )
264 })?
265 .to_string_lossy();
266 let dirname = file
267 .parent()
268 .ok_or_else(|| {
269 Error::new(
270 ErrorKind::InvalidArgument,
271 "No parent for object file path!",
272 )
273 })?
274 .to_string_lossy();
275
276 // Hash the dirname. This should prevent conflicts if we have multiple
277 // object files with the same filename in different subfolders.
278 let mut hasher = hash_map::DefaultHasher::new();
279 hasher.write(dirname.to_string().as_bytes());
280 let obj = dst
281 .join(format!("{:016x}-{}", hasher.finish(), basename))
282 .with_extension("o");
283
284 match obj.parent() {
285 Some(s) => fs::create_dir_all(s)?,
286 None => {
287 return Err(Error::new(
288 ErrorKind::InvalidArgument,
289 "dst is an invalid path with no parent",
290 ));
291 }
292 };
293
294 objects.push(Object::new(file.to_path_buf(), obj));
295 }
296
297 Ok(objects)
298}
299
300pub(crate) fn run(
301 cmd: &mut Command,
302 program: &str,
303 cargo_output: &CargoOutput,
304) -> Result<(), Error> {
305 let mut child: Child = spawn(cmd, program, cargo_output)?;
306 wait_on_child(cmd, program, &mut child, cargo_output)
307}
308
309pub(crate) fn run_output(
310 cmd: &mut Command,
311 program: &str,
312 cargo_output: &CargoOutput,
313) -> Result<Vec<u8>, Error> {
314 cmd.stdout(cfg:Stdio::piped());
315
316 let mut child: Child = spawn(cmd, program, cargo_output)?;
317
318 let mut stdout: Vec = vec![];
319 childResult
320 .stdout
321 .take()
322 .unwrap()
323 .read_to_end(&mut stdout)
324 .unwrap();
325
326 wait_on_child(cmd, program, &mut child, cargo_output)?;
327
328 Ok(stdout)
329}
330
331pub(crate) fn spawn(
332 cmd: &mut Command,
333 program: &str,
334 cargo_output: &CargoOutput,
335) -> Result<Child, Error> {
336 struct ResetStderr<'cmd>(&'cmd mut Command);
337
338 impl Drop for ResetStderr<'_> {
339 fn drop(&mut self) {
340 // Reset stderr to default to release pipe_writer so that print thread will
341 // not block forever.
342 self.0.stderr(Stdio::inherit());
343 }
344 }
345
346 cargo_output.print_debug(&format_args!("running: {:?}", cmd));
347
348 let cmd = ResetStderr(cmd);
349 let child = cmd.0.stderr(cargo_output.stdio_for_warnings()).spawn();
350 match child {
351 Ok(child) => Ok(child),
352 Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
353 let extra = if cfg!(windows) {
354 " (see https://github.com/rust-lang/cc-rs#compile-time-requirements \
355for help)"
356 } else {
357 ""
358 };
359 Err(Error::new(
360 ErrorKind::ToolNotFound,
361 format!("Failed to find tool. Is `{}` installed?{}", program, extra),
362 ))
363 }
364 Err(e) => Err(Error::new(
365 ErrorKind::ToolExecError,
366 format!(
367 "Command {:?} with args {:?} failed to start: {:?}",
368 cmd.0, program, e
369 ),
370 )),
371 }
372}
373
374pub(crate) fn command_add_output_file(
375 cmd: &mut Command,
376 dst: &Path,
377 cuda: bool,
378 msvc: bool,
379 clang: bool,
380 gnu: bool,
381 is_asm: bool,
382 is_arm: bool,
383) {
384 if msvc && !clang && !gnu && !cuda && !(is_asm && is_arm) {
385 let mut s: OsString = OsString::from("-Fo");
386 s.push(dst);
387 cmd.arg(s);
388 } else {
389 cmd.arg("-o").arg(dst);
390 }
391}
392
393#[cfg(feature = "parallel")]
394pub(crate) fn try_wait_on_child(
395 cmd: &Command,
396 program: &str,
397 child: &mut Child,
398 stdout: &mut dyn io::Write,
399 stderr_forwarder: &mut StderrForwarder,
400) -> Result<Option<()>, Error> {
401 stderr_forwarder.forward_available();
402
403 match child.try_wait() {
404 Ok(Some(status)) => {
405 stderr_forwarder.forward_all();
406
407 let _ = writeln!(stdout, "{}", status);
408
409 if status.success() {
410 Ok(Some(()))
411 } else {
412 Err(Error::new(
413 ErrorKind::ToolExecError,
414 format!(
415 "Command {:?} with args {:?} did not execute successfully (status code {}).",
416 cmd, program, status
417 ),
418 ))
419 }
420 }
421 Ok(None) => Ok(None),
422 Err(e) => {
423 stderr_forwarder.forward_all();
424 Err(Error::new(
425 ErrorKind::ToolExecError,
426 format!(
427 "Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
428 cmd, program, e
429 ),
430 ))
431 }
432 }
433}
434