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
+4 -2
View File
@@ -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(_) => {}
+4 -1
View File
@@ -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();
+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,
}
#[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<SmallVec<[PlanKey<'a>; 8]>, PlanError> {
fn dependencies(&self, recipes: &RecipeSet) -> Result<SmallVec<[PlanKey; 8]>, 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<PlanKey<'a>>,
recipes: &'a RecipeSet<'a>,
wanted: HashSet<PlanKey>,
}
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<Vec<PlanKey<'a>>, PlanError> {
let mut stack: Vec<_> = self.wanted.iter().copied().collect();
pub fn steps(&self) -> Result<Vec<PlanKey>, 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();
+97 -26
View File
@@ -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<dyn Source>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub trait Source {}
pub struct ToolRecipe {
pub name: String,
pub sources: Vec<String>,
@@ -17,40 +24,25 @@ pub struct ToolRecipe {
pub pkgs_wanted: Vec<String>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Debug)]
pub struct PackageRecipe {
pub name: String,
pub meta: Option<Metadata>,
pub version: String,
pub revision: u32,
pub sources: Vec<String>,
pub tools_wanted: Vec<String>,
pub pkgs_wanted: Vec<String>,
}
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<String, SourceRecipe>,
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<()> {
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<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()
)
})
}