From 019e229ce166370a878aeaec835617a266a89d39 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 17 Feb 2022 10:10:39 -0500 Subject: [PATCH 1/2] Updating the `BindingsGenerator` trait - Updated `BindingsGeneratorConfig` and added an empty implementation for bindings that don't have config values. - Instead of having `generate_external_bindings` construct the `BindingsGenerator` it now inputs one instead. The advantage here is that bindings generators can have whatever fields they want. For the desktop JS project I want to make a generator store an enum that controls if it's generating the .webidl, .cpp, .h, or .jsm file. - Have `write_bindings()` input the config since we're no longer passing that to the `BindingsGenerator` constructor. - Removed the `compile_bindings()` and `run_script()` methods. These are going to be replaced with some other system to run tests. - Separated the trait bounds for the `AsRef` arguments to `write_bindings()`. I think the previous code required all of them to be the same type. Now one can be a `PathBuf` and another can be a `Path`. - Made ComponentInterface public, since it's used in `BindingsGenerator` - Added `ComponentInterface` method to get all FFI functions, except the `RustBuffer` ones - Pass in an owned ComponentInterface rather than a reference - Don't return an error when `uniffi.toml` is missing - Added some anyhow context calls for better error tracking --- uniffi_bindgen/src/interface/mod.rs | 14 ++++- uniffi_bindgen/src/lib.rs | 93 ++++++++++++++++------------- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 2c3c6bba78..65ff16a922 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -403,6 +403,18 @@ impl<'ci> ComponentInterface { /// The set of FFI functions is derived automatically from the set of higher-level types /// along with the builtin FFI helper functions. pub fn iter_ffi_function_definitions(&self) -> Vec { + let mut functions = self.iter_user_ffi_function_definitions(); + functions.append(&mut self.iter_rust_buffer_ffi_function_definitions()); + functions + } + + /// List all FFI functions definitions for user-defined interfaces + /// + /// This includes FFI functions for: + /// - Top-level functions + /// - Object methods + /// - Callback interfaces + pub fn iter_user_ffi_function_definitions(&self) -> Vec { vec![] .into_iter() .chain( @@ -416,10 +428,10 @@ impl<'ci> ComponentInterface { .flat_map(|cb| cb.iter_ffi_function_definitions()), ) .chain(self.functions.iter().map(|f| f.ffi_func.clone())) - .chain(self.iter_rust_buffer_ffi_function_definitions()) .collect() } + /// List all FFI functions definitions for RustBuffer functionality pub fn iter_rust_buffer_ffi_function_definitions(&self) -> Vec { vec![ self.ffi_rustbuffer_alloc(), diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index 5634d0e0db..d72e5368f6 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -113,7 +113,7 @@ pub mod interface; pub mod scaffolding; use bindings::TargetLanguage; -use interface::ComponentInterface; +pub use interface::ComponentInterface; use scaffolding::RustScaffolding; /// A trait representing a Binding Generator Configuration @@ -122,8 +122,8 @@ use scaffolding::RustScaffolding; /// the `BindingGenerator.config` associated type. `generate_external_bindings()` then uses it to /// generate the config that's passed to `BindingGenerator.write_bindings()` pub trait BindingGeneratorConfig: for<'de> Deserialize<'de> { - /// Key that specifies this bindings config in the `bindings` table from `uniffi.toml`. - fn language_key() -> String; + /// Get the entry for this config from the `bindings` table. + fn get_entry_from_bindings_table(bindings: &toml::Value) -> Option; /// Get default config values from the `ComponentInterface` /// @@ -151,7 +151,35 @@ fn load_bindings_config( } // Leverage serde to convert toml::Value into the config type - Ok(toml::Value::from(config_map).try_into()?) + toml::Value::from(config_map) + .try_into() + .context("Generating bindings config from toml::Value") +} + +/// Binding generator config with no members +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)] +pub struct EmptyBindingGeneratorConfig; + +impl BindingGeneratorConfig for EmptyBindingGeneratorConfig { + fn get_entry_from_bindings_table(_bindings: &toml::Value) -> Option { + None + } + + fn get_config_defaults(_ci: &ComponentInterface) -> Vec<(String, toml::Value)> { + Vec::new() + } +} + +// EmptyBindingGeneratorConfig is a unit struct, so the `derive(Deserialize)` implementation +// expects a null value rather than the empty map that we pass it. So we need to implement +// `Deserialize` ourselves. +impl<'de> Deserialize<'de> for EmptyBindingGeneratorConfig { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(EmptyBindingGeneratorConfig) + } } // Load the binding-specific config @@ -167,9 +195,7 @@ fn load_bindings_config_toml( ) -> Result> { let config_path = match config_file_override { Some(cfg) => cfg.to_owned(), - None => guess_crate_root(udl_file)? - .join("uniffi.toml") - .canonicalize()?, + None => guess_crate_root(udl_file)?.join("uniffi.toml"), }; if !config_path.exists() { @@ -183,9 +209,8 @@ fn load_bindings_config_toml( Ok(full_config .get("bindings") - .map(|c| c.get(&BC::language_key())) - .flatten() - .cloned()) + .map(BC::get_entry_from_bindings_table) + .flatten()) } /// A trait representing a UniFFI Binding Generator @@ -197,36 +222,18 @@ pub trait BindingGenerator: Sized { /// uniffi.toml type Config: BindingGeneratorConfig; - /// A Constructor, allows passing configuration that was - /// parsed from the `uniffi.toml` to the type that implements - /// BindingGenerator - fn new(config: Self::Config) -> Self; - /// Writes the bindings to the output directory /// /// # Arguments - /// - `ci`: A reference to a [`ComponentInterface`] representing the interface - /// - `out_dir`: The path to where the binding generator should write the output bindings - fn write_bindings(&self, ci: &ComponentInterface, out_dir: &Path) -> anyhow::Result<()>; - - /// Compiles the bindings that are written by `write_bindings`, this is only relevant to run tests - /// and for languages that are compiled - /// - /// # Arguments - /// - `ci`: A reference to a [`ComponentInterface`] representing the interface + /// - `ci`: A [`ComponentInterface`] representing the interface + /// - `config`: A instance of the BindingGeneratorConfig associated with this type /// - `out_dir`: The path to where the binding generator should write the output bindings - fn compile_bindings(&self, _ci: &ComponentInterface, _out_dir: &Path) -> anyhow::Result<()> { - Ok(()) - } - - /// Runs a script against the written, and compiled bindings. This is only relevant to run tests - /// - /// # Arguments - /// - `out_dir`: The directory where the bindings are located - /// - `script_file`: The script file to run against the bindings - fn run_script(&self, _out_dir: &Path, _script_file: &Path) -> anyhow::Result<()> { - unimplemented!() - } + fn write_bindings( + &self, + ci: ComponentInterface, + config: Self::Config, + out_dir: &Path, + ) -> anyhow::Result<()>; } /// Generate bindings for an external binding generator @@ -239,21 +246,23 @@ pub trait BindingGenerator: Sized { /// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it /// /// # Arguments +/// - `binding_generator`: Type that implements BindingGenerator /// - `udl_file`: The path to the UDL file /// - `config_file_override`: The path to the configuration toml file, most likely called `uniffi.toml`. If [`None`], the function will try to guess based on the crate's root. /// - `out_dir_override`: The path to write the bindings to. If [`None`], it will be the path to the parent directory of the `udl_file` -pub fn generate_external_bindings>( - udl_file: P, - config_file_override: Option

, - out_dir_override: Option

, +pub fn generate_external_bindings( + binding_generator: impl BindingGenerator, + udl_file: impl AsRef, + config_file_override: Option>, + out_dir_override: Option>, ) -> Result<()> { let out_dir_override = out_dir_override.as_ref().map(|p| p.as_ref()); let config_file_override = config_file_override.as_ref().map(|p| p.as_ref()); let out_dir = get_out_dir(udl_file.as_ref(), out_dir_override)?; - let component = parse_udl(udl_file.as_ref())?; + let component = parse_udl(udl_file.as_ref()).context("Error parsing UDL")?; let bindings_config = load_bindings_config(&component, udl_file.as_ref(), config_file_override)?; - B::new(bindings_config).write_bindings(&component, out_dir.as_path()) + binding_generator.write_bindings(component, bindings_config, out_dir.as_path()) } // Generate the infrastructural Rust code for implementing the UDL interface, From fd0cf3ba8f63ed9cd10b1fd1b373652dce62ad02 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 17 Feb 2022 16:40:37 -0500 Subject: [PATCH 2/2] Added an external hack function --- uniffi/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index 557ae429b5..0025e907bb 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -639,6 +639,16 @@ where } } +// This function is not intended to be called, but can be used as for the to workaround +// https://github.com/rust-lang/rust/issues/50007 +// +// Basically, if another library adds uniffi as an external crate, then builds into a dylib, that +// library normally won't have the symbols from uniffi. However, if that library calls a uniffi +// function, then it *will* have the symbols. So as a workaround to 50007, we create a dummy +// function here, and call it from a dummy function in the library,. +#[no_mangle] +pub extern "C" fn extern_symbol_hack() {} + #[cfg(test)] mod test { use super::*;