Package dependencies

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2024-12-02 12:21:29 +01:00
parent 631c3a19e5
commit 00e93ce612
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
8 changed files with 320 additions and 148 deletions

View File

@ -84,7 +84,7 @@ pub fn repo_impl(attrs: TokenStream, input: TokenStream) -> TokenStream {
/// Create the methods that will return the builders and packages /// Create the methods that will return the builders and packages
/// Usage: /// Usage:
/// ``` /// ```
/// #[::bok_macro::repo_impl(MyRepo)] /// #[::bok_macro::repo_impl_methods(MyRepo)]
/// impl MyRepo{} /// impl MyRepo{}
/// ``` /// ```
#[::macro_magic::import_tokens_attr] #[::macro_magic::import_tokens_attr]
@ -96,6 +96,24 @@ pub fn repo_impl_methods(
crate::repos::repo_impl_methods(attrs, input, __source_path) crate::repos::repo_impl_methods(attrs, input, __source_path)
} }
/// Internal. **Do not use unless you know what you are doing**
/// given a Repository and a trait "BokDeps...",
/// implement all the fn returning the dependencies needed by the package
///
/// Usage:
/// ```
/// #[::bok_macro::repo_impl_(MyRepo)]
/// impl MyRepo{}
/// ```
#[::macro_magic::import_tokens_attr]
#[proc_macro_attribute]
pub fn repo_impl_pkg_deps(
attrs: TokenStream,
input: TokenStream,
) -> TokenStream {
crate::repos::repo_impl_pkg_deps(attrs, input)
}
// //
// ####### Package stuff ########## // ####### Package stuff ##########
// //
@ -138,6 +156,19 @@ pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream {
crate::pkgs::package(attrs, input, __source_path) crate::pkgs::package(attrs, input, __source_path)
} }
/// Specify one or more build-time dependencies
///
/// e.g:
/// ```
/// #[::bok::package(::bok::PkgEmpty)]
/// #[::bok::deps_build(some::Package, other::Package)]
/// pub struct MyPkg {}
/// ```
#[proc_macro_attribute]
pub fn deps_build(attrs: TokenStream, input: TokenStream) -> TokenStream {
crate::pkgs::deps_build(attrs, input)
}
/// Use as #[::bok::package_impl] /// Use as #[::bok::package_impl]
/// will add `#[::bok::package_impl_base(..)]` with proper arguments /// will add `#[::bok::package_impl_base(..)]` with proper arguments
/// and export the resulting symbols /// and export the resulting symbols
@ -205,3 +236,34 @@ pub fn package_impl_base(
pub fn derive_package(input: TokenStream) -> TokenStream { pub fn derive_package(input: TokenStream) -> TokenStream {
crate::pkgs::derive_package(input) crate::pkgs::derive_package(input)
} }
// ==== Common package stuff ====
pub(crate) struct PathList(Vec<::syn::Path>);
impl ::syn::parse::Parse for PathList {
fn parse(input: ::syn::parse::ParseStream) -> syn::Result<Self> {
use ::syn::punctuated::Punctuated;
let raw =
Punctuated::<::syn::Path, ::syn::Token![,]>::parse_terminated(
input,
)?;
let mut result = Vec::with_capacity(raw.len());
for r in raw.into_iter() {
result.push(r)
}
Ok(PathList(result))
}
}
pub(crate) fn path_to_magic_export(p: &::syn::Path) -> ::syn::Path {
let mut tmp = p.clone();
let new_id = {
macro_magic::mm_core::export_tokens_macro_ident(
&tmp.segments.last().unwrap().ident,
)
};
tmp.segments.last_mut().unwrap().ident = new_id;
tmp
}

View File

@ -157,6 +157,52 @@ pub(crate) fn package(
.into() .into()
} }
pub(crate) fn deps_build(
attrs: TokenStream,
input: TokenStream,
) -> TokenStream {
let packages = parse_macro_input!(attrs as crate::PathList);
let local = parse_macro_input!(input as ::syn::ItemStruct);
let ::syn::Fields::Named(local_fields) = local.fields else {
use ::syn::spanned::Spanned;
return ::syn::Error::new(
local.fields.span(),
"unnamed fields are not supported",
)
.to_compile_error()
.into();
};
let local_attrs = local.attrs.iter();
let local_fields = local_fields.named.iter();
let generics = local.generics;
let ident = local.ident;
let vis = local.vis;
let pkg_trait = quote::format_ident!("BokDeps{}", ident);
let deps = packages
.0
.iter()
.map(|x| x.segments.last().unwrap().ident.clone())
.collect::<Vec<::syn::Ident>>();
quote! {
#(#local_attrs)
*
#vis struct #ident<#generics> {
#(#local_fields),
*
}
#[::macro_magic::export_tokens(#pkg_trait)]
pub trait #pkg_trait {
#(fn #deps(&self) -> ::std::boxed::Box<dyn ::bok::Pkg>;)
*
}
}
.into()
}
// package_impl has 3 stages, and uses macro_magic import for the last two: // package_impl has 3 stages, and uses macro_magic import for the last two:
// * called without parameters => add default ::bok::Pkg functions, then add the // * called without parameters => add default ::bok::Pkg functions, then add the
// same macro with the package type as argument // same macro with the package type as argument

View File

@ -19,122 +19,6 @@ use ::proc_macro::TokenStream;
use ::quote::quote; use ::quote::quote;
use ::syn::{parse_macro_input, DeriveInput, Fields, ItemImpl, ItemStruct}; use ::syn::{parse_macro_input, DeriveInput, Fields, ItemImpl, ItemStruct};
pub(crate) fn repo_impl(
_attrs: TokenStream,
input: TokenStream,
) -> TokenStream {
let local = parse_macro_input!(input as ItemImpl);
let reponame = &local.self_ty;
quote! {
#[::bok_macro::repo_impl_methods(#reponame)]
#local
}
.into()
}
pub(crate) fn repo_impl_methods(
attrs: TokenStream,
input: TokenStream,
__source_name: TokenStream,
) -> TokenStream {
let base = parse_macro_input!(attrs as ItemStruct);
let local = parse_macro_input!(input as ItemImpl);
let source_name = parse_macro_input!(__source_name as ::syn::Path);
if let ::syn::Type::Path(self_type_path) = local.self_ty.as_ref() {
if self_type_path.path != source_name {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: argument and impl \
type differ",
)
.to_compile_error()
.into();
}
} else {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: argument and impl type \
differ",
)
.to_compile_error()
.into();
}
let local_attrs = local.attrs.iter();
let generics = local.generics;
let ident = &local.self_ty;
let items = local.items.iter();
let ::syn::Fields::Named(base_fields) = base.fields else {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: type has unsupported \
unnamed fields",
)
.to_compile_error()
.into();
};
let mut fn_to_add = Vec::new();
for f in base_fields.named.iter() {
if !f
.ident
.as_ref()
.is_some_and(|id| id.to_string().starts_with("_p_"))
{
continue;
};
let ::syn::Type::Path(f_type) = &f.ty else {
continue;
};
let t_phantom = &f_type.path;
let t_id = {
let args = &t_phantom
.segments
.last()
.expect("t_phantom no last?")
.arguments;
let ::syn::PathArguments::AngleBracketed(bracketed) = &args else {
panic!("phantom without anglebracket?");
};
let ::syn::GenericArgument::Type(::syn::Type::Path(t)) =
bracketed.args.first().expect("phantom bracketed, no args")
else {
panic!("phantom bracketed, generic not a type path");
};
t.path.clone()
};
let pkg_name = {
let segment = t_id.segments.iter().last().unwrap();
&segment.ident
};
let pkg_fn: ::syn::ImplItemFn = ::syn::parse_quote! {
pub fn #pkg_name(&self) -> #t_id {
#t_id::default()
}
};
fn_to_add.push(pkg_fn);
}
let new_fn = fn_to_add.iter();
quote! {
#(#local_attrs)
*
impl #ident<#generics> {
#(#items)
*
#(#new_fn)
*
}
}
.into()
}
pub(crate) fn repository( pub(crate) fn repository(
attrs: TokenStream, attrs: TokenStream,
input: TokenStream, input: TokenStream,
@ -368,22 +252,6 @@ pub(crate) fn derive_repository(input: TokenStream) -> TokenStream {
} }
}.into() }.into()
} }
struct PathList(Vec<::syn::Path>);
impl ::syn::parse::Parse for PathList {
fn parse(input: ::syn::parse::ParseStream) -> syn::Result<Self> {
use ::syn::punctuated::Punctuated;
let raw =
Punctuated::<::syn::Path, ::syn::Token![,]>::parse_terminated(
input,
)?;
let mut result = Vec::with_capacity(raw.len());
for r in raw.into_iter() {
result.push(r)
}
Ok(PathList(result))
}
}
pub(crate) fn repo_packages( pub(crate) fn repo_packages(
attrs: TokenStream, attrs: TokenStream,
@ -446,7 +314,7 @@ pub(crate) fn repo_packages(
}) })
.collect::<Vec<&::syn::Field>>(); .collect::<Vec<&::syn::Field>>();
let packages = parse_macro_input!(attrs as PathList); let packages = parse_macro_input!(attrs as crate::PathList);
// the packages added manually must not be repeated manually. // the packages added manually must not be repeated manually.
// but they will override any other package added by // but they will override any other package added by
@ -530,20 +398,213 @@ pub(crate) fn repo_packages(
.into() .into()
} }
fn path_to_snake_case(path: &::syn::Path) -> String { pub(crate) fn repo_impl(
let mut s = String::new(); _attrs: TokenStream,
input: TokenStream,
) -> TokenStream {
let local = parse_macro_input!(input as ItemImpl);
let mut is_first = true; let reponame = &local.self_ty;
for segment in path.segments.iter() {
if !is_first { quote! {
s += "_"; #[::bok_macro::repo_impl_methods(#reponame)]
} else { #local
is_first = false
} }
s += segment.ident.to_string().as_str(); .into()
} }
use ::convert_case::{Case, Casing}; pub(crate) fn repo_impl_methods(
attrs: TokenStream,
s.to_case(Case::Snake) input: TokenStream,
__source_name: TokenStream,
) -> TokenStream {
let base = parse_macro_input!(attrs as ItemStruct);
let local = parse_macro_input!(input as ItemImpl);
let source_name = parse_macro_input!(__source_name as ::syn::Path);
if let ::syn::Type::Path(self_type_path) = local.self_ty.as_ref() {
if self_type_path.path != source_name {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: argument and impl \
type differ",
)
.to_compile_error()
.into();
}
} else {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: argument and impl type \
differ",
)
.to_compile_error()
.into();
}
let local_attrs = local.attrs.iter();
let generics = local.generics;
let ::syn::Type::Path(local_tp) = local.self_ty.as_ref() else {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: no ident?",
)
.to_compile_error()
.into();
};
let local_ident = local_tp.path.get_ident().expect("NOT AN IDENT");
let items = local.items.iter();
// FIXME: make sure `items` does not have methods that are not packages in
// the impl
let ::syn::Fields::Named(base_fields) = base.fields else {
return ::syn::Error::new(
proc_macro2::Span::call_site(),
"#[::bok_macro::repo_impl_methods(..)]: type has unsupported \
unnamed fields",
)
.to_compile_error()
.into();
};
let mut all_pkgs_types = Vec::with_capacity(base_fields.named.len());
let mut fn_to_add = Vec::new();
for f in base_fields.named.iter() {
if !f
.ident
.as_ref()
.is_some_and(|id| id.to_string().starts_with("_p_"))
{
continue;
};
let ::syn::Type::Path(f_type) = &f.ty else {
continue;
};
let t_phantom = &f_type.path;
let t_id = {
let args = &t_phantom
.segments
.last()
.expect("t_phantom no last?")
.arguments;
let ::syn::PathArguments::AngleBracketed(bracketed) = &args else {
panic!("phantom without anglebracket?");
};
let ::syn::GenericArgument::Type(::syn::Type::Path(t)) =
bracketed.args.first().expect("phantom bracketed, no args")
else {
panic!("phantom bracketed, generic not a type path");
};
t.path.clone()
};
let pkg_name = {
let segment = t_id.segments.iter().last().unwrap();
&segment.ident
};
all_pkgs_types.push(t_id.clone());
if local
.items
.iter()
.find(|&func_it| {
let ::syn::ImplItem::Fn(func) = &func_it else {
return false;
};
func.sig.ident.to_string() == pkg_name.to_string()
})
.is_some()
{
// the user overrode the `::default()` package build
// don't try to add it again
continue;
}
let pkg_fn: ::syn::ImplItemFn = ::syn::parse_quote! {
pub fn #pkg_name(&self) -> #t_id {
#t_id::default()
}
};
fn_to_add.push(pkg_fn);
}
let new_fn = fn_to_add.iter();
let mut all_impl_deps = Vec::with_capacity(all_pkgs_types.len());
for p_type in all_pkgs_types.into_iter() {
let bok_dep_trait = {
let mut tmp = p_type.clone();
tmp.segments.last_mut().unwrap().ident = quote::format_ident!(
"BokDeps{}",
tmp.segments.last().unwrap().ident
);
tmp
};
let pkg_trait_impl = &bok_dep_trait;
let dep_view = quote! {
#[::bok_macro::repo_impl_pkg_deps(#pkg_trait_impl)]
impl #bok_dep_trait for #local_ident {}
};
all_impl_deps.push(dep_view);
break;
}
quote! {
#(#local_attrs)
*
impl #local_ident<#generics> {
#(#items)
*
#(#new_fn)
*
}
#(#all_impl_deps)
*
}
.into()
}
pub(crate) fn repo_impl_pkg_deps(
attrs: TokenStream,
input: TokenStream,
) -> TokenStream {
let trait_deps = parse_macro_input!(attrs as ::syn::ItemTrait);
let local = parse_macro_input!(input as ItemImpl);
let impl_trait = local
.trait_
.expect("#[::bok_macro::repo_impl_pkg_deps()]: no trait found")
.1;
let local_attrs = local.attrs.iter();
let generics = local.generics;
let ident = &local.self_ty;
//let deps = Vec::<::syn::TraitItemFn>::new();
let deps = trait_deps
.items
.iter()
.filter_map(|x| {
if let ::syn::TraitItem::Fn(func) = &x {
let name = &func.sig.ident;
let dep_impl: ::syn::TraitItemFn = ::syn::parse_quote! {
fn #name(&self) -> ::std::boxed::Box<dyn ::bok::Pkg> {
::std::boxed::Box::new(self.#name())
}
};
Some(dep_impl)
} else {
None
}
})
.collect::<Vec<::syn::TraitItemFn>>();
quote! {
#(#local_attrs)
*
impl #impl_trait for #ident<#generics> {
#(#deps)
*
}
}
.into()
} }

View File

@ -23,7 +23,7 @@ pub mod pkgs;
// FIXME: why? // FIXME: why?
use ::bok_macro::repo_impl_methods; use ::bok_macro::repo_impl_methods;
use ::bok_macro::repo_impl_pkg_deps;
// Export multiple packages in this module // Export multiple packages in this module
use ::bok::repository; use ::bok::repository;

View File

@ -22,6 +22,7 @@ use ::bok::package;
/// Example package /// Example package
/// Automatically implements `.builder().my_attr(42).build()` pattern /// Automatically implements `.builder().my_attr(42).build()` pattern
#[::bok::package(::bok::PkgEmpty)] #[::bok::package(::bok::PkgEmpty)]
#[::bok::deps_build()]
pub struct One { pub struct One {
pub my_attr: u32, pub my_attr: u32,
} }

View File

@ -19,6 +19,7 @@ use ::bok::package;
/// Example package /// Example package
#[::bok::package(::bok::PkgEmpty)] #[::bok::package(::bok::PkgEmpty)]
#[::bok::deps_build(super::One)]
pub struct Three { pub struct Three {
pub my_attr: u32, pub my_attr: u32,
} }

View File

@ -19,6 +19,7 @@ use ::bok::package;
/// Example package /// Example package
#[::bok::package(super::one::One)] #[::bok::package(super::one::One)]
#[::bok::deps_build(super::one::One)]
pub struct Two { pub struct Two {
pub my_attr2: u32, pub my_attr2: u32,
} }

View File

@ -27,8 +27,8 @@ pub mod deps {
} }
pub use ::bok_macro::{ pub use ::bok_macro::{
package, package_impl, pkg_fn_to_code, repo_impl, repo_packages, deps_build, package, package_impl, pkg_fn_to_code, repo_impl,
repository, Package, Repository, repo_packages, repository, Package, Repository,
}; };
pub use ::semver::{BuildMetadata, Prerelease, Version}; pub use ::semver::{BuildMetadata, Prerelease, Version};