From 720600dd244c32626294ba23fdbaa3690d48ebf6 Mon Sep 17 00:00:00 2001 From: Be Wilson Date: Sun, 28 Jan 2024 23:19:47 -0600 Subject: [PATCH] split miscellaneous code into a new command_helpers module --- src/command_helpers.rs | 298 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 291 +--------------------------------------- 2 files changed, 305 insertions(+), 284 deletions(-) create mode 100644 src/command_helpers.rs diff --git a/src/command_helpers.rs b/src/command_helpers.rs new file mode 100644 index 000000000..143463d2d --- /dev/null +++ b/src/command_helpers.rs @@ -0,0 +1,298 @@ +//! Miscellaneous helpers for running commands + +use std::{ + collections::hash_map, + ffi::OsString, + fmt::Display, + fs::{self, File}, + hash::Hasher, + io::{self, BufRead, BufReader, Read, Write}, + path::Path, + process::{Child, Command, Stdio}, + sync::Arc, + thread::{self, JoinHandle}, +}; + +use crate::{Error, ErrorKind, Object}; + +#[derive(Clone, Debug)] +pub(crate) struct CargoOutput { + pub(crate) metadata: bool, + pub(crate) warnings: bool, +} + +impl CargoOutput { + pub(crate) const fn new() -> Self { + Self { + metadata: true, + warnings: true, + } + } + + 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_thread(&self) -> Result, Error> { + self.warnings.then(PrintThread::new).transpose() + } +} + +pub(crate) struct PrintThread { + handle: Option>, + pipe_writer: Option, +} + +impl PrintThread { + pub(crate) fn new() -> Result { + let (pipe_reader, pipe_writer) = crate::os_pipe::pipe()?; + + // Capture the standard error coming from compilation, and write it out + // with cargo:warning= prefixes. Note that this is a bit wonky to avoid + // requiring the output to be UTF-8, we instead just ship bytes from one + // location to another. + let print = thread::spawn(move || { + let mut stderr = BufReader::with_capacity(4096, pipe_reader); + let mut line = Vec::with_capacity(20); + let stdout = io::stdout(); + + // read_until returns 0 on Eof + while stderr.read_until(b'\n', &mut line).unwrap() != 0 { + { + let mut stdout = stdout.lock(); + + stdout.write_all(b"cargo:warning=").unwrap(); + stdout.write_all(&line).unwrap(); + stdout.write_all(b"\n").unwrap(); + } + + // read_until does not clear the buffer + line.clear(); + } + }); + + Ok(Self { + handle: Some(print), + pipe_writer: Some(pipe_writer), + }) + } + + /// # Panics + /// + /// Will panic if the pipe writer has already been taken. + pub(crate) fn take_pipe_writer(&mut self) -> File { + self.pipe_writer.take().unwrap() + } + + /// # Panics + /// + /// Will panic if the pipe writer has already been taken. + pub(crate) fn clone_pipe_writer(&self) -> Result { + self.try_clone_pipe_writer().map(Option::unwrap) + } + + pub(crate) fn try_clone_pipe_writer(&self) -> Result, Error> { + self.pipe_writer + .as_ref() + .map(File::try_clone) + .transpose() + .map_err(From::from) + } +} + +impl Drop for PrintThread { + fn drop(&mut self) { + // Drop pipe_writer first to avoid deadlock + self.pipe_writer.take(); + + self.handle.take().unwrap().join().unwrap(); + } +} + +fn wait_on_child(cmd: &Command, program: &str, child: &mut Child) -> Result<(), Error> { + 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 + ), + )); + } + }; + println!("{}", 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], dst: &Path) -> Result, 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) +} + +fn run_inner(cmd: &mut Command, program: &str, pipe_writer: Option) -> Result<(), Error> { + let mut child = spawn(cmd, program, pipe_writer)?; + wait_on_child(cmd, program, &mut child) +} + +pub(crate) fn run( + cmd: &mut Command, + program: &str, + print: Option<&PrintThread>, +) -> Result<(), Error> { + let pipe_writer = print.map(PrintThread::clone_pipe_writer).transpose()?; + run_inner(cmd, program, pipe_writer)?; + + Ok(()) +} + +pub(crate) fn run_output( + cmd: &mut Command, + program: &str, + cargo_output: &CargoOutput, +) -> Result, Error> { + cmd.stdout(Stdio::piped()); + + let mut print = cargo_output.print_thread()?; + let mut child = spawn( + cmd, + program, + print.as_mut().map(PrintThread::take_pipe_writer), + )?; + + let mut stdout = vec![]; + child + .stdout + .take() + .unwrap() + .read_to_end(&mut stdout) + .unwrap(); + + wait_on_child(cmd, program, &mut child)?; + + Ok(stdout) +} + +fn spawn(cmd: &mut Command, program: &str, pipe_writer: Option) -> Result { + 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()); + } + } + + println!("running: {:?}", cmd); + + let cmd = ResetStderr(cmd); + let child = cmd + .0 + .stderr(pipe_writer.map_or_else(Stdio::null, Stdio::from)) + .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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 98af4ff1f..b615ef24d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,17 +54,15 @@ #![deny(missing_docs)] use std::borrow::Cow; -use std::collections::{hash_map, HashMap}; +use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt::{self, Display, Formatter}; -use std::fs::{self, File}; -use std::hash::Hasher; -use std::io::{self, BufRead, BufReader, Read, Write}; +use std::fs; +use std::io::{self, Write}; use std::path::{Component, Path, PathBuf}; -use std::process::{Child, Command, Stdio}; +use std::process::Command; use std::sync::{Arc, Mutex}; -use std::thread::{self, JoinHandle}; mod os_pipe; #[cfg(feature = "parallel")] @@ -87,6 +85,9 @@ mod windows_sys; pub mod windows_registry; +mod command_helpers; +use command_helpers::*; + /// A builder for compilation of a native library. /// /// A `Build` is the main type of the `cc` crate and is used to control all the @@ -3823,82 +3824,6 @@ impl Tool { } } -fn wait_on_child(cmd: &Command, program: &str, child: &mut Child) -> Result<(), Error> { - 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 - ), - )); - } - }; - println!("{}", 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. -fn objects_from_files(files: &[Arc], dst: &Path) -> Result, 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) -} - #[cfg(feature = "parallel")] fn try_wait_on_child( cmd: &Command, @@ -3933,111 +3858,11 @@ fn try_wait_on_child( } } -fn run_inner(cmd: &mut Command, program: &str, pipe_writer: Option) -> Result<(), Error> { - let mut child = spawn(cmd, program, pipe_writer)?; - wait_on_child(cmd, program, &mut child) -} - -fn run(cmd: &mut Command, program: &str, print: Option<&PrintThread>) -> Result<(), Error> { - let pipe_writer = print.map(PrintThread::clone_pipe_writer).transpose()?; - run_inner(cmd, program, pipe_writer)?; - - Ok(()) -} - -fn run_output( - cmd: &mut Command, - program: &str, - cargo_output: &CargoOutput, -) -> Result, Error> { - cmd.stdout(Stdio::piped()); - - let mut print = cargo_output.print_thread()?; - let mut child = spawn( - cmd, - program, - print.as_mut().map(PrintThread::take_pipe_writer), - )?; - - let mut stdout = vec![]; - child - .stdout - .take() - .unwrap() - .read_to_end(&mut stdout) - .unwrap(); - - wait_on_child(cmd, program, &mut child)?; - - Ok(stdout) -} - -fn spawn(cmd: &mut Command, program: &str, pipe_writer: Option) -> Result { - 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()); - } - } - - println!("running: {:?}", cmd); - - let cmd = ResetStderr(cmd); - let child = cmd - .0 - .stderr(pipe_writer.map_or_else(Stdio::null, Stdio::from)) - .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 - ), - )), - } -} - fn fail(s: &str) -> ! { eprintln!("\n\nerror occurred: {}\n\n", s); std::process::exit(1); } -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); - } -} - #[derive(Clone, Copy)] enum AppleOs { MacOs, @@ -4243,108 +4068,6 @@ impl AsmFileExt { } } -#[derive(Clone, Debug)] -struct CargoOutput { - metadata: bool, - warnings: bool, -} - -impl CargoOutput { - const fn new() -> Self { - Self { - metadata: true, - warnings: true, - } - } - - fn print_metadata(&self, s: &dyn Display) { - if self.metadata { - println!("{}", s); - } - } - - fn print_warning(&self, arg: &dyn Display) { - if self.warnings { - println!("cargo:warning={}", arg); - } - } - - fn print_thread(&self) -> Result, Error> { - self.warnings.then(PrintThread::new).transpose() - } -} - -struct PrintThread { - handle: Option>, - pipe_writer: Option, -} - -impl PrintThread { - fn new() -> Result { - let (pipe_reader, pipe_writer) = os_pipe::pipe()?; - - // Capture the standard error coming from compilation, and write it out - // with cargo:warning= prefixes. Note that this is a bit wonky to avoid - // requiring the output to be UTF-8, we instead just ship bytes from one - // location to another. - let print = thread::spawn(move || { - let mut stderr = BufReader::with_capacity(4096, pipe_reader); - let mut line = Vec::with_capacity(20); - let stdout = io::stdout(); - - // read_until returns 0 on Eof - while stderr.read_until(b'\n', &mut line).unwrap() != 0 { - { - let mut stdout = stdout.lock(); - - stdout.write_all(b"cargo:warning=").unwrap(); - stdout.write_all(&line).unwrap(); - stdout.write_all(b"\n").unwrap(); - } - - // read_until does not clear the buffer - line.clear(); - } - }); - - Ok(Self { - handle: Some(print), - pipe_writer: Some(pipe_writer), - }) - } - - /// # Panics - /// - /// Will panic if the pipe writer has already been taken. - fn take_pipe_writer(&mut self) -> File { - self.pipe_writer.take().unwrap() - } - - /// # Panics - /// - /// Will panic if the pipe writer has already been taken. - fn clone_pipe_writer(&self) -> Result { - self.try_clone_pipe_writer().map(Option::unwrap) - } - - fn try_clone_pipe_writer(&self) -> Result, Error> { - self.pipe_writer - .as_ref() - .map(File::try_clone) - .transpose() - .map_err(From::from) - } -} - -impl Drop for PrintThread { - fn drop(&mut self) { - // Drop pipe_writer first to avoid deadlock - self.pipe_writer.take(); - - self.handle.take().unwrap().join().unwrap(); - } -} - /// Remove all element in `vec` which `f(element)` returns `false`. /// /// TODO: Remove this once the MSRV is bumped to v1.61