use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; use tempfile::{tempdir, TempDir}; const OBNAM_URL: &str = "https://gitlab.com/obnam/obnam.git"; #[derive(Debug, Clone)] pub enum WhichObnam { Installed, Built(String), } impl FromStr for WhichObnam { type Err = ObnamBuilderError; fn from_str(s: &str) -> Result { match s { "installed" => Ok(WhichObnam::Installed), _ => Ok(WhichObnam::Built(s.to_string())), } } } pub fn which_obnam(s: &str) -> Result { WhichObnam::from_str(s) } #[derive(Debug, thiserror::Error)] pub enum ObnamBuilderError { #[error("don't understand which Obnam to use: {0}")] Unknown(String), #[error("failed to create temporary directory for building Obnam: {0}")] TempDir(std::io::Error), #[error("failed to run client binary {0}: {1}")] Client(PathBuf, std::io::Error), #[error("failed to run git: {0}")] Git(std::io::Error), #[error("failed to run cargo: {0}")] Cargo(std::io::Error), } pub struct ObnamBuilder { #[allow(dead_code)] tempdir: TempDir, client: PathBuf, server: PathBuf, commit: Option, } impl ObnamBuilder { pub fn new(which: &WhichObnam) -> Result { let tempdir = tempdir().map_err(ObnamBuilderError::TempDir)?; let builder = match which { WhichObnam::Installed => Self { tempdir, client: Path::new("/usr/bin/obnam").to_path_buf(), server: Path::new("/usr/bin/obnam-server").to_path_buf(), commit: None, }, WhichObnam::Built(commit) => { let (commit, bin) = build_obnam(tempdir.path(), commit)?; let client = bin.join("obnam"); let server = bin.join("obnam-server"); assert!(client.exists()); assert!(server.exists()); Self { tempdir, client, server, commit: Some(commit), } } }; Ok(builder) } pub fn version(&self) -> Result { let binary = self.client_binary(); let output = Command::new(binary) .arg("--version") .output() .map_err(|err| ObnamBuilderError::Client(binary.to_path_buf(), err))?; if output.status.code() != Some(0) { eprintln!("{}", String::from_utf8_lossy(&output.stdout)); eprintln!("{}", String::from_utf8_lossy(&output.stderr)); std::process::exit(1); } let v = String::from_utf8_lossy(&output.stdout); let v = v.strip_suffix('\n').unwrap_or(&v).to_string(); Ok(v) } pub fn commit(&self) -> Option { self.commit.clone() } pub fn client_binary(&self) -> &Path { &self.client } pub fn server_binary(&self) -> &Path { &self.server } } fn build_obnam(dir: &Path, commit: &str) -> Result<(String, PathBuf), ObnamBuilderError> { let src = dir.join("git"); let bin = dir.join("bin"); git_clone(OBNAM_URL, &src)?; git_create_branch(&src, "build", commit)?; let commit = git_resolve(&src, commit)?; cargo_build(&src)?; cargo_install(&src, dir)?; Ok((commit, bin)) } fn git_clone(url: &str, dir: &Path) -> Result<(), ObnamBuilderError> { eprintln!("cloning {} to {}", url, dir.display()); run( "git", &["clone", url, &dir.display().to_string()], Path::new("."), ) .map_err(ObnamBuilderError::Git) .map(|_| ()) } fn git_create_branch(dir: &Path, branch: &str, commit: &str) -> Result<(), ObnamBuilderError> { eprintln!("checking out {}", commit); run("git", &["checkout", "-b", branch, commit], dir) .map_err(ObnamBuilderError::Git) .map(|_| ()) } fn git_resolve(dir: &Path, commit: &str) -> Result { run("git", &["rev-parse", commit], dir) .map(|s| s.strip_suffix('\n').unwrap_or("").to_string()) .map_err(ObnamBuilderError::Git) } fn cargo_build(dir: &Path) -> Result<(), ObnamBuilderError> { eprintln!("building in {}", dir.display()); run("cargo", &["build", "--release"], dir) .map_err(ObnamBuilderError::Git) .map(|_| ()) } fn cargo_install(src: &Path, bin: &Path) -> Result<(), ObnamBuilderError> { eprintln!("install to {}", bin.display()); run( "cargo", &["install", "--path=.", "--root", &bin.display().to_string()], src, ) .map_err(ObnamBuilderError::Git) .map(|_| ()) } fn run(cmd: &str, args: &[&str], cwd: &Path) -> Result { let output = Command::new(cmd).args(args).current_dir(cwd).output()?; if output.status.code() != Some(0) { eprintln!("{}", String::from_utf8_lossy(&output.stdout)); eprintln!("{}", String::from_utf8_lossy(&output.stderr)); std::process::exit(1); } let stdout = String::from_utf8_lossy(&output.stdout).to_string(); Ok(stdout) }