OSDN Git Service

rusty-gd: initial commit for GDDI
authorZach Johnson <zachoverflow@google.com>
Mon, 16 Nov 2020 22:43:06 +0000 (14:43 -0800)
committerZach Johnson <zachoverflow@google.com>
Tue, 24 Nov 2020 00:34:24 +0000 (16:34 -0800)
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

gd/rust/gddi-macros/Android.bp [new file with mode: 0644]
gd/rust/gddi-macros/src/lib.rs [new file with mode: 0644]
gd/rust/gddi/Android.bp [new file with mode: 0644]
gd/rust/gddi/src/lib.rs [new file with mode: 0644]
gd/rust/hal/Android.bp
gd/rust/hal/src/rootcanal_hal.rs

diff --git a/gd/rust/gddi-macros/Android.bp b/gd/rust/gddi-macros/Android.bp
new file mode 100644 (file)
index 0000000..6abd03c
--- /dev/null
@@ -0,0 +1,11 @@
+rust_proc_macro {
+    name: "libgddi_macros",
+    crate_name: "gddi_macros",
+    srcs: ["src/lib.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libproc_macro2",
+        "libquote",
+        "libsyn",
+    ],
+}
diff --git a/gd/rust/gddi-macros/src/lib.rs b/gd/rust/gddi-macros/src/lib.rs
new file mode 100644 (file)
index 0000000..5323bff
--- /dev/null
@@ -0,0 +1,150 @@
+//! 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()
+}
diff --git a/gd/rust/gddi/Android.bp b/gd/rust/gddi/Android.bp
new file mode 100644 (file)
index 0000000..2271b34
--- /dev/null
@@ -0,0 +1,8 @@
+rust_library {
+    name: "libgddi",
+    crate_name: "gddi",
+    srcs: ["src/lib.rs"],
+    host_supported: true,
+    edition: "2018",
+    proc_macros: ["libgddi_macros"],
+}
diff --git a/gd/rust/gddi/src/lib.rs b/gd/rust/gddi/src/lib.rs
new file mode 100644 (file)
index 0000000..2e9ec93
--- /dev/null
@@ -0,0 +1,58 @@
+//! 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")
+    }
+}
index 99d8e07..6a8f4f4 100644 (file)
@@ -13,6 +13,7 @@ rust_library {
         "libtokio",
         "libprotobuf",
         "libbt_packets",
+        "libgddi",
     ],
     host_supported: true,
 }
index 03e8900..268be32 100644 (file)
@@ -11,8 +11,8 @@ use tokio::net::TcpStream;
 use tokio::select;
 
 use tokio::runtime::Runtime;
-
 use tokio::sync::mpsc;
+use gddi::{module, provides};
 
 use bt_packet::{HciCommand, HciEvent, HciPacketHeaderSize, HciPacketType, RawPacket};
 
@@ -20,6 +20,19 @@ use std::sync::Arc;
 
 use crate::{Hal, HalExports, Result, H4_HEADER_SIZE};
 
+module! {
+    rootcanal_hal,
+    providers {
+        HalExports => provide_rootcanal_hal,
+    }
+}
+
+#[provides]
+async fn provide_rootcanal_hal(config: RootcanalConfig, rt: Arc<Runtime>) -> HalExports {
+    // Temporarily unwrap, until GDDI supports returning Result types
+    RootcanalHal::start(config, rt).await.unwrap()
+}
+
 /// Rootcanal configuration
 #[derive(Clone, Debug, Default)]
 pub struct RootcanalConfig {