1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
use anyhow::{anyhow, bail, Result};
use versions::Versioning;

const CONJURE_MIN_VERSION: &str = "2.5.1";
const CORRECT_FIRST_LINE: &str = "Conjure: The Automated Constraint Modelling Tool";

/// Checks if the conjure executable is present in PATH and if it is the correct version.
/// Returns () on success and an error on failure.
pub fn conjure_executable() -> Result<()> {
    let mut cmd = std::process::Command::new("conjure");
    let output = cmd.arg("--version").output()?;
    let stdout = String::from_utf8(output.stdout)?;
    let stderr = String::from_utf8(output.stderr)?;

    if !stderr.is_empty() {
        bail!("'conjure' results in error: ".to_string() + &stderr);
    }
    let first = stdout
        .lines()
        .next()
        .ok_or(anyhow!("Could not read stdout"))?;
    if first != CORRECT_FIRST_LINE {
        let path = std::env::var("PATH")?;
        let paths = std::env::split_paths(&path);
        let num_conjures = paths.filter(|path| path.join("conjure").exists()).count();
        if num_conjures > 1 {
            bail!(
                "Conjure may be present in PATH after a conflicting name. \
            Make sure to prepend the correct path to Conjure to PATH."
            )
        } else {
            bail!("The correct Conjure executable is not present in PATH.")
        }
    }
    let version_line = stdout
        .lines()
        .nth(1)
        .ok_or(anyhow!("Could not read Conjure's stdout"))?;

    let version = match version_line.strip_prefix("Release version ") {
        Some(v) => Ok(v),
        None => match version_line.strip_prefix("Conjure v") {
            // New format: Conjure v2.5.1 (Repository version ...)
            Some(v) => v.split_whitespace().next().ok_or(anyhow!(
                "Could not read Conjure's version from: {}",
                version_line
            )),
            None => Err(anyhow!(
                "Could not read Conjure's version from: {}",
                version_line
            )),
        },
    }?;

    if Versioning::new(version) < Versioning::new(CONJURE_MIN_VERSION) {
        bail!(
            "Conjure version is too old (< {}): {}",
            CONJURE_MIN_VERSION,
            version
        );
    }
    Ok(())
}