scuffle_bootstrap_derive/
main_impl.rs1#![allow(unused)]
2
3use darling::FromMeta;
4use darling::ast::NestedMeta;
5use proc_macro2::{Span, TokenStream};
6use quote::{quote, quote_spanned};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{Ident, Token, parse_macro_input, parse_quote};
10
11struct Main {
12 options: ParseArgs,
13 entry: syn::Type,
14 braced: syn::token::Brace,
15 items: Vec<Item>,
16}
17
18impl syn::parse::Parse for Main {
19 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
20 let content;
21
22 Ok(Main {
23 options: ParseArgs::from_attrs(syn::Attribute::parse_outer(input)?)?,
24 entry: input.parse()?,
25 braced: syn::braced!(content in input),
26 items: content.parse_terminated(Item::parse, Token![,])?.into_iter().collect(),
27 })
28 }
29}
30
31#[derive(Debug)]
32struct Item {
33 cfg_attrs: Vec<syn::Attribute>,
34 expr: syn::Expr,
35 item_kind: ItemKind,
36}
37
38#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
39enum ItemKind {
40 Service,
41}
42
43impl syn::parse::Parse for Item {
44 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
45 let attrs = syn::Attribute::parse_outer(input)?;
47 let mut cfg_attrs = Vec::new();
48 let mut item_kind = None;
49
50 for attr in attrs {
51 if attr.path().is_ident("cfg") {
52 cfg_attrs.push(attr);
53 } else {
54 return Err(syn::Error::new_spanned(attr, "unknown attribute"));
55 }
56 }
57
58 Ok(Item {
59 cfg_attrs,
60 expr: input.parse()?,
61 item_kind: item_kind.unwrap_or(ItemKind::Service),
62 })
63 }
64}
65
66#[derive(Debug, darling::FromMeta)]
67#[darling(default)]
68struct ParseArgs {
69 crate_path: syn::Path,
70}
71
72impl ParseArgs {
73 fn from_attrs(attrs: Vec<syn::Attribute>) -> syn::Result<Self> {
74 let mut meta = Vec::new();
75
76 for attr in attrs {
77 if attr.path().is_ident("bootstrap") {
78 match attr.meta {
79 syn::Meta::List(list) => {
80 let meta_list =
81 syn::parse::Parser::parse2(Punctuated::<NestedMeta, Token![,]>::parse_terminated, list.tokens)?;
82 meta.extend(meta_list);
83 }
84 _ => {
85 return Err(syn::Error::new_spanned(attr, "expected list, #[bootstrap(...)]"));
86 }
87 }
88 } else {
89 return Err(syn::Error::new_spanned(attr, "unknown attribute"));
90 }
91 }
92
93 Ok(Self::from_list(&meta)?)
94 }
95}
96
97impl Default for ParseArgs {
98 fn default() -> Self {
99 Self {
100 crate_path: syn::parse_str("::scuffle_bootstrap").unwrap(),
101 }
102 }
103}
104
105impl syn::parse::Parse for ParseArgs {
106 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
107 if input.is_empty() {
108 Ok(ParseArgs::default())
109 } else {
110 let meta_list = input
111 .parse_terminated(NestedMeta::parse, Token![,])?
112 .into_iter()
113 .collect::<Vec<_>>();
114 Ok(ParseArgs::from_list(&meta_list)?)
115 }
116 }
117}
118
119pub(crate) fn impl_main(input: TokenStream) -> Result<TokenStream, syn::Error> {
120 let span = input.span();
121 let Main {
122 options,
123 entry,
124 braced,
125 items,
126 } = syn::parse2(input)?;
127
128 let crate_path = &options.crate_path;
129
130 let service_type = quote!(#crate_path::service::Service::<#entry>);
131
132 let global_ident = Ident::new("global", Span::mixed_site());
133 let ctx_handle_ident = Ident::new("ctx_handle", Span::mixed_site());
134 let services_vec_ident = Ident::new("services_vec", Span::mixed_site());
135 let runtime_ident = Ident::new("runtime", Span::mixed_site());
136 let config_ident = Ident::new("config", Span::mixed_site());
137 let shared_global_ident = Ident::new("shared_global", Span::mixed_site());
138
139 let handle_type =
140 quote!(#crate_path::service::NamedFuture<#crate_path::prelude::tokio::task::JoinHandle<anyhow::Result<()>>>);
141
142 let services = items.iter().filter(|item| item.item_kind == ItemKind::Service).map(|item| {
143 let expr = &item.expr;
144 let cfg_attrs = &item.cfg_attrs;
145
146 let expr = quote_spanned!(Span::mixed_site().located_at(expr.span()) => #expr);
147
148 let stringify_expr = quote! { #expr }.to_string();
149
150 quote_spanned! { expr.span() =>
151 #(#cfg_attrs)*
152 {
153 #[doc(hidden)]
154 pub async fn spawn_service(
155 svc: impl #service_type,
156 global: &::std::sync::Arc<#entry>,
157 ctx_handle: &#crate_path::prelude::scuffle_context::Handler,
158 name: &'static str,
159 ) -> anyhow::Result<Option<#crate_path::service::NamedFuture<#crate_path::prelude::tokio::task::JoinHandle<anyhow::Result<()>>>>> {
160 let name = #service_type::name(&svc).unwrap_or_else(|| name);
161 if #crate_path::prelude::anyhow::Context::context(#service_type::enabled(&svc, &global).await, name)? {
162 Ok(Some(#crate_path::service::NamedFuture::new(
163 name,
164 #crate_path::prelude::tokio::spawn(#service_type::run(svc, global.clone(), ctx_handle.context())),
165 )))
166 } else {
167 Ok(None)
168 }
169 }
170
171 let res = spawn_service(#expr, &#global_ident, &#ctx_handle_ident, #stringify_expr).await;
172
173 if let Some(spawned) = res? {
174 #services_vec_ident.push(spawned);
175 }
176 }
177 }
178 });
179
180 let entry_as_global = quote_spanned! { entry.span() =>
181 <#entry as #crate_path::global::Global>
182 };
183
184 let boilerplate = quote_spanned! { Span::mixed_site() =>
185 #crate_path::prelude::anyhow::Context::context(#entry_as_global::pre_init(), "pre_init")?;
186
187 let #runtime_ident = #entry_as_global::tokio_runtime();
188
189 let #config_ident = #crate_path::prelude::anyhow::Context::context(
190 #runtime_ident.block_on(
191 <#entry_as_global::Config as #crate_path::config::ConfigParser>::parse()
192 ),
193 "config parse",
194 )?;
195
196 let #ctx_handle_ident = #crate_path::prelude::scuffle_context::Handler::global();
197
198 let mut #shared_global_ident = ::core::option::Option::None;
199 let mut #services_vec_ident = ::std::vec::Vec::<#handle_type>::new();
200 };
201
202 Ok(quote! {
203 #[automatically_derived]
204 fn main() -> #crate_path::prelude::anyhow::Result<()> {
205 #[doc(hidden)]
206 pub const fn impl_global<G: #crate_path::global::Global>() {}
207 const _: () = impl_global::<#entry>();
208
209 #boilerplate
210
211 let result = #runtime_ident.block_on(async {
212 let #global_ident = #entry_as_global::init(#config_ident).await?;
213
214 #shared_global_ident = ::core::option::Option::Some(#global_ident.clone());
215
216 #(#services)*
217
218 #entry_as_global::on_services_start(&#global_ident).await?;
219
220 let mut remaining = #services_vec_ident;
221
222 while !remaining.is_empty() {
223 let ((name, result), _, new_remaining) = #crate_path::prelude::futures::future::select_all(remaining).await;
224
225 let result = #crate_path::prelude::anyhow::Context::context(#crate_path::prelude::anyhow::Context::context(result, name)?, name);
226
227 #entry_as_global::on_service_exit(&#global_ident, name, result).await?;
228
229 remaining = new_remaining;
230 }
231
232 #crate_path::prelude::anyhow::Ok(())
233 });
234
235 let ::core::option::Option::Some(global) = #shared_global_ident else {
236 return result;
237 };
238
239 #runtime_ident.block_on(#entry_as_global::on_exit(&global, result))
240 }
241 })
242}