Skip to content

Commit 47d5590

Browse files
committed
split miscellaneous code into a new command_helpers module
1 parent 6320cf3 commit 47d5590

File tree

2 files changed

+312
-285
lines changed

2 files changed

+312
-285
lines changed

src/command_helpers.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
//! Miscellaneous helpers for running commands
2+
3+
use std::{
4+
collections::hash_map,
5+
ffi::OsString,
6+
fmt::Display,
7+
fs::{self, File},
8+
hash::Hasher,
9+
io::{self, BufRead, BufReader, Read, Write},
10+
path::Path,
11+
process::{Child, Command, Stdio},
12+
sync::Arc,
13+
thread::{self, JoinHandle},
14+
};
15+
16+
use crate::{Error, ErrorKind, Object};
17+
18+
#[derive(Clone, Debug)]
19+
pub(crate) struct CargoOutput {
20+
pub(crate) metadata: bool,
21+
pub(crate) warnings: bool,
22+
}
23+
24+
impl CargoOutput {
25+
pub(crate) const fn new() -> Self {
26+
Self {
27+
metadata: true,
28+
warnings: true,
29+
}
30+
}
31+
32+
pub(crate) fn print_metadata(&self, s: &dyn Display) {
33+
if self.metadata {
34+
println!("{}", s);
35+
}
36+
}
37+
38+
pub(crate) fn print_warning(&self, arg: &dyn Display) {
39+
if self.warnings {
40+
println!("cargo:warning={}", arg);
41+
}
42+
}
43+
44+
pub(crate) fn print_thread(&self) -> Result<Option<PrintThread>, Error> {
45+
self.warnings.then(PrintThread::new).transpose()
46+
}
47+
}
48+
49+
pub(crate) struct PrintThread {
50+
handle: Option<JoinHandle<()>>,
51+
pipe_writer: Option<File>,
52+
}
53+
54+
impl PrintThread {
55+
pub(crate) fn new() -> Result<Self, Error> {
56+
let (pipe_reader, pipe_writer) = crate::os_pipe::pipe()?;
57+
58+
// Capture the standard error coming from compilation, and write it out
59+
// with cargo:warning= prefixes. Note that this is a bit wonky to avoid
60+
// requiring the output to be UTF-8, we instead just ship bytes from one
61+
// location to another.
62+
let print = thread::spawn(move || {
63+
let mut stderr = BufReader::with_capacity(4096, pipe_reader);
64+
let mut line = Vec::with_capacity(20);
65+
let stdout = io::stdout();
66+
67+
// read_until returns 0 on Eof
68+
while stderr.read_until(b'\n', &mut line).unwrap() != 0 {
69+
{
70+
let mut stdout = stdout.lock();
71+
72+
stdout.write_all(b"cargo:warning=").unwrap();
73+
stdout.write_all(&line).unwrap();
74+
stdout.write_all(b"\n").unwrap();
75+
}
76+
77+
// read_until does not clear the buffer
78+
line.clear();
79+
}
80+
});
81+
82+
Ok(Self {
83+
handle: Some(print),
84+
pipe_writer: Some(pipe_writer),
85+
})
86+
}
87+
88+
/// # Panics
89+
///
90+
/// Will panic if the pipe writer has already been taken.
91+
pub(crate) fn take_pipe_writer(&mut self) -> File {
92+
self.pipe_writer.take().unwrap()
93+
}
94+
95+
/// # Panics
96+
///
97+
/// Will panic if the pipe writer has already been taken.
98+
pub(crate) fn clone_pipe_writer(&self) -> Result<File, Error> {
99+
self.try_clone_pipe_writer().map(Option::unwrap)
100+
}
101+
102+
pub(crate) fn try_clone_pipe_writer(&self) -> Result<Option<File>, Error> {
103+
self.pipe_writer
104+
.as_ref()
105+
.map(File::try_clone)
106+
.transpose()
107+
.map_err(From::from)
108+
}
109+
}
110+
111+
impl Drop for PrintThread {
112+
fn drop(&mut self) {
113+
// Drop pipe_writer first to avoid deadlock
114+
self.pipe_writer.take();
115+
116+
self.handle.take().unwrap().join().unwrap();
117+
}
118+
}
119+
120+
fn wait_on_child(cmd: &Command, program: &str, child: &mut Child) -> Result<(), Error> {
121+
let status = match child.wait() {
122+
Ok(s) => s,
123+
Err(e) => {
124+
return Err(Error::new(
125+
ErrorKind::ToolExecError,
126+
format!(
127+
"Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
128+
cmd, program, e
129+
),
130+
));
131+
}
132+
};
133+
println!("{}", status);
134+
135+
if status.success() {
136+
Ok(())
137+
} else {
138+
Err(Error::new(
139+
ErrorKind::ToolExecError,
140+
format!(
141+
"Command {:?} with args {:?} did not execute successfully (status code {}).",
142+
cmd, program, status
143+
),
144+
))
145+
}
146+
}
147+
148+
/// Find the destination object path for each file in the input source files,
149+
/// and store them in the output Object.
150+
pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
151+
let mut objects = Vec::with_capacity(files.len());
152+
for file in files {
153+
let basename = file
154+
.file_name()
155+
.ok_or_else(|| {
156+
Error::new(
157+
ErrorKind::InvalidArgument,
158+
"No file_name for object file path!",
159+
)
160+
})?
161+
.to_string_lossy();
162+
let dirname = file
163+
.parent()
164+
.ok_or_else(|| {
165+
Error::new(
166+
ErrorKind::InvalidArgument,
167+
"No parent for object file path!",
168+
)
169+
})?
170+
.to_string_lossy();
171+
172+
// Hash the dirname. This should prevent conflicts if we have multiple
173+
// object files with the same filename in different subfolders.
174+
let mut hasher = hash_map::DefaultHasher::new();
175+
hasher.write(dirname.to_string().as_bytes());
176+
let obj = dst
177+
.join(format!("{:016x}-{}", hasher.finish(), basename))
178+
.with_extension("o");
179+
180+
match obj.parent() {
181+
Some(s) => fs::create_dir_all(s)?,
182+
None => {
183+
return Err(Error::new(
184+
ErrorKind::InvalidArgument,
185+
"dst is an invalid path with no parent",
186+
));
187+
}
188+
};
189+
190+
objects.push(Object::new(file.to_path_buf(), obj));
191+
}
192+
193+
Ok(objects)
194+
}
195+
196+
fn run_inner(cmd: &mut Command, program: &str, pipe_writer: Option<File>) -> Result<(), Error> {
197+
let mut child = spawn(cmd, program, pipe_writer)?;
198+
wait_on_child(cmd, program, &mut child)
199+
}
200+
201+
pub(crate) fn run(
202+
cmd: &mut Command,
203+
program: &str,
204+
print: Option<&PrintThread>,
205+
) -> Result<(), Error> {
206+
let pipe_writer = print.map(PrintThread::clone_pipe_writer).transpose()?;
207+
run_inner(cmd, program, pipe_writer)?;
208+
209+
Ok(())
210+
}
211+
212+
pub(crate) fn run_output(
213+
cmd: &mut Command,
214+
program: &str,
215+
cargo_output: &CargoOutput,
216+
) -> Result<Vec<u8>, Error> {
217+
cmd.stdout(Stdio::piped());
218+
219+
let mut print = cargo_output.print_thread()?;
220+
let mut child = spawn(
221+
cmd,
222+
program,
223+
print.as_mut().map(PrintThread::take_pipe_writer),
224+
)?;
225+
226+
let mut stdout = vec![];
227+
child
228+
.stdout
229+
.take()
230+
.unwrap()
231+
.read_to_end(&mut stdout)
232+
.unwrap();
233+
234+
wait_on_child(cmd, program, &mut child)?;
235+
236+
Ok(stdout)
237+
}
238+
239+
pub(crate) fn spawn(
240+
cmd: &mut Command,
241+
program: &str,
242+
pipe_writer: Option<File>,
243+
) -> Result<Child, Error> {
244+
struct ResetStderr<'cmd>(&'cmd mut Command);
245+
246+
impl Drop for ResetStderr<'_> {
247+
fn drop(&mut self) {
248+
// Reset stderr to default to release pipe_writer so that print thread will
249+
// not block forever.
250+
self.0.stderr(Stdio::inherit());
251+
}
252+
}
253+
254+
println!("running: {:?}", cmd);
255+
256+
let cmd = ResetStderr(cmd);
257+
let child = cmd
258+
.0
259+
.stderr(pipe_writer.map_or_else(Stdio::null, Stdio::from))
260+
.spawn();
261+
match child {
262+
Ok(child) => Ok(child),
263+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
264+
let extra = if cfg!(windows) {
265+
" (see https://github.com/rust-lang/cc-rs#compile-time-requirements \
266+
for help)"
267+
} else {
268+
""
269+
};
270+
Err(Error::new(
271+
ErrorKind::ToolNotFound,
272+
format!("Failed to find tool. Is `{}` installed?{}", program, extra),
273+
))
274+
}
275+
Err(e) => Err(Error::new(
276+
ErrorKind::ToolExecError,
277+
format!(
278+
"Command {:?} with args {:?} failed to start: {:?}",
279+
cmd.0, program, e
280+
),
281+
)),
282+
}
283+
}
284+
285+
pub(crate) fn command_add_output_file(
286+
cmd: &mut Command,
287+
dst: &Path,
288+
cuda: bool,
289+
msvc: bool,
290+
clang: bool,
291+
gnu: bool,
292+
is_asm: bool,
293+
is_arm: bool,
294+
) {
295+
if msvc && !clang && !gnu && !cuda && !(is_asm && is_arm) {
296+
let mut s = OsString::from("-Fo");
297+
s.push(dst);
298+
cmd.arg(s);
299+
} else {
300+
cmd.arg("-o").arg(dst);
301+
}
302+
}

0 commit comments

Comments
 (0)