summaryrefslogtreecommitdiff
path: root/vendor/cc/src/command_helpers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/cc/src/command_helpers.rs')
-rw-r--r--vendor/cc/src/command_helpers.rs461
1 files changed, 461 insertions, 0 deletions
diff --git a/vendor/cc/src/command_helpers.rs b/vendor/cc/src/command_helpers.rs
new file mode 100644
index 00000000..89dc1a33
--- /dev/null
+++ b/vendor/cc/src/command_helpers.rs
@@ -0,0 +1,461 @@
+//! 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}"),
+ ))
+ }
+ }
+}