recipe: basic recipe parsing

This commit is contained in:
2026-05-22 20:54:44 +02:00
parent a525868969
commit 1a7c817fb9
7 changed files with 238 additions and 82 deletions
+5 -5
View File
@@ -13,8 +13,8 @@ source = tarball(
def configure(ctx): def configure(ctx):
ctx.run( ctx.run(
ctx.source_dir / "configure", ctx.source_dir / "configure",
"--prefix=" + cfg.prefix, "--prefix=" + options.prefix,
"--target=" + cfg.target_triple, "--target=" + options.target_triple,
"--with-sysroot=" + ctx.sysroot_dir, "--with-sysroot=" + ctx.sysroot_dir,
"--with-pic", "--with-pic",
"--enable-cet", "--enable-cet",
@@ -31,9 +31,9 @@ def configure(ctx):
# gprofng's libcollector relies on glibc-specific internals. # gprofng's libcollector relies on glibc-specific internals.
"--disable-gprofng", "--disable-gprofng",
env = { env = {
"CFLAGS": cfg.host_cflags, "CFLAGS": options.host_cflags,
"CXXFLAGS": cfg.host_cxxflags, "CXXFLAGS": options.host_cxxflags,
"LDFLAGS": cfg.host_ldflags, "LDFLAGS": options.host_ldflags,
}) })
_, build, install = autotools() _, build, install = autotools()
+1 -1
View File
@@ -12,7 +12,7 @@ source = tarball(
def build(ctx): def build(ctx):
ctx.run("cp", "-rp", ctx.source_dir / ".", ctx.build_dir) 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") ctx.run("find", ctx.build_dir / "usr" / "include", "-type", "f", "!", "-name", "*.h", "-delete")
def install(ctx): def install(ctx):
+4 -2
View File
@@ -81,14 +81,16 @@ pub fn run() -> anyhow::Result<()> {
}; };
let mut container_manager = ContainerManager::new(container_runtime); let mut container_manager = ContainerManager::new(container_runtime);
let mut recipes = RecipeSet::default(); let mut recipes = RecipeSet::new(&config);
recipes.load_recipes( recipes.load_recipes(
&root_path.join(config.recipes_dir()), &root_path.join(config.recipes_dir()),
&root_path.join(config.host_recipes_dir()), &root_path.join(config.host_recipes_dir()),
)?; )?;
println!("{:?}", recipes); for (name, recipe) in recipes.packages.iter() {
println!("{name}: {:#?}", recipe);
}
match cli.command { match cli.command {
Command::Fetch(_) => {} Command::Fetch(_) => {}
+4 -1
View File
@@ -8,11 +8,14 @@ use starlark::{
use std::path::Path as StdPath; use std::path::Path as StdPath;
mod config; mod config;
mod recipe;
mod types; mod types;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use config::*; pub use config::*;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use recipe::*;
#[allow(unused_imports)]
pub use types::*; pub use types::*;
pub fn eval_files( pub fn eval_files(
@@ -29,7 +32,7 @@ pub fn eval_files(
} }
if let Some(config) = config { 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(); let mut paths = path.to_vec();
+75
View File
@@ -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<String>,
description: Option<String>,
license: Option<String>,
website: Option<String>,
}
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<TarballSource> {
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<Metadata> {
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()),
})
}
}
+52 -47
View File
@@ -23,23 +23,23 @@ pub enum PlanError {
CycleDetected, CycleDetected,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PlanKey<'a> { pub enum PlanKey {
SourceFetch(&'a SourceRecipe), SourceFetch(String),
SourcePatch(&'a SourceRecipe), SourcePatch(String),
SourcePrepare(&'a SourceRecipe), SourcePrepare(String),
ToolConfigure(&'a ToolRecipe), ToolConfigure(String),
ToolBuild(&'a ToolRecipe), ToolBuild(String),
ToolInstall(&'a ToolRecipe), ToolInstall(String),
PkgConfigure(&'a PackageRecipe), PkgConfigure(String),
PkgBuild(&'a PackageRecipe), PkgBuild(String),
PkgInstall(&'a PackageRecipe), PkgInstall(String),
PkgPackage(&'a PackageRecipe), PkgPackage(String),
} }
impl<'a> PlanKey<'a> { impl PlanKey {
fn weight(&self) -> i8 { fn weight(&self) -> i8 {
match self { match self {
PlanKey::SourceFetch(_) => 0, PlanKey::SourceFetch(_) => 0,
@@ -55,67 +55,72 @@ impl<'a> PlanKey<'a> {
} }
} }
fn dependencies( fn dependencies(&self, recipes: &RecipeSet) -> Result<SmallVec<[PlanKey; 8]>, PlanError> {
&self,
recipes: &'a RecipeSet,
) -> Result<SmallVec<[PlanKey<'a>; 8]>, PlanError> {
match self { match self {
PlanKey::SourceFetch(_) => Ok(smallvec![]), PlanKey::SourceFetch(_) => Ok(smallvec![]),
PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe)]), PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe.clone())]),
PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe)]), PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe.clone())]),
PlanKey::ToolConfigure(recipe) => { PlanKey::ToolConfigure(recipe) => {
let recipe = recipes
.tool(recipe)
.ok_or(PlanError::MissingTool(recipe.clone()))?;
let source_deps = recipe.sources.iter().map(|name| { let source_deps = recipe.sources.iter().map(|name| {
recipes recipes
.source(name) .source(name)
.map(PlanKey::SourcePrepare) .map(|_| PlanKey::SourcePrepare(name.to_string()))
.ok_or(PlanError::MissingSource(name.clone())) .ok_or(PlanError::MissingSource(name.to_string()))
}); });
let tool_deps = recipe.tools_wanted.iter().map(|name| { let tool_deps = recipe.tools_wanted.iter().map(|name| {
recipes recipes
.tool(name) .tool(name)
.map(PlanKey::ToolInstall) .map(|_| PlanKey::ToolInstall(name.to_string()))
.ok_or(PlanError::MissingTool(name.clone())) .ok_or(PlanError::MissingTool(name.to_string()))
}); });
let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { let pkg_deps = recipe.pkgs_wanted.iter().map(|name| {
recipes recipes
.package(name) .package(name)
.map(PlanKey::PkgPackage) .map(|_| PlanKey::PkgPackage(name.to_string()))
.ok_or(PlanError::MissingPackage(name.clone())) .ok_or(PlanError::MissingPackage(name.to_string()))
}); });
source_deps.chain(tool_deps).chain(pkg_deps).collect() source_deps.chain(tool_deps).chain(pkg_deps).collect()
} }
PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe)]), PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe.clone())]),
PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe)]), PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe.clone())]),
PlanKey::PkgConfigure(recipe) => { PlanKey::PkgConfigure(recipe) => {
let recipe = recipes
.package(recipe)
.ok_or(PlanError::MissingPackage(recipe.clone()))?;
let source_deps = recipe.sources.iter().map(|name| { let source_deps = recipe.sources.iter().map(|name| {
recipes recipes
.source(name) .source(name)
.map(PlanKey::SourcePrepare) .map(|_| PlanKey::SourcePrepare(name.to_string()))
.ok_or(PlanError::MissingSource(name.clone())) .ok_or(PlanError::MissingSource(name.to_string()))
}); });
let tool_deps = recipe.tools_wanted.iter().map(|name| { let tool_deps = recipe.tools_wanted.iter().map(|name| {
recipes recipes
.tool(name) .tool(name)
.map(PlanKey::ToolInstall) .map(|_| PlanKey::ToolInstall(name.to_string()))
.ok_or(PlanError::MissingTool(name.clone())) .ok_or(PlanError::MissingTool(name.to_string()))
}); });
let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { let pkg_deps = recipe.pkgs_wanted.iter().map(|name| {
recipes recipes
.package(name) .package(name)
.map(PlanKey::PkgPackage) .map(|_| PlanKey::PkgPackage(name.to_string()))
.ok_or(PlanError::MissingPackage(name.clone())) .ok_or(PlanError::MissingPackage(name.to_string()))
}); });
source_deps.chain(tool_deps).chain(pkg_deps).collect() source_deps.chain(tool_deps).chain(pkg_deps).collect()
} }
PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe)]), PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe.clone())]),
PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe)]), PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe.clone())]),
PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe)]), PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe.clone())]),
} }
} }
@@ -125,8 +130,8 @@ impl<'a> PlanKey<'a> {
} }
pub struct Plan<'a> { pub struct Plan<'a> {
recipes: &'a RecipeSet, recipes: &'a RecipeSet<'a>,
wanted: HashSet<PlanKey<'a>>, wanted: HashSet<PlanKey>,
} }
impl<'a> Plan<'a> { 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); self.wanted.insert(key);
} }
pub fn steps(&self) -> Result<Vec<PlanKey<'a>>, PlanError> { pub fn steps(&self) -> Result<Vec<PlanKey>, PlanError> {
let mut stack: Vec<_> = self.wanted.iter().copied().collect(); let mut stack: Vec<_> = self.wanted.iter().cloned().collect();
let mut graph: DiGraph<_, ()> = DiGraph::new(); let mut graph: DiGraph<_, ()> = DiGraph::new();
let mut nodes = HashMap::new(); let mut nodes = HashMap::new();
@@ -150,18 +155,18 @@ impl<'a> Plan<'a> {
let node_idx = match nodes.get(&node) { let node_idx = match nodes.get(&node) {
Some(&idx) => idx, Some(&idx) => idx,
None => { None => {
let idx = graph.add_node(node); let idx = graph.add_node(node.clone());
nodes.insert(node, idx); nodes.insert(node.clone(), idx);
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) { let dep_idx = match nodes.get(&dep) {
Some(&idx) => idx, Some(&idx) => idx,
None => { None => {
let idx = graph.add_node(dep); let idx = graph.add_node(dep.clone());
nodes.insert(dep, idx); nodes.insert(dep.clone(), idx);
stack.push(dep); stack.push(dep);
idx idx
} }
@@ -195,7 +200,7 @@ impl<'a> Plan<'a> {
let mut result = Vec::with_capacity(graph.node_count()); let mut result = Vec::with_capacity(graph.node_count());
while let Some((_, idx)) = heap.pop() { while let Some((_, idx)) = heap.pop() {
result.push(graph[idx]); result.push(graph[idx].clone());
for neighbor in graph.neighbors_directed(idx, Direction::Outgoing) { for neighbor in graph.neighbors_directed(idx, Direction::Outgoing) {
let d = in_degree.get_mut(&neighbor).unwrap(); let d = in_degree.get_mut(&neighbor).unwrap();
+97 -26
View File
@@ -1,15 +1,22 @@
use anyhow::Context; use anyhow::Context;
use starlark::{
environment::{GlobalsBuilder, Module},
values::UnpackValue,
};
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
#[derive(Clone, PartialEq, Eq, Hash)] use crate::eval::{Config, Metadata, TarballSource, eval_files, recipe_globals, types_globals};
pub struct SourceRecipe { pub struct SourceRecipe {
pub name: String, pub name: String,
pub source: Box<dyn Source>,
} }
#[derive(Clone, PartialEq, Eq, Hash)] pub trait Source {}
pub struct ToolRecipe { pub struct ToolRecipe {
pub name: String, pub name: String,
pub sources: Vec<String>, pub sources: Vec<String>,
@@ -17,40 +24,25 @@ pub struct ToolRecipe {
pub pkgs_wanted: Vec<String>, pub pkgs_wanted: Vec<String>,
} }
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Debug)]
pub struct PackageRecipe { pub struct PackageRecipe {
pub name: String, pub name: String,
pub meta: Option<Metadata>,
pub version: String,
pub revision: u32,
pub sources: Vec<String>, pub sources: Vec<String>,
pub tools_wanted: Vec<String>, pub tools_wanted: Vec<String>,
pub pkgs_wanted: Vec<String>, pub pkgs_wanted: Vec<String>,
} }
impl std::fmt::Debug for SourceRecipe { pub struct RecipeSet<'a> {
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 {
sources: HashMap<String, SourceRecipe>, sources: HashMap<String, SourceRecipe>,
tools: HashMap<String, ToolRecipe>, tools: HashMap<String, ToolRecipe>,
packages: HashMap<String, PackageRecipe>, pub packages: HashMap<String, PackageRecipe>,
config: &'a Config,
} }
impl RecipeSet { impl<'a> RecipeSet<'a> {
fn add_source(&mut self, name: &str, recipe: SourceRecipe) -> anyhow::Result<()> { fn add_source(&mut self, name: &str, recipe: SourceRecipe) -> anyhow::Result<()> {
if self.sources.insert(name.to_string(), recipe).is_some() { if self.sources.insert(name.to_string(), recipe).is_some() {
anyhow::bail!("source '{name}' already exists"); anyhow::bail!("source '{name}' already exists");
@@ -77,7 +69,54 @@ impl RecipeSet {
} }
fn load_package_recipe(&mut self, name: &str, path: &Path) -> anyhow::Result<()> { 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( pub fn load_recipes(
@@ -143,3 +182,35 @@ fn get_recipe_name_and_patch(
Ok(None) Ok(None)
} }
fn get_value_option<'v, T: UnpackValue<'v>>(
module: &'v Module,
name: &str,
) -> anyhow::Result<Option<T>> {
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<T> {
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()
)
})
}