diff --git a/host-recipes/binutils.star b/host-recipes/binutils.star index f9a2d2c..d1a5c7f 100644 --- a/host-recipes/binutils.star +++ b/host-recipes/binutils.star @@ -13,8 +13,8 @@ source = tarball( def configure(ctx): ctx.run( ctx.source_dir / "configure", - "--prefix=" + cfg.prefix, - "--target=" + cfg.target_triple, + "--prefix=" + options.prefix, + "--target=" + options.target_triple, "--with-sysroot=" + ctx.sysroot_dir, "--with-pic", "--enable-cet", @@ -31,9 +31,9 @@ def configure(ctx): # gprofng's libcollector relies on glibc-specific internals. "--disable-gprofng", env = { - "CFLAGS": cfg.host_cflags, - "CXXFLAGS": cfg.host_cxxflags, - "LDFLAGS": cfg.host_ldflags, + "CFLAGS": options.host_cflags, + "CXXFLAGS": options.host_cxxflags, + "LDFLAGS": options.host_ldflags, }) _, build, install = autotools() diff --git a/recipes/linux-headers.star b/recipes/linux-headers.star index 9bac4d5..5ec785f 100644 --- a/recipes/linux-headers.star +++ b/recipes/linux-headers.star @@ -12,7 +12,7 @@ source = tarball( def build(ctx): ctx.run("cp", "-rp", ctx.source_dir / ".", ctx.build_dir) - ctx.run("make", "headers_install", "ARCH=" + cfg.target_arch) + ctx.run("make", "headers_install", "ARCH=" + options.target_arch) ctx.run("find", ctx.build_dir / "usr" / "include", "-type", "f", "!", "-name", "*.h", "-delete") def install(ctx): diff --git a/src/cli.rs b/src/cli.rs index 91ee681..8c50b34 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -81,14 +81,16 @@ pub fn run() -> anyhow::Result<()> { }; let mut container_manager = ContainerManager::new(container_runtime); - let mut recipes = RecipeSet::default(); + let mut recipes = RecipeSet::new(&config); recipes.load_recipes( &root_path.join(config.recipes_dir()), &root_path.join(config.host_recipes_dir()), )?; - println!("{:?}", recipes); + for (name, recipe) in recipes.packages.iter() { + println!("{name}: {:#?}", recipe); + } match cli.command { Command::Fetch(_) => {} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 351e238..601bec6 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -8,11 +8,14 @@ use starlark::{ use std::path::Path as StdPath; mod config; +mod recipe; mod types; #[allow(unused_imports)] pub use config::*; #[allow(unused_imports)] +pub use recipe::*; +#[allow(unused_imports)] pub use types::*; pub fn eval_files( @@ -29,7 +32,7 @@ pub fn eval_files( } if let Some(config) = config { - module.set("cfg", module.heap().alloc(config.clone())); + module.set("options", module.heap().alloc(config.clone())); } let mut paths = path.to_vec(); diff --git a/src/eval/recipe.rs b/src/eval/recipe.rs new file mode 100644 index 0000000..da0f004 --- /dev/null +++ b/src/eval/recipe.rs @@ -0,0 +1,75 @@ +use allocative::Allocative; +use starlark::{ + environment::GlobalsBuilder, starlark_module, starlark_simple_value, values::StarlarkValue, +}; +use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value}; + +use crate::recipe::Source; + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub struct TarballSource { + url: String, + sha256: String, + strip_components: u32, +} + +impl std::fmt::Display for TarballSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "tarball") + } +} + +starlark_simple_value!(TarballSource); + +#[starlark_value(type = "tarball")] +impl<'v> StarlarkValue<'v> for TarballSource {} + +impl Source for TarballSource {} + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub struct Metadata { + maintainer: Option, + description: Option, + license: Option, + website: Option, +} + +impl std::fmt::Display for Metadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "metadata") + } +} + +starlark_simple_value!(Metadata); + +#[starlark_value(type = "metadata")] +impl<'v> StarlarkValue<'v> for Metadata {} + +#[starlark_module] +pub fn recipe_globals(b: &mut GlobalsBuilder) { + fn tarball( + #[starlark(require = named)] url: &str, + #[starlark(require = named)] sha256: &str, + #[starlark(require = named, default = 0)] strip_components: u32, + ) -> anyhow::Result { + Ok(TarballSource { + url: url.to_string(), + sha256: sha256.to_string(), + strip_components, + }) + } + + fn meta( + #[starlark(require = named)] maintainer: Option<&str>, + #[starlark(require = named)] description: Option<&str>, + #[starlark(require = named)] license: Option<&str>, + #[starlark(require = named)] website: Option<&str>, + ) -> anyhow::Result { + Ok(Metadata { + maintainer: maintainer.map(|x| x.to_string()), + description: description.map(|x| x.to_string()), + license: license.map(|x| x.to_string()), + website: website.map(|x| x.to_string()), + }) + } +} diff --git a/src/plan.rs b/src/plan.rs index 6ee0620..2cf2a38 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -23,23 +23,23 @@ pub enum PlanError { CycleDetected, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum PlanKey<'a> { - SourceFetch(&'a SourceRecipe), - SourcePatch(&'a SourceRecipe), - SourcePrepare(&'a SourceRecipe), +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PlanKey { + SourceFetch(String), + SourcePatch(String), + SourcePrepare(String), - ToolConfigure(&'a ToolRecipe), - ToolBuild(&'a ToolRecipe), - ToolInstall(&'a ToolRecipe), + ToolConfigure(String), + ToolBuild(String), + ToolInstall(String), - PkgConfigure(&'a PackageRecipe), - PkgBuild(&'a PackageRecipe), - PkgInstall(&'a PackageRecipe), - PkgPackage(&'a PackageRecipe), + PkgConfigure(String), + PkgBuild(String), + PkgInstall(String), + PkgPackage(String), } -impl<'a> PlanKey<'a> { +impl PlanKey { fn weight(&self) -> i8 { match self { PlanKey::SourceFetch(_) => 0, @@ -55,67 +55,72 @@ impl<'a> PlanKey<'a> { } } - fn dependencies( - &self, - recipes: &'a RecipeSet, - ) -> Result; 8]>, PlanError> { + fn dependencies(&self, recipes: &RecipeSet) -> Result, PlanError> { match self { PlanKey::SourceFetch(_) => Ok(smallvec![]), - PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe)]), - PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe)]), + PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe.clone())]), + PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe.clone())]), PlanKey::ToolConfigure(recipe) => { + let recipe = recipes + .tool(recipe) + .ok_or(PlanError::MissingTool(recipe.clone()))?; + let source_deps = recipe.sources.iter().map(|name| { recipes .source(name) - .map(PlanKey::SourcePrepare) - .ok_or(PlanError::MissingSource(name.clone())) + .map(|_| PlanKey::SourcePrepare(name.to_string())) + .ok_or(PlanError::MissingSource(name.to_string())) }); let tool_deps = recipe.tools_wanted.iter().map(|name| { recipes .tool(name) - .map(PlanKey::ToolInstall) - .ok_or(PlanError::MissingTool(name.clone())) + .map(|_| PlanKey::ToolInstall(name.to_string())) + .ok_or(PlanError::MissingTool(name.to_string())) }); let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { recipes .package(name) - .map(PlanKey::PkgPackage) - .ok_or(PlanError::MissingPackage(name.clone())) + .map(|_| PlanKey::PkgPackage(name.to_string())) + .ok_or(PlanError::MissingPackage(name.to_string())) }); source_deps.chain(tool_deps).chain(pkg_deps).collect() } - PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe)]), - PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe)]), + PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe.clone())]), + PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe.clone())]), PlanKey::PkgConfigure(recipe) => { + let recipe = recipes + .package(recipe) + .ok_or(PlanError::MissingPackage(recipe.clone()))?; + let source_deps = recipe.sources.iter().map(|name| { recipes .source(name) - .map(PlanKey::SourcePrepare) - .ok_or(PlanError::MissingSource(name.clone())) + .map(|_| PlanKey::SourcePrepare(name.to_string())) + .ok_or(PlanError::MissingSource(name.to_string())) }); let tool_deps = recipe.tools_wanted.iter().map(|name| { recipes .tool(name) - .map(PlanKey::ToolInstall) - .ok_or(PlanError::MissingTool(name.clone())) + .map(|_| PlanKey::ToolInstall(name.to_string())) + .ok_or(PlanError::MissingTool(name.to_string())) }); let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { recipes .package(name) - .map(PlanKey::PkgPackage) - .ok_or(PlanError::MissingPackage(name.clone())) + .map(|_| PlanKey::PkgPackage(name.to_string())) + .ok_or(PlanError::MissingPackage(name.to_string())) }); source_deps.chain(tool_deps).chain(pkg_deps).collect() } - PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe)]), - PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe)]), - PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe)]), + PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe.clone())]), + PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe.clone())]), + PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe.clone())]), } } @@ -125,8 +130,8 @@ impl<'a> PlanKey<'a> { } pub struct Plan<'a> { - recipes: &'a RecipeSet, - wanted: HashSet>, + recipes: &'a RecipeSet<'a>, + wanted: HashSet, } impl<'a> Plan<'a> { @@ -137,12 +142,12 @@ impl<'a> Plan<'a> { } } - pub fn add_wanted(&mut self, key: PlanKey<'a>) { + pub fn add_wanted(&mut self, key: PlanKey) { self.wanted.insert(key); } - pub fn steps(&self) -> Result>, PlanError> { - let mut stack: Vec<_> = self.wanted.iter().copied().collect(); + pub fn steps(&self) -> Result, PlanError> { + let mut stack: Vec<_> = self.wanted.iter().cloned().collect(); let mut graph: DiGraph<_, ()> = DiGraph::new(); let mut nodes = HashMap::new(); @@ -150,18 +155,18 @@ impl<'a> Plan<'a> { let node_idx = match nodes.get(&node) { Some(&idx) => idx, None => { - let idx = graph.add_node(node); - nodes.insert(node, idx); + let idx = graph.add_node(node.clone()); + nodes.insert(node.clone(), idx); idx } }; - for dep in node.dependencies(self.recipes)?.iter().copied() { + for dep in node.dependencies(self.recipes)? { let dep_idx = match nodes.get(&dep) { Some(&idx) => idx, None => { - let idx = graph.add_node(dep); - nodes.insert(dep, idx); + let idx = graph.add_node(dep.clone()); + nodes.insert(dep.clone(), idx); stack.push(dep); idx } @@ -195,7 +200,7 @@ impl<'a> Plan<'a> { let mut result = Vec::with_capacity(graph.node_count()); while let Some((_, idx)) = heap.pop() { - result.push(graph[idx]); + result.push(graph[idx].clone()); for neighbor in graph.neighbors_directed(idx, Direction::Outgoing) { let d = in_degree.get_mut(&neighbor).unwrap(); diff --git a/src/recipe.rs b/src/recipe.rs index 2e463c9..439662b 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -1,15 +1,22 @@ use anyhow::Context; +use starlark::{ + environment::{GlobalsBuilder, Module}, + values::UnpackValue, +}; use std::{ collections::HashMap, path::{Path, PathBuf}, }; -#[derive(Clone, PartialEq, Eq, Hash)] +use crate::eval::{Config, Metadata, TarballSource, eval_files, recipe_globals, types_globals}; + pub struct SourceRecipe { pub name: String, + pub source: Box, } -#[derive(Clone, PartialEq, Eq, Hash)] +pub trait Source {} + pub struct ToolRecipe { pub name: String, pub sources: Vec, @@ -17,40 +24,25 @@ pub struct ToolRecipe { pub pkgs_wanted: Vec, } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct PackageRecipe { pub name: String, + pub meta: Option, + pub version: String, + pub revision: u32, pub sources: Vec, pub tools_wanted: Vec, pub pkgs_wanted: Vec, } -impl std::fmt::Debug for SourceRecipe { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "source:{}", self.name) - } -} - -impl std::fmt::Debug for ToolRecipe { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "tool:{}", self.name) - } -} - -impl std::fmt::Debug for PackageRecipe { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "package:{}", self.name) - } -} - -#[derive(Default, Debug)] -pub struct RecipeSet { +pub struct RecipeSet<'a> { sources: HashMap, tools: HashMap, - packages: HashMap, + pub packages: HashMap, + config: &'a Config, } -impl RecipeSet { +impl<'a> RecipeSet<'a> { fn add_source(&mut self, name: &str, recipe: SourceRecipe) -> anyhow::Result<()> { if self.sources.insert(name.to_string(), recipe).is_some() { anyhow::bail!("source '{name}' already exists"); @@ -77,7 +69,54 @@ impl RecipeSet { } fn load_package_recipe(&mut self, name: &str, path: &Path) -> anyhow::Result<()> { - Ok(()) + let module = eval_files( + &[path], + &GlobalsBuilder::standard() + .with(types_globals) + .with(recipe_globals) + .build(), + None, + Some(self.config), + None, + )?; + + let version: String = get_value_required(&module, "version")?; + + let revision = get_value_option(&module, "revision")?.unwrap_or(1); + + let metadata: Option<&Metadata> = get_value_option(&module, "metadata")?; + + let source: &TarballSource = get_value_required(&module, "source")?; + + self.add_source( + name, + SourceRecipe { + name: name.to_string(), + source: Box::new(source.clone()), + }, + )?; + + self.add_package( + name, + PackageRecipe { + name: name.to_string(), + meta: metadata.cloned(), + version: version.to_string(), + revision: revision as u32, + sources: vec![name.to_string()], + tools_wanted: vec![], + pkgs_wanted: vec![], + }, + ) + } + + pub fn new(config: &'a Config) -> Self { + Self { + sources: HashMap::new(), + tools: HashMap::new(), + packages: HashMap::new(), + config, + } } pub fn load_recipes( @@ -143,3 +182,35 @@ fn get_recipe_name_and_patch( Ok(None) } + +fn get_value_option<'v, T: UnpackValue<'v>>( + module: &'v Module, + name: &str, +) -> anyhow::Result> { + module + .get(name) + .map(|value| { + T::unpack_value(value).unwrap().ok_or_else(|| { + anyhow::anyhow!( + "`{name}` should be of type `{}` but got `{}`", + T::starlark_type_repr(), + value.get_type() + ) + }) + }) + .transpose() +} + +fn get_value_required<'v, T: UnpackValue<'v>>(module: &'v Module, name: &str) -> anyhow::Result { + let value = module + .get(name) + .ok_or_else(|| anyhow::anyhow!("`{name}` is required"))?; + + T::unpack_value(value).unwrap().ok_or_else(|| { + anyhow::anyhow!( + "`{name}` should be of type `{}` but got `{}`", + T::starlark_type_repr(), + value.get_type() + ) + }) +}