ptx_builder/executable/
runner.rs

1use 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(&current) => {
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}