diff --git a/CHANGELOG.md b/CHANGELOG.md index e7ff85d8ff..400e827e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,9 @@ ### What's changed? - The internal bindings generation has changed to make it friendlier for external language bindings. - However, this is likely to be a small **breaking change** for these bindings. + However, this a **breaking change** for these bindings. No consumers of any languages are impacted, only the maintainers of these language bindings. - ([#2066](https://github.com/mozilla/uniffi-rs/issues/2066)) + ([#2066](https://github.com/mozilla/uniffi-rs/issues/2066)), ([#2094](https://github.com/mozilla/uniffi-rs/pull/2094)) - The async runtime can be specified for constructors/methods, this will override the runtime specified at the impl block level. diff --git a/uniffi/src/cli.rs b/uniffi/src/cli.rs index 1688488ec6..09f408d628 100644 --- a/uniffi/src/cli.rs +++ b/uniffi/src/cli.rs @@ -141,6 +141,14 @@ fn gen_library_mode( ) -> anyhow::Result<()> { use uniffi_bindgen::library_mode::generate_bindings; for language in languages { + // to help avoid mistakes we check the library is actually a cdylib, except + // for swift where static libs are often used to extract the metadata. + if !matches!(language, TargetLanguage::Swift) && !uniffi_bindgen::is_cdylib(library_path) { + anyhow::bail!( + "Generate bindings for {language} requires a cdylib, but {library_path} was given" + ); + } + // Type-bounds on trait implementations makes selecting between languages a bit tedious. match language { TargetLanguage::Kotlin => generate_bindings( diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index 12fdeb2f46..54f29e8e4f 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize}; use crate::backend::TemplateExpression; use crate::interface::*; -use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -72,13 +71,13 @@ trait CodeType: Debug { // config options to customize the generated Kotlin. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { - package_name: Option, - cdylib_name: Option, + pub(super) package_name: Option, + pub(super) cdylib_name: Option, generate_immutable_records: Option, #[serde(default)] custom_types: HashMap, #[serde(default)] - external_packages: HashMap, + pub(super) external_packages: HashMap, #[serde(default)] android: bool, #[serde(default)] @@ -122,29 +121,6 @@ impl Config { } } -impl BindingsConfig for Config { - fn update_from_ci(&mut self, ci: &ComponentInterface) { - self.package_name - .get_or_insert_with(|| format!("uniffi.{}", ci.namespace())); - self.cdylib_name - .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); - } - - fn update_from_cdylib_name(&mut self, cdylib_name: &str) { - self.cdylib_name - .get_or_insert_with(|| cdylib_name.to_string()); - } - - fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) { - for (crate_name, config) in config_map { - if !self.external_packages.contains_key(crate_name) { - self.external_packages - .insert(crate_name.to_string(), config.package_name()); - } - } - } -} - // Generate kotlin bindings for the given ComponentInterface, as a string. pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { KotlinWrapper::new(config.clone(), ci) diff --git a/uniffi_bindgen/src/bindings/kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/mod.rs index 3bcb876beb..4a447d13d9 100644 --- a/uniffi_bindgen/src/bindings/kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/mod.rs @@ -2,10 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::{BindingGenerator, ComponentInterface}; +use crate::{BindingGenerator, Component, GenerationSettings}; use anyhow::Result; use camino::{Utf8Path, Utf8PathBuf}; use fs_err as fs; +use std::collections::HashMap; use std::process::Command; mod gen_kotlin; @@ -26,33 +27,60 @@ impl BindingGenerator for KotlinBindingGenerator { ) } - fn write_bindings( + fn update_component_configs( &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, + settings: &GenerationSettings, + components: &mut Vec>, ) -> Result<()> { - let mut kt_file = full_bindings_path(config, out_dir); - fs::create_dir_all(&kt_file)?; - kt_file.push(format!("{}.kt", ci.namespace())); - fs::write(&kt_file, generate_bindings(config, ci)?)?; - if try_format_code { - if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() { - println!( - "Warning: Unable to auto-format {} using ktlint: {e:?}", - kt_file.file_name().unwrap(), - ); + for c in &mut *components { + c.config + .package_name + .get_or_insert_with(|| format!("uniffi.{}", c.ci.namespace())); + c.config.cdylib_name.get_or_insert_with(|| { + settings + .cdylib + .clone() + .unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace())) + }); + } + // We need to update package names + let packages = HashMap::::from_iter( + components + .iter() + .map(|c| (c.ci.crate_name().to_string(), c.config.package_name())), + ); + for c in components { + for (ext_crate, ext_package) in &packages { + if ext_crate != c.ci.crate_name() + && !c.config.external_packages.contains_key(ext_crate) + { + c.config + .external_packages + .insert(ext_crate.to_string(), ext_package.clone()); + } } } Ok(()) } - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - anyhow::bail!( - "Generate bindings for Kotlin requires a cdylib, but {library_path} was given" - ); + fn write_bindings( + &self, + settings: &GenerationSettings, + components: &[Component], + ) -> Result<()> { + for Component { ci, config, .. } in components { + let mut kt_file = full_bindings_path(config, &settings.out_dir); + fs::create_dir_all(&kt_file)?; + kt_file.push(format!("{}.kt", ci.namespace())); + fs::write(&kt_file, generate_bindings(config, ci)?)?; + if settings.try_format_code { + if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() { + println!( + "Warning: Unable to auto-format {} using ktlint: {e:?}", + kt_file.file_name().unwrap(), + ); + } + } } Ok(()) } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 1ec714f047..61ab04166e 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -16,7 +16,6 @@ use std::fmt::Debug; use crate::backend::TemplateExpression; use crate::interface::*; -use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -112,7 +111,7 @@ static KEYWORDS: Lazy> = Lazy::new(|| { // Config options to customize the generated python. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { - cdylib_name: Option, + pub(super) cdylib_name: Option, #[serde(default)] custom_types: HashMap, #[serde(default)] @@ -148,18 +147,6 @@ impl Config { } } -impl BindingsConfig for Config { - fn update_from_ci(&mut self, ci: &ComponentInterface) { - self.cdylib_name - .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); - } - - fn update_from_cdylib_name(&mut self, cdylib_name: &str) { - self.cdylib_name - .get_or_insert_with(|| cdylib_name.to_string()); - } -} - // Generate python bindings for the given ComponentInterface, as a string. pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result { PythonWrapper::new(config.clone(), ci) diff --git a/uniffi_bindgen/src/bindings/python/mod.rs b/uniffi_bindgen/src/bindings/python/mod.rs index 31bbfa4a43..04c8f7f76c 100644 --- a/uniffi_bindgen/src/bindings/python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/mod.rs @@ -5,12 +5,11 @@ use std::process::Command; use anyhow::Result; -use camino::Utf8Path; use fs_err as fs; mod gen_python; mod test; -use super::super::interface::ComponentInterface; +use crate::{Component, GenerationSettings}; use gen_python::{generate_python_bindings, Config}; pub use test::{run_script, run_test}; @@ -28,34 +27,41 @@ impl crate::BindingGenerator for PythonBindingGenerator { ) } - fn write_bindings( + fn update_component_configs( &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, + settings: &GenerationSettings, + components: &mut Vec>, ) -> Result<()> { - let py_file = out_dir.join(format!("{}.py", ci.namespace())); - fs::write(&py_file, generate_python_bindings(config, ci)?)?; - - if try_format_code { - if let Err(e) = Command::new("yapf").arg(&py_file).output() { - println!( - "Warning: Unable to auto-format {} using yapf: {e:?}", - py_file.file_name().unwrap(), - ) - } + for c in &mut *components { + c.config.cdylib_name.get_or_insert_with(|| { + settings + .cdylib + .clone() + .unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace())) + }); } - Ok(()) } - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - anyhow::bail!( - "Generate bindings for Python requires a cdylib, but {library_path} was given" - ); + fn write_bindings( + &self, + settings: &GenerationSettings, + components: &[Component], + ) -> Result<()> { + for Component { ci, config, .. } in components { + let py_file = settings.out_dir.join(format!("{}.py", ci.namespace())); + fs::write(&py_file, generate_python_bindings(config, ci)?)?; + + if settings.try_format_code { + if let Err(e) = Command::new("yapf").arg(&py_file).output() { + println!( + "Warning: Unable to auto-format {} using yapf: {e:?}", + py_file.file_name().unwrap(), + ) + } + } } + Ok(()) } } diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index ba6f69ff3d..7608acd7c2 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use crate::interface::*; -use crate::BindingsConfig; const RESERVED_WORDS: &[&str] = &[ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", @@ -82,7 +81,7 @@ pub fn canonical_name(t: &Type) -> String { // since the details of the underlying component are entirely determined by the `ComponentInterface`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { - cdylib_name: Option, + pub(super) cdylib_name: Option, cdylib_path: Option, } @@ -102,18 +101,6 @@ impl Config { } } -impl BindingsConfig for Config { - fn update_from_ci(&mut self, ci: &ComponentInterface) { - self.cdylib_name - .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); - } - - fn update_from_cdylib_name(&mut self, cdylib_name: &str) { - self.cdylib_name - .get_or_insert_with(|| cdylib_name.to_string()); - } -} - #[derive(Template)] #[template(syntax = "rb", escape = "none", path = "wrapper.rb")] pub struct RubyWrapper<'a> { diff --git a/uniffi_bindgen/src/bindings/ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/mod.rs index d7cd43150e..f970b50fc1 100644 --- a/uniffi_bindgen/src/bindings/ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/mod.rs @@ -4,9 +4,8 @@ use std::process::Command; -use crate::{BindingGenerator, ComponentInterface}; +use crate::{BindingGenerator, Component, ComponentInterface, GenerationSettings}; use anyhow::{Context, Result}; -use camino::Utf8Path; use fs_err as fs; mod gen_ruby; @@ -27,33 +26,39 @@ impl BindingGenerator for RubyBindingGenerator { ) } - fn write_bindings( + fn update_component_configs( &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, + settings: &GenerationSettings, + components: &mut Vec>, ) -> Result<()> { - let rb_file = out_dir.join(format!("{}.rb", ci.namespace())); - fs::write(&rb_file, generate_ruby_bindings(config, ci)?)?; - - if try_format_code { - if let Err(e) = Command::new("rubocop").arg("-A").arg(&rb_file).output() { - println!( - "Warning: Unable to auto-format {} using rubocop: {e:?}", - rb_file.file_name().unwrap(), - ) - } + for c in &mut *components { + c.config.cdylib_name.get_or_insert_with(|| { + settings + .cdylib + .clone() + .unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace())) + }); } - Ok(()) } - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - anyhow::bail!( - "Generate bindings for Ruby requires a cdylib, but {library_path} was given" - ); + fn write_bindings( + &self, + settings: &GenerationSettings, + components: &[Component], + ) -> Result<()> { + for Component { ci, config, .. } in components { + let rb_file = settings.out_dir.join(format!("{}.rb", ci.namespace())); + fs::write(&rb_file, generate_ruby_bindings(config, ci)?)?; + + if settings.try_format_code { + if let Err(e) = Command::new("rubocop").arg("-A").arg(&rb_file).output() { + println!( + "Warning: Unable to auto-format {} using rubocop: {e:?}", + rb_file.file_name().unwrap(), + ) + } + } } Ok(()) } diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 533fd884f1..b0b14765c0 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -18,7 +18,6 @@ use super::Bindings; use crate::backend::TemplateExpression; use crate::interface::*; -use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -191,8 +190,8 @@ pub fn quote_arg_keyword(nm: String) -> String { /// since the details of the underlying component are entirely determined by the `ComponentInterface`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { - cdylib_name: Option, - module_name: Option, + pub(super) cdylib_name: Option, + pub(super) module_name: Option, ffi_module_name: Option, ffi_module_filename: Option, generate_module_map: Option, @@ -276,20 +275,6 @@ impl Config { } } -impl BindingsConfig for Config { - fn update_from_ci(&mut self, ci: &ComponentInterface) { - self.module_name - .get_or_insert_with(|| ci.namespace().into()); - self.cdylib_name - .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); - } - - fn update_from_cdylib_name(&mut self, cdylib_name: &str) { - self.cdylib_name - .get_or_insert_with(|| cdylib_name.to_string()); - } -} - /// Generate UniFFI component bindings for Swift, as strings in memory. /// pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { diff --git a/uniffi_bindgen/src/bindings/swift/mod.rs b/uniffi_bindgen/src/bindings/swift/mod.rs index f30fbbb190..f625e5863d 100644 --- a/uniffi_bindgen/src/bindings/swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/mod.rs @@ -4,7 +4,7 @@ //! # Swift bindings backend for UniFFI //! -//! This module generates Swift bindings from a [`ComponentInterface`] definition, +//! This module generates Swift bindings from a [`crate::ComponentInterface`] definition, //! using Swift's builtin support for loading C header files. //! //! Conceptually, the generated bindings are split into two Swift modules, one for the low-level @@ -17,7 +17,7 @@ //! * A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it //! to provide the higher-level Swift API. //! -//! Most of the concepts in a [`ComponentInterface`] have an obvious counterpart in Swift, +//! Most of the concepts in a [`crate::ComponentInterface`] have an obvious counterpart in Swift, //! with the details documented in inline comments where appropriate. //! //! To handle lifting/lowering/serializing types across the FFI boundary, the Swift code @@ -29,9 +29,8 @@ //! * How to read from and write into a byte buffer. //! -use crate::{BindingGenerator, ComponentInterface}; +use crate::{BindingGenerator, Component, GenerationSettings}; use anyhow::Result; -use camino::Utf8Path; use fs_err as fs; use std::process::Command; @@ -40,7 +39,7 @@ use gen_swift::{generate_bindings, Config}; mod test; pub use test::{run_script, run_test}; -/// The Swift bindings generated from a [`ComponentInterface`]. +/// The Swift bindings generated from a [`crate::ComponentInterface`]. /// struct Bindings { /// The contents of the generated `.swift` file, as a string. @@ -64,53 +63,66 @@ impl BindingGenerator for SwiftBindingGenerator { ) } + fn update_component_configs( + &self, + settings: &GenerationSettings, + components: &mut Vec>, + ) -> Result<()> { + for c in &mut *components { + c.config + .module_name + .get_or_insert_with(|| c.ci.namespace().into()); + c.config.cdylib_name.get_or_insert_with(|| { + settings + .cdylib + .clone() + .unwrap_or_else(|| format!("uniffi_{}", c.ci.namespace())) + }); + } + Ok(()) + } + /// Unlike other target languages, binding to Rust code from Swift involves more than just /// generating a `.swift` file. We also need to produce a `.h` file with the C-level API /// declarations, and a `.modulemap` file to tell Swift how to use it. fn write_bindings( &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, + settings: &GenerationSettings, + components: &[Component], ) -> Result<()> { - let Bindings { - header, - library, - modulemap, - } = generate_bindings(config, ci)?; + for Component { ci, config, .. } in components { + let Bindings { + header, + library, + modulemap, + } = generate_bindings(config, ci)?; - let source_file = out_dir.join(format!("{}.swift", config.module_name())); - fs::write(&source_file, library)?; + let source_file = settings + .out_dir + .join(format!("{}.swift", config.module_name())); + fs::write(&source_file, library)?; - let header_file = out_dir.join(config.header_filename()); - fs::write(header_file, header)?; + let header_file = settings.out_dir.join(config.header_filename()); + fs::write(header_file, header)?; - if let Some(modulemap) = modulemap { - let modulemap_file = out_dir.join(config.modulemap_filename()); - fs::write(modulemap_file, modulemap)?; - } + if let Some(modulemap) = modulemap { + let modulemap_file = settings.out_dir.join(config.modulemap_filename()); + fs::write(modulemap_file, modulemap)?; + } - if try_format_code { - if let Err(e) = Command::new("swiftformat") - .arg(source_file.as_str()) - .output() - { - println!( - "Warning: Unable to auto-format {} using swiftformat: {e:?}", - source_file.file_name().unwrap(), - ); + if settings.try_format_code { + if let Err(e) = Command::new("swiftformat") + .arg(source_file.as_str()) + .output() + { + println!( + "Warning: Unable to auto-format {} using swiftformat: {e:?}", + source_file.file_name().unwrap(), + ); + } } } Ok(()) } - - fn check_library_path( - &self, - _library_path: &Utf8Path, - _cdylib_name: Option<&str>, - ) -> Result<()> { - Ok(()) - } } diff --git a/uniffi_bindgen/src/bindings/swift/test.rs b/uniffi_bindgen/src/bindings/swift/test.rs index d5378253b9..7417775295 100644 --- a/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi_bindgen/src/bindings/swift/test.rs @@ -124,7 +124,7 @@ struct GeneratedSources { } impl GeneratedSources { - fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result { + fn new(package_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result { let sources = generate_bindings( cdylib_path, None, @@ -135,7 +135,7 @@ impl GeneratedSources { )?; let main_source = sources .iter() - .find(|s| s.package.name == crate_name) + .find(|s| s.package_name.as_deref() == Some(package_name)) .unwrap(); let main_module = main_source.config.module_name(); let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index 5df9aade90..18f20f3ad6 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -95,10 +95,10 @@ use anyhow::{anyhow, bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use fs_err::{self as fs, File}; -use serde::{de::DeserializeOwned, Deserialize}; +use serde::Deserialize; use std::io::prelude::*; use std::io::ErrorKind; -use std::{collections::HashMap, process::Command}; +use std::process::Command; pub mod backend; pub mod bindings; @@ -110,24 +110,16 @@ pub mod scaffolding; pub use interface::ComponentInterface; use scaffolding::RustScaffolding; -/// Trait for bindings configuration. Each bindings language defines one of these. -/// -/// BindingsConfigs are initially loaded from `uniffi.toml` file. Then the trait methods are used -/// to fill in missing values. -pub trait BindingsConfig: DeserializeOwned { - /// Update missing values using the `ComponentInterface` - fn update_from_ci(&mut self, ci: &ComponentInterface); - - /// Update missing values using the dylib file for the main crate, when in library mode. - /// - /// cdylib_name will be the library filename without the leading `lib` and trailing extension - fn update_from_cdylib_name(&mut self, cdylib_name: &str); - - /// Update missing values from config instances from dependent crates - /// - /// config_map maps crate names to config instances. This is mostly used to set up external - /// types. - fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +/// The options used when creating bindings. Named such +/// it doesn't cause confusion that it's settings specific to +/// the generator itself. +// TODO: We should try and move the public interface of the module to +// this struct. For now, only the BindingGenerator uses it. +#[derive(Debug, Default)] +pub struct GenerationSettings { + pub out_dir: Utf8PathBuf, + pub try_format_code: bool, + pub cdylib: Option, } /// A trait representing a UniFFI Binding Generator @@ -136,27 +128,47 @@ pub trait BindingsConfig: DeserializeOwned { /// and call the [`generate_external_bindings`] using a type that implements this trait. pub trait BindingGenerator: Sized { /// Handles configuring the bindings - type Config: BindingsConfig; + type Config; /// Creates a new config. fn new_config(&self, root_toml: &toml::Value) -> Result; + /// Update the various config items in preparation to write one or more of them. + /// + /// # Arguments + /// - `cdylib`: The name of the cdylib file, if known. + /// - `library_path`: The name of library used to extract the symbols. + /// - `components`: A mutable array of [`Component`]s to be updated. + fn update_component_configs( + &self, + settings: &GenerationSettings, + components: &mut Vec>, + ) -> Result<()>; + /// Writes the bindings to the output directory /// /// # Arguments - /// - `ci`: A [`ComponentInterface`] representing the interface - /// - `config`: An instance of the [`BindingsConfig`] associated with this type + /// - `components`: An array of [`Component`]s representing the items to be generated. /// - `out_dir`: The path to where the binding generator should write the output bindings fn write_bindings( &self, - ci: &ComponentInterface, - config: &Self::Config, - out_dir: &Utf8Path, - try_format_code: bool, + settings: &GenerationSettings, + components: &[Component], ) -> Result<()>; +} + +/// Everything needed to generate a ComponentInterface. +#[derive(Debug)] +pub struct Component { + pub ci: ComponentInterface, + pub config: Config, + pub package_name: Option, +} - /// Check if `library_path` used by library mode is valid for this generator - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>; +/// A convenience function for the CLI to help avoid using static libs +/// in places cdylibs are required. +pub fn is_cdylib(library_file: impl AsRef) -> bool { + crate::library_mode::calc_cdylib_name(library_file.as_ref()).is_some() } /// Generate bindings for an external binding generator @@ -165,7 +177,6 @@ pub trait BindingGenerator: Sized { /// Implements an entry point for external binding generators. /// The function does the following: /// - It parses the `udl` in a [`ComponentInterface`] -/// - Parses the `uniffi.toml` and loads it into the type that implements [`BindingsConfig`] /// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it /// /// # Arguments @@ -187,9 +198,9 @@ pub fn generate_external_bindings( let crate_name = crate_name .map(|c| Ok(c.to_string())) .unwrap_or_else(|| crate_name_from_cargo_toml(udl_file.as_ref()))?; - let mut component = parse_udl(udl_file.as_ref(), &crate_name)?; + let mut ci = parse_udl(udl_file.as_ref(), &crate_name)?; if let Some(ref library_file) = library_file { - macro_metadata::add_to_ci_from_library(&mut component, library_file.as_ref())?; + macro_metadata::add_to_ci_from_library(&mut ci, library_file.as_ref())?; } let crate_root = &guess_crate_root(udl_file.as_ref()).context("Failed to guess crate root")?; @@ -197,22 +208,30 @@ pub fn generate_external_bindings( let config = { let toml = load_initial_config(crate_root, config_file_override)?; - let mut config = binding_generator.new_config(&toml)?; - config.update_from_ci(&component); - if let Some(ref library_file) = library_file { - if let Some(cdylib_name) = crate::library_mode::calc_cdylib_name(library_file.as_ref()) - { - config.update_from_cdylib_name(cdylib_name) + binding_generator.new_config(&toml)? + }; + + let settings = GenerationSettings { + cdylib: match library_file { + Some(ref library_file) => { + crate::library_mode::calc_cdylib_name(library_file.as_ref()).map(ToOwned::to_owned) } - }; - config + None => None, + }, + out_dir: get_out_dir( + udl_file.as_ref(), + out_dir_override.as_ref().map(|p| p.as_ref()), + )?, + try_format_code, }; - let out_dir = get_out_dir( - udl_file.as_ref(), - out_dir_override.as_ref().map(|p| p.as_ref()), - )?; - binding_generator.write_bindings(&component, &config, &out_dir, try_format_code) + let mut components = vec![Component { + ci, + config, + package_name: None, + }]; + binding_generator.update_component_configs(&settings, &mut components)?; + binding_generator.write_bindings(&settings, &components) } // Generate the infrastructural Rust code for implementing the UDL interface, diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index d7749e1677..47dfc75504 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -16,16 +16,13 @@ /// - UniFFI can figure out the package/module names for each crate, eliminating the external /// package maps. use crate::{ - load_initial_config, macro_metadata, BindingGenerator, BindingsConfig, ComponentInterface, - Result, + load_initial_config, macro_metadata, BindingGenerator, Component, ComponentInterface, + GenerationSettings, Result, }; use anyhow::{bail, Context}; use camino::Utf8Path; use cargo_metadata::{MetadataCommand, Package}; -use std::{ - collections::{HashMap, HashSet}, - fs, -}; +use std::{collections::HashMap, fs}; use uniffi_meta::{ create_metadata_groups, fixup_external_type, group_metadata, Metadata, MetadataGroup, }; @@ -33,6 +30,10 @@ use uniffi_meta::{ /// Generate foreign bindings /// /// Returns the list of sources used to generate the bindings, in no particular order. +// XXX - we should consider killing this function and replace it with a function +// which just locates the `Components` and returns them, leaving the filtering +// and actual generation to the callers, which also would allow removing the potentially +// confusing crate_name param. pub fn generate_bindings( library_path: &Utf8Path, crate_name: Option, @@ -40,101 +41,51 @@ pub fn generate_bindings( config_file_override: Option<&Utf8Path>, out_dir: &Utf8Path, try_format_code: bool, -) -> Result>> { - generate_external_bindings( - binding_generator, - library_path, - crate_name.clone(), - config_file_override, - out_dir, - try_format_code, - ) -} - -/// Generate foreign bindings -/// -/// Returns the list of sources used to generate the bindings, in no particular order. -pub fn generate_external_bindings( - binding_generator: &T, - library_path: &Utf8Path, - crate_name: Option, - config_file_override: Option<&Utf8Path>, - out_dir: &Utf8Path, - try_format_code: bool, -) -> Result>> { +) -> Result>> { let cargo_metadata = MetadataCommand::new() .exec() .context("error running cargo metadata")?; - let cdylib_name = calc_cdylib_name(library_path); - binding_generator.check_library_path(library_path, cdylib_name)?; - let mut sources = find_components(&cargo_metadata, library_path)? + let mut components = find_components(&cargo_metadata, library_path)? .into_iter() .map(|(ci, package)| { let crate_root = package .manifest_path .parent() .context("manifest path has no parent")?; - let mut config = binding_generator + let config = binding_generator .new_config(&load_initial_config(crate_root, config_file_override)?)?; - if let Some(cdylib_name) = cdylib_name { - config.update_from_cdylib_name(cdylib_name); - } - config.update_from_ci(&ci); - Ok(Source { - config, + Ok(Component { ci, - package, + config, + package_name: Some(package.name), }) }) .collect::>>()?; - for i in 0..sources.len() { - // Partition up the sources list because we're eventually going to call - // `update_from_dependency_configs()` which requires an exclusive reference to one source and - // shared references to all other sources. - let (sources_before, rest) = sources.split_at_mut(i); - let (source, sources_after) = rest.split_first_mut().unwrap(); - let other_sources = sources_before.iter().chain(sources_after.iter()); - // Calculate which configs come from dependent crates - let dependencies = - HashSet::<&str>::from_iter(source.package.dependencies.iter().map(|d| d.name.as_str())); - let config_map: HashMap<&str, &T::Config> = other_sources - .filter_map(|s| { - dependencies - .contains(s.package.name.as_str()) - .then_some((s.ci.crate_name(), &s.config)) - }) - .collect(); - // We can finally call update_from_dependency_configs - source.config.update_from_dependency_configs(config_map); - } + let settings = GenerationSettings { + out_dir: out_dir.to_owned(), + try_format_code, + cdylib: calc_cdylib_name(library_path).map(ToOwned::to_owned), + }; + binding_generator.update_component_configs(&settings, &mut components)?; + fs::create_dir_all(out_dir)?; if let Some(crate_name) = &crate_name { - let old_elements = sources.drain(..); + let old_elements = components.drain(..); let mut matches: Vec<_> = old_elements .filter(|s| s.ci.crate_name() == crate_name) .collect(); match matches.len() { 0 => bail!("Crate {crate_name} not found in {library_path}"), - 1 => sources.push(matches.pop().unwrap()), + 1 => components.push(matches.pop().unwrap()), n => bail!("{n} crates named {crate_name} found in {library_path}"), } } - for source in sources.iter() { - binding_generator.write_bindings(&source.ci, &source.config, out_dir, try_format_code)?; - } - - Ok(sources) -} + binding_generator.write_bindings(&settings, &components)?; -// A single source that we generate bindings for -#[derive(Debug)] -pub struct Source { - pub package: Package, - pub ci: ComponentInterface, - pub config: Config, + Ok(components) } // If `library_path` is a C dynamic library, return its name