1use std::{
4 borrow::Cow,
5 collections::hash_map,
6 ffi::OsString,
7 fmt::Display,
8 fs,
9 hash::Hasher,
10 io::{self, Read, Write},
11 path::Path,
12 process::{Child, ChildStderr, Command, Stdio},
13 sync::{
14 atomic::{AtomicBool, Ordering},
15 Arc,
16 },
17};
18
19use crate::{Error, ErrorKind, Object};
20
21#[derive(Clone, Debug)]
22pub(crate) struct CargoOutput {
23 pub(crate) metadata: bool,
24 pub(crate) warnings: bool,
25 pub(crate) debug: bool,
26 pub(crate) output: OutputKind,
27 checked_dbg_var: Arc<AtomicBool>,
28}
29
30#[derive(Clone, Debug)]
32pub(crate) enum OutputKind {
33 Forward,
35 Discard,
37 Capture,
39}
40
41impl CargoOutput {
42 pub(crate) fn new() -> Self {
43 #[allow(clippy::disallowed_methods)]
44 Self {
45 metadata: true,
46 warnings: true,
47 output: OutputKind::Forward,
48 debug: match std::env::var_os("CC_ENABLE_DEBUG_OUTPUT") {
49 Some(v) => v != "0" && v != "false" && v != "",
50 None => false,
51 },
52 checked_dbg_var: Arc::new(AtomicBool::new(false)),
53 }
54 }
55
56 pub(crate) fn print_metadata(&self, s: &dyn Display) {
57 if self.metadata {
58 println!("{}", s);
59 }
60 }
61
62 pub(crate) fn print_warning(&self, arg: &dyn Display) {
63 if self.warnings {
64 println!("cargo:warning={}", arg);
65 }
66 }
67
68 pub(crate) fn print_debug(&self, arg: &dyn Display) {
69 if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) {
70 self.checked_dbg_var.store(true, Ordering::Relaxed);
71 println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT");
72 }
73 if self.debug {
74 println!("{}", arg);
75 }
76 }
77
78 fn stdio_for_warnings(&self) -> Stdio {
79 if self.warnings {
80 Stdio::piped()
81 } else {
82 Stdio::null()
83 }
84 }
85
86 fn stdio_for_output(&self) -> Stdio {
87 match self.output {
88 OutputKind::Capture => Stdio::piped(),
89 OutputKind::Forward => Stdio::inherit(),
90 OutputKind::Discard => Stdio::null(),
91 }
92 }
93}
94
95pub(crate) struct StderrForwarder {
96 inner: Option<(ChildStderr, Vec<u8>)>,
97 #[cfg(feature = "parallel")]
98 is_non_blocking: bool,
99 #[cfg(feature = "parallel")]
100 bytes_available_failed: bool,
101 bytes_buffered: usize,
103}
104
105const MIN_BUFFER_CAPACITY: usize = 100;
106
107impl StderrForwarder {
108 pub(crate) fn new(child: &mut Child) -> Self {
109 Self {
110 inner: child
111 .stderr
112 .take()
113 .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))),
114 bytes_buffered: 0,
115 #[cfg(feature = "parallel")]
116 is_non_blocking: false,
117 #[cfg(feature = "parallel")]
118 bytes_available_failed: false,
119 }
120 }
121
122 fn forward_available(&mut self) -> bool {
123 if let Some((stderr, buffer)) = self.inner.as_mut() {
124 loop {
125 #[cfg(not(feature = "parallel"))]
128 let to_reserve = MIN_BUFFER_CAPACITY;
129 #[cfg(feature = "parallel")]
130 let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
131 match crate::parallel::stderr::bytes_available(stderr) {
132 #[cfg(windows)]
133 Ok(0) => break false,
134 #[cfg(unix)]
135 Ok(0) => {
136 MIN_BUFFER_CAPACITY
140 }
141 #[cfg(windows)]
142 Err(_) => {
143 if !buffer.is_empty() {
146 write_warning(&buffer[..]);
147 }
148 self.inner = None;
149 break true;
150 }
151 #[cfg(unix)]
152 Err(_) => {
153 self.bytes_available_failed = true;
157 MIN_BUFFER_CAPACITY
158 }
159 #[cfg(target_family = "wasm")]
160 Err(_) => panic!("bytes_available should always succeed on wasm"),
161 Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
162 }
163 } else {
164 MIN_BUFFER_CAPACITY
165 };
166 if self.bytes_buffered + to_reserve > buffer.len() {
167 buffer.resize(self.bytes_buffered + to_reserve, 0);
168 }
169
170 match stderr.read(&mut buffer[self.bytes_buffered..]) {
171 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
172 break false;
174 }
175 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
176 continue;
178 }
179 Ok(bytes_read) if bytes_read != 0 => {
180 self.bytes_buffered += bytes_read;
181 let mut consumed = 0;
182 for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n') {
183 if let Some((b'\n', line)) = line.split_last() {
185 consumed += line.len() + 1;
186 write_warning(line);
187 }
188 }
189 if consumed > 0 && consumed < self.bytes_buffered {
190 buffer.copy_within(consumed.., 0);
192 }
193 self.bytes_buffered -= consumed;
194 }
195 res => {
196 if self.bytes_buffered > 0 {
198 write_warning(&buffer[..self.bytes_buffered]);
199 }
200 if let Err(err) = res {
201 write_warning(
202 format!("Failed to read from child stderr: {err}").as_bytes(),
203 );
204 }
205 self.inner.take();
206 break true;
207 }
208 }
209 }
210 } else {
211 true
212 }
213 }
214
215 #[cfg(feature = "parallel")]
216 pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> {
217 assert!(!self.is_non_blocking);
218
219 #[cfg(unix)]
220 if let Some((stderr, _)) = self.inner.as_ref() {
221 crate::parallel::stderr::set_non_blocking(stderr)?;
222 }
223
224 self.is_non_blocking = true;
225 Ok(())
226 }
227
228 #[cfg(feature = "parallel")]
229 fn forward_all(&mut self) {
230 while !self.forward_available() {}
231 }
232
233 #[cfg(not(feature = "parallel"))]
234 fn forward_all(&mut self) {
235 let forward_result = self.forward_available();
236 assert!(forward_result, "Should have consumed all data");
237 }
238}
239
240fn write_warning(line: &[u8]) {
241 let stdout = io::stdout();
242 let mut stdout = stdout.lock();
243 stdout.write_all(b"cargo:warning=").unwrap();
244 stdout.write_all(line).unwrap();
245 stdout.write_all(b"\n").unwrap();
246}
247
248fn wait_on_child(
249 cmd: &Command,
250 program: &Path,
251 child: &mut Child,
252 cargo_output: &CargoOutput,
253) -> Result<(), Error> {
254 StderrForwarder::new(child).forward_all();
255
256 let status = match child.wait() {
257 Ok(s) => s,
258 Err(e) => {
259 return Err(Error::new(
260 ErrorKind::ToolExecError,
261 format!(
262 "Failed to wait on spawned child process, command {:?} with args {}: {}.",
263 cmd,
264 program.display(),
265 e
266 ),
267 ));
268 }
269 };
270
271 cargo_output.print_debug(&status);
272
273 if status.success() {
274 Ok(())
275 } else {
276 Err(Error::new(
277 ErrorKind::ToolExecError,
278 format!(
279 "Command {:?} with args {} did not execute successfully (status code {}).",
280 cmd,
281 program.display(),
282 status
283 ),
284 ))
285 }
286}
287
288pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
291 let mut objects = Vec::with_capacity(files.len());
292 for file in files {
293 let basename = file
294 .file_name()
295 .ok_or_else(|| {
296 Error::new(
297 ErrorKind::InvalidArgument,
298 "No file_name for object file path!",
299 )
300 })?
301 .to_string_lossy();
302 let dirname = file
303 .parent()
304 .ok_or_else(|| {
305 Error::new(
306 ErrorKind::InvalidArgument,
307 "No parent for object file path!",
308 )
309 })?
310 .to_string_lossy();
311
312 let mut hasher = hash_map::DefaultHasher::new();
315
316 #[allow(clippy::disallowed_methods)]
322 let dirname = if let Some(root) = std::env::var_os("CARGO_MANIFEST_DIR") {
323 let root = root.to_string_lossy();
324 Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname))
325 } else {
326 dirname
327 };
328
329 hasher.write(dirname.as_bytes());
330 let obj = dst
331 .join(format!("{:016x}-{}", hasher.finish(), basename))
332 .with_extension("o");
333
334 match obj.parent() {
335 Some(s) => fs::create_dir_all(s)?,
336 None => {
337 return Err(Error::new(
338 ErrorKind::InvalidArgument,
339 "dst is an invalid path with no parent",
340 ));
341 }
342 };
343
344 objects.push(Object::new(file.to_path_buf(), obj));
345 }
346
347 Ok(objects)
348}
349
350pub(crate) fn run(
351 cmd: &mut Command,
352 program: impl AsRef<Path>,
353 cargo_output: &CargoOutput,
354) -> Result<(), Error> {
355 let program = program.as_ref();
356
357 let mut child = spawn(cmd, program, cargo_output)?;
358 wait_on_child(cmd, program, &mut child, cargo_output)
359}
360
361pub(crate) fn run_output(
362 cmd: &mut Command,
363 program: impl AsRef<Path>,
364 cargo_output: &CargoOutput,
365) -> Result<Vec<u8>, Error> {
366 let program = program.as_ref();
367
368 let mut captured_cargo_output = cargo_output.clone();
370 captured_cargo_output.output = OutputKind::Capture;
371 let mut child = spawn(cmd, program, &captured_cargo_output)?;
372
373 let mut stdout = vec![];
374 child
375 .stdout
376 .take()
377 .unwrap()
378 .read_to_end(&mut stdout)
379 .unwrap();
380
381 wait_on_child(cmd, program, &mut child, cargo_output)?;
383
384 Ok(stdout)
385}
386
387pub(crate) fn spawn(
388 cmd: &mut Command,
389 program: &Path,
390 cargo_output: &CargoOutput,
391) -> Result<Child, Error> {
392 struct ResetStderr<'cmd>(&'cmd mut Command);
393
394 impl Drop for ResetStderr<'_> {
395 fn drop(&mut self) {
396 self.0.stderr(Stdio::inherit());
399 }
400 }
401
402 cargo_output.print_debug(&format_args!("running: {:?}", cmd));
403
404 let cmd = ResetStderr(cmd);
405 let child = cmd
406 .0
407 .stderr(cargo_output.stdio_for_warnings())
408 .stdout(cargo_output.stdio_for_output())
409 .spawn();
410 match child {
411 Ok(child) => Ok(child),
412 Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
413 let extra = if cfg!(windows) {
414 " (see https://docs.rs/cc/latest/cc/#compile-time-requirements \
415for help)"
416 } else {
417 ""
418 };
419 Err(Error::new(
420 ErrorKind::ToolNotFound,
421 format!(
422 "Failed to find tool. Is `{}` installed?{}",
423 program.display(),
424 extra
425 ),
426 ))
427 }
428 Err(e) => Err(Error::new(
429 ErrorKind::ToolExecError,
430 format!(
431 "Command {:?} with args {} failed to start: {:?}",
432 cmd.0,
433 program.display(),
434 e
435 ),
436 )),
437 }
438}
439
440pub(crate) struct CmdAddOutputFileArgs {
441 pub(crate) cuda: bool,
442 pub(crate) is_assembler_msvc: bool,
443 pub(crate) msvc: bool,
444 pub(crate) clang: bool,
445 pub(crate) gnu: bool,
446 pub(crate) is_asm: bool,
447 pub(crate) is_arm: bool,
448}
449
450pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) {
451 if args.is_assembler_msvc
452 || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm))
453 {
454 let mut s = OsString::from("-Fo");
455 s.push(dst);
456 cmd.arg(s);
457 } else {
458 cmd.arg("-o").arg(dst);
459 }
460}
461
462#[cfg(feature = "parallel")]
463pub(crate) fn try_wait_on_child(
464 cmd: &Command,
465 program: &Path,
466 child: &mut Child,
467 stdout: &mut dyn io::Write,
468 stderr_forwarder: &mut StderrForwarder,
469) -> Result<Option<()>, Error> {
470 stderr_forwarder.forward_available();
471
472 match child.try_wait() {
473 Ok(Some(status)) => {
474 stderr_forwarder.forward_all();
475
476 let _ = writeln!(stdout, "{}", status);
477
478 if status.success() {
479 Ok(Some(()))
480 } else {
481 Err(Error::new(
482 ErrorKind::ToolExecError,
483 format!(
484 "Command {:?} with args {} did not execute successfully (status code {}).",
485 cmd,
486 program.display(),
487 status
488 ),
489 ))
490 }
491 }
492 Ok(None) => Ok(None),
493 Err(e) => {
494 stderr_forwarder.forward_all();
495 Err(Error::new(
496 ErrorKind::ToolExecError,
497 format!(
498 "Failed to wait on spawned child process, command {:?} with args {}: {}.",
499 cmd,
500 program.display(),
501 e
502 ),
503 ))
504 }
505 }
506}