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()); } let request = plan.package_request(workspace, apt_config)?; std::fs::create_dir_all(workspace) .with_context(|| format!("create workspace {}", workspace.display()))?; 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() } }