diff --git a/Cargo.lock b/Cargo.lock index eb0ee9168..0bfa1478c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1592,6 +1592,7 @@ dependencies = [ "uniffi_build", "uniffi_core", "uniffi_macros", + "uniffi_meta", ] [[package]] diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index 0fc294e0d..5660f78f5 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -18,6 +18,7 @@ uniffi_bindgen = { path = "../uniffi_bindgen", version = "=0.28.3", optional = t uniffi_build = { path = "../uniffi_build", version = "=0.28.3", optional = true } uniffi_core = { path = "../uniffi_core", version = "=0.28.3" } uniffi_macros = { path = "../uniffi_macros", version = "=0.28.3" } +uniffi_meta = { path = "../uniffi_meta", version = "=0.28.3" } anyhow = "1" camino = { version = "1.0.8", optional = true } cargo_metadata = { version = "0.15", optional = true } diff --git a/uniffi/src/cli/uniffi_bindgen.rs b/uniffi/src/cli/uniffi_bindgen.rs index e3d5cf41e..3911d4b81 100644 --- a/uniffi/src/cli/uniffi_bindgen.rs +++ b/uniffi/src/cli/uniffi_bindgen.rs @@ -2,15 +2,17 @@ * 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 std::fmt; + use anyhow::{bail, Context, Result}; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; -use std::fmt; -use uniffi_bindgen::bindings::*; +use uniffi_bindgen::{bindings::*, cli_support}; +use uniffi_meta::MetadataGroup; /// Enumeration of all foreign language targets currently supported by our CLI. /// -#[derive(Copy, Clone, Eq, PartialEq, Hash, clap::ValueEnum)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, clap::Subcommand, clap::ValueEnum)] enum TargetLanguage { Kotlin, Swift, @@ -132,13 +134,143 @@ enum Commands { udl_file: Utf8PathBuf, }, - /// Print a debug representation of the interface from a dynamic library - PrintRepr { - /// Path to the library file (.so, .dll, .dylib, or .a) - path: Utf8PathBuf, + /// Print a stage of the bindings render pipeline + Peek { + /// Pass in a cdylib path rather than a UDL file + #[clap(long = "library")] + library_mode: bool, + + /// Path to the UDL file, or cdylib if `library-mode` is specified + source: Utf8PathBuf, + + /// When `--library` is passed, only generate bindings for one crate. + /// When `--library` is not passed, use this as the crate name instead of attempting to + /// locate and parse Cargo.toml. + #[clap(long = "crate")] + crate_name: Option, + + /// Whether we should exclude dependencies when running "cargo metadata". + /// This will mean external types may not be resolved if they are implemented in crates + /// outside of this workspace. + /// This can be used in environments when all types are in the namespace and fetching + /// all sub-dependencies causes obscure platform specific problems. + #[clap(long)] + metadata_no_deps: bool, + + /// Target to display + #[clap(subcommand)] + target: PeekTarget, + }, + + /// Save a stage of the bindings render pipeline for later diffing + DiffSave { + /// Pass in a cdylib path rather than a UDL file + #[clap(long = "library")] + library_mode: bool, + + /// Path to the UDL file, or cdylib if `library-mode` is specified + source: Utf8PathBuf, + + /// When `--library` is passed, only generate bindings for one crate. + /// When `--library` is not passed, use this as the crate name instead of attempting to + /// locate and parse Cargo.toml. + #[clap(long = "crate")] + crate_name: Option, + + /// Whether we should exclude dependencies when running "cargo metadata". + /// This will mean external types may not be resolved if they are implemented in crates + /// outside of this workspace. + /// This can be used in environments when all types are in the namespace and fetching + /// all sub-dependencies causes obscure platform specific problems. + #[clap(long)] + metadata_no_deps: bool, + }, + + /// Diff a stage of the bindings render pipeline against the data last saved with DiffSave + Diff { + /// Pass in a cdylib path rather than a UDL file + #[clap(long = "library")] + library_mode: bool, + + /// Path to the UDL file, or cdylib if `library-mode` is specified + source: Utf8PathBuf, + + /// When `--library` is passed, only generate bindings for one crate. + /// When `--library` is not passed, use this as the crate name instead of attempting to + /// locate and parse Cargo.toml. + #[clap(long = "crate")] + crate_name: Option, + + /// Whether we should exclude dependencies when running "cargo metadata". + /// This will mean external types may not be resolved if they are implemented in crates + /// outside of this workspace. + /// This can be used in environments when all types are in the namespace and fetching + /// all sub-dependencies causes obscure platform specific problems. + #[clap(long)] + metadata_no_deps: bool, + + /// Target to display + #[clap(subcommand)] + target: PeekTarget, }, } +#[derive(Clone, Debug, clap::Subcommand)] +enum PeekTarget { + Metadata, + Ir, + PythonIr, + Kotlin, + Swift, + Python, + Ruby, +} + +impl PeekTarget { + fn all() -> Vec { + vec![ + PeekTarget::Metadata, + PeekTarget::Ir, + PeekTarget::PythonIr, + PeekTarget::Kotlin, + PeekTarget::Swift, + PeekTarget::Python, + PeekTarget::Ruby, + ] + } +} + +impl fmt::Display for PeekTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Metadata => write!(f, "metadata"), + Self::Ir => write!(f, "ir"), + Self::PythonIr => write!(f, "python-ir"), + Self::Kotlin => write!(f, "kotlin"), + Self::Swift => write!(f, "swift"), + Self::Python => write!(f, "python"), + Self::Ruby => write!(f, "ruby"), + } + } +} + +fn config_supplier( + metadata_no_deps: bool, +) -> Result { + #[cfg(feature = "cargo-metadata")] + { + use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; + let mut cmd = cargo_metadata::MetadataCommand::new(); + if metadata_no_deps { + cmd.no_deps(); + } + let metadata = cmd.exec().context("error running cargo metadata")?; + Ok(CrateConfigSupplier::from(metadata)) + } + #[cfg(not(feature = "cargo-metadata"))] + Ok(Auniffi_bindgen::EmptyCrateConfigSupplier) +} + fn gen_library_mode( library_path: &camino::Utf8Path, crate_name: Option, @@ -150,18 +282,7 @@ fn gen_library_mode( ) -> anyhow::Result<()> { use uniffi_bindgen::library_mode::generate_bindings; - #[cfg(feature = "cargo-metadata")] - let config_supplier = { - use uniffi_bindgen::cargo_metadata::CrateConfigSupplier; - let mut cmd = cargo_metadata::MetadataCommand::new(); - if metadata_no_deps { - cmd.no_deps(); - } - let metadata = cmd.exec().context("error running cargo metadata")?; - CrateConfigSupplier::from(metadata) - }; - #[cfg(not(feature = "cargo-metadata"))] - let config_supplier = uniffi_bindgen::EmptyCrateConfigSupplier; + let config_supplier = config_supplier(metadata_no_deps)?; for language in languages { // to help avoid mistakes we check the library is actually a cdylib, except @@ -329,9 +450,198 @@ pub fn run_main() -> anyhow::Result<()> { !no_format, )?; } - Commands::PrintRepr { path } => { - uniffi_bindgen::print_repr(&path)?; + Commands::Peek { + crate_name, + source, + library_mode, + metadata_no_deps, + target, + } => { + let config_supplier = config_supplier(metadata_no_deps)?; + let (metadata, cdylib) = if library_mode { + let metadata = uniffi_bindgen::load_metadata_from_library( + &source, + crate_name.as_deref(), + config_supplier, + )?; + (metadata, Some(source.to_string())) + } else { + let metadata = + uniffi_bindgen::load_metadata_from_udl(&source, crate_name.as_deref())?; + (metadata, None) + }; + + cli_support::peek(get_peek_items(&target, metadata, cdylib, metadata_no_deps)?); + } + Commands::DiffSave { + crate_name, + source, + library_mode, + metadata_no_deps, + } => { + let config_supplier = config_supplier(metadata_no_deps)?; + let (metadata, cdylib) = if library_mode { + let metadata = uniffi_bindgen::load_metadata_from_library( + &source, + crate_name.as_deref(), + config_supplier, + )?; + (metadata, Some(source.to_string())) + } else { + let metadata = + uniffi_bindgen::load_metadata_from_udl(&source, crate_name.as_deref())?; + (metadata, None) + }; + + for target in PeekTarget::all() { + let diff_dir = cli_support::diff_dir_from_cargo_metadata(&target)?; + cli_support::save_diff( + &diff_dir, + get_peek_items(&target, metadata.clone(), cdylib.clone(), metadata_no_deps)?, + )? + } + } + Commands::Diff { + crate_name, + source, + library_mode, + metadata_no_deps, + target, + } => { + let config_supplier = config_supplier(metadata_no_deps)?; + let (metadata, cdylib) = if library_mode { + let metadata = uniffi_bindgen::load_metadata_from_library( + &source, + crate_name.as_deref(), + config_supplier, + )?; + (metadata, Some(source.to_string())) + } else { + let metadata = + uniffi_bindgen::load_metadata_from_udl(&source, crate_name.as_deref())?; + (metadata, None) + }; + + let diff_dir = cli_support::diff_dir_from_cargo_metadata(&target)?; + cli_support::diff( + &diff_dir, + get_peek_items(&target, metadata, cdylib, metadata_no_deps)?, + )?; } }; Ok(()) } + +fn get_peek_items( + target: &PeekTarget, + metadata: Vec, + cdylib: Option, + metadata_no_deps: bool, +) -> Result> { + match target { + PeekTarget::Metadata => metadata + .into_iter() + .map(|group| Ok((group.namespace.name.clone(), format!("{group:#?}")))) + .collect(), + PeekTarget::Ir => { + let irs = uniffi_bindgen::metadata_groups_to_irs(metadata)?; + irs.into_iter() + .map(|ir| Ok((ir.namespace.clone(), format!("{ir:#?}")))) + .collect() + } + PeekTarget::PythonIr => { + let irs_and_configs = uniffi_bindgen::metadata_groups_to_irs_and_configs( + metadata, + cdylib, + PythonBindingGenerator, + config_supplier(metadata_no_deps)?, + )?; + irs_and_configs + .into_iter() + .map(|(ir, config)| { + let ir = PythonBindingsIr::from_general_ir(ir, config)?; + Ok((ir.namespace.clone(), format!("{ir:#?}"))) + }) + .collect() + } + PeekTarget::Kotlin => { + let components = uniffi_bindgen::metadata_groups_to_components( + metadata, + cdylib, + KotlinBindingGenerator, + config_supplier(metadata_no_deps)?, + )?; + components + .into_iter() + .map(|component| { + let name = format!("{}.py", component.ci.namespace()); + let contents = kotlin::generate_bindings(&component.config, &component.ci)?; + Ok((name, contents)) + }) + .collect() + } + PeekTarget::Swift => { + let components = uniffi_bindgen::metadata_groups_to_components( + metadata, + cdylib, + SwiftBindingGenerator, + config_supplier(metadata_no_deps)?, + )?; + let mut all_content = vec![( + "module.modulemap".to_string(), + swift::generate_modulemap( + "module".to_string(), + components + .iter() + .map(|c| format!("{}.h", c.ci.namespace())) + .collect(), + false, + )?, + )]; + for component in components { + all_content.push(( + format!("{}.h", component.ci.namespace()), + swift::generate_header(&component.config, &component.ci)?, + )); + all_content.push(( + format!("{}.swift", component.ci.namespace()), + swift::generate_swift(&component.config, &component.ci)?, + )); + } + Ok(all_content) + } + PeekTarget::Python => { + let irs_and_configs = uniffi_bindgen::metadata_groups_to_irs_and_configs( + metadata, + cdylib, + PythonBindingGenerator, + config_supplier(metadata_no_deps)?, + )?; + irs_and_configs + .into_iter() + .map(|(ir, config)| { + let ir = PythonBindingsIr::from_general_ir(ir, config)?; + let name = format!("{}.py", ir.namespace); + let contents = python::generate_python_bindings_from_ir(ir)?; + Ok((name, contents)) + }) + .collect() + } + PeekTarget::Ruby => { + let components = uniffi_bindgen::metadata_groups_to_components( + metadata, + cdylib, + RubyBindingGenerator, + config_supplier(metadata_no_deps)?, + )?; + components + .into_iter() + .map(|component| { + let name = format!("{}.py", component.ci.namespace()); + let contents = ruby::generate_ruby_bindings(&component.config, &component.ci)?; + Ok((name, contents)) + }) + .collect() + } + } +} diff --git a/uniffi_bindgen/src/bindings/kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/mod.rs index 3b69cd8cf..256bc30de 100644 --- a/uniffi_bindgen/src/bindings/kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/mod.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::process::Command; mod gen_kotlin; -use gen_kotlin::{generate_bindings, Config}; +pub use gen_kotlin::{generate_bindings, Config}; #[cfg(feature = "bindgen-tests")] pub mod test; diff --git a/uniffi_bindgen/src/bindings/mod.rs b/uniffi_bindgen/src/bindings/mod.rs index f76dea50b..27a10d5c6 100644 --- a/uniffi_bindgen/src/bindings/mod.rs +++ b/uniffi_bindgen/src/bindings/mod.rs @@ -7,13 +7,13 @@ //! This module contains all the code for generating foreign language bindings, //! along with some helpers for executing foreign language scripts or tests. -mod kotlin; +pub mod kotlin; pub use kotlin::KotlinBindingGenerator; -mod python; -pub use python::PythonBindingGenerator; -mod ruby; +pub mod python; +pub use python::{PythonBindingGenerator, PythonBindingsIr}; +pub mod ruby; pub use ruby::RubyBindingGenerator; -mod swift; +pub mod swift; pub use swift::{generate_swift_bindings, SwiftBindingGenerator, SwiftBindingsOptions}; #[cfg(feature = "bindgen-tests")] diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index e51527471..d784fd0a3 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -33,6 +33,10 @@ pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Res .context("failed to render python bindings") } +pub fn generate_python_bindings_from_ir(ir: PythonBindingsIr) -> Result { + ir.render().context("failed to render python bindings") +} + // Config options to customize the generated python. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { diff --git a/uniffi_bindgen/src/bindings/python/mod.rs b/uniffi_bindgen/src/bindings/python/mod.rs index 428fa2fdf..7aa644dcc 100644 --- a/uniffi_bindgen/src/bindings/python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/mod.rs @@ -13,7 +13,9 @@ pub mod test; use crate::{BindingGenerator, Component, GenerationSettings}; -use gen_python::{generate_python_bindings, Config}; +pub use gen_python::{ + generate_python_bindings, generate_python_bindings_from_ir, Config, PythonBindingsIr, +}; pub struct PythonBindingGenerator; diff --git a/uniffi_bindgen/src/bindings/swift/mod.rs b/uniffi_bindgen/src/bindings/swift/mod.rs index a2ea84440..2a0d3e12b 100644 --- a/uniffi_bindgen/src/bindings/swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/mod.rs @@ -36,14 +36,16 @@ use fs_err as fs; use std::process::Command; mod gen_swift; -use gen_swift::{generate_bindings, generate_header, generate_modulemap, generate_swift, Config}; +pub use gen_swift::{ + generate_bindings, generate_header, generate_modulemap, generate_swift, Config, +}; #[cfg(feature = "bindgen-tests")] pub mod test; /// The Swift bindings generated from a [`crate::ComponentInterface`]. /// -struct Bindings { +pub struct Bindings { /// The contents of the generated `.swift` file, as a string. library: String, /// The contents of the generated `.h` file, as a string. diff --git a/uniffi_bindgen/src/cli_support.rs b/uniffi_bindgen/src/cli_support.rs new file mode 100644 index 000000000..17886d0b6 --- /dev/null +++ b/uniffi_bindgen/src/cli_support.rs @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +//! Support for the `peek` and `diff` CLI subcommands + +use std::{fs, io::Write, process::Command}; + +use anyhow::Result; +use camino::Utf8Path; + +#[cfg(feature = "cargo-metadata")] +pub fn diff_dir_from_cargo_metadata(target: impl ToString) -> Result { + let metadata = cargo_metadata::MetadataCommand::new().exec()?; + Ok(metadata + .target_directory + .join("diff") + .join(target.to_string())) +} + +pub fn peek(items: impl IntoIterator) { + for (name, contents) in items.into_iter() { + println!("-------------------- {name} --------------------"); + println!("{contents}"); + println!(); + } +} + +pub fn save_diff( + diff_dir: &Utf8Path, + items: impl IntoIterator, +) -> Result<()> { + let out_dir = diff_dir.join("old"); + if !out_dir.exists() { + fs::create_dir_all(&out_dir)?; + } + + for (name, contents) in items.into_iter() { + let mut file = fs::File::create(out_dir.join(name))?; + write!(file, "{contents}")?; + } + Ok(()) +} + +pub fn diff(diff_dir: &Utf8Path, items: impl IntoIterator) -> Result<()> { + let old_out_dir = diff_dir.join("old"); + let out_dir = diff_dir.join("new"); + if !old_out_dir.exists() { + fs::create_dir_all(&old_out_dir)?; + } + if !out_dir.exists() { + fs::create_dir_all(&out_dir)?; + } + + for (name, contents) in items.into_iter() { + let mut file = fs::File::create(out_dir.join(name))?; + write!(file, "{contents}")?; + } + Command::new("diff") + .args(["-dur", "old", "new", "--color=auto"]) + .current_dir(diff_dir) + .spawn()? + .wait()?; + + Ok(()) +} diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 2ee22fdf0..4f5a9f3c6 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -56,6 +56,7 @@ pub use uniffi_meta::{AsType, EnumShape, ExternalKind, ObjectImpl, Type}; use universe::{TypeIterator, TypeUniverse}; pub mod ir; +pub use ir::BindingsIr; mod callbacks; pub use callbacks::CallbackInterface; diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index ab5317a57..9572478e4 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -102,6 +102,7 @@ use std::process::Command; pub mod backend; pub mod bindings; +pub mod cli_support; pub mod interface; pub mod library_mode; pub mod macro_metadata; @@ -114,8 +115,9 @@ use crate::interface::{ Argument, Constructor, Enum, FfiArgument, FfiField, Field, Function, Method, Object, Record, Variant, }; -pub use interface::ComponentInterface; +pub use interface::{BindingsIr, ComponentInterface}; pub use library_mode::find_components; +pub use macro_metadata::{load_metadata_from_library, load_metadata_from_udl}; use scaffolding::RustScaffolding; use uniffi_meta::Type; @@ -392,6 +394,67 @@ pub fn generate_bindings( ) } +pub fn metadata_groups_to_irs( + metadata_groups: Vec, +) -> Result> { + metadata_groups + .into_iter() + .map(|metadata_group| { + let mut ci = ComponentInterface::new(&metadata_group.namespace.crate_name); + ci.add_metadata(metadata_group)?; + BindingsIr::try_from(ci) + }) + .collect() +} + +pub fn metadata_groups_to_components( + metadata_groups: Vec, + cdylib: Option, + binding_generator: B, + config_supplier: C, +) -> Result>> +where + B: BindingGenerator, + C: BindgenCrateConfigSupplier, +{ + let settings = GenerationSettings { + cdylib, + // Use placeholder values for these, they don't matter for this purpose + out_dir: "".into(), + try_format_code: false, + }; + let mut components = metadata_groups + .into_iter() + .map(|metadata_group| { + let toml_table = config_supplier + .get_toml(&metadata_group.namespace.crate_name)? + .unwrap_or_default(); + let config = binding_generator.new_config(&toml::Value::from(toml_table))?; + let mut ci = ComponentInterface::new(&metadata_group.namespace.crate_name); + ci.add_metadata(metadata_group)?; + Ok(Component { ci, config }) + }) + .collect::>>()?; + binding_generator.update_component_configs(&settings, &mut components)?; + Ok(components) +} + +pub fn metadata_groups_to_irs_and_configs( + metadata_groups: Vec, + cdylib: Option, + binding_generator: B, + config_supplier: C, +) -> Result> +where + B: BindingGenerator, + C: BindgenCrateConfigSupplier, +{ + metadata_groups_to_components(metadata_groups, cdylib, binding_generator, config_supplier)? + .into_iter() + .map(|component| Ok((BindingsIr::try_from(component.ci)?, component.config))) + .collect() +} + pub fn print_repr(library_path: &Utf8Path) -> Result<()> { let metadata = macro_metadata::extract_from_library(library_path)?; println!("{metadata:#?}"); diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index 3a6a3f013..2e4b43db6 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -143,7 +143,7 @@ pub fn find_components( .collect() } -fn load_udl_metadata( +pub fn load_udl_metadata( group: &MetadataGroup, crate_name: &str, config_supplier: &dyn BindgenCrateConfigSupplier, diff --git a/uniffi_bindgen/src/macro_metadata/mod.rs b/uniffi_bindgen/src/macro_metadata/mod.rs index bc5a0a790..ec5f3e796 100644 --- a/uniffi_bindgen/src/macro_metadata/mod.rs +++ b/uniffi_bindgen/src/macro_metadata/mod.rs @@ -2,9 +2,16 @@ * 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 super::ComponentInterface; -use anyhow::Context; +use crate::{ + crate_name_from_cargo_toml, BindgenCrateConfigSupplier, + {interface::ComponentInterface, library_mode::load_udl_metadata}, +}; +use anyhow::{bail, Context, Result}; use camino::Utf8Path; +use std::{collections::HashMap, fs}; +use uniffi_meta::{ + create_metadata_groups, fixup_external_type, group_metadata, Metadata, MetadataGroup, +}; mod ci; mod extract; @@ -15,10 +22,73 @@ pub use extract::extract_from_library; pub fn add_to_ci_from_library( iface: &mut ComponentInterface, library_path: &Utf8Path, -) -> anyhow::Result<()> { +) -> Result<()> { add_to_ci( iface, extract_from_library(library_path).context("Failed to extract proc-macro metadata")?, ) .context("Failed to add proc-macro metadata to ComponentInterface") } + +pub fn load_metadata_from_library( + library_path: &Utf8Path, + crate_name: Option<&str>, + config_supplier: impl BindgenCrateConfigSupplier, +) -> Result> { + let items = extract_from_library(library_path)?; + let mut metadata_groups = create_metadata_groups(&items); + group_metadata(&mut metadata_groups, items)?; + + // Collect and process all UDL from all groups at the start - the fixups + // of external types makes this tricky to do as we finalize the group. + let mut udl_items: HashMap = HashMap::new(); + + for group in metadata_groups.values() { + let crate_name = group.namespace.crate_name.clone(); + if let Some(mut metadata_group) = load_udl_metadata(group, &crate_name, &config_supplier)? { + // fixup the items. + metadata_group.items = metadata_group + .items + .into_iter() + .map(|item| fixup_external_type(item, &metadata_groups)) + // some items are both in UDL and library metadata. For many that's fine but + // uniffi-traits aren't trivial to compare meaning we end up with dupes. + // We filter out such problematic items here. + .filter(|item| !matches!(item, Metadata::UniffiTrait { .. })) + .collect(); + udl_items.insert(crate_name, metadata_group); + }; + } + + for group in metadata_groups.values_mut() { + if let Some(udl_group) = udl_items.remove(&group.namespace.crate_name) { + group.items.extend(udl_group.items); + } + } + + if let Some(crate_name) = crate_name { + let filtered: Vec = metadata_groups + .into_values() + .filter(|group| group.namespace.crate_name == crate_name) + .collect(); + match filtered.len() { + 0 => bail!("Crate {crate_name} not found in {library_path}"), + 1 => Ok(filtered), + n => bail!("{n} crates named {crate_name} found in {library_path}"), + } + } else { + Ok(metadata_groups.into_values().collect()) + } +} + +pub fn load_metadata_from_udl( + udl_path: &Utf8Path, + crate_name: Option<&str>, +) -> Result> { + let crate_name = crate_name + .map(|c| Ok(c.to_string())) + .unwrap_or_else(|| crate_name_from_cargo_toml(udl_path))?; + let udl = fs::read_to_string(udl_path) + .with_context(|| format!("Failed to read UDL from {udl_path}"))?; + Ok(vec![uniffi_udl::parse_udl(&udl, &crate_name)?]) +} diff --git a/uniffi_meta/src/group.rs b/uniffi_meta/src/group.rs index a977cbf4e..0d8581691 100644 --- a/uniffi_meta/src/group.rs +++ b/uniffi_meta/src/group.rs @@ -62,7 +62,7 @@ pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec) -> Ok(()) } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct MetadataGroup { pub namespace: NamespaceMetadata, pub namespace_docstring: Option,