ptx_builder/executable/
runner.rs1use std::ffi::OsStr;
2use std::path::Path;
3use std::process::Command;
4
5use failure::ResultExt;
6use regex::Regex;
7use semver::Version;
8
9use super::Executable;
10use crate::error::*;
11
12pub struct ExecutableRunner<Ex: Executable> {
13 command: Command,
14 executable: Ex,
15}
16
17#[derive(Debug)]
18pub struct Output {
19 pub stdout: String,
20 pub stderr: String,
21}
22
23impl<Ex: Executable> ExecutableRunner<Ex> {
24 pub fn new(executable: Ex) -> Self {
25 ExecutableRunner {
26 command: Command::new(executable.get_name()),
27 executable,
28 }
29 }
30
31 pub fn with_args<I, S>(&mut self, args: I) -> &mut Self
32 where
33 I: IntoIterator<Item = S>,
34 S: AsRef<OsStr>,
35 {
36 self.command.args(args);
37 self
38 }
39
40 pub fn with_env<K, V>(&mut self, key: K, val: V) -> &mut Self
41 where
42 K: AsRef<OsStr>,
43 V: AsRef<OsStr>,
44 {
45 self.command.env(key, val);
46 self
47 }
48
49 pub fn with_cwd<P>(&mut self, path: P) -> &mut Self
50 where
51 P: AsRef<Path>,
52 {
53 self.command.current_dir(path);
54 self
55 }
56
57 pub fn run(&mut self) -> Result<Output> {
58 self.check_version()?;
59
60 let raw_output = {
61 self.command.output().with_context(|_| {
62 BuildErrorKind::InternalError(format!(
63 "Unable to execute command '{}'",
64 self.executable.get_name()
65 ))
66 })?
67 };
68
69 let output = Output {
70 stdout: String::from_utf8(raw_output.stdout).context(BuildErrorKind::OtherError)?,
71 stderr: String::from_utf8(raw_output.stderr).context(BuildErrorKind::OtherError)?,
72 };
73
74 if raw_output.status.success() {
75 Ok(output)
76 } else {
77 Err(Error::from(BuildErrorKind::CommandFailed {
78 command: self.executable.get_name(),
79 code: raw_output.status.code().unwrap_or(-1),
80 stderr: output.stderr,
81 }))
82 }
83 }
84
85 fn check_version(&self) -> Result<()> {
86 let current = self.executable.get_current_version()?;
87 let required = self.executable.get_required_version();
88
89 match required {
90 Some(ref required) if !required.matches(¤t) => {
91 Err(Error::from(BuildErrorKind::CommandVersionNotFulfilled {
92 command: self.executable.get_name(),
93 current,
94 required: required.clone(),
95 hint: self.executable.get_version_hint(),
96 }))
97 }
98
99 _ => Ok(()),
100 }
101 }
102}
103
104pub(crate) fn parse_executable_version<E: Executable>(executable: &E) -> Result<Version> {
105 let mut command = Command::new(executable.get_name());
106
107 command.args(&["-V"]);
108
109 let raw_output = {
110 command
111 .output()
112 .with_context(|_| BuildErrorKind::CommandNotFound {
113 command: executable.get_name(),
114 hint: executable.get_verification_hint(),
115 })?
116 };
117
118 let output = Output {
119 stdout: String::from_utf8(raw_output.stdout).context(BuildErrorKind::OtherError)?,
120 stderr: String::from_utf8(raw_output.stderr).context(BuildErrorKind::OtherError)?,
121 };
122
123 if !raw_output.status.success() {
124 bail!(BuildErrorKind::CommandFailed {
125 command: executable.get_name(),
126 code: raw_output.status.code().unwrap_or(-1),
127 stderr: output.stderr,
128 });
129 }
130
131 let version_regex = Regex::new(&format!(r"{}\s(\S+)", executable.get_name()))
132 .context(BuildErrorKind::OtherError)?;
133
134 match version_regex.captures(&(output.stdout + &output.stderr)) {
135 Some(captures) => Ok(Version::parse(&captures[1]).context(BuildErrorKind::OtherError)?),
136
137 None => Err(Error::from(BuildErrorKind::InternalError(
138 "Unable to find executable version".into(),
139 ))),
140 }
141}