use std::path::Path;
use crate::compose::types::{BuildConfig, Service};
use super::{owner_marker, sorted_label_pairs, unit_stem, QuadletUnit, Section};
fn abs_context(base_dir: &Path, context: &str) -> String {
super::abs_against(base_dir, context)
}
pub(crate) fn build_unit_filename(project: &str, name: &str) -> String {
format!("{}.build", unit_stem(project, name))
}
pub(crate) fn emits_build_unit(service: &Service) -> bool {
match &service.build {
None => false,
Some(BuildConfig::Context(_)) => true,
Some(BuildConfig::Config {
dockerfile_inline, ..
}) => dockerfile_inline.is_none(),
}
}
pub(crate) fn build_unit(
name: &str,
project: &str,
service: &Service,
base_dir: &Path,
warnings: &mut Vec<String>,
) -> Option<QuadletUnit> {
let build = service.build.as_ref()?;
let mut section = Section::new("Build");
let image_tag = service
.image
.clone()
.unwrap_or_else(|| format!("{project}-{name}"));
section.add("ImageTag", image_tag);
match build {
BuildConfig::Context(context) => {
section.add("SetWorkingDirectory", abs_context(base_dir, context));
}
BuildConfig::Config {
context,
dockerfile,
dockerfile_inline,
args,
target,
labels,
network,
..
} => {
if dockerfile_inline.is_some() {
warnings.push(format!(
"{name}: build.dockerfile_inline has no Quadlet `.build` equivalent; \
no .build unit emitted — build the image first and set `image`"
));
return None;
}
section.add(
"SetWorkingDirectory",
abs_context(base_dir, context.as_deref().unwrap_or(".")),
);
if let Some(df) = dockerfile {
section.add("File", df.clone());
}
if let Some(t) = target {
section.add("Target", t.clone());
}
if let Some(net) = network {
section.add("Network", net.clone());
}
let mut build_args: Vec<(String, Option<String>)> = args.to_map().into_iter().collect();
build_args.sort_by(|a, b| a.0.cmp(&b.0));
for (key, val) in build_args {
match val {
Some(v) => section.add("PodmanArgs", format!("--build-arg {key}={v}")),
None => section.add("PodmanArgs", format!("--build-arg {key}")),
}
}
for (key, val) in sorted_label_pairs(labels.to_map()) {
section.add("Label", format!("{key}={val}"));
}
}
}
section.add("Label", format!("podup.project={project}"));
let mut contents = owner_marker(project);
contents.push_str(§ion.render());
Some(QuadletUnit {
filename: build_unit_filename(project, name),
contents,
})
}
#[cfg(all(test, unix))]
mod tests {
use super::abs_context;
use std::path::Path;
#[test]
fn abs_context_makes_relative_build_contexts_absolute() {
let base = Path::new("/srv/app");
assert_eq!(abs_context(base, "."), "/srv/app");
assert_eq!(abs_context(base, "./src"), "/srv/app/src");
assert_eq!(abs_context(base, "src"), "/srv/app/src");
assert_eq!(abs_context(base, "../shared"), "/srv/app/../shared");
assert_eq!(abs_context(base, "/opt/build"), "/opt/build");
}
}