From 7f60ddc186c11147e7345c1ecc09513aca3fdc8e Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 00:46:26 +0000 Subject: [PATCH 1/9] Add and use method --- conjure_oxide/Cargo.toml | 1 + conjure_oxide/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++++ conjure_oxide/src/main.rs | 6 +++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/conjure_oxide/Cargo.toml b/conjure_oxide/Cargo.toml index 6ad746f397..ec34599061 100644 --- a/conjure_oxide/Cargo.toml +++ b/conjure_oxide/Cargo.toml @@ -18,6 +18,7 @@ anyhow = "1.0.75" clap = { version = "4.4.8", features = ["derive"] } strum_macros = "0.25.3" strum = "0.25.0" +versions = "5.0.1" [lints] workspace = true diff --git a/conjure_oxide/src/lib.rs b/conjure_oxide/src/lib.rs index e5257242ec..1aff8582a7 100644 --- a/conjure_oxide/src/lib.rs +++ b/conjure_oxide/src/lib.rs @@ -5,3 +5,46 @@ pub mod parse; pub use ast::Model; pub use error::Error; mod solvers; + +use versions::Versioning; + +pub fn conjure_executable() -> Result { + let path = std::env::var("PATH") + .map_err(|_| "Could not read PATH environment variable".to_string())?; + let mut paths = std::env::split_paths(&path); + let conjure_dir = paths + .find(|path| path.join("conjure").exists()) + .ok_or("Could not find conjure in PATH")?; + let conjure_exec = conjure_dir.join("conjure") + .to_str() + .ok_or("Could not unwrap conjure executable path")? + .to_string(); + + let mut cmd = std::process::Command::new(&conjure_exec); + let output = cmd + .arg("--version") + .output() + .map_err(|_| "Could not execute conjure")?; + let stdout = String::from_utf8(output.stderr) + .map_err(|_| "Could not read conjure's stdout")?; + + let first = stdout + .lines() + .next() + .ok_or("Could not read conjure's stdout")?; + if first != "Conjure: The Automated Constraint Modelling Tool" { + return Err("'conjure' points to an incorrect executable".to_string()); + } + let second = stdout + .lines() + .nth(1) + .ok_or("Could not read conjure's stdout")?; + let version = second + .strip_prefix("Release version ") + .ok_or("Could not read conjure's stdout")?; + if Versioning::new(version) < Versioning::new("2.5.0") { + return Err("Conjure version is too old".to_string()); + } + + Ok(conjure_exec) +} diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index f20e7147f0..da075d2d72 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -6,6 +6,8 @@ use std::path::PathBuf; use anyhow::Result as AnyhowResult; use clap::{arg, command, Parser}; use conjure_oxide::parse::parse_json; +use conjure_oxide::conjure_executable; + #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { @@ -27,7 +29,9 @@ pub fn main() -> AnyhowResult<()> { /* Parse essence to json using Conjure */ /******************************************************/ - let mut cmd = std::process::Command::new("conjure"); + let conjure_exec = conjure_executable() + .map_err(|e| anyhow!("Could not find conjure executable: {}", e))?; + let mut cmd = std::process::Command::new(conjure_exec); let output = cmd .arg("pretty") .arg("--output-format=astjson") From 16728235c93b49e0697808553acb330329b7acfa Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 10:58:53 +0000 Subject: [PATCH 2/9] Move function to its own file --- Cargo.lock | 20 ++++++++++++++ conjure_oxide/src/find_conjure.rs | 42 ++++++++++++++++++++++++++++ conjure_oxide/src/lib.rs | 46 ++----------------------------- conjure_oxide/src/main.rs | 2 +- 4 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 conjure_oxide/src/find_conjure.rs diff --git a/Cargo.lock b/Cargo.lock index 8417c9b749..64d5aa4c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,7 @@ dependencies = [ "strum", "strum_macros", "thiserror", + "versions", "walkdir", ] @@ -454,6 +455,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -874,6 +884,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "versions" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73a36bc44e3039f51fbee93e39f41225f6b17b380eb70cc2aab942df06b34dd" +dependencies = [ + "itertools", + "nom", +] + [[package]] name = "walkdir" version = "2.4.0" diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs new file mode 100644 index 0000000000..4be2baa478 --- /dev/null +++ b/conjure_oxide/src/find_conjure.rs @@ -0,0 +1,42 @@ +use versions::Versioning; + +pub fn conjure_executable() -> Result { + let path = std::env::var("PATH") + .map_err(|_| "Could not read PATH environment variable".to_string())?; + let mut paths = std::env::split_paths(&path); + let conjure_dir = paths + .find(|path| path.join("conjure").exists()) + .ok_or("Could not find conjure in PATH")?; + let conjure_exec = conjure_dir.join("conjure") + .to_str() + .ok_or("Could not unwrap conjure executable path")? + .to_string(); + + let mut cmd = std::process::Command::new(&conjure_exec); + let output = cmd + .arg("--version") + .output() + .map_err(|_| "Could not execute conjure")?; + let stdout = String::from_utf8(output.stderr) + .map_err(|_| "Could not read conjure's stdout")?; + + let first = stdout + .lines() + .next() + .ok_or("Could not read conjure's stdout")?; + if first != "Conjure: The Automated Constraint Modelling Tool" { + return Err("'conjure' points to an incorrect executable".to_string()); + } + let second = stdout + .lines() + .nth(1) + .ok_or("Could not read conjure's stdout")?; + let version = second + .strip_prefix("Release version ") + .ok_or("Could not read conjure's stdout")?; + if Versioning::new(version) < Versioning::new("2.5.0") { + return Err("Conjure version is too old".to_string()); + } + + Ok(conjure_exec) +} diff --git a/conjure_oxide/src/lib.rs b/conjure_oxide/src/lib.rs index 1aff8582a7..4f520522c4 100644 --- a/conjure_oxide/src/lib.rs +++ b/conjure_oxide/src/lib.rs @@ -1,50 +1,8 @@ pub mod ast; pub mod error; pub mod parse; +mod solvers; +pub mod find_conjure; pub use ast::Model; pub use error::Error; -mod solvers; - -use versions::Versioning; - -pub fn conjure_executable() -> Result { - let path = std::env::var("PATH") - .map_err(|_| "Could not read PATH environment variable".to_string())?; - let mut paths = std::env::split_paths(&path); - let conjure_dir = paths - .find(|path| path.join("conjure").exists()) - .ok_or("Could not find conjure in PATH")?; - let conjure_exec = conjure_dir.join("conjure") - .to_str() - .ok_or("Could not unwrap conjure executable path")? - .to_string(); - - let mut cmd = std::process::Command::new(&conjure_exec); - let output = cmd - .arg("--version") - .output() - .map_err(|_| "Could not execute conjure")?; - let stdout = String::from_utf8(output.stderr) - .map_err(|_| "Could not read conjure's stdout")?; - - let first = stdout - .lines() - .next() - .ok_or("Could not read conjure's stdout")?; - if first != "Conjure: The Automated Constraint Modelling Tool" { - return Err("'conjure' points to an incorrect executable".to_string()); - } - let second = stdout - .lines() - .nth(1) - .ok_or("Could not read conjure's stdout")?; - let version = second - .strip_prefix("Release version ") - .ok_or("Could not read conjure's stdout")?; - if Versioning::new(version) < Versioning::new("2.5.0") { - return Err("Conjure version is too old".to_string()); - } - - Ok(conjure_exec) -} diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index da075d2d72..58e517265f 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use anyhow::Result as AnyhowResult; use clap::{arg, command, Parser}; use conjure_oxide::parse::parse_json; -use conjure_oxide::conjure_executable; +use conjure_oxide::find_conjure::conjure_executable; #[derive(Parser)] #[command(author, version, about, long_about = None)] From abd4f0c576ec83a6b5de721b414db812b2f68ab2 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 11:02:10 +0000 Subject: [PATCH 3/9] Update minimum conjure version --- conjure_oxide/src/find_conjure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index 4be2baa478..9852de8772 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -34,7 +34,7 @@ pub fn conjure_executable() -> Result { let version = second .strip_prefix("Release version ") .ok_or("Could not read conjure's stdout")?; - if Versioning::new(version) < Versioning::new("2.5.0") { + if Versioning::new(version) < Versioning::new("2.5.1") { return Err("Conjure version is too old".to_string()); } From cf7a34c934c52d177f4267b914705a7434acb26a Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 11:07:40 +0000 Subject: [PATCH 4/9] Check for stderr --- conjure_oxide/src/find_conjure.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index 9852de8772..971da4e33f 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -11,15 +11,20 @@ pub fn conjure_executable() -> Result { .to_str() .ok_or("Could not unwrap conjure executable path")? .to_string(); - + let mut cmd = std::process::Command::new(&conjure_exec); let output = cmd .arg("--version") .output() .map_err(|_| "Could not execute conjure")?; - let stdout = String::from_utf8(output.stderr) + let stdout = String::from_utf8(output.stdout) .map_err(|_| "Could not read conjure's stdout")?; + let stderr = String::from_utf8(output.stderr) + .map_err(|_| "Could not read conjure's stderr")?; + if !stderr.is_empty() { + return Err("Stderr is not empty: ".to_string() + &stderr); + } let first = stdout .lines() .next() From d682d07e3e6b00033527bc6b96d98faf98f49b40 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 11:17:18 +0000 Subject: [PATCH 5/9] Use anyhow for better error handling --- conjure_oxide/src/find_conjure.rs | 35 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index 971da4e33f..fffbf57185 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -1,46 +1,45 @@ use versions::Versioning; +use anyhow::{anyhow, Result, bail}; -pub fn conjure_executable() -> Result { - let path = std::env::var("PATH") - .map_err(|_| "Could not read PATH environment variable".to_string())?; +const CONJURE_MIN_VERSION: &str = "2.5.1"; + +pub fn conjure_executable() -> Result { + let path = std::env::var("PATH")?; let mut paths = std::env::split_paths(&path); let conjure_dir = paths .find(|path| path.join("conjure").exists()) - .ok_or("Could not find conjure in PATH")?; + .ok_or(anyhow!("Could not find conjure in PATH"))?; let conjure_exec = conjure_dir.join("conjure") .to_str() - .ok_or("Could not unwrap conjure executable path")? + .ok_or(anyhow!("Could not unwrap conjure executable path"))? .to_string(); let mut cmd = std::process::Command::new(&conjure_exec); let output = cmd .arg("--version") - .output() - .map_err(|_| "Could not execute conjure")?; - let stdout = String::from_utf8(output.stdout) - .map_err(|_| "Could not read conjure's stdout")?; - let stderr = String::from_utf8(output.stderr) - .map_err(|_| "Could not read conjure's stderr")?; + .output()?; + let stdout = String::from_utf8(output.stdout)?; + let stderr = String::from_utf8(output.stderr)?; if !stderr.is_empty() { - return Err("Stderr is not empty: ".to_string() + &stderr); + bail!("Stderr is not empty: ".to_string() + &stderr); } let first = stdout .lines() .next() - .ok_or("Could not read conjure's stdout")?; + .ok_or(anyhow!("Could not read conjure's stdout"))?; if first != "Conjure: The Automated Constraint Modelling Tool" { - return Err("'conjure' points to an incorrect executable".to_string()); + bail!("'conjure' points to an incorrect executable"); } let second = stdout .lines() .nth(1) - .ok_or("Could not read conjure's stdout")?; + .ok_or(anyhow!("Could not read conjure's stdout"))?; let version = second .strip_prefix("Release version ") - .ok_or("Could not read conjure's stdout")?; - if Versioning::new(version) < Versioning::new("2.5.1") { - return Err("Conjure version is too old".to_string()); + .ok_or(anyhow!("Could not read conjure's stdout"))?; + if Versioning::new(version) < Versioning::new(CONJURE_MIN_VERSION) { + bail!("Conjure version is too old (<{}): {}", CONJURE_MIN_VERSION, version); } Ok(conjure_exec) From 655996e870dd98b71e132bbce1c3d5fc2be96133 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 11:57:30 +0000 Subject: [PATCH 6/9] Apply fmt --- conjure_oxide/src/find_conjure.rs | 15 +++++++++------ conjure_oxide/src/lib.rs | 2 +- conjure_oxide/src/main.rs | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index fffbf57185..ec6e13e868 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -1,5 +1,5 @@ +use anyhow::{anyhow, bail, Result}; use versions::Versioning; -use anyhow::{anyhow, Result, bail}; const CONJURE_MIN_VERSION: &str = "2.5.1"; @@ -9,15 +9,14 @@ pub fn conjure_executable() -> Result { let conjure_dir = paths .find(|path| path.join("conjure").exists()) .ok_or(anyhow!("Could not find conjure in PATH"))?; - let conjure_exec = conjure_dir.join("conjure") + let conjure_exec = conjure_dir + .join("conjure") .to_str() .ok_or(anyhow!("Could not unwrap conjure executable path"))? .to_string(); let mut cmd = std::process::Command::new(&conjure_exec); - let output = cmd - .arg("--version") - .output()?; + let output = cmd.arg("--version").output()?; let stdout = String::from_utf8(output.stdout)?; let stderr = String::from_utf8(output.stderr)?; @@ -39,7 +38,11 @@ pub fn conjure_executable() -> Result { .strip_prefix("Release version ") .ok_or(anyhow!("Could not read conjure's stdout"))?; if Versioning::new(version) < Versioning::new(CONJURE_MIN_VERSION) { - bail!("Conjure version is too old (<{}): {}", CONJURE_MIN_VERSION, version); + bail!( + "Conjure version is too old (<{}): {}", + CONJURE_MIN_VERSION, + version + ); } Ok(conjure_exec) diff --git a/conjure_oxide/src/lib.rs b/conjure_oxide/src/lib.rs index 4f520522c4..747851612b 100644 --- a/conjure_oxide/src/lib.rs +++ b/conjure_oxide/src/lib.rs @@ -1,8 +1,8 @@ pub mod ast; pub mod error; +pub mod find_conjure; pub mod parse; mod solvers; -pub mod find_conjure; pub use ast::Model; pub use error::Error; diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index 58e517265f..5852fe4459 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -5,8 +5,8 @@ use std::path::PathBuf; use anyhow::Result as AnyhowResult; use clap::{arg, command, Parser}; -use conjure_oxide::parse::parse_json; use conjure_oxide::find_conjure::conjure_executable; +use conjure_oxide::parse::parse_json; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -29,8 +29,8 @@ pub fn main() -> AnyhowResult<()> { /* Parse essence to json using Conjure */ /******************************************************/ - let conjure_exec = conjure_executable() - .map_err(|e| anyhow!("Could not find conjure executable: {}", e))?; + let conjure_exec = + conjure_executable().map_err(|e| anyhow!("Could not find conjure executable: {}", e))?; let mut cmd = std::process::Command::new(conjure_exec); let output = cmd .arg("pretty") From d684013bcca315277adc40844d6d0b0e25e055cc Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 15:22:08 +0000 Subject: [PATCH 7/9] Clean up and check for conflicting executables --- conjure_oxide/src/find_conjure.rs | 46 +++++++++++++++---------------- conjure_oxide/src/main.rs | 6 ++-- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index ec6e13e868..6b3ee7ff8d 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -2,48 +2,46 @@ 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"; -pub fn conjure_executable() -> Result { - let path = std::env::var("PATH")?; - let mut paths = std::env::split_paths(&path); - let conjure_dir = paths - .find(|path| path.join("conjure").exists()) - .ok_or(anyhow!("Could not find conjure in PATH"))?; - let conjure_exec = conjure_dir - .join("conjure") - .to_str() - .ok_or(anyhow!("Could not unwrap conjure executable path"))? - .to_string(); - - let mut cmd = std::process::Command::new(&conjure_exec); +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!("Stderr is not empty: ".to_string() + &stderr); + bail!("'conjure' results in error: ".to_string() + &stderr); } let first = stdout .lines() .next() - .ok_or(anyhow!("Could not read conjure's stdout"))?; - if first != "Conjure: The Automated Constraint Modelling Tool" { - bail!("'conjure' points to an incorrect executable"); + .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 second = stdout + let version = stdout .lines() .nth(1) - .ok_or(anyhow!("Could not read conjure's stdout"))?; - let version = second + .ok_or(anyhow!("Could not read Conjure's stdout"))? .strip_prefix("Release version ") - .ok_or(anyhow!("Could not read conjure's stdout"))?; + .ok_or(anyhow!("Could not read Conjure version"))?; if Versioning::new(version) < Versioning::new(CONJURE_MIN_VERSION) { bail!( - "Conjure version is too old (<{}): {}", + "Conjure version is too old (< {}): {}", CONJURE_MIN_VERSION, version ); } - - Ok(conjure_exec) + Ok(()) } diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index 5852fe4459..f60496bc46 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -29,9 +29,9 @@ pub fn main() -> AnyhowResult<()> { /* Parse essence to json using Conjure */ /******************************************************/ - let conjure_exec = - conjure_executable().map_err(|e| anyhow!("Could not find conjure executable: {}", e))?; - let mut cmd = std::process::Command::new(conjure_exec); + conjure_executable() + .map_err(|e| anyhow!("Could not find correct conjure executable: {}", e))?; + let mut cmd = std::process::Command::new("conjure"); let output = cmd .arg("pretty") .arg("--output-format=astjson") From ec52e0b3ef2cdd168e9ccd6e696c63cca8c3d7ac Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 15:34:31 +0000 Subject: [PATCH 8/9] Assert Conjure is present during testing --- conjure_oxide/tests/generated_tests.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 03d86a8c8e..5d43adefc1 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -66,4 +66,9 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box Ok(()) } +#[test] +fn assert_conjure_present() { + conjure_oxide::find_conjure::conjure_executable().unwrap(); +} + include!(concat!(env!("OUT_DIR"), "/gen_tests.rs")); From 455f080c536322e352949c35a16a054249507bb1 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 22 Nov 2023 15:56:19 +0000 Subject: [PATCH 9/9] Document function --- conjure_oxide/src/find_conjure.rs | 2 ++ conjure_oxide/tests/generated_tests.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conjure_oxide/src/find_conjure.rs b/conjure_oxide/src/find_conjure.rs index 6b3ee7ff8d..41dbc2f710 100644 --- a/conjure_oxide/src/find_conjure.rs +++ b/conjure_oxide/src/find_conjure.rs @@ -4,6 +4,8 @@ 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()?; diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 73daee4048..b25af0e658 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -123,7 +123,6 @@ fn sort_json_variables(value: &Value) -> Value { } } - #[test] fn assert_conjure_present() { conjure_oxide::find_conjure::conjure_executable().unwrap();