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.is_empty(),
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
70 && self
71 .checked_dbg_var
72 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
73 .is_ok()
74 {
75 println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT");
76 }
77 if self.debug {
78 println!("{arg}");
79 }
80 }
81
82 fn stdio_for_warnings(&self) -> Stdio {
83 if self.warnings {
84 Stdio::piped()
85 } else {
86 Stdio::null()
87 }
88 }
89
90 fn stdio_for_output(&self) -> Stdio {
91 match self.output {
92 OutputKind::Capture => Stdio::piped(),
93 OutputKind::Forward => Stdio::inherit(),
94 OutputKind::Discard => Stdio::null(),
95 }
96 }
97}
98
99pub(crate) struct StderrForwarder {
100 inner: Option<(ChildStderr, Vec<u8>)>,
101 #[cfg(feature = "parallel")]
102 is_non_blocking: bool,
103 #[cfg(feature = "parallel")]
104 bytes_available_failed: bool,
105 bytes_buffered: usize,
107}
108
109const MIN_BUFFER_CAPACITY: usize = 100;
110
111impl StderrForwarder {
112 pub(crate) fn new(child: &mut Child) -> Self {
113 Self {
114 inner: child
115 .stderr
116 .take()
117 .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))),
118 bytes_buffered: 0,
119 #[cfg(feature = "parallel")]
120 is_non_blocking: false,
121 #[cfg(feature = "parallel")]
122 bytes_available_failed: false,
123 }
124 }
125
126 pub(crate) fn forward_available(&mut self) -> bool {
127 if let Some((stderr, buffer)) = self.inner.as_mut() {
128 loop {
129 #[cfg(not(feature = "parallel"))]
132 let to_reserve = MIN_BUFFER_CAPACITY;
133 #[cfg(feature = "parallel")]
134 let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
135 match crate::parallel::stderr::bytes_available(stderr) {
136 #[cfg(windows)]
137 Ok(0) => break false,
138 #[cfg(unix)]
139 Ok(0) => {
140 MIN_BUFFER_CAPACITY
144 }
145 #[cfg(windows)]
146 Err(_) => {
147 if !buffer.is_empty() {
150 write_warning(&buffer[..]);
151 }
152 self.inner = None;
153 break true;
154 }
155 #[cfg(unix)]
156 Err(_) => {
157 self.bytes_available_failed = true;
161 MIN_BUFFER_CAPACITY
162 }
163 #[cfg(target_family = "wasm")]
164 Err(_) => panic!("bytes_available should always succeed on wasm"),
165 Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
166 }
167 } else {
168 MIN_BUFFER_CAPACITY
169 };
170 if self.bytes_buffered + to_reserve > buffer.len() {
171 buffer.resize(self.bytes_buffered + to_reserve, 0);
172 }
173
174 match stderr.read(&mut buffer[self.bytes_buffered..]) {
175 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
176 break false;
178 }
179 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
180 continue;
182 }
183 Ok(bytes_read) if bytes_read != 0 => {
184 self.bytes_buffered += bytes_read;
185 let mut consumed = 0;
186 for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n') {
187 if let Some((b'\n', line)) = line.split_last() {
189 consumed += line.len() + 1;
190 write_warning(line);
191 }
192 }
193 if consumed > 0 && consumed < self.bytes_buffered {
194 buffer.copy_within(consumed.., 0);
196 }
197 self.bytes_buffered -= consumed;
198 }
199 res => {
200 if self.bytes_buffered > 0 {
202 write_warning(&buffer[..self.bytes_buffered]);
203 }
204 if let Err(err) = res {
205 write_warning(
206 format!("Failed to read from child stderr: {err}").as_bytes(),
207 );
208 }
209 self.inner.take();
210 break true;
211 }
212 }
213 }
214 } else {
215 true
216 }
217 }
218
219 #[cfg(feature = "parallel")]
220 pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> {
221 assert!(!self.is_non_blocking);
222
223 #[cfg(unix)]
224 if let Some((stderr, _)) = self.inner.as_ref() {
225 crate::parallel::stderr::set_non_blocking(stderr)?;
226 }
227
228 self.is_non_blocking = true;
229 Ok(())
230 }
231
232 #[cfg(feature = "parallel")]
233 pub(crate) fn forward_all(&mut self) {
234 while !self.forward_available() {}
235 }
236
237 #[cfg(not(feature = "parallel"))]
238 fn forward_all(&mut self) {
239 let forward_result = self.forward_available();
240 assert!(forward_result, "Should have consumed all data");
241 }
242}
243
244fn write_warning(line: &[u8]) {
245 let stdout = io::stdout();
246 let mut stdout = stdout.lock();
247 stdout.write_all(b"cargo:warning=").unwrap();
248 stdout.write_all(line).unwrap();
249 stdout.write_all(b"\n").unwrap();
250}
251
252fn wait_on_child(
253 cmd: &Command,
254 child: &mut Child,
255 cargo_output: &CargoOutput,
256) -> Result<(), Error> {
257 StderrForwarder::new(child).forward_all();
258
259 let status = match child.wait() {
260 Ok(s) => s,
261 Err(e) => {
262 return Err(Error::new(
263 ErrorKind::ToolExecError,
264 format!("failed to wait on spawned child process `{cmd:?}`: {e}"),
265 ));
266 }
267 };
268
269 cargo_output.print_debug(&status);
270
271 if status.success() {
272 Ok(())
273 } else {
274 Err(Error::new(
275 ErrorKind::ToolExecError,
276 format!("command did not execute successfully (status code {status}): {cmd:?}"),
277 ))
278 }
279}
280
281pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
284 let mut objects = Vec::with_capacity(files.len());
285 for file in files {
286 let basename = file
287 .file_name()
288 .ok_or_else(|| {
289 Error::new(
290 ErrorKind::InvalidArgument,
291 "No file_name for object file path!",
292 )
293 })?
294 .to_string_lossy();
295 let dirname = file
296 .parent()
297 .ok_or_else(|| {
298 Error::new(
299 ErrorKind::InvalidArgument,
300 "No parent for object file path!",
301 )
302 })?
303 .to_string_lossy();
304
305 let mut hasher = hash_map::DefaultHasher::new();
308
309 #[allow(clippy::disallowed_methods)]
315 let dirname = if let Some(root) = std::env::var_os("CARGO_MANIFEST_DIR") {
316 let root = root.to_string_lossy();
317 Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname))
318 } else {
319 dirname
320 };
321
322 hasher.write(dirname.as_bytes());
323 if let Some(extension) = file.extension() {
324 hasher.write(extension.to_string_lossy().as_bytes());
325 }
326 let obj = dst
327 .join(format!("{:016x}-{}", hasher.finish(), basename))
328 .with_extension("o");
329
330 match obj.parent() {
331 Some(s) => fs::create_dir_all(s)?,
332 None => {
333 return Err(Error::new(
334 ErrorKind::InvalidArgument,
335 "dst is an invalid path with no parent",
336 ));
337 }
338 };
339
340 objects.push(Object::new(file.to_path_buf(), obj));
341 }
342
343 Ok(objects)
344}
345
346pub(crate) fn run(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<(), Error> {
347 let mut child = spawn(cmd, cargo_output)?;
348 wait_on_child(cmd, &mut child, cargo_output)
349}
350
351pub(crate) fn run_output(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Vec<u8>, Error> {
352 let mut captured_cargo_output = cargo_output.clone();
354 captured_cargo_output.output = OutputKind::Capture;
355 let mut child = spawn(cmd, &captured_cargo_output)?;
356
357 let mut stdout = vec![];
358 child
359 .stdout
360 .take()
361 .unwrap()
362 .read_to_end(&mut stdout)
363 .unwrap();
364
365 wait_on_child(cmd, &mut child, cargo_output)?;
367
368 Ok(stdout)
369}
370
371pub(crate) fn spawn(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Child, Error> {
372 struct ResetStderr<'cmd>(&'cmd mut Command);
373
374 impl Drop for ResetStderr<'_> {
375 fn drop(&mut self) {
376 self.0.stderr(Stdio::inherit());
379 }
380 }
381
382 cargo_output.print_debug(&format_args!("running: {cmd:?}"));
383
384 let cmd = ResetStderr(cmd);
385 let child = cmd
386 .0
387 .stderr(cargo_output.stdio_for_warnings())
388 .stdout(cargo_output.stdio_for_output())
389 .spawn();
390 match child {
391 Ok(child) => Ok(child),
392 Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
393 let extra = if cfg!(windows) {
394 " (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)"
395 } else {
396 ""
397 };
398 Err(Error::new(
399 ErrorKind::ToolNotFound,
400 format!("failed to find tool {:?}: {e}{extra}", cmd.0.get_program()),
401 ))
402 }
403 Err(e) => Err(Error::new(
404 ErrorKind::ToolExecError,
405 format!("command `{:?}` failed to start: {e}", cmd.0),
406 )),
407 }
408}
409
410pub(crate) struct CmdAddOutputFileArgs {
411 pub(crate) cuda: bool,
412 pub(crate) is_assembler_msvc: bool,
413 pub(crate) msvc: bool,
414 pub(crate) clang: bool,
415 pub(crate) gnu: bool,
416 pub(crate) is_asm: bool,
417 pub(crate) is_arm: bool,
418}
419
420pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) {
421 if args.is_assembler_msvc
422 || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm))
423 {
424 let mut s = OsString::from("-Fo");
425 s.push(dst);
426 cmd.arg(s);
427 } else {
428 cmd.arg("-o").arg(dst);
429 }
430}