recipe: Implement run

This commit is contained in:
2026-05-23 00:10:08 +02:00
parent 1a7c817fb9
commit 2e6704516a
8 changed files with 275 additions and 47 deletions
Generated
+1
View File
@@ -183,6 +183,7 @@ dependencies = [
"allocative", "allocative",
"anyhow", "anyhow",
"clap", "clap",
"either",
"petgraph 0.8.3", "petgraph 0.8.3",
"smallvec", "smallvec",
"starlark", "starlark",
+1
View File
@@ -7,6 +7,7 @@ edition = "2024"
allocative = "0.3.4" allocative = "0.3.4"
anyhow = "1.0.102" anyhow = "1.0.102"
clap = { version = "4.6.1", features = ["derive"] } clap = { version = "4.6.1", features = ["derive"] }
either = "1.16.0"
petgraph = "0.8.3" petgraph = "0.8.3"
smallvec = "1.15.1" smallvec = "1.15.1"
starlark = "0.13.0" starlark = "0.13.0"
+43 -7
View File
@@ -1,11 +1,20 @@
use crate::{ use crate::{
container::{ContainerManager, PodmanRuntime}, container::{ContainerManager, PodmanRuntime},
eval::{Config, ContainerConfig, config_globals, eval_files, types_globals}, eval::{
Config, ContainerConfig, ContainerManagerWrapper, Context, Path, config_globals,
eval_files, types_globals,
},
log,
plan::{Plan, PlanKey},
recipe::RecipeSet, recipe::RecipeSet,
}; };
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use starlark::environment::GlobalsBuilder; use starlark::{
environment::{GlobalsBuilder, Module},
eval,
values::Value,
};
use std::{cell::Cell, path::PathBuf, sync::Arc}; use std::{cell::Cell, path::PathBuf, sync::Arc};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@@ -80,7 +89,7 @@ pub fn run() -> anyhow::Result<()> {
ContainerConfig::Podman(_) => Arc::new(PodmanRuntime::new()?), ContainerConfig::Podman(_) => Arc::new(PodmanRuntime::new()?),
}; };
let mut container_manager = ContainerManager::new(container_runtime); let container_manager = ContainerManager::new(container_runtime);
let mut recipes = RecipeSet::new(&config); let mut recipes = RecipeSet::new(&config);
recipes.load_recipes( recipes.load_recipes(
@@ -88,14 +97,41 @@ pub fn run() -> anyhow::Result<()> {
&root_path.join(config.host_recipes_dir()), &root_path.join(config.host_recipes_dir()),
)?; )?;
for (name, recipe) in recipes.packages.iter() { // let wrapper = ContainerManagerWrapper(&container_manager);
println!("{name}: {:#?}", recipe); // for (name, recipe) in recipes.packages.iter() {
} // println!("{name}: {:#?}", recipe);
// let mo = Module::new();
// let mut eval = eval::Evaluator::new(&mo);
// eval.extra = Some(&wrapper);
// eval.eval_function(
// recipe.build.unwrap().0.to_value(),
// &[mo.heap().alloc(Context {
// source_dir: Path::new("/source"),
// build_dir: Path::new("/build"),
// jobs: 4,
// })],
// &[],
// )
// .unwrap();
// }
let mut plan = Plan::new(&recipes);
match cli.command { match cli.command {
Command::Fetch(_) => {} Command::Fetch(_) => {}
Command::Build(_) => {} Command::Build(cmd) => {
for recipe in cmd.recipes.iter() {
plan.add_wanted(if let Some(recipe) = recipe.strip_prefix("host:") {
PlanKey::ToolInstall(recipe.to_string())
} else {
PlanKey::PkgPackage(recipe.clone())
});
}
}
} }
log!("plan", "{:#?}", plan.steps());
Ok(()) Ok(())
} }
+24 -13
View File
@@ -1,9 +1,14 @@
use std::{collections::HashMap, path::Path, sync::Arc}; use std::{
collections::HashMap,
path::Path,
sync::{Arc, Mutex},
};
mod podman; mod podman;
pub use podman::PodmanRuntime; pub use podman::PodmanRuntime;
#[derive(Clone)]
pub struct Container { pub struct Container {
id: String, id: String,
runtime: Arc<dyn ContainerRuntime>, runtime: Arc<dyn ContainerRuntime>,
@@ -51,6 +56,10 @@ pub trait ContainerRuntime {
} }
pub struct ContainerManager { pub struct ContainerManager {
inner: Mutex<ContainerManagerInner>,
}
struct ContainerManagerInner {
containers: HashMap<String, Container>, containers: HashMap<String, Container>,
runtime: Arc<dyn ContainerRuntime>, runtime: Arc<dyn ContainerRuntime>,
} }
@@ -58,31 +67,33 @@ pub struct ContainerManager {
impl ContainerManager { impl ContainerManager {
pub fn new(runtime: Arc<dyn ContainerRuntime>) -> Self { pub fn new(runtime: Arc<dyn ContainerRuntime>) -> Self {
Self { Self {
containers: HashMap::new(), inner: Mutex::new(ContainerManagerInner {
runtime, containers: HashMap::new(),
runtime,
}),
} }
} }
pub fn container(&mut self, name: &str) -> anyhow::Result<&Container> { pub fn container(&self, name: &str) -> anyhow::Result<Container> {
if self.containers.get(name).is_none() { let mut inner = self.inner.lock().unwrap();
let container_id = self.runtime.start_container("alpine:edge", &[])?; if inner.containers.get(name).is_none() {
let container_id = inner.runtime.start_container("alpine:edge", &[])?;
crate::log!("info", "Started new container ({container_id})"); crate::log!("info", "Started new container ({container_id})");
self.containers.insert( let container = Container::new(container_id, inner.runtime.clone());
name.into(), inner.containers.insert(name.into(), container);
Container::new(container_id, self.runtime.clone()),
);
} }
Ok(self.containers.get(name).unwrap()) Ok(inner.containers.get(name).cloned().unwrap())
} }
} }
impl Drop for ContainerManager { impl Drop for ContainerManager {
fn drop(&mut self) { fn drop(&mut self) {
for (_, container) in self.containers.iter() { let inner = self.inner.lock().unwrap();
self.runtime.stop_container(container.id()); for (_, container) in inner.containers.iter() {
inner.runtime.stop_container(container.id());
} }
} }
} }
+16 -1
View File
@@ -1,9 +1,9 @@
use anyhow::Context;
use starlark::{ use starlark::{
any::AnyLifetime, any::AnyLifetime,
environment::{FrozenModule, Globals, Module}, environment::{FrozenModule, Globals, Module},
eval::Evaluator, eval::Evaluator,
syntax::{AstModule, Dialect, DialectTypes}, syntax::{AstModule, Dialect, DialectTypes},
values::{UnpackValue, Value, type_repr::StarlarkTypeRepr},
}; };
use std::path::Path as StdPath; use std::path::Path as StdPath;
@@ -18,6 +18,19 @@ pub use recipe::*;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use types::*; pub use types::*;
pub trait UnpackCloned: Sized + StarlarkTypeRepr {
fn unpack_cloned(value: Value<'_>) -> Option<Self>;
}
impl<T> UnpackCloned for T
where
for<'v> T: UnpackValue<'v>,
{
fn unpack_cloned(value: Value<'_>) -> Option<Self> {
T::unpack_value(value).unwrap()
}
}
pub fn eval_files( pub fn eval_files(
path: &[&StdPath], path: &[&StdPath],
globals: &Globals, globals: &Globals,
@@ -25,6 +38,8 @@ pub fn eval_files(
config: Option<&Config>, config: Option<&Config>,
extra: Option<&dyn AnyLifetime>, extra: Option<&dyn AnyLifetime>,
) -> anyhow::Result<Module> { ) -> anyhow::Result<Module> {
use anyhow::Context;
let module = Module::new(); let module = Module::new();
if let Some(lib_module) = lib_module { if let Some(lib_module) = lib_module {
+129 -2
View File
@@ -1,10 +1,24 @@
use std::cell::Cell;
use allocative::Allocative; use allocative::Allocative;
use starlark::{ use starlark::{
environment::GlobalsBuilder, starlark_module, starlark_simple_value, values::StarlarkValue, environment::{GlobalsBuilder, Methods, MethodsBuilder, MethodsStatic},
eval::Evaluator,
starlark_module, starlark_simple_value,
typing::Ty,
values::{
Heap, StarlarkValue, UnpackValue, Value, ValueLike, none::NoneType, tuple::UnpackTuple,
type_repr::StarlarkTypeRepr,
},
}; };
use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value}; use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value};
use crate::recipe::Source; use crate::{
container::{Container, ContainerManager},
eval::{Path, UnpackCloned},
log,
recipe::Source,
};
#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] #[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)]
pub struct TarballSource { pub struct TarballSource {
@@ -24,6 +38,12 @@ starlark_simple_value!(TarballSource);
#[starlark_value(type = "tarball")] #[starlark_value(type = "tarball")]
impl<'v> StarlarkValue<'v> for TarballSource {} impl<'v> StarlarkValue<'v> for TarballSource {}
impl UnpackCloned for TarballSource {
fn unpack_cloned(value: Value<'_>) -> Option<Self> {
value.downcast_ref().cloned()
}
}
impl Source for TarballSource {} impl Source for TarballSource {}
#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] #[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)]
@@ -45,6 +65,113 @@ starlark_simple_value!(Metadata);
#[starlark_value(type = "metadata")] #[starlark_value(type = "metadata")]
impl<'v> StarlarkValue<'v> for Metadata {} impl<'v> StarlarkValue<'v> for Metadata {}
impl UnpackCloned for Metadata {
fn unpack_cloned(value: Value<'_>) -> Option<Self> {
value.downcast_ref().cloned()
}
}
#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)]
pub struct Context {
pub source_dir: Path,
pub build_dir: Path,
pub jobs: i32,
}
impl std::fmt::Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "context")
}
}
starlark_simple_value!(Context);
#[derive(Debug)]
struct RunArg(pub String);
impl UnpackValue<'_> for RunArg {
type Error = anyhow::Error;
fn unpack_value_impl(value: Value) -> anyhow::Result<Option<Self>> {
Ok(if let Some(str) = value.unpack_str() {
Some(RunArg(str.to_owned()))
} else if let Some(int) = value.unpack_i32() {
Some(RunArg(int.to_string()))
} else if let Some(path) = value.downcast_ref::<Path>() {
Some(RunArg(path.path().to_str().unwrap_or("").to_string()))
} else {
None
})
}
}
impl StarlarkTypeRepr for RunArg {
type Canonical = Self;
fn starlark_type_repr() -> starlark::typing::Ty {
Ty::string()
}
}
#[derive(ProvidesStaticType)]
pub struct ContainerManagerWrapper<'a>(pub &'a ContainerManager);
#[starlark_module]
fn context_methods(b: &mut MethodsBuilder) {
fn run(
#[starlark(this)] this: &Context,
#[starlark(args)] args: UnpackTuple<RunArg>,
eval: &mut Evaluator,
) -> anyhow::Result<NoneType> {
let ContainerManagerWrapper(container_manager) = eval
.extra
.and_then(|extra| extra.downcast_ref())
.ok_or_else(|| anyhow::anyhow!("`config` called outside of config.star"))?;
let argv = args.items.iter().map(|x| x.0.as_str()).collect::<Vec<_>>();
log!("run", "Running command: {argv:?}");
container_manager
.container("changeme")? // TODO
.exec(argv, [], std::path::Path::new("/"))?;
Ok(NoneType)
}
}
#[starlark_value(type = "context")]
impl<'v> StarlarkValue<'v> for Context {
fn get_methods() -> Option<&'static Methods> {
static RES: MethodsStatic = MethodsStatic::new();
RES.methods(context_methods)
}
fn has_attr(&self, attr: &str, _heap: &Heap) -> bool {
match attr {
"source_dir" => true,
"build_dir" => true,
"jobs" => true,
_ => false,
}
}
fn get_attr(&self, attr: &str, heap: &'v Heap) -> Option<Value<'v>> {
match attr {
"source_dir" => Some(heap.alloc(self.source_dir.clone())),
"build_dir" => Some(heap.alloc(self.build_dir.clone())),
"jobs" => Some(heap.alloc(self.jobs)),
_ => None,
}
}
}
impl UnpackCloned for Context {
fn unpack_cloned(value: Value<'_>) -> Option<Self> {
value.downcast_ref().cloned()
}
}
#[starlark_module] #[starlark_module]
pub fn recipe_globals(b: &mut GlobalsBuilder) { pub fn recipe_globals(b: &mut GlobalsBuilder) {
fn tarball( fn tarball(
-3
View File
@@ -14,9 +14,6 @@ pub fn __emit(color: &str, action: &str, args: std::fmt::Arguments) {
#[macro_export] #[macro_export]
macro_rules! log { macro_rules! log {
($action:literal) => {{
$crate::log::__emit("1;34", $action, format_args!());
}};
($action:literal, $($arg:tt)*) => {{ ($action:literal, $($arg:tt)*) => {{
$crate::log::__emit("1;34", $action, format_args!($($arg)*)); $crate::log::__emit("1;34", $action, format_args!($($arg)*));
}}; }};
+61 -21
View File
@@ -1,14 +1,20 @@
use anyhow::Context; use anyhow::Context;
use starlark::{ use starlark::{
environment::{GlobalsBuilder, Module}, environment::{FrozenModule, GlobalsBuilder, Module},
values::UnpackValue, eval,
values::{
UnpackValue,
typing::{FrozenStarlarkCallable, StarlarkCallable, StarlarkCallableParamSpec},
},
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use crate::eval::{Config, Metadata, TarballSource, eval_files, recipe_globals, types_globals}; use crate::eval::{
Config, Metadata, TarballSource, UnpackCloned, eval_files, recipe_globals, types_globals,
};
pub struct SourceRecipe { pub struct SourceRecipe {
pub name: String, pub name: String,
@@ -33,6 +39,10 @@ pub struct PackageRecipe {
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>,
pub module: FrozenModule,
pub configure: Option<FrozenStarlarkCallable>,
pub build: Option<FrozenStarlarkCallable>,
pub install: Option<FrozenStarlarkCallable>,
} }
pub struct RecipeSet<'a> { pub struct RecipeSet<'a> {
@@ -80,19 +90,22 @@ impl<'a> RecipeSet<'a> {
None, None,
)?; )?;
let version: String = get_value_required(&module, "version")?; let module = module.freeze().map_err(|err| anyhow::anyhow!("{err:?}"))?;
let revision = get_value_option(&module, "revision")?.unwrap_or(1); let version: String = get_value(&module, "version")?;
let revision: u32 = get_value_option(&module, "revision")?.unwrap_or(1);
let metadata: Option<Metadata> = get_value_option(&module, "metadata")?;
let source: TarballSource = get_value(&module, "source")?;
let metadata: Option<&Metadata> = get_value_option(&module, "metadata")?; let configure = get_frozen_callable(&module, "configure")?;
let build = get_frozen_callable(&module, "build")?;
let source: &TarballSource = get_value_required(&module, "source")?; let install = get_frozen_callable(&module, "install")?;
self.add_source( self.add_source(
name, name,
SourceRecipe { SourceRecipe {
name: name.to_string(), name: name.to_string(),
source: Box::new(source.clone()), source: Box::new(source),
}, },
)?; )?;
@@ -100,12 +113,16 @@ impl<'a> RecipeSet<'a> {
name, name,
PackageRecipe { PackageRecipe {
name: name.to_string(), name: name.to_string(),
meta: metadata.cloned(), meta: metadata,
version: version.to_string(), version,
revision: revision as u32, revision,
sources: vec![name.to_string()], sources: vec![name.to_string()],
tools_wanted: vec![], tools_wanted: vec![],
pkgs_wanted: vec![], pkgs_wanted: vec![],
module,
configure,
build,
install,
}, },
) )
} }
@@ -183,34 +200,57 @@ fn get_recipe_name_and_patch(
Ok(None) Ok(None)
} }
fn get_value_option<'v, T: UnpackValue<'v>>( fn get_value_option<T: UnpackCloned>(
module: &'v Module, module: &FrozenModule,
name: &str, name: &str,
) -> anyhow::Result<Option<T>> { ) -> anyhow::Result<Option<T>> {
module module
.get(name) .get_option(name)?
.map(|value| { .map(|value| {
T::unpack_value(value).unwrap().ok_or_else(|| { T::unpack_cloned(value.value()).ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"`{name}` should be of type `{}` but got `{}`", "`{name}` should be of type `{}` but got `{}`",
T::starlark_type_repr(), T::starlark_type_repr(),
value.get_type() value.value().get_type()
) )
}) })
}) })
.transpose() .transpose()
} }
fn get_value_required<'v, T: UnpackValue<'v>>(module: &'v Module, name: &str) -> anyhow::Result<T> { fn get_value<T: UnpackCloned>(module: &FrozenModule, name: &str) -> anyhow::Result<T> {
let value = module let value = module
.get(name) .get_option(name)?
.ok_or_else(|| anyhow::anyhow!("`{name}` is required"))?; .ok_or_else(|| anyhow::anyhow!("`{name}` is required"))?;
T::unpack_value(value).unwrap().ok_or_else(|| { T::unpack_cloned(value.value()).ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"`{name}` should be of type `{}` but got `{}`", "`{name}` should be of type `{}` but got `{}`",
T::starlark_type_repr(), T::starlark_type_repr(),
value.get_type() value.value().get_type()
) )
}) })
} }
fn get_frozen_callable<P: StarlarkCallableParamSpec>(
module: &FrozenModule,
name: &str,
) -> anyhow::Result<Option<FrozenStarlarkCallable<P>>> {
let Some(value) = module.get_option(name)? else {
return Ok(None);
};
let callable = StarlarkCallable::unpack_value(value.value())
.map_err(|err| anyhow::anyhow!("{err}"))?
.ok_or_else(|| {
anyhow::anyhow!(
"`{name}` should be callable but got `{}`",
value.value().get_type()
)
})?;
callable
.unpack_frozen()
.ok_or_else(|| anyhow::anyhow!("`{name}` was callable but not frozen"))
.map(Some)
}