From a73d7e29c0064e0a40ffcfb98f6b349ced5463b2 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 12 Aug 2026 04:45:25 +0000 Subject: Execute native image build stages --- src/build.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/build.rs (limited to 'src/build.rs') diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..4439d2f --- /dev/null +++ b/src/build.rs @@ -0,0 +1,114 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{bail, Context, Result}; + +use crate::archive::NativeTarWriter; +use crate::initramfs::{InitramfsBuilder, InitramfsRequest, InitramfsResult}; +use crate::manifest::{ArtifactManifest, InitrdRecord}; +use crate::package_installer::PackageInstaller; +use crate::rootfs::{InitrdOem, RootfsFinalization}; +use crate::BuildPlan; + +/// Executes the native stages after a plan has been validated. +/// +/// The only platform facilities are injected package and initramfs adapters; +/// filesystem finalization, archive writing, and manifest collection are native. +#[derive(Debug)] +pub struct BuildExecutor { + installer: I, + initramfs_builder: B, + tar_writer: NativeTarWriter, +} + +impl BuildExecutor { + pub fn new(installer: I, initramfs_builder: B) -> Self { + Self { installer, initramfs_builder, tar_writer: NativeTarWriter::new() } + } +} + +impl BuildExecutor { + pub fn execute( + &mut self, + plan: &BuildPlan, + workspace: impl AsRef, + apt_config: impl AsRef, + artifact: impl AsRef, + ) -> Result { + let workspace = workspace.as_ref(); + if workspace.exists() { + bail!("workspace already exists; refusing to reuse it: {}", workspace.display()); + } + let artifact = artifact.as_ref(); + if artifact.exists() { + bail!("output artifact already exists; refusing to overwrite it: {}", artifact.display()); + } + let parent = artifact + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .ok_or_else(|| anyhow::anyhow!("artifact path has no parent: {}", artifact.display()))?; + if parent.exists() && !parent.is_dir() { + bail!("artifact parent is not a directory: {}", parent.display()); + } + + std::fs::create_dir_all(workspace) + .with_context(|| format!("create workspace {}", workspace.display()))?; + let request = plan.package_request(workspace, apt_config)?; + self.installer.install(&request)?; + + let rootfs = workspace.join("chroot"); + if !rootfs.is_dir() { + bail!("package installer did not create rootfs: {}", rootfs.display()); + } + RootfsFinalization::new( + Vec::new(), + InitrdOem::new( + plan.spec().boot.initrd_features.iter().cloned(), + plan.spec().boot.initrd_modules.iter().cloned(), + ), + Vec::new(), + ) + .apply(&rootfs)?; + + let initrd = self.initramfs_builder.build(&InitramfsRequest::discover(&rootfs)?)?; + std::fs::create_dir_all(parent) + .with_context(|| format!("create artifact directory {}", parent.display()))?; + self.tar_writer.write(&rootfs, artifact)?; + let manifest = ArtifactManifest::collect( + &rootfs, + Vec::new(), + Some(InitrdRecord::new( + initrd.initrd_path().display().to_string(), + initrd.sha256(), + )), + artifact, + )?; + let manifest_path = manifest.write_beside(artifact)?; + + Ok(BuildResult { + artifact: artifact.to_path_buf(), + manifest: manifest_path, + initrd: Some(initrd), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BuildResult { + artifact: PathBuf, + manifest: PathBuf, + initrd: Option, +} + +impl BuildResult { + pub fn artifact(&self) -> PathBuf { + self.artifact.clone() + } + + pub fn manifest(&self) -> &Path { + &self.manifest + } + + pub fn initrd(&self) -> Option<&InitramfsResult> { + self.initrd.as_ref() + } +} -- cgit v1.2.3