use std::cell::Cell; use allocative::Allocative; use starlark::{ 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::{ container::{Container, ContainerManager}, eval::{Path, UnpackCloned}, log, 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 UnpackCloned for TarballSource { fn unpack_cloned(value: Value<'_>) -> Option { value.downcast_ref().cloned() } } impl Source for TarballSource {} #[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] pub struct Metadata { maintainer: Option, description: Option, license: Option, website: Option, } 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 {} impl UnpackCloned for Metadata { fn unpack_cloned(value: Value<'_>) -> Option { 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> { 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::() { 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, eval: &mut Evaluator, ) -> anyhow::Result { 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::>(); 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> { 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 { value.downcast_ref().cloned() } } #[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 { 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 { 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()), }) } }