use std::process::Command;
use std::sync::OnceLock;
use which::which;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunMethod {
Native,
Docker,
Podman,
}
impl std::str::FromStr for RunMethod {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"native" => Ok(RunMethod::Native),
"docker" => Ok(RunMethod::Docker),
"podman" => Ok(RunMethod::Podman),
_ => Err(format!("Invalid RunMethod: {s}")),
}
}
}
impl std::fmt::Display for RunMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RunMethod::Native => write!(f, "native"),
RunMethod::Docker => write!(f, "docker"),
RunMethod::Podman => write!(f, "podman"),
}
}
}
pub static RUN_METHOD: OnceLock<RunMethod> = OnceLock::new();
pub fn get_run_method() -> RunMethod {
*RUN_METHOD.get_or_init(detect_run_method)
}
pub fn set_run_method(method: RunMethod) {
let _ = RUN_METHOD.set(method);
}
fn detect_run_method() -> RunMethod {
if which("conjure").is_ok() && which("savilerow").is_ok() {
let output = Command::new("conjure")
.arg("--version")
.output()
.expect("Failed to execute conjure --version");
if !output.status.success() {
eprintln!(
"ERROR: The command 'conjure' does not appear to be constraint tool, you have another 'conjure' program installed:"
);
eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr));
eprintln!("Going to try docker or podman instead.")
} else {
return RunMethod::Native;
}
}
if which("podman").is_ok() {
eprintln!("Using podman");
return RunMethod::Podman;
}
if which("docker").is_ok() {
eprintln!("Using docker. Make sure docker is installed and running!");
return RunMethod::Docker;
}
eprintln!(
"Cannot find conjure, or podman or docker (so I can install conjure using them). The easist fix is to install docker or podman!"
);
RunMethod::Native
}
pub struct ProgramRunner;
impl ProgramRunner {
pub fn get_conjure_version() -> Result<String, String> {
let current_dir =
std::env::current_dir().map_err(|e| format!("Failed to get current directory: {e}"))?;
let mut cmd = Self::prepare("conjure", ¤t_dir);
cmd.arg("--version");
let output = cmd
.output()
.map_err(|e| format!("Failed to execute conjure: {e}"))?;
if output.status.success() {
Ok("Using ".to_owned()
+ &get_run_method().to_string()
+ " conjure, version:\n"
+ &String::from_utf8_lossy(&output.stdout))
} else {
Err(format!(
"Conjure failed with status {}: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
))
}
}
#[must_use]
pub fn prepare(program: &str, localdir: &std::path::Path) -> Command {
match get_run_method() {
RunMethod::Native => {
let mut cmd = Command::new(program);
cmd.current_dir(localdir);
cmd
}
RunMethod::Docker | RunMethod::Podman => {
let container_cmd = if get_run_method() == RunMethod::Docker {
"docker"
} else {
"podman"
};
let mut container_command = Command::new(container_cmd);
container_command
.current_dir(localdir)
.arg("run")
.arg("--rm")
.arg("-v")
.arg(".:/workspace:Z")
.arg("-w")
.arg("/workspace")
.arg("ghcr.io/conjure-cp/conjure:main")
.arg(program);
container_command
}
}
}
}