use std::{ fs, path::{Path, PathBuf}, }; use anyhow::Context; use sha2::{Digest, Sha256}; use crate::recipe::{OutputPackage, Recipe}; pub struct Layout<'a> { pub root: &'a Path, pub arch: &'a str, } impl<'a> Layout<'a> { pub fn new(root: &'a Path, arch: &'a str) -> Self { Self { root, arch } } pub fn source_cache_dir(&self) -> PathBuf { self.root.join("build/cache/sources") } pub fn source_cache_path(&self, key: &str) -> PathBuf { self.source_cache_dir().join(key) } pub fn source_workdir(&self, recipe: &Recipe) -> PathBuf { self.root.join("build/sources").join(recipe.slug()) } pub fn build_workdir(&self, recipe: &Recipe) -> PathBuf { self.root.join("build/builds").join(recipe.slug()) } pub fn host_install_dir(&self, recipe: &Recipe) -> PathBuf { self.root.join("build/host-pkgs").join(recipe.slug()) } pub fn host_install_root(&self, recipe: &Recipe) -> PathBuf { self.root.join("build/host-pkgs").join(recipe.slug()) } pub fn apk_path(&self, recipe: &Recipe, output: &OutputPackage) -> PathBuf { self.root.join("build/pkgs").join(self.arch).join(format!( "{}-{}-r{}.apk", output.name(), recipe.version(), recipe.revision() )) } pub fn source_stamp(&self, recipe: &Recipe, kind: &str) -> PathBuf { self.root .join("build/sources") .join(format!("{}.{kind}", recipe.slug())) } pub fn recipe_task_stamp(&self, recipe: &Recipe, kind: &str) -> PathBuf { self.root .join("build/tasks") .join(format!("{}.{kind}", recipe.slug())) } pub fn output_task_stamp(&self, output: &OutputPackage, kind: &str) -> PathBuf { self.root .join("build/tasks") .join(format!("{}.{kind}", output.key().replace(':', "-"))) } pub fn recipe_fingerprint(&self, recipe: &Recipe) -> anyhow::Result { let mut hasher = Sha256::new(); hasher.update(self.arch.as_bytes()); hasher.update(recipe.key().as_bytes()); hasher.update(recipe.version().as_bytes()); hasher.update(recipe.revision().to_le_bytes()); hasher.update( fs::read(recipe.path()) .with_context(|| format!("reading recipe {}", recipe.path().display()))?, ); for (name, source) in recipe.sources().entries() { hasher.update(name.unwrap_or("").as_bytes()); hasher.update(source.url().as_bytes()); hasher.update(source.cache_key().as_bytes()); } for patch in self.recipe_patches(recipe)? { hasher.update(patch.display().to_string().as_bytes()); hasher .update(fs::read(&patch).with_context(|| format!("reading {}", patch.display()))?); } Ok(hex::encode(hasher.finalize())) } pub fn output_fingerprint( &self, recipe: &Recipe, output: &OutputPackage, ) -> anyhow::Result { let mut hasher = Sha256::new(); hasher.update(self.recipe_fingerprint(recipe)?.as_bytes()); hasher.update(output.key().as_bytes()); Ok(hex::encode(hasher.finalize())) } pub fn recipe_has_patches(&self, recipe: &Recipe) -> anyhow::Result { Ok(!self.recipe_patches(recipe)?.is_empty()) } pub fn recipe_patches(&self, recipe: &Recipe) -> anyhow::Result> { let Some(data_dir) = recipe.data_dir() else { return Ok(Vec::new()); }; let patches_dir = data_dir.join("patches"); let mut out = Vec::new(); for (_, source) in recipe.sources().entries() { for name in source.patches() { out.push(patches_dir.join(name)); } } Ok(out) } }