Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d7d84ff authored by Zach Johnson's avatar Zach Johnson
Browse files

rusty-gd: initial commit for GDDI

go/gddi for an overview

GDDI (GD dependency injection) aims to solve the issue
of start & stop order, especially in the world where we
bring the stack up partially for testing.

this patch introduces modules & providers, but does not yet save
& remember instances to prevent multiple calls, or provide a way
to seed some object instances (e.g. config), or provide a way to
tear down the started objects

The rest is coming soon.

Bug: 171749953
Tag: #gd-refactor
Test: gd/cert/run --rhost SimpleHalTest
Change-Id: I85f0dac139cfab3174053b92e077c80245da4f85
parent dd916bdc
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
rust_proc_macro {
    name: "libgddi_macros",
    crate_name: "gddi_macros",
    srcs: ["src/lib.rs"],
    edition: "2018",
    rustlibs: [
        "libproc_macro2",
        "libquote",
        "libsyn",
    ],
}
+150 −0
Original line number Diff line number Diff line
//! Core dependency injection macros

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{braced, parse, parse_macro_input, FnArg, Ident, ItemFn, Token, Type};

/// Defines a provider function, with generated helper that implicitly fetches argument instances from the registry
#[proc_macro_attribute]
pub fn provides(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let function: ItemFn = parse(item).expect("can only be applied to functions");

    // Create the info needed to refer to the function & the injected version we generate
    let ident = function.sig.ident.clone();
    let injected_ident = format_ident!("__gddi_{}_injected", ident);

    // Create the info needed to generate the call to the original function
    let inputs = function.sig.inputs.iter().map(|arg| {
        if let FnArg::Typed(t) = arg {
            return t.ty.clone();
        }
        panic!("can't be applied to struct methods");
    });
    let local_var_idents = (0..inputs.len()).map(|i| format_ident!("__input{}", i));
    let local_var_idents_for_call = local_var_idents.clone();

    let emitted_code = quote! {
        // Injecting wrapper
        fn #injected_ident(registry: std::sync::Arc<gddi::Registry>) -> std::pin::Pin<Box<dyn std::future::Future<Output = Box<dyn std::any::Any>>>> {
            Box::pin(async move {
                // Create a local variable for each argument, to ensure they get generated in a
                // deterministic order (compiler complains otherwise)
                #(let #local_var_idents = registry.get::<#inputs>().await;)*

                // Actually call the original function
                Box::new(#ident(#(#local_var_idents_for_call),*).await) as Box<dyn std::any::Any>
            })
        }
        #function
    };
    emitted_code.into()
}

struct ModuleDef {
    name: Ident,
    providers: Punctuated<ProviderDef, Token![,]>,
    submodules: Punctuated<Ident, Token![,]>,
}

enum ModuleEntry {
    Providers(Punctuated<ProviderDef, Token![,]>),
    Submodules(Punctuated<Ident, Token![,]>),
}

struct ProviderDef {
    ty: Type,
    ident: Ident,
}

impl Parse for ModuleDef {
    fn parse(input: ParseStream) -> Result<Self> {
        // first thing is the module name followed by a comma
        let name = input.parse()?;
        input.parse::<Token![,]>()?;
        // Then comes submodules or provider sections, in any order
        let entries: Punctuated<ModuleEntry, Token![,]> = Punctuated::parse_terminated(input)?;
        let mut providers = Punctuated::new();
        let mut submodules = Punctuated::new();
        for entry in entries.into_iter() {
            match entry {
                ModuleEntry::Providers(value) => {
                    if !providers.is_empty() {
                        panic!("providers specified more than once");
                    }
                    providers = value;
                },
                ModuleEntry::Submodules(value) => {
                    if !submodules.is_empty() {
                        panic!("submodules specified more than once");
                    }
                    submodules = value;
                },
            }
        }
        Ok(ModuleDef {
            name,
            providers,
            submodules,
        })
    }
}

impl Parse for ProviderDef {
    fn parse(input: ParseStream) -> Result<Self> {
        // A provider definition follows this format: <Type> -> <function name>
        let ty = input.parse()?;
        input.parse::<Token![=>]>()?;
        let ident = input.parse()?;
        Ok(ProviderDef { ty, ident })
    }
}

impl Parse for ModuleEntry {
    fn parse(input: ParseStream) -> Result<Self> {
        match input.parse::<Ident>()?.to_string().as_str() {
            "providers" => {
                let entries;
                braced!(entries in input);
                Ok(ModuleEntry::Providers(
                    entries.parse_terminated(ProviderDef::parse)?,
                ))
            }
            "submodules" => {
                let entries;
                braced!(entries in input);
                Ok(ModuleEntry::Submodules(
                    entries.parse_terminated(Ident::parse)?,
                ))
            }
            keyword => {
                panic!("unexpected keyword: {}", keyword);
            }
        }
    }
}

/// Emits a module function that registers submodules & providers with the registry
#[proc_macro]
pub fn module(item: TokenStream) -> TokenStream {
    let module = parse_macro_input!(item as ModuleDef);
    let init_ident = module.name.clone();
    let types = module.providers.iter().map(|p| p.ty.clone());
    let provider_idents = module
        .providers
        .iter()
        .map(|p| format_ident!("__gddi_{}_injected", p.ident.clone()));
    let submodule_idents = module.submodules.iter();
    let emitted_code = quote! {
        #[doc(hidden)]
        pub fn #init_ident(registry: &mut gddi::Registry) {
            // Register all providers on this module
            #(registry.register_provider::<#types>(Box::new(#provider_idents));)*
            // Register all submodules on this module
            #(registry.register_module(#submodule_idents);)*
        }
    };
    emitted_code.into()
}
+8 −0
Original line number Diff line number Diff line
rust_library {
    name: "libgddi",
    crate_name: "gddi",
    srcs: ["src/lib.rs"],
    host_supported: true,
    edition: "2018",
    proc_macros: ["libgddi_macros"],
}
+58 −0
Original line number Diff line number Diff line
//! Core dependency injection objects

use std::collections::HashMap;

use std::any::Any;
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

pub use gddi_macros::{module, provides};

/// Keeps track of central injection state
pub struct Registry {
    providers: HashMap<TypeId, Provider>,
}

struct Provider {
    f: Box<dyn Fn(Arc<Registry>) -> Pin<Box<dyn Future<Output = Box<dyn Any>>>>>,
}

impl Default for Registry {
    fn default() -> Self {
        Self::new()
    }
}

impl Registry {
    /// Creates a new registry
    pub fn new() -> Self {
        Registry {
            providers: HashMap::new(),
        }
    }

    /// Registers a module with this registry
    pub fn register_module<F>(&mut self, init: F)
    where
        F: Fn(&mut Registry),
    {
        init(self);
    }

    /// Registers a provider function with this registry
    pub fn register_provider<T: 'static>(
        &mut self,
        f: Box<dyn Fn(Arc<Registry>) -> Pin<Box<dyn Future<Output = Box<dyn Any>>>>>,
    ) {
        self.providers.insert(TypeId::of::<T>(), Provider { f });
    }

    /// Gets an instance of a type, implicitly starting any dependencies if necessary
    pub async fn get<T: 'static + Clone>(self: &Arc<Self>) -> T {
        let provider = &self.providers[&TypeId::of::<T>()];
        let result = (provider.f)(self.clone()).await;
        *result.downcast::<T>().expect("was not correct type")
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ rust_library {
        "libtokio",
        "libprotobuf",
        "libbt_packets",
        "libgddi",
    ],
    host_supported: true,
}
Loading