swarm-derive: Add prelude configuration option to NetworkBehaviour macro (#3055)

Currently, our `NetworkBehaviour` derive macro depends on the `libp2p` crate to be in scope. This prevents standalone usage which forces us to depend on `libp2p` in all our tests where we want to derive a `NetworkBehaviour`.

This PR introduces a `prelude` option that - by default - points to `libp2p::swarm::derive_prelude`, a new module added to `libp2p_swarm`. With this config option, users of `libp2p_swarm` can now refer to the macro without depending on `libp2p`, breaking the circular dependency in our workspace. For consistency with the ecosystem, the macro is now also re-exported by `libp2p_swarm` instead of `libp2p` at the same position as the trait that it implements.

Lastly, we introduce an off-by-default `macros` feature flag that shrinks the dependency tree for users that don't need the derive macro.
This commit is contained in:
Thomas Eizinger
2022-11-13 10:59:14 +11:00
committed by GitHub
parent c32f03c317
commit afb777e937
31 changed files with 211 additions and 132 deletions

View File

@@ -24,6 +24,7 @@
use heck::ToUpperCamelCase;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Parse;
use syn::{parse_macro_input, Data, DataStruct, DeriveInput};
/// Generates a delegating `NetworkBehaviour` implementation for the struct this is used for. See
@@ -47,21 +48,24 @@ fn build(ast: &DeriveInput) -> TokenStream {
fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
let name = &ast.ident;
let (_, ty_generics, where_clause) = ast.generics.split_for_impl();
let multiaddr = quote! {::libp2p::core::Multiaddr};
let trait_to_impl = quote! {::libp2p::swarm::NetworkBehaviour};
let either_ident = quote! {::libp2p::core::either::EitherOutput};
let network_behaviour_action = quote! {::libp2p::swarm::NetworkBehaviourAction};
let into_connection_handler = quote! {::libp2p::swarm::IntoConnectionHandler};
let connection_handler = quote! {::libp2p::swarm::ConnectionHandler};
let into_proto_select_ident = quote! {::libp2p::swarm::IntoConnectionHandlerSelect};
let peer_id = quote! {::libp2p::core::PeerId};
let connection_id = quote! {::libp2p::core::connection::ConnectionId};
let dial_errors = quote! {Option<&Vec<::libp2p::core::Multiaddr>>};
let connected_point = quote! {::libp2p::core::ConnectedPoint};
let listener_id = quote! {::libp2p::core::transport::ListenerId};
let dial_error = quote! {::libp2p::swarm::DialError};
let poll_parameters = quote! {::libp2p::swarm::PollParameters};
let prelude_path = parse_attribute_value_by_key::<syn::Path>(ast, "prelude")
.unwrap_or_else(|| syn::parse_quote! { ::libp2p::swarm::derive_prelude });
let multiaddr = quote! { #prelude_path::Multiaddr };
let trait_to_impl = quote! { #prelude_path::NetworkBehaviour };
let either_ident = quote! { #prelude_path::EitherOutput };
let network_behaviour_action = quote! { #prelude_path::NetworkBehaviourAction };
let into_connection_handler = quote! { #prelude_path::IntoConnectionHandler };
let connection_handler = quote! { #prelude_path::ConnectionHandler };
let into_proto_select_ident = quote! { #prelude_path::IntoConnectionHandlerSelect };
let peer_id = quote! { #prelude_path::PeerId };
let connection_id = quote! { #prelude_path::ConnectionId };
let dial_errors = quote! {Option<&Vec<#prelude_path::Multiaddr>> };
let connected_point = quote! { #prelude_path::ConnectedPoint };
let listener_id = quote! { #prelude_path::ListenerId };
let dial_error = quote! { #prelude_path::DialError };
let poll_parameters = quote! { #prelude_path::PollParameters };
// Build the generics.
let impl_generics = {
@@ -75,22 +79,8 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
// If we find a `#[behaviour(out_event = "Foo")]` attribute on the
// struct, we set `Foo` as the out event. If not, the `OutEvent` is
// generated.
let user_provided_out_event_name: Option<syn::Type> = ast
.attrs
.iter()
.filter_map(get_meta_items)
.flatten()
.filter_map(|meta_item| {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) = meta_item {
if m.path.is_ident("out_event") {
if let syn::Lit::Str(ref s) = m.lit {
return Some(syn::parse_str(&s.value()).unwrap());
}
}
}
None
})
.next();
let user_provided_out_event_name =
parse_attribute_value_by_key::<syn::Type>(ast, "out_event");
match user_provided_out_event_name {
// User provided `OutEvent`.
@@ -625,7 +615,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
}
fn poll(&mut self, cx: &mut std::task::Context, poll_params: &mut impl #poll_parameters) -> std::task::Poll<#network_behaviour_action<Self::OutEvent, Self::ConnectionHandler>> {
use libp2p::futures::prelude::*;
use #prelude_path::futures::*;
#(#poll_stmts)*
std::task::Poll::Pending
}
@@ -635,6 +625,30 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
final_quote.into()
}
/// Parses the `value` of a key=value pair in the `#[behaviour]` attribute into the requested type.
///
/// Only `String` values are supported, e.g. `#[behaviour(foo="bar")]`.
fn parse_attribute_value_by_key<T>(ast: &DeriveInput, key: &str) -> Option<T>
where
T: Parse,
{
ast.attrs
.iter()
.filter_map(get_meta_items)
.flatten()
.filter_map(|meta_item| {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) = meta_item {
if m.path.is_ident(key) {
if let syn::Lit::Str(ref s) = m.lit {
return Some(syn::parse_str(&s.value()).unwrap());
}
}
}
None
})
.next()
}
fn get_meta_items(attr: &syn::Attribute) -> Option<Vec<syn::NestedMeta>> {
if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "behaviour" {
match attr.parse_meta() {