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
10pub 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
52pub 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}