docker_test/
build.rs

1use anyhow::anyhow;
2use camino::Utf8PathBuf as PathBuf;
3use once_cell::sync::Lazy;
4use quick_cache::{sync::Cache, GuardResult};
5use std::process::Output;
6use std::sync::Once;
7
8use crate::{cmd, Result};
9
10// FIXME: Could merge this with Container, but not worth it ATM.
11pub fn build_in_container(
12    target_ext: &str,
13    projdir: &str,
14    features: &str,
15    image: &str,
16) -> Result<Output> {
17    let builddir = "/opt/src";
18    let target_base = format!("{builddir}/target");
19    let imgtarget = format!("{target_base}/{target_ext}");
20    let volume = format!("{projdir}:{builddir}");
21    let cargo_env = format!("CARGO_HOME={target_base}/.cargo");
22
23    let cargo_cli = vec![
24        "cargo",
25        "build",
26        "--release",
27        "--features",
28        features,
29        "--target-dir",
30        imgtarget.as_str(),
31    ];
32
33    let docker_cli = vec![
34        "run",
35        "--rm",
36        "--volume",
37        volume.as_str(),
38        "--workdir",
39        builddir,
40        "--env",
41        cargo_env.as_str(),
42        image,
43    ];
44
45    let out = cmd([docker_cli, cargo_cli].concat())?;
46
47    Ok(out)
48}
49
50static APP_BUILD_LOCK: Once = Once::new();
51
52// Build a project in a rust container. Uses locking to ensure
53// concurrent test runs share a common build.
54pub fn build_target(
55    bin_name: &str,
56    projdir: &str,
57    features: Option<&str>,
58    image: &str,
59) -> Result<PathBuf> {
60    let ext_base = "docker";
61    let fstr = features.unwrap_or("");
62    let target_ext = format!("{ext_base}/{}", fstr.replace(" ", "_"));
63
64    APP_BUILD_LOCK.call_once(|| {
65        build_in_container(&target_ext, projdir, fstr, image).unwrap();
66    });
67
68    let bin = PathBuf::from(format!("{projdir}/target/{target_ext}/release/{bin_name}"));
69    Ok(bin)
70}
71
72pub fn build_image(dir: &str, name: &str) -> Result<String> {
73    let cli = vec!["build", "--tag", name, dir];
74
75    let out = cmd(cli)?;
76    let stdout = String::from_utf8(out.stdout)?;
77    let id = stdout
78        .lines()
79        .last()
80        .ok_or(anyhow!("No output from build command"))?;
81
82    println!("BUILD IMAGE ID: '{}'", id);
83    Ok(id.to_string())
84}
85
86static IMAGE_CACHE: Lazy<Cache<String, String>> = Lazy::new(|| Cache::new(16));
87
88pub fn build_image_sync(dir: &str, name: &str) -> Result<String> {
89    let key = format!("{}-{}", dir, name);
90    match IMAGE_CACHE.get_value_or_guard(&key, None) {
91        GuardResult::Timeout => Err(anyhow!("Unexpected timeout building base image")),
92        GuardResult::Value(val) => Ok(val),
93        GuardResult::Guard(guard) => {
94            let id = build_image(dir, name)?;
95            guard.insert(id.clone());
96            Ok(id)
97        }
98    }
99}