eval_containers/bake.rs
1//! Bake artifact discovery — single source of truth for "which
2//! docker-bake.hcl files compose the fleet's build graph." Used by
3//! `src/build.rs` (the CLI), `tests/common/mod.rs` (test bootstraps),
4//! and `tests/build/test.rs` (the principle-15 lint). Per RULES.md
5//! principle 11 (Reuse over repetition): one home for the category
6//! list, the seed file, and the walker.
7
8use std::path::PathBuf;
9
10/// The five artifact categories whose subdirectories ship Dockerfiles
11/// and `docker-bake.hcl` files per RULES.md principle 15. Adding a
12/// sixth category is the only place this constant changes.
13pub const ARTIFACT_CATEGORIES: &[&str] = &["core", "agents", "benchmarks", "models", "gateways"];
14
15/// Path to the parameterized eval combination template.
16pub const COMBINATION_BAKE_FILE: &str = "core/combination.docker-bake.hcl";
17
18/// Path to the root bake file. Declares fleet-wide variables (`REGISTRY`)
19/// that per-artifact files reference via `${REGISTRY}/...` without
20/// redeclaring (principle 11 — reuse over repetition).
21pub const ROOT_BAKE_FILE: &str = "docker-bake.hcl";
22
23/// Every `docker-bake.hcl` in the fleet, plus the root and combination
24/// seeds. Order doesn't matter — bake merges by target name.
25pub fn artifact_bake_files() -> Vec<PathBuf> {
26 let mut files: Vec<PathBuf> = vec![ROOT_BAKE_FILE.into(), COMBINATION_BAKE_FILE.into()];
27 for category in ARTIFACT_CATEGORIES {
28 let Ok(entries) = std::fs::read_dir(category) else {
29 continue;
30 };
31 for entry in entries.flatten() {
32 let p = entry.path().join("docker-bake.hcl");
33 if p.exists() {
34 files.push(p);
35 }
36 }
37 }
38 files
39}
40
41/// Construct the `docker buildx bake` argument list for the given
42/// targets and `--set` overrides. Bake files come from
43/// [`artifact_bake_files`]. With no `builder`, `--load` is appended so
44/// the result lands in the local image store; with a named `builder`
45/// (a remote/in-cluster BuildKit), `--builder <name> --push` is appended
46/// instead, since a remote builder can't load into local Docker.
47///
48/// Both consumers (`src/build.rs` for the CLI, `tests/common/mod.rs`
49/// for test bootstraps) need this shape; sync vs. async `Command` and
50/// per-consumer env passthrough prevent a single Command-builder helper,
51/// but the arg list itself is identical.
52pub fn base_args(targets: &[&str], overrides: &[&str], builder: Option<&str>) -> Vec<String> {
53 let mut args: Vec<String> = vec!["buildx".into(), "bake".into()];
54 for f in artifact_bake_files() {
55 args.push("-f".into());
56 args.push(f.to_string_lossy().into_owned());
57 }
58 // A named builder is a remote/in-cluster BuildKit (e.g. the
59 // `--driver kubernetes` builder); it can't `--load` into the local
60 // Docker image store, so its output goes to the registry via `--push`.
61 // The default (local docker driver) loads into the local store.
62 match builder {
63 Some(name) => {
64 args.push("--builder".into());
65 args.push(name.to_string());
66 args.push("--push".into());
67 }
68 None => args.push("--load".into()),
69 }
70 for o in overrides {
71 args.push("--set".into());
72 args.push((*o).to_string());
73 }
74 for t in targets {
75 args.push((*t).to_string());
76 }
77 args
78}
79
80/// Every artifact directory (a subdirectory of one of the five
81/// categories) that contains a `Dockerfile`. Sorted for stable test
82/// failure output.
83pub fn artifact_dirs_with_dockerfile() -> Vec<PathBuf> {
84 let mut out = Vec::new();
85 for category in ARTIFACT_CATEGORIES {
86 let Ok(entries) = std::fs::read_dir(category) else {
87 continue;
88 };
89 for e in entries.flatten() {
90 let p = e.path();
91 if p.is_dir() && p.join("Dockerfile").exists() {
92 out.push(p);
93 }
94 }
95 }
96 out.sort();
97 out
98}