obnam_benchmark/
builder.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3use std::str::FromStr;
4use tempfile::{tempdir, TempDir};
5
6const OBNAM_URL: &str = "https://gitlab.com/obnam/obnam.git";
7
8#[derive(Debug, Clone)]
9pub enum WhichObnam {
10    Installed,
11    Built(String),
12}
13
14impl FromStr for WhichObnam {
15    type Err = ObnamBuilderError;
16
17    fn from_str(s: &str) -> Result<Self, Self::Err> {
18        match s {
19            "installed" => Ok(WhichObnam::Installed),
20            _ => Ok(WhichObnam::Built(s.to_string())),
21        }
22    }
23}
24
25pub fn which_obnam(s: &str) -> Result<WhichObnam, ObnamBuilderError> {
26    WhichObnam::from_str(s)
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum ObnamBuilderError {
31    #[error("don't understand which Obnam to use: {0}")]
32    Unknown(String),
33
34    #[error("failed to create temporary directory for building Obnam: {0}")]
35    TempDir(std::io::Error),
36
37    #[error("failed to run client binary {0}: {1}")]
38    Client(PathBuf, std::io::Error),
39
40    #[error("failed to run git: {0}")]
41    Git(std::io::Error),
42
43    #[error("failed to run cargo: {0}")]
44    Cargo(std::io::Error),
45}
46
47pub struct ObnamBuilder {
48    #[allow(dead_code)]
49    tempdir: TempDir,
50    client: PathBuf,
51    server: PathBuf,
52    commit: Option<String>,
53}
54
55impl ObnamBuilder {
56    pub fn new(which: &WhichObnam) -> Result<Self, ObnamBuilderError> {
57        let tempdir = tempdir().map_err(ObnamBuilderError::TempDir)?;
58        let builder = match which {
59            WhichObnam::Installed => Self {
60                tempdir,
61                client: Path::new("/usr/bin/obnam").to_path_buf(),
62                server: Path::new("/usr/bin/obnam-server").to_path_buf(),
63                commit: None,
64            },
65            WhichObnam::Built(commit) => {
66                let (commit, bin) = build_obnam(tempdir.path(), commit)?;
67                let client = bin.join("obnam");
68                let server = bin.join("obnam-server");
69                assert!(client.exists());
70                assert!(server.exists());
71                Self {
72                    tempdir,
73                    client,
74                    server,
75                    commit: Some(commit),
76                }
77            }
78        };
79        Ok(builder)
80    }
81
82    pub fn version(&self) -> Result<String, ObnamBuilderError> {
83        let binary = self.client_binary();
84        let output = Command::new(binary)
85            .arg("--version")
86            .output()
87            .map_err(|err| ObnamBuilderError::Client(binary.to_path_buf(), err))?;
88        if output.status.code() != Some(0) {
89            eprintln!("{}", String::from_utf8_lossy(&output.stdout));
90            eprintln!("{}", String::from_utf8_lossy(&output.stderr));
91            std::process::exit(1);
92        }
93
94        let v = String::from_utf8_lossy(&output.stdout);
95        let v = v.strip_suffix('\n').or(Some(&v)).unwrap().to_string();
96        Ok(v)
97    }
98
99    pub fn commit(&self) -> Option<String> {
100        self.commit.clone()
101    }
102
103    pub fn client_binary(&self) -> &Path {
104        &self.client
105    }
106
107    pub fn server_binary(&self) -> &Path {
108        &self.server
109    }
110}
111
112fn build_obnam(dir: &Path, commit: &str) -> Result<(String, PathBuf), ObnamBuilderError> {
113    let src = dir.join("git");
114    let bin = dir.join("bin");
115    git_clone(OBNAM_URL, &src)?;
116    git_create_branch(&src, "build", commit)?;
117    let commit = git_resolve(&src, commit)?;
118    cargo_build(&src)?;
119    cargo_install(&src, dir)?;
120    Ok((commit, bin))
121}
122
123fn git_clone(url: &str, dir: &Path) -> Result<(), ObnamBuilderError> {
124    eprintln!("cloning {} to {}", url, dir.display());
125    run(
126        "git",
127        &["clone", url, &dir.display().to_string()],
128        Path::new("."),
129    )
130    .map_err(ObnamBuilderError::Git)
131    .map(|_| ())
132}
133
134fn git_create_branch(dir: &Path, branch: &str, commit: &str) -> Result<(), ObnamBuilderError> {
135    eprintln!("checking out {}", commit);
136    run("git", &["checkout", "-b", branch, commit], dir)
137        .map_err(ObnamBuilderError::Git)
138        .map(|_| ())
139}
140
141fn git_resolve(dir: &Path, commit: &str) -> Result<String, ObnamBuilderError> {
142    run("git", &["rev-parse", commit], dir)
143        .map(|s| s.strip_suffix('\n').or(Some("")).unwrap().to_string())
144        .map_err(ObnamBuilderError::Git)
145}
146
147fn cargo_build(dir: &Path) -> Result<(), ObnamBuilderError> {
148    eprintln!("building in {}", dir.display());
149    run("cargo", &["build", "--release"], dir)
150        .map_err(ObnamBuilderError::Git)
151        .map(|_| ())
152}
153
154fn cargo_install(src: &Path, bin: &Path) -> Result<(), ObnamBuilderError> {
155    eprintln!("install to {}", bin.display());
156    run(
157        "cargo",
158        &["install", "--path=.", "--root", &bin.display().to_string()],
159        src,
160    )
161    .map_err(ObnamBuilderError::Git)
162    .map(|_| ())
163}
164
165fn run(cmd: &str, args: &[&str], cwd: &Path) -> Result<String, std::io::Error> {
166    let output = Command::new(cmd).args(args).current_dir(cwd).output()?;
167
168    if output.status.code() != Some(0) {
169        eprintln!("{}", String::from_utf8_lossy(&output.stdout));
170        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
171        std::process::exit(1);
172    }
173
174    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
175    Ok(stdout)
176}