Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature gating subsystems via #[cfg] #41

Merged
merged 29 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
eda1983
Prepare for feature guarding of subsystems
skunert Mar 13, 2023
0c94f11
Guard more occurences
skunert Mar 13, 2023
24d8962
Improve
skunert Mar 17, 2023
527a8bb
Remove old implementation
skunert Mar 19, 2023
8110935
Prepare new version
skunert Mar 19, 2023
f525b37
Hacky initial version
skunert Mar 20, 2023
9776941
First working version
skunert Mar 20, 2023
fc9c397
Remove duplicated code
skunert Mar 20, 2023
9efdffc
Deduplicate more code
skunert Mar 20, 2023
6ace355
Keep subsystems in definition order
skunert Mar 21, 2023
81ba2ef
Allow arbitrary feature guards
skunert Mar 22, 2023
331c269
Remove trailing commas where not necessary
skunert Mar 22, 2023
eae56ac
Improve naming
skunert Mar 23, 2023
940f9e0
Clean up abstractions
skunert Mar 23, 2023
5abe55a
First version of cfg parsing
skunert Mar 23, 2023
ced61f1
Improve parsing
skunert Mar 24, 2023
d68df45
Implement ToTokens
skunert Mar 24, 2023
abb71bf
Simplify code
skunert Mar 24, 2023
d53d131
Remove unnecessary clones
skunert Mar 24, 2023
a975f85
Improve test naming
skunert Mar 24, 2023
5616259
Improve naming
skunert Mar 24, 2023
759813a
Use `unique` instead of dedup
skunert Mar 27, 2023
13bd484
Add README entry
skunert Mar 27, 2023
b79e21d
Merge branch 'master' into skunert/feature-guarded-subsystems
skunert Mar 27, 2023
d083dd7
Unify naming, remove unwanted changes
skunert Mar 27, 2023
bba7c0b
Update orchestra/proc-macro/src/impl_builder.rs
skunert Mar 27, 2023
bb29432
Apply suggested naming
skunert Mar 27, 2023
30083b3
Improve docs
skunert Mar 28, 2023
0e97097
Allow multiple `#[cfg]` attributes
skunert Mar 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declarative.
#[subsystem(MsgA, sends: [MsgB])]
sub_a: AwesomeSubSysA,

#[cfg(any(feature = "feature1", feature = "feature2"))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

#[subsystem(MsgB, sends: [MsgA])]
sub_b: AwesomeSubSysB,
}
Expand All @@ -32,6 +33,7 @@ error type, if not provided a builtin one is used. Note that this is the one err
into the orchestra, without participating in the subsystem pattern.
* `signal=` defines a signal type to be used for the orchestra. This is a shared "tick" or "clock" for all subsystems.
* `gen=` defines a wrapping `enum` type that is used to wrap all messages that can be consumed by _any_ subsystem.
* Features can be feature gated by `#[cfg(feature = "feature")]` attribute macro expressions. Currently supported are `any`, `all`, `not` and `feature`.

```rust
/// Execution context, always required.
Expand Down
246 changes: 146 additions & 100 deletions orchestra/proc-macro/src/impl_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,144 @@ fn recollect_without_idx<T: Clone>(x: &[T], idx: usize) -> Vec<T> {
/// Elements tagged with `wip` are not covered here.
pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
let orchestra_name = info.orchestra_name.clone();

let builder = format_ident!("{}Builder", orchestra_name);
let handle = format_ident!("{}Handle", orchestra_name);
let connector = format_ident!("{}Connector", orchestra_name);
let subsystem_ctx_name = format_ident!("{}SubsystemContext", orchestra_name);
let feature_powerset_cfgs = &info.feature_gated_subsystem_sets();

let mut ts = proc_macro2::TokenStream::new();

// In this loop we generate items that are required for each config set separately
for cfg_set in feature_powerset_cfgs.into_iter() {
let builder_items_for_config = impl_feature_gated_items(
info,
cfg_set,
&builder,
&subsystem_ctx_name,
&connector,
&orchestra_name,
&handle,
);
ts.extend(builder_items_for_config);
}

let event = &info.extern_event_ty;
let support_crate = info.support_crate_name();

ts.extend(quote! {
/// Handle for an orchestra.
pub type #handle = #support_crate ::metered::MeteredSender< #event >;

/// External connector.
pub struct #connector {
/// Publicly accessible handle, to be used for setting up
/// components that are _not_ subsystems but access is needed
/// due to other limitations.
///
/// For subsystems, use the `_with` variants of the builder.
handle: #handle,
/// The side consumed by the `spawned` side of the orchestra pattern.
consumer: #support_crate ::metered::MeteredReceiver < #event >,
}

impl #connector {
/// Obtain access to the orchestra handle.
pub fn as_handle_mut(&mut self) -> &mut #handle {
&mut self.handle
}
/// Obtain access to the orchestra handle.
pub fn as_handle(&self) -> &#handle {
&self.handle
}
/// Obtain a clone of the handle.
pub fn handle(&self) -> #handle {
self.handle.clone()
}

/// Create a new connector with non-default event channel capacity.
pub fn with_event_capacity(event_channel_capacity: usize) -> Self {
let (events_tx, events_rx) = #support_crate ::metered::channel::<
#event
>(event_channel_capacity);

Self {
handle: events_tx,
consumer: events_rx,
}
}
}

let subsystem_name = &info.subsystem_names_without_wip();
let subsystem_generics = &info.subsystem_generic_types();
impl ::std::default::Default for #connector {
fn default() -> Self {
Self::with_event_capacity(SIGNAL_CHANNEL_CAPACITY)
}
}
});

let error_ty = &info.extern_error_ty;
ts.extend(quote! {
/// Convenience alias.
type SubsystemInitFn<T> = Box<dyn FnOnce(#handle) -> ::std::result::Result<T, #error_ty> + Send + 'static>;

/// Type for the initialized field of the orchestra builder
pub enum Init<T> {
/// Defer initialization to a point where the `handle` is available.
Fn(SubsystemInitFn<T>),
/// Directly initialize the subsystem with the given subsystem type `T`.
/// Also used for baggage fields
Value(T),
}
/// Type marker for the uninitialized field of the orchestra builder.
/// `PhantomData` is used for type hinting when creating uninitialized
/// builder, e.g. to avoid specifying the generics when instantiating
/// the `FooBuilder` when calling `Foo::builder()`
#[derive(Debug)]
pub struct Missing<T>(::core::marker::PhantomData<T>);

/// Trait used to mark fields status in a builder
trait OrchestraFieldState<T> {}

impl<T> OrchestraFieldState<T> for Init<T> {}
impl<T> OrchestraFieldState<T> for Missing<T> {}

impl<T> ::std::default::Default for Missing<T> {
fn default() -> Self {
Missing::<T>(::core::marker::PhantomData::<T>::default())
}
}
});
ts.extend(impl_task_kind(info));
ts
}

let consumes = &info.consumes_without_wip();
let channel_name = &info.channel_names_without_wip(None);
let channel_name_unbounded = &info.channel_names_without_wip("_unbounded");
pub(crate) fn impl_feature_gated_items(
info: &OrchestraInfo,
cfg_set: &SubsystemConfigSet,
builder: &Ident,
subsystem_ctx_name: &Ident,
connector: &Ident,
orchestra_name: &Ident,
handle: &Ident,
) -> proc_macro2::TokenStream {
let mut ts = quote! {};

let cfg_guard = &cfg_set.feature_gate;
let subsystem_name = &cfg_set.subsystem_names_without_wip();
let subsystem_generics = &cfg_set.subsystem_generic_types();
let consumes = &cfg_set.consumes_without_wip();
let channel_name = &cfg_set.channel_names_without_wip(None);
let channel_name_unbounded = &cfg_set.channel_names_without_wip("_unbounded");
let message_channel_capacity =
&info.message_channel_capacities_without_wip(info.message_channel_capacity);
&cfg_set.message_channel_capacities_without_wip(info.message_channel_capacity);
let signal_channel_capacity =
&info.signal_channel_capacities_without_wip(info.signal_channel_capacity);
&cfg_set.signal_channel_capacities_without_wip(info.signal_channel_capacity);

let channel_name_tx = &info.channel_names_without_wip("_tx");
let channel_name_unbounded_tx = &info.channel_names_without_wip("_unbounded_tx");
let channel_name_tx = &cfg_set.channel_names_without_wip("_tx");
let channel_name_unbounded_tx = &cfg_set.channel_names_without_wip("_unbounded_tx");

let channel_name_rx = &info.channel_names_without_wip("_rx");
let channel_name_rx = &cfg_set.channel_names_without_wip("_rx");
let channel_name_unbounded_rx = &info.channel_names_without_wip("_unbounded_rx");

let baggage_name = &info.baggage_names();
Expand All @@ -72,8 +190,8 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {

let support_crate = info.support_crate_name();

let blocking = &info
.subsystems()
let blocking = &cfg_set
.enabled_subsystems
.iter()
.map(|x| {
if x.blocking {
Expand Down Expand Up @@ -108,7 +226,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
// remove the field that we are fixing in this setter
// For subsystems `*_with` and `replace_*` setters are needed.
let subsystem_specific_setters =
info.subsystems().iter().filter(|ssf| !ssf.wip).enumerate().map(|(idx, ssf)| {
cfg_set.enabled_subsystems.iter().filter(|ssf| !ssf.wip).enumerate().map(|(idx, ssf)| {
let field_name = &ssf.name;
let field_type = &ssf.generic;
let subsystem_consumes = &ssf.message_to_consume();
Expand Down Expand Up @@ -149,6 +267,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {

// Create the field init `fn`
quote! {
#cfg_guard
impl <InitStateSpawner, #field_type, #( #impl_subsystem_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder <InitStateSpawner, #( #current_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
Expand Down Expand Up @@ -203,6 +322,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
}

#[allow(clippy::type_complexity)]
#cfg_guard
impl <InitStateSpawner, #field_type, #( #impl_subsystem_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder <InitStateSpawner, #( #post_setter_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
Expand Down Expand Up @@ -264,6 +384,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
// Baggage can also be generic, so we need to include that to a signature
let preserved_baggage_generics = &bag_field.generic_types;
quote! {
#cfg_guard
impl <InitStateSpawner, #( #preserved_baggage_generics, )* #( #subsystem_passthrough_state_generics, )* #( #impl_baggage_state_generics, )* >
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #pre_setter_generics, )* >
{
Expand All @@ -288,6 +409,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
}
}

#cfg_guard
impl <InitStateSpawner, #( #preserved_baggage_generics, )* #( #subsystem_passthrough_state_generics, )* #( #impl_baggage_state_generics, )* >
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #post_setter_generics, )* > {
/// Specify the baggage in the builder when it has been previously initialized
Expand All @@ -313,15 +435,14 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
}
});

let event = &info.extern_event_ty;
let initialized_builder = format_ident!("Initialized{}", builder);
// The direct generics as expected by the `Orchestra<_,_,..>`, without states
let initialized_builder_generics = quote! {
S, #( #baggage_generic_ty, )* #( #subsystem_generics, )*
};

let builder_where_clause = info
.subsystems()
let builder_where_clause = cfg_set
.enabled_subsystems
.iter()
.map(|ssf| {
let field_type = &ssf.generic;
Expand All @@ -342,37 +463,8 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
ts
});

let mut ts = quote! {
/// Convenience alias.
type SubsystemInitFn<T> = Box<dyn FnOnce(#handle) -> ::std::result::Result<T, #error_ty> + Send + 'static>;

/// Type for the initialized field of the orchestra builder
pub enum Init<T> {
/// Defer initialization to a point where the `handle` is available.
Fn(SubsystemInitFn<T>),
/// Directly initialize the subsystem with the given subsystem type `T`.
/// Also used for baggage fields
Value(T),
}
/// Type marker for the uninitialized field of the orchestra builder.
/// `PhantomData` is used for type hinting when creating uninitialized
/// builder, e.g. to avoid specifying the generics when instantiating
/// the `FooBuilder` when calling `Foo::builder()`
#[derive(Debug)]
pub struct Missing<T>(::core::marker::PhantomData<T>);

/// Trait used to mark fields status in a builder
trait OrchestraFieldState<T> {}

impl<T> OrchestraFieldState<T> for Init<T> {}
impl<T> OrchestraFieldState<T> for Missing<T> {}

impl<T> ::std::default::Default for Missing<T> {
fn default() -> Self {
Missing::<T>(::core::marker::PhantomData::<T>::default())
}
}

ts.extend(quote! {
#cfg_guard
impl<S #(, #baggage_generic_ty )*> #orchestra_name <S #(, #baggage_generic_ty)*>
where
#spawner_where_clause,
Expand All @@ -387,61 +479,12 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
#builder :: new()
}
}
};

ts.extend(quote! {
/// Handle for an orchestra.
pub type #handle = #support_crate ::metered::MeteredSender< #event >;

/// External connector.
pub struct #connector {
/// Publicly accessible handle, to be used for setting up
/// components that are _not_ subsystems but access is needed
/// due to other limitations.
///
/// For subsystems, use the `_with` variants of the builder.
handle: #handle,
/// The side consumed by the `spawned` side of the orchestra pattern.
consumer: #support_crate ::metered::MeteredReceiver < #event >,
}

impl #connector {
/// Obtain access to the orchestra handle.
pub fn as_handle_mut(&mut self) -> &mut #handle {
&mut self.handle
}
/// Obtain access to the orchestra handle.
pub fn as_handle(&self) -> &#handle {
&self.handle
}
/// Obtain a clone of the handle.
pub fn handle(&self) -> #handle {
self.handle.clone()
}

/// Create a new connector with non-default event channel capacity.
pub fn with_event_capacity(event_channel_capacity: usize) -> Self {
let (events_tx, events_rx) = #support_crate ::metered::channel::<
#event
>(event_channel_capacity);

Self {
handle: events_tx,
consumer: events_rx,
}
}
}

impl ::std::default::Default for #connector {
fn default() -> Self {
Self::with_event_capacity(SIGNAL_CHANNEL_CAPACITY)
}
}
});

ts.extend(quote!{
/// Builder pattern to create compile time safe construction path.
#[allow(clippy::type_complexity)]
#cfg_guard
pub struct #builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
{
#(
Expand All @@ -461,6 +504,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {

ts.extend(quote! {
#[allow(clippy::type_complexity)]
#cfg_guard
impl<#initialized_builder_generics> #builder<Missing<S>, #( Missing<#field_type>, )*>
{
/// Create a new builder pattern, with all fields being uninitialized.
Expand Down Expand Up @@ -489,6 +533,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
// Spawner setter
ts.extend(quote!{
#[allow(clippy::type_complexity)]
#cfg_guard
impl<S, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder<Missing<S>, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
Expand Down Expand Up @@ -517,6 +562,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
// message and signal channel capacity
ts.extend(quote! {
#[allow(clippy::type_complexity)]
#cfg_guard
impl<S, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder<Init<S>, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
Expand Down Expand Up @@ -550,9 +596,11 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {

ts.extend(quote! {
/// Type used to represent a builder where all fields are initialized and the orchestra could be constructed.
#cfg_guard
pub type #initialized_builder<#initialized_builder_generics> = #builder<Init<S>, #( Init<#field_type>, )*>;

// A builder specialization where all fields are set
#cfg_guard
impl<#initialized_builder_generics> #initialized_builder<#initialized_builder_generics>
where
#spawner_where_clause,
Expand Down Expand Up @@ -640,8 +688,7 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
#subsystem_name_str_literal
);

let #subsystem_name: OrchestratedSubsystem< #consumes > =
spawn::<_,_, #blocking, _, _, _>(
let #subsystem_name: OrchestratedSubsystem< #consumes > = spawn::<_,_, #blocking, _, _, _>(
&mut spawner,
#channel_name_tx,
signal_tx,
Expand Down Expand Up @@ -685,7 +732,6 @@ pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {

ts.extend(baggage_specific_setters);
ts.extend(subsystem_specific_setters);
ts.extend(impl_task_kind(info));
ts
}

Expand Down
Loading