summaryrefslogtreecommitdiff
path: root/src/build.rs
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 04:45:25 +0000
committerHermes Agent <hermes@localhost>2026-08-12 04:45:25 +0000
commita73d7e29c0064e0a40ffcfb98f6b349ced5463b2 (patch)
treec3b1ae2456141945e0ebc1d892b1d3f4be4604a1 /src/build.rs
parentbdab2223c98bb0b42e7f93f4e40f54f1ac16bb71 (diff)
Execute native image build stages
Diffstat (limited to 'src/build.rs')
-rw-r--r--src/build.rs114
1 files changed, 114 insertions, 0 deletions
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<I, B> {
+ installer: I,
+ initramfs_builder: B,
+ tar_writer: NativeTarWriter,
+}
+
+impl<I, B> BuildExecutor<I, B> {
+ pub fn new(installer: I, initramfs_builder: B) -> Self {
+ Self { installer, initramfs_builder, tar_writer: NativeTarWriter::new() }
+ }
+}
+
+impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> {
+ pub fn execute(
+ &mut self,
+ plan: &BuildPlan,
+ workspace: impl AsRef<Path>,
+ apt_config: impl AsRef<Path>,
+ artifact: impl AsRef<Path>,
+ ) -> Result<BuildResult> {
+ 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<InitramfsResult>,
+}
+
+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()
+ }
+}