recipe: Implement run
This commit is contained in:
Generated
+1
@@ -183,6 +183,7 @@ dependencies = [
|
||||
"allocative",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"either",
|
||||
"petgraph 0.8.3",
|
||||
"smallvec",
|
||||
"starlark",
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user