diff options
Diffstat (limited to 'vendor/cc/src/command_helpers.rs')
| -rw-r--r-- | vendor/cc/src/command_helpers.rs | 461 |
1 files changed, 0 insertions, 461 deletions
diff --git a/vendor/cc/src/command_helpers.rs b/vendor/cc/src/command_helpers.rs deleted file mode 100644 index 89dc1a33..00000000 --- a/vendor/cc/src/command_helpers.rs +++ /dev/null @@ -1,461 +0,0 @@ -//! Miscellaneous helpers for running commands - -use std::{ - borrow::Cow, - collections::hash_map, - ffi::OsString, - fmt::Display, - fs, - hash::Hasher, - io::{self, Read, Write}, - path::Path, - process::{Child, ChildStderr, Command, Stdio}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; - -use crate::{Error, ErrorKind, Object}; - -#[derive(Clone, Debug)] -pub(crate) struct CargoOutput { - pub(crate) metadata: bool, - pub(crate) warnings: bool, - pub(crate) debug: bool, - pub(crate) output: OutputKind, - checked_dbg_var: Arc<AtomicBool>, -} - -/// Different strategies for handling compiler output (to stdout) -#[derive(Clone, Debug)] -pub(crate) enum OutputKind { - /// Forward the output to this process' stdout ([`Stdio::inherit()`]) - Forward, - /// Discard the output ([`Stdio::null()`]) - Discard, - /// Capture the result (`[Stdio::piped()`]) - Capture, -} - -impl CargoOutput { - pub(crate) fn new() -> Self { - #[allow(clippy::disallowed_methods)] - Self { - metadata: true, - warnings: true, - output: OutputKind::Forward, - debug: match std::env::var_os("CC_ENABLE_DEBUG_OUTPUT") { - Some(v) => v != "0" && v != "false" && !v.is_empty(), - None => false, - }, - checked_dbg_var: Arc::new(AtomicBool::new(false)), - } - } - - pub(crate) fn print_metadata(&self, s: &dyn Display) { - if self.metadata { - println!("{}", s); - } - } - - pub(crate) fn print_warning(&self, arg: &dyn Display) { - if self.warnings { - println!("cargo:warning={}", arg); - } - } - - pub(crate) fn print_debug(&self, arg: &dyn Display) { - if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) { - self.checked_dbg_var.store(true, Ordering::Relaxed); - println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT"); - } - if self.debug { - println!("{}", arg); - } - } - - fn stdio_for_warnings(&self) -> Stdio { - if self.warnings { - Stdio::piped() - } else { - Stdio::null() - } - } - - fn stdio_for_output(&self) -> Stdio { - match self.output { - OutputKind::Capture => Stdio::piped(), - OutputKind::Forward => Stdio::inherit(), - OutputKind::Discard => Stdio::null(), - } - } -} - -pub(crate) struct StderrForwarder { - inner: Option<(ChildStderr, Vec<u8>)>, - #[cfg(feature = "parallel")] - is_non_blocking: bool, - #[cfg(feature = "parallel")] - bytes_available_failed: bool, - /// number of bytes buffered in inner - bytes_buffered: usize, -} - -const MIN_BUFFER_CAPACITY: usize = 100; - -impl StderrForwarder { - pub(crate) fn new(child: &mut Child) -> Self { - Self { - inner: child - .stderr - .take() - .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))), - bytes_buffered: 0, - #[cfg(feature = "parallel")] - is_non_blocking: false, - #[cfg(feature = "parallel")] - bytes_available_failed: false, - } - } - - fn forward_available(&mut self) -> bool { - if let Some((stderr, buffer)) = self.inner.as_mut() { - loop { - // For non-blocking we check to see if there is data available, so we should try to - // read at least that much. For blocking, always read at least the minimum amount. - #[cfg(not(feature = "parallel"))] - let to_reserve = MIN_BUFFER_CAPACITY; - #[cfg(feature = "parallel")] - let to_reserve = if self.is_non_blocking && !self.bytes_available_failed { - match crate::parallel::stderr::bytes_available(stderr) { - #[cfg(windows)] - Ok(0) => break false, - #[cfg(unix)] - Ok(0) => { - // On Unix, depending on the implementation, we may sometimes get 0 in a - // loop (either there is data available or the pipe is broken), so - // continue with the non-blocking read anyway. - MIN_BUFFER_CAPACITY - } - #[cfg(windows)] - Err(_) => { - // On Windows, if we get an error then the pipe is broken, so flush - // the buffer and bail. - if !buffer.is_empty() { - write_warning(&buffer[..]); - } - self.inner = None; - break true; - } - #[cfg(unix)] - Err(_) => { - // On Unix, depending on the implementation, we may get spurious - // errors so make a note not to use bytes_available again and try - // the non-blocking read anyway. - self.bytes_available_failed = true; - MIN_BUFFER_CAPACITY - } - #[cfg(target_family = "wasm")] - Err(_) => panic!("bytes_available should always succeed on wasm"), - Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available), - } - } else { - MIN_BUFFER_CAPACITY - }; - if self.bytes_buffered + to_reserve > buffer.len() { - buffer.resize(self.bytes_buffered + to_reserve, 0); - } - - match stderr.read(&mut buffer[self.bytes_buffered..]) { - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { - // No data currently, yield back. - break false; - } - Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { - // Interrupted, try again. - continue; - } - Ok(bytes_read) if bytes_read != 0 => { - self.bytes_buffered += bytes_read; - let mut consumed = 0; - for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n') { - // Only forward complete lines, leave the rest in the buffer. - if let Some((b'\n', line)) = line.split_last() { - consumed += line.len() + 1; - write_warning(line); - } - } - if consumed > 0 && consumed < self.bytes_buffered { - // Remove the consumed bytes from buffer - buffer.copy_within(consumed.., 0); - } - self.bytes_buffered -= consumed; - } - res => { - // End of stream: flush remaining data and bail. - if self.bytes_buffered > 0 { - write_warning(&buffer[..self.bytes_buffered]); - } - if let Err(err) = res { - write_warning( - format!("Failed to read from child stderr: {err}").as_bytes(), - ); - } - self.inner.take(); - break true; - } - } - } - } else { - true - } - } - - #[cfg(feature = "parallel")] - pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> { - assert!(!self.is_non_blocking); - - #[cfg(unix)] - if let Some((stderr, _)) = self.inner.as_ref() { - crate::parallel::stderr::set_non_blocking(stderr)?; - } - - self.is_non_blocking = true; - Ok(()) - } - - #[cfg(feature = "parallel")] - fn forward_all(&mut self) { - while !self.forward_available() {} - } - - #[cfg(not(feature = "parallel"))] - fn forward_all(&mut self) { - let forward_result = self.forward_available(); - assert!(forward_result, "Should have consumed all data"); - } -} - -fn write_warning(line: &[u8]) { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - stdout.write_all(b"cargo:warning=").unwrap(); - stdout.write_all(line).unwrap(); - stdout.write_all(b"\n").unwrap(); -} - -fn wait_on_child( - cmd: &Command, - child: &mut Child, - cargo_output: &CargoOutput, -) -> Result<(), Error> { - StderrForwarder::new(child).forward_all(); - - let status = match child.wait() { - Ok(s) => s, - Err(e) => { - return Err(Error::new( - ErrorKind::ToolExecError, - format!("failed to wait on spawned child process `{cmd:?}`: {e}"), - )); - } - }; - - cargo_output.print_debug(&status); - - if status.success() { - Ok(()) - } else { - Err(Error::new( - ErrorKind::ToolExecError, - format!("command did not execute successfully (status code {status}): {cmd:?}"), - )) - } -} - -/// Find the destination object path for each file in the input source files, -/// and store them in the output Object. -pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> { - let mut objects = Vec::with_capacity(files.len()); - for file in files { - let basename = file - .file_name() - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidArgument, - "No file_name for object file path!", - ) - })? - .to_string_lossy(); - let dirname = file - .parent() - .ok_or_else(|| { - Error::new( - ErrorKind::InvalidArgument, - "No parent for object file path!", - ) - })? - .to_string_lossy(); - - // Hash the dirname. This should prevent conflicts if we have multiple - // object files with the same filename in different subfolders. - let mut hasher = hash_map::DefaultHasher::new(); - - // Make the dirname relative (if possible) to avoid full system paths influencing the sha - // and making the output system-dependent - // - // NOTE: Here we allow using std::env::var (instead of Build::getenv) because - // CARGO_* variables always trigger a rebuild when changed - #[allow(clippy::disallowed_methods)] - let dirname = if let Some(root) = std::env::var_os("CARGO_MANIFEST_DIR") { - let root = root.to_string_lossy(); - Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname)) - } else { - dirname - }; - - hasher.write(dirname.as_bytes()); - if let Some(extension) = file.extension() { - hasher.write(extension.to_string_lossy().as_bytes()); - } - let obj = dst - .join(format!("{:016x}-{}", hasher.finish(), basename)) - .with_extension("o"); - - match obj.parent() { - Some(s) => fs::create_dir_all(s)?, - None => { - return Err(Error::new( - ErrorKind::InvalidArgument, - "dst is an invalid path with no parent", - )); - } - }; - - objects.push(Object::new(file.to_path_buf(), obj)); - } - - Ok(objects) -} - -pub(crate) fn run(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<(), Error> { - let mut child = spawn(cmd, cargo_output)?; - wait_on_child(cmd, &mut child, cargo_output) -} - -pub(crate) fn run_output(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Vec<u8>, Error> { - // We specifically need the output to be captured, so override default - let mut captured_cargo_output = cargo_output.clone(); - captured_cargo_output.output = OutputKind::Capture; - let mut child = spawn(cmd, &captured_cargo_output)?; - - let mut stdout = vec![]; - child - .stdout - .take() - .unwrap() - .read_to_end(&mut stdout) - .unwrap(); - - // Don't care about this output, use the normal settings - wait_on_child(cmd, &mut child, cargo_output)?; - - Ok(stdout) -} - -pub(crate) fn spawn(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Child, Error> { - struct ResetStderr<'cmd>(&'cmd mut Command); - - impl Drop for ResetStderr<'_> { - fn drop(&mut self) { - // Reset stderr to default to release pipe_writer so that print thread will - // not block forever. - self.0.stderr(Stdio::inherit()); - } - } - - cargo_output.print_debug(&format_args!("running: {:?}", cmd)); - - let cmd = ResetStderr(cmd); - let child = cmd - .0 - .stderr(cargo_output.stdio_for_warnings()) - .stdout(cargo_output.stdio_for_output()) - .spawn(); - match child { - Ok(child) => Ok(child), - Err(ref e) if e.kind() == io::ErrorKind::NotFound => { - let extra = if cfg!(windows) { - " (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)" - } else { - "" - }; - Err(Error::new( - ErrorKind::ToolNotFound, - format!("failed to find tool {:?}: {e}{extra}", cmd.0.get_program()), - )) - } - Err(e) => Err(Error::new( - ErrorKind::ToolExecError, - format!("command `{:?}` failed to start: {e}", cmd.0), - )), - } -} - -pub(crate) struct CmdAddOutputFileArgs { - pub(crate) cuda: bool, - pub(crate) is_assembler_msvc: bool, - pub(crate) msvc: bool, - pub(crate) clang: bool, - pub(crate) gnu: bool, - pub(crate) is_asm: bool, - pub(crate) is_arm: bool, -} - -pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) { - if args.is_assembler_msvc - || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm)) - { - let mut s = OsString::from("-Fo"); - s.push(dst); - cmd.arg(s); - } else { - cmd.arg("-o").arg(dst); - } -} - -#[cfg(feature = "parallel")] -pub(crate) fn try_wait_on_child( - cmd: &Command, - child: &mut Child, - stdout: &mut dyn io::Write, - stderr_forwarder: &mut StderrForwarder, -) -> Result<Option<()>, Error> { - stderr_forwarder.forward_available(); - - match child.try_wait() { - Ok(Some(status)) => { - stderr_forwarder.forward_all(); - - let _ = writeln!(stdout, "{}", status); - - if status.success() { - Ok(Some(())) - } else { - Err(Error::new( - ErrorKind::ToolExecError, - format!("command did not execute successfully (status code {status}): {cmd:?}"), - )) - } - } - Ok(None) => Ok(None), - Err(e) => { - stderr_forwarder.forward_all(); - Err(Error::new( - ErrorKind::ToolExecError, - format!("failed to wait on spawned child process `{cmd:?}`: {e}"), - )) - } - } -} |
