Add comprehensive tests and fix compiler warnings
- Fix false-positive unused_assignments warnings from thiserror/miette
derive macros in Rust 2024 edition with crate-level #![allow]
- Add 5 tests for tar_layer (empty dir, files, nested dirs, symlinks,
deterministic digest)
- Add 5 tests for manifest (default options, entrypoint/env, multiple
layers, config digest verification, no entrypoint)
- Add 6 tests for layout (structure creation, oci-layout content,
index.json references, layer blobs, config digest, multiple layers)
- Add 11 tests for overlays (file copy, empty file, missing source,
ensure dir, symlink, remove file, remove dir contents, shadow
create/update, multiple overlays)
- Add 4 tests for customizations (single user, multiple users, append
to existing, no users noop)
- Add 3 tests for phase2/oci (layout output, entrypoint/env, empty
staging)
- Add tempfile dev-dependency to forge-oci for test support
42 tests passing, 0 warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:40:16 +01:00
|
|
|
// thiserror/miette derive macros generate code that triggers false-positive unused_assignments
|
|
|
|
|
#![allow(unused_assignments)]
|
|
|
|
|
|
2026-02-15 15:30:22 +01:00
|
|
|
pub mod error;
|
|
|
|
|
pub mod phase1;
|
|
|
|
|
pub mod phase2;
|
|
|
|
|
pub mod tools;
|
|
|
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
use error::ForgeError;
|
|
|
|
|
use spec_parser::schema::{ImageSpec, Target, TargetKind};
|
|
|
|
|
use tools::ToolRunner;
|
|
|
|
|
use tracing::info;
|
|
|
|
|
|
|
|
|
|
/// Context for running a build.
|
|
|
|
|
pub struct BuildContext<'a> {
|
|
|
|
|
/// The resolved and profile-filtered image spec.
|
|
|
|
|
pub spec: &'a ImageSpec,
|
|
|
|
|
/// Directory containing overlay source files (images/files/).
|
|
|
|
|
pub files_dir: &'a Path,
|
|
|
|
|
/// Output directory for build artifacts.
|
|
|
|
|
pub output_dir: &'a Path,
|
|
|
|
|
/// Tool runner for executing external commands.
|
|
|
|
|
pub runner: &'a dyn ToolRunner,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> BuildContext<'a> {
|
|
|
|
|
/// Build a specific target by name, or all targets if name is None.
|
|
|
|
|
pub async fn build(&self, target_name: Option<&str>) -> Result<(), ForgeError> {
|
|
|
|
|
let targets = self.select_targets(target_name)?;
|
|
|
|
|
|
|
|
|
|
std::fs::create_dir_all(self.output_dir)?;
|
|
|
|
|
|
|
|
|
|
for target in targets {
|
|
|
|
|
info!(target = %target.name, kind = %target.kind, "Building target");
|
|
|
|
|
|
|
|
|
|
// Phase 1: Assemble rootfs
|
|
|
|
|
let phase1_result =
|
|
|
|
|
phase1::execute(self.spec, self.files_dir, self.runner).await?;
|
|
|
|
|
|
|
|
|
|
// Phase 2: Produce target artifact
|
|
|
|
|
phase2::execute(
|
|
|
|
|
target,
|
|
|
|
|
&phase1_result.staging_root,
|
|
|
|
|
self.files_dir,
|
|
|
|
|
self.output_dir,
|
|
|
|
|
self.runner,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
info!(target = %target.name, "Target built successfully");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn select_targets(&self, target_name: Option<&str>) -> Result<Vec<&Target>, ForgeError> {
|
|
|
|
|
match target_name {
|
|
|
|
|
Some(name) => {
|
|
|
|
|
let target = self
|
|
|
|
|
.spec
|
|
|
|
|
.targets
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|t| t.name == name)
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
let available = self
|
|
|
|
|
.spec
|
|
|
|
|
.targets
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|t| t.name.as_str())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
|
|
|
|
ForgeError::TargetNotFound {
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
available,
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
Ok(vec![target])
|
|
|
|
|
}
|
|
|
|
|
None => Ok(self.spec.targets.iter().collect()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// List available targets from a spec.
|
|
|
|
|
pub fn list_targets(spec: &ImageSpec) -> Vec<(&str, &TargetKind)> {
|
|
|
|
|
spec.targets
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|t| (t.name.as_str(), &t.kind))
|
|
|
|
|
.collect()
|
|
|
|
|
}
|