summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cc/src/command_helpers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cc/src/command_helpers.rs')
-rw-r--r--third_party/rust/cc/src/command_helpers.rs433
1 files changed, 433 insertions, 0 deletions
diff --git a/third_party/rust/cc/src/command_helpers.rs b/third_party/rust/cc/src/command_helpers.rs
new file mode 100644
index 0000000000..919d276c84
--- /dev/null
+++ b/third_party/rust/cc/src/command_helpers.rs
@@ -0,0 +1,433 @@
+//! Miscellaneous helpers for running commands
+
+use std::{
+ 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,
+ checked_dbg_var: Arc<AtomicBool>,
+}
+
+impl CargoOutput {
+ pub(crate) fn new() -> Self {
+ Self {
+ metadata: true,
+ warnings: true,
+ debug: std::env::var_os("CC_ENABLE_DEBUG_OUTPUT").is_some(),
+ 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()
+ }
+ }
+}
+
+pub(crate) struct StderrForwarder {
+ inner: Option<(ChildStderr, Vec<u8>)>,
+ #[cfg(feature = "parallel")]
+ is_non_blocking: bool,
+ #[cfg(feature = "parallel")]
+ bytes_available_failed: bool,
+}
+
+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))),
+ #[cfg(feature = "parallel")]
+ is_non_blocking: false,
+ #[cfg(feature = "parallel")]
+ bytes_available_failed: false,
+ }
+ }
+
+ #[allow(clippy::uninit_vec)]
+ fn forward_available(&mut self) -> bool {
+ if let Some((stderr, buffer)) = self.inner.as_mut() {
+ loop {
+ let old_data_end = buffer.len();
+
+ // 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) => return 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;
+ return 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
+ }
+ Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
+ }
+ } else {
+ MIN_BUFFER_CAPACITY
+ };
+ buffer.reserve(to_reserve);
+
+ // SAFETY: 1) the length is set to the capacity, so we are never using memory beyond
+ // the underlying buffer and 2) we always call `truncate` below to set the len back
+ // to the initialized data.
+ unsafe {
+ buffer.set_len(buffer.capacity());
+ }
+ match stderr.read(&mut buffer[old_data_end..]) {
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
+ // No data currently, yield back.
+ buffer.truncate(old_data_end);
+ return false;
+ }
+ Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
+ // Interrupted, try again.
+ buffer.truncate(old_data_end);
+ }
+ Ok(0) | Err(_) => {
+ // End of stream: flush remaining data and bail.
+ if old_data_end > 0 {
+ write_warning(&buffer[..old_data_end]);
+ }
+ self.inner = None;
+ return true;
+ }
+ Ok(bytes_read) => {
+ buffer.truncate(old_data_end + bytes_read);
+ let mut consumed = 0;
+ for line in buffer.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);
+ }
+ }
+ buffer.drain(..consumed);
+ }
+ }
+ }
+ } 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,
+ program: &str,
+ 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, command {:?} with args {:?}: {}.",
+ cmd, program, e
+ ),
+ ));
+ }
+ };
+
+ cargo_output.print_debug(&status);
+
+ if status.success() {
+ Ok(())
+ } else {
+ Err(Error::new(
+ ErrorKind::ToolExecError,
+ format!(
+ "Command {:?} with args {:?} did not execute successfully (status code {}).",
+ cmd, program, status
+ ),
+ ))
+ }
+}
+
+/// 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();
+ hasher.write(dirname.to_string().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,
+ program: &str,
+ cargo_output: &CargoOutput,
+) -> Result<(), Error> {
+ let mut child = spawn(cmd, program, cargo_output)?;
+ wait_on_child(cmd, program, &mut child, cargo_output)
+}
+
+pub(crate) fn run_output(
+ cmd: &mut Command,
+ program: &str,
+ cargo_output: &CargoOutput,
+) -> Result<Vec<u8>, Error> {
+ cmd.stdout(Stdio::piped());
+
+ let mut child = spawn(cmd, program, cargo_output)?;
+
+ let mut stdout = vec![];
+ child
+ .stdout
+ .take()
+ .unwrap()
+ .read_to_end(&mut stdout)
+ .unwrap();
+
+ wait_on_child(cmd, program, &mut child, cargo_output)?;
+
+ Ok(stdout)
+}
+
+pub(crate) fn spawn(
+ cmd: &mut Command,
+ program: &str,
+ 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()).spawn();
+ match child {
+ Ok(child) => Ok(child),
+ Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
+ let extra = if cfg!(windows) {
+ " (see https://github.com/rust-lang/cc-rs#compile-time-requirements \
+for help)"
+ } else {
+ ""
+ };
+ Err(Error::new(
+ ErrorKind::ToolNotFound,
+ format!("Failed to find tool. Is `{}` installed?{}", program, extra),
+ ))
+ }
+ Err(e) => Err(Error::new(
+ ErrorKind::ToolExecError,
+ format!(
+ "Command {:?} with args {:?} failed to start: {:?}",
+ cmd.0, program, e
+ ),
+ )),
+ }
+}
+
+pub(crate) fn command_add_output_file(
+ cmd: &mut Command,
+ dst: &Path,
+ cuda: bool,
+ msvc: bool,
+ clang: bool,
+ gnu: bool,
+ is_asm: bool,
+ is_arm: bool,
+) {
+ if msvc && !clang && !gnu && !cuda && !(is_asm && 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,
+ program: &str,
+ 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 {:?} with args {:?} did not execute successfully (status code {}).",
+ cmd, program, status
+ ),
+ ))
+ }
+ }
+ Ok(None) => Ok(None),
+ Err(e) => {
+ stderr_forwarder.forward_all();
+ Err(Error::new(
+ ErrorKind::ToolExecError,
+ format!(
+ "Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
+ cmd, program, e
+ ),
+ ))
+ }
+ }
+}