diff options
| author | Hermes Agent <hermes@localhost> | 2026-08-12 04:45:25 +0000 |
|---|---|---|
| committer | Hermes Agent <hermes@localhost> | 2026-08-12 04:45:25 +0000 |
| commit | a73d7e29c0064e0a40ffcfb98f6b349ced5463b2 (patch) | |
| tree | c3b1ae2456141945e0ebc1d892b1d3f4be4604a1 /src | |
| parent | bdab2223c98bb0b42e7f93f4e40f54f1ac16bb71 (diff) | |
Execute native image build stages
Diffstat (limited to 'src')
| -rw-r--r-- | src/build.rs | 114 | ||||
| -rw-r--r-- | src/cli.rs | 25 | ||||
| -rw-r--r-- | src/initramfs.rs | 3 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/package_installer.rs | 6 |
5 files changed, 147 insertions, 2 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() + } +} @@ -3,7 +3,10 @@ use std::path::PathBuf; use anyhow::{bail, Result}; use clap::{Parser, Subcommand}; +use crate::build::BuildExecutor; use crate::compare::compare; +use crate::hasher::{HasherInstaller, ProcessRunner as HasherProcessRunner}; +use crate::initramfs::{MakeInitrdBuilder, ProcessRunner as InitramfsProcessRunner}; use crate::manifest::ArtifactManifest; use crate::{BuildPlan, ImageSpec}; @@ -57,7 +60,27 @@ pub fn run(cli: Cli) -> Result<()> { } Ok(()) } - Command::Build { .. } => bail!("build execution is not available yet"), + Command::Build { + spec, + workspace, + output, + } => { + let plan = BuildPlan::compile(ImageSpec::load(&spec)?)?; + let apt_config = spec + .parent() + .unwrap_or_else(|| std::path::Path::new(".")) + .join("apt.conf"); + let installer = HasherInstaller::new(HasherProcessRunner); + let initramfs = MakeInitrdBuilder::new(InitramfsProcessRunner); + let mut executor = BuildExecutor::new(installer, initramfs); + let result = executor.execute(&plan, workspace, apt_config, output)?; + println!("artifact: {}", result.artifact().display()); + println!("manifest: {}", result.manifest().display()); + if let Some(initrd) = result.initrd() { + println!("initrd: {} sha256:{}", initrd.initrd_path().display(), initrd.sha256()); + } + Ok(()) + } Command::Inspect { artifact } => { let manifest = load_artifact(&artifact)?; println!("{}", toml::to_string_pretty(&manifest)?); diff --git a/src/initramfs.rs b/src/initramfs.rs index ddb1010..1c5c089 100644 --- a/src/initramfs.rs +++ b/src/initramfs.rs @@ -178,7 +178,8 @@ pub struct InitramfsResult { } impl InitramfsResult { - fn from_rootfs(rootfs: &Path, kernel: &KernelVersion) -> Result<Self> { + /// Record the generated initrd as a rootfs-relative semantic fact. + pub fn from_rootfs(rootfs: &Path, kernel: &KernelVersion) -> Result<Self> { let initrd_path = PathBuf::from(format!("boot/initrd-{}.img", kernel.as_str())); let contents = fs::read(rootfs.join(&initrd_path)) .with_context(|| format!("read generated initrd {}", initrd_path.display()))?; @@ -1,4 +1,5 @@ pub mod archive; +pub mod build; pub mod cli; pub mod compare; pub mod files; diff --git a/src/package_installer.rs b/src/package_installer.rs index b08c469..c5c6451 100644 --- a/src/package_installer.rs +++ b/src/package_installer.rs @@ -89,3 +89,9 @@ fn normalize( pub trait PackageInstaller { fn install(&self, request: &PackageRequest) -> Result<()>; } + +impl<T: PackageInstaller + ?Sized> PackageInstaller for &T { + fn install(&self, request: &PackageRequest) -> Result<()> { + (*self).install(request) + } +} |