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",
"anyhow",
"clap",
"either",
"petgraph 0.8.3",
"smallvec",
"starlark",
+1
View File
@@ -7,6 +7,7 @@ edition = "2024"
allocative = "0.3.4"
anyhow = "1.0.102"
clap = { version = "4.6.1", features = ["derive"] }
either = "1.16.0"
petgraph = "0.8.3"
smallvec = "1.15.1"
starlark = "0.13.0"
+43 -7
View File
@@ -1,11 +1,20 @@
use crate::{
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,
};
use clap::{Parser, Subcommand};
use starlark::environment::GlobalsBuilder;
use starlark::{
environment::{GlobalsBuilder, Module},
eval,
values::Value,
};
use std::{cell::Cell, path::PathBuf, sync::Arc};
#[derive(Debug, Parser)]
@@ -80,7 +89,7 @@ pub fn run() -> anyhow::Result<()> {
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);
recipes.load_recipes(
@@ -88,14 +97,41 @@ pub fn run() -> anyhow::Result<()> {
&root_path.join(config.host_recipes_dir()),
)?;
for (name, recipe) in recipes.packages.iter() {
println!("{name}: {:#?}", recipe);
}
// let wrapper = ContainerManagerWrapper(&container_manager);
// 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 {
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(())
}
+22 -11
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;
pub use podman::PodmanRuntime;
#[derive(Clone)]
pub struct Container {
id: String,
runtime: Arc<dyn ContainerRuntime>,
@@ -51,6 +56,10 @@ pub trait ContainerRuntime {
}
pub struct ContainerManager {
inner: Mutex<ContainerManagerInner>,
}
struct ContainerManagerInner {
containers: HashMap<String, Container>,
runtime: Arc<dyn ContainerRuntime>,
}
@@ -58,31 +67,33 @@ pub struct ContainerManager {
impl ContainerManager {
pub fn new(runtime: Arc<dyn ContainerRuntime>) -> Self {
Self {
inner: Mutex::new(ContainerManagerInner {
containers: HashMap::new(),
runtime,
}),
}
}
pub fn container(&mut self, name: &str) -> anyhow::Result<&Container> {
if self.containers.get(name).is_none() {
let container_id = self.runtime.start_container("alpine:edge", &[])?;
pub fn container(&self, name: &str) -> anyhow::Result<Container> {
let mut inner = self.inner.lock().unwrap();
if inner.containers.get(name).is_none() {
let container_id = inner.runtime.start_container("alpine:edge", &[])?;
crate::log!("info", "Started new container ({container_id})");
self.containers.insert(
name.into(),
Container::new(container_id, self.runtime.clone()),
);
let container = Container::new(container_id, inner.runtime.clone());
inner.containers.insert(name.into(), container);
}
Ok(self.containers.get(name).unwrap())
Ok(inner.containers.get(name).cloned().unwrap())
}
}
impl Drop for ContainerManager {
fn drop(&mut self) {
for (_, container) in self.containers.iter() {
self.runtime.stop_container(container.id());
let inner = self.inner.lock().unwrap();
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::{
any::AnyLifetime,
environment::{FrozenModule, Globals, Module},
eval::Evaluator,
syntax::{AstModule, Dialect, DialectTypes},
values::{UnpackValue, Value, type_repr::StarlarkTypeRepr},
};
use std::path::Path as StdPath;
@@ -18,6 +18,19 @@ pub use recipe::*;
#[allow(unused_imports)]
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(
path: &[&StdPath],
globals: &Globals,
@@ -25,6 +38,8 @@ pub fn eval_files(
config: Option<&Config>,
extra: Option<&dyn AnyLifetime>,
) -> anyhow::Result<Module> {
use anyhow::Context;
let module = Module::new();
if let Some(lib_module) = lib_module {
+129 -2
View File
@@ -1,10 +1,24 @@
use std::cell::Cell;
use allocative::Allocative;
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 crate::recipe::Source;
use crate::{
container::{Container, ContainerManager},
eval::{Path, UnpackCloned},
log,
recipe::Source,
};
#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)]
pub struct TarballSource {
@@ -24,6 +38,12 @@ starlark_simple_value!(TarballSource);
#[starlark_value(type = "tarball")]
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 {}
#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)]
@@ -45,6 +65,113 @@ starlark_simple_value!(Metadata);
#[starlark_value(type = "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]
pub fn recipe_globals(b: &mut GlobalsBuilder) {
fn tarball(
-3
View File
@@ -14,9 +14,6 @@ pub fn __emit(color: &str, action: &str, args: std::fmt::Arguments) {
#[macro_export]
macro_rules! log {
($action:literal) => {{
$crate::log::__emit("1;34", $action, format_args!());
}};
($action:literal, $($arg:tt)*) => {{
$crate::log::__emit("1;34", $action, format_args!($($arg)*));
}};
+61 -21
View File
@@ -1,14 +1,20 @@
use anyhow::Context;
use starlark::{
environment::{GlobalsBuilder, Module},
values::UnpackValue,
environment::{FrozenModule, GlobalsBuilder, Module},
eval,
values::{
UnpackValue,
typing::{FrozenStarlarkCallable, StarlarkCallable, StarlarkCallableParamSpec},
},
};
use std::{
collections::HashMap,
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 name: String,
@@ -33,6 +39,10 @@ pub struct PackageRecipe {
pub sources: Vec<String>,
pub tools_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> {
@@ -80,19 +90,22 @@ impl<'a> RecipeSet<'a> {
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 source: &TarballSource = get_value_required(&module, "source")?;
let configure = get_frozen_callable(&module, "configure")?;
let build = get_frozen_callable(&module, "build")?;
let install = get_frozen_callable(&module, "install")?;
self.add_source(
name,
SourceRecipe {
name: name.to_string(),
source: Box::new(source.clone()),
source: Box::new(source),
},
)?;
@@ -100,12 +113,16 @@ impl<'a> RecipeSet<'a> {
name,
PackageRecipe {
name: name.to_string(),
meta: metadata.cloned(),
version: version.to_string(),
revision: revision as u32,
meta: metadata,
version,
revision,
sources: vec![name.to_string()],
tools_wanted: vec![],
pkgs_wanted: vec![],
module,
configure,
build,
install,
},
)
}
@@ -183,34 +200,57 @@ fn get_recipe_name_and_patch(
Ok(None)
}
fn get_value_option<'v, T: UnpackValue<'v>>(
module: &'v Module,
fn get_value_option<T: UnpackCloned>(
module: &FrozenModule,
name: &str,
) -> anyhow::Result<Option<T>> {
module
.get(name)
.get_option(name)?
.map(|value| {
T::unpack_value(value).unwrap().ok_or_else(|| {
T::unpack_cloned(value.value()).ok_or_else(|| {
anyhow::anyhow!(
"`{name}` should be of type `{}` but got `{}`",
T::starlark_type_repr(),
value.get_type()
value.value().get_type()
)
})
})
.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
.get(name)
.get_option(name)?
.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!(
"`{name}` should be of type `{}` but got `{}`",
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)
}