diff options
Diffstat (limited to 'src/builder.rs')
-rw-r--r-- | src/builder.rs | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..2de2085 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,177 @@ +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"; +const COMMIT_DIGITS: usize = 7; + +#[derive(Debug, Clone)] +pub enum WhichObnam { + Installed, + Built(String), +} + +impl FromStr for WhichObnam { + type Err = ObnamBuilderError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "installed" => Ok(WhichObnam::Installed), + _ => Ok(WhichObnam::Built(s.to_string())), + } + } +} + +pub fn which_obnam(s: &str) -> Result<WhichObnam, ObnamBuilderError> { + 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<String>, +} + +impl ObnamBuilder { + pub fn new(which: &WhichObnam) -> Result<Self, ObnamBuilderError> { + 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[..COMMIT_DIGITS].to_string()), + } + } + }; + Ok(builder) + } + + pub fn version(&self) -> Result<String, ObnamBuilderError> { + if let Some(commit) = &self.commit { + return Ok(commit.to_string()); + } + + 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').or(Some(&v)).unwrap().to_string(); + Ok(v) + } + + 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<String, ObnamBuilderError> { + run("git", &["rev-parse", commit], dir) + .map(|s| s.strip_suffix('\n').or(Some("")).unwrap().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<String, std::io::Error> { + 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) +} |