diff --git a/CHANGELOG.md b/CHANGELOG.md index 22523cc7c7..6df36bc3e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ ([#2073](https://github.com/mozilla/uniffi-rs/issues/2073)) ### 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. + No consumers of any languages are impacted, only the maintainers of these language bindings. + ([#2066](https://github.com/mozilla/uniffi-rs/issues/2066)) + - The async runtime can be specified for constructors/methods, this will override the runtime specified at the impl block level. [All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.27.1...HEAD). diff --git a/fixtures/benchmarks/benches/benchmarks.rs b/fixtures/benchmarks/benches/benchmarks.rs index 17282c7a45..891d9821e4 100644 --- a/fixtures/benchmarks/benches/benchmarks.rs +++ b/fixtures/benchmarks/benches/benchmarks.rs @@ -5,7 +5,9 @@ use clap::Parser; use std::env; use uniffi_benchmarks::Args; -use uniffi_bindgen::bindings::{kotlin, python, swift, RunScriptOptions}; +use uniffi_bindgen::bindings::{ + kotlin_run_script, python_run_script, swift_run_script, RunScriptOptions, +}; fn main() { let args = Args::parse(); @@ -18,7 +20,7 @@ fn main() { }; if args.should_run_python() { - python::run_script( + python_run_script( std::env!("CARGO_TARGET_TMPDIR"), "uniffi-fixture-benchmarks", "benches/bindings/run_benchmarks.py", @@ -29,7 +31,7 @@ fn main() { } if args.should_run_kotlin() { - kotlin::run_script( + kotlin_run_script( std::env!("CARGO_TARGET_TMPDIR"), "uniffi-fixture-benchmarks", "benches/bindings/run_benchmarks.kts", @@ -40,7 +42,7 @@ fn main() { } if args.should_run_swift() { - swift::run_script( + swift_run_script( std::env!("CARGO_TARGET_TMPDIR"), "uniffi-fixture-benchmarks", "benches/bindings/run_benchmarks.swift", diff --git a/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs b/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs index f622d5e698..61c3d6e89d 100644 --- a/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs +++ b/fixtures/docstring-proc-macro/tests/test_generated_bindings.rs @@ -6,8 +6,7 @@ uniffi::build_foreign_language_testcases!( #[cfg(test)] mod tests { - use uniffi_bindgen::bindings::TargetLanguage; - use uniffi_bindgen::BindingGeneratorDefault; + use uniffi_bindgen::{bindings::*, BindingGenerator}; use uniffi_testing::UniFFITestHelper; const DOCSTRINGS: &[&str] = &[ @@ -36,13 +35,16 @@ mod tests { "", ]; - fn test_docstring(language: TargetLanguage, file_extension: &str) { + fn test_docstring(gen: T, file_extension: &str) { let test_helper = UniFFITestHelper::new(std::env!("CARGO_PKG_NAME")).unwrap(); let out_dir = test_helper .create_out_dir( std::env!("CARGO_TARGET_TMPDIR"), - format!("test-docstring-proc-macro-{}", language), + format!( + "test-docstring-proc-macro-{}", + file_extension.to_string().replace('.', "") + ), ) .unwrap(); @@ -51,10 +53,7 @@ mod tests { uniffi_bindgen::library_mode::generate_bindings( &cdylib_path, None, - &BindingGeneratorDefault { - target_languages: vec![language], - try_format_code: false, - }, + &gen, None, &out_dir, false, @@ -88,16 +87,16 @@ mod tests { #[test] fn test_docstring_kotlin() { - test_docstring(TargetLanguage::Kotlin, "kt"); + test_docstring(KotlinBindingGenerator, "kt"); } #[test] fn test_docstring_python() { - test_docstring(TargetLanguage::Python, "py"); + test_docstring(PythonBindingGenerator, "py"); } #[test] fn test_docstring_swift() { - test_docstring(TargetLanguage::Swift, "swift"); + test_docstring(SwiftBindingGenerator, "swift"); } } diff --git a/fixtures/docstring/tests/test_generated_bindings.rs b/fixtures/docstring/tests/test_generated_bindings.rs index 575138babe..51935674ac 100644 --- a/fixtures/docstring/tests/test_generated_bindings.rs +++ b/fixtures/docstring/tests/test_generated_bindings.rs @@ -7,8 +7,7 @@ uniffi::build_foreign_language_testcases!( #[cfg(test)] mod tests { use camino::Utf8PathBuf; - use uniffi_bindgen::bindings::TargetLanguage; - use uniffi_bindgen::BindingGeneratorDefault; + use uniffi_bindgen::{bindings::*, BindingGenerator}; use uniffi_testing::UniFFITestHelper; const DOCSTRINGS: &[&str] = &[ @@ -36,23 +35,23 @@ mod tests { "", ]; - fn test_docstring(language: TargetLanguage, file_extension: &str) { + fn test_docstring(gen: T, file_extension: &str) { let test_helper = UniFFITestHelper::new(std::env!("CARGO_PKG_NAME")).unwrap(); let out_dir = test_helper .create_out_dir( std::env!("CARGO_TARGET_TMPDIR"), - format!("test-docstring-{}", language), + format!( + "test-docstring-{}", + file_extension.to_string().replace('.', "") + ), ) .unwrap(); uniffi_bindgen::generate_bindings( &Utf8PathBuf::from("src/docstring.udl"), None, - BindingGeneratorDefault { - target_languages: vec![language], - try_format_code: false, - }, + gen, Some(&out_dir), None, None, @@ -87,16 +86,16 @@ mod tests { #[test] fn test_docstring_kotlin() { - test_docstring(TargetLanguage::Kotlin, "kt"); + test_docstring(KotlinBindingGenerator, "kt"); } #[test] fn test_docstring_python() { - test_docstring(TargetLanguage::Python, "py"); + test_docstring(PythonBindingGenerator, "py"); } #[test] fn test_docstring_swift() { - test_docstring(TargetLanguage::Swift, "swift"); + test_docstring(SwiftBindingGenerator, "swift"); } } diff --git a/uniffi/src/cli.rs b/uniffi/src/cli.rs index 77d7f219a9..2b7e0bc5a2 100644 --- a/uniffi/src/cli.rs +++ b/uniffi/src/cli.rs @@ -4,8 +4,7 @@ use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; -use uniffi_bindgen::bindings::TargetLanguage; -use uniffi_bindgen::BindingGeneratorDefault; +use uniffi_bindgen::bindings::*; // Structs to help our cmdline parsing. Note that docstrings below form part // of the "help" output. @@ -79,6 +78,112 @@ enum Commands { }, } +fn gen_library_mode( + library_path: &camino::Utf8Path, + crate_name: Option, + languages: Vec, + cfo: Option<&camino::Utf8Path>, + out_dir: &camino::Utf8Path, + fmt: bool, +) -> anyhow::Result<()> { + use uniffi_bindgen::library_mode::generate_bindings; + for language in languages { + // Type-bounds on trait implementations makes selecting between languages a bit tedious. + match language { + TargetLanguage::Kotlin => generate_bindings( + library_path, + crate_name.clone(), + &KotlinBindingGenerator, + cfo, + out_dir, + fmt, + )? + .len(), + TargetLanguage::Python => generate_bindings( + library_path, + crate_name.clone(), + &PythonBindingGenerator, + cfo, + out_dir, + fmt, + )? + .len(), + TargetLanguage::Ruby => generate_bindings( + library_path, + crate_name.clone(), + &RubyBindingGenerator, + cfo, + out_dir, + fmt, + )? + .len(), + TargetLanguage::Swift => generate_bindings( + library_path, + crate_name.clone(), + &SwiftBindingGenerator, + cfo, + out_dir, + fmt, + )? + .len(), + }; + } + Ok(()) +} + +fn gen_bindings( + udl_file: &camino::Utf8Path, + cfo: Option<&camino::Utf8Path>, + languages: Vec, + odo: Option<&camino::Utf8Path>, + library_file: Option<&camino::Utf8Path>, + crate_name: Option<&str>, + fmt: bool, +) -> anyhow::Result<()> { + use uniffi_bindgen::generate_bindings; + for language in languages { + match language { + TargetLanguage::Kotlin => generate_bindings( + udl_file, + cfo, + KotlinBindingGenerator, + odo, + library_file, + crate_name, + fmt, + )?, + TargetLanguage::Python => generate_bindings( + udl_file, + cfo, + PythonBindingGenerator, + odo, + library_file, + crate_name, + fmt, + )?, + TargetLanguage::Ruby => generate_bindings( + udl_file, + cfo, + RubyBindingGenerator, + odo, + library_file, + crate_name, + fmt, + )?, + TargetLanguage::Swift => generate_bindings( + udl_file, + cfo, + SwiftBindingGenerator, + odo, + library_file, + crate_name, + fmt, + )?, + }; + } + Ok(()) +} + pub fn run_main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { @@ -100,25 +205,19 @@ pub fn run_main() -> anyhow::Result<()> { if language.is_empty() { panic!("please specify at least one language with --language") } - uniffi_bindgen::library_mode::generate_bindings( + gen_library_mode( &source, crate_name, - &BindingGeneratorDefault { - target_languages: language, - try_format_code: !no_format, - }, + language, config.as_deref(), &out_dir, !no_format, )?; } else { - uniffi_bindgen::generate_bindings( + gen_bindings( &source, config.as_deref(), - BindingGeneratorDefault { - target_languages: language, - try_format_code: !no_format, - }, + language, out_dir.as_deref(), lib_file.as_deref(), crate_name.as_deref(), diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index 319b3c7836..7b20f06e94 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -8,20 +8,21 @@ pub use uniffi_macros::*; #[cfg(feature = "cli")] mod cli; #[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::kotlin::run_test as kotlin_run_test; +pub use uniffi_bindgen::bindings::kotlin_run_test; #[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::python::run_test as python_run_test; +pub use uniffi_bindgen::bindings::python_run_test; #[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::ruby::run_test as ruby_run_test; +pub use uniffi_bindgen::bindings::ruby_run_test; #[cfg(feature = "bindgen-tests")] -pub use uniffi_bindgen::bindings::swift::run_test as swift_run_test; +pub use uniffi_bindgen::bindings::swift_run_test; #[cfg(feature = "bindgen")] pub use uniffi_bindgen::{ - bindings::kotlin::gen_kotlin::KotlinBindingGenerator, - bindings::python::gen_python::PythonBindingGenerator, - bindings::ruby::gen_ruby::RubyBindingGenerator, - bindings::swift::gen_swift::SwiftBindingGenerator, bindings::TargetLanguage, generate_bindings, - generate_component_scaffolding, generate_component_scaffolding_for_crate, print_repr, + bindings::{ + KotlinBindingGenerator, PythonBindingGenerator, RubyBindingGenerator, + SwiftBindingGenerator, TargetLanguage, + }, + generate_bindings, generate_component_scaffolding, generate_component_scaffolding_for_crate, + print_repr, }; #[cfg(feature = "build")] pub use uniffi_build::{generate_scaffolding, generate_scaffolding_for_crate}; diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index c4fc8e0ed6..12fdeb2f46 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -7,16 +7,16 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use askama::Template; -use camino::Utf8Path; + use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use crate::backend::TemplateExpression; -use crate::bindings::kotlin; + use crate::interface::*; -use crate::{BindingGenerator, BindingsConfig}; +use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -29,28 +29,6 @@ mod primitives; mod record; mod variant; -pub struct KotlinBindingGenerator; -impl BindingGenerator for KotlinBindingGenerator { - type Config = Config; - - fn write_bindings( - &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, - ) -> Result<()> { - kotlin::write_bindings(config, ci, out_dir, try_format_code) - } - - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given"); - } - Ok(()) - } -} - trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in /// method signatures and property declarations. diff --git a/uniffi_bindgen/src/bindings/kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/mod.rs index 466fe77879..3bcb876beb 100644 --- a/uniffi_bindgen/src/bindings/kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/mod.rs @@ -2,37 +2,60 @@ * 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 anyhow::Result; use camino::{Utf8Path, Utf8PathBuf}; use fs_err as fs; use std::process::Command; -pub mod gen_kotlin; -pub use gen_kotlin::{generate_bindings, Config}; +mod gen_kotlin; +use gen_kotlin::{generate_bindings, Config}; mod test; - -use super::super::interface::ComponentInterface; pub use test::{run_script, run_test}; -pub fn write_bindings( - config: &Config, - ci: &ComponentInterface, - out_dir: &Utf8Path, - try_format_code: bool, -) -> 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(), +pub struct KotlinBindingGenerator; +impl BindingGenerator for KotlinBindingGenerator { + type Config = Config; + + fn new_config(&self, root_toml: &toml::Value) -> Result { + Ok( + match root_toml.get("bindings").and_then(|b| b.get("kotlin")) { + Some(v) => v.clone().try_into()?, + None => Default::default(), + }, + ) + } + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> 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(), + ); + } + } + 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" ); } + Ok(()) } - Ok(()) } fn full_bindings_path(config: &Config, out_dir: &Utf8Path) -> Utf8PathBuf { diff --git a/uniffi_bindgen/src/bindings/kotlin/test.rs b/uniffi_bindgen/src/bindings/kotlin/test.rs index 0824015751..48c3d06cf1 100644 --- a/uniffi_bindgen/src/bindings/kotlin/test.rs +++ b/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -2,8 +2,8 @@ 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::bindings::TargetLanguage; -use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; +use crate::bindings::RunScriptOptions; +use crate::library_mode::generate_bindings; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env; @@ -38,10 +38,7 @@ pub fn run_script( generate_bindings( &cdylib_path, None, - &BindingGeneratorDefault { - target_languages: vec![TargetLanguage::Kotlin], - try_format_code: false, - }, + &super::KotlinBindingGenerator, None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/mod.rs b/uniffi_bindgen/src/bindings/mod.rs index d39202bcf2..5a9cd99e28 100644 --- a/uniffi_bindgen/src/bindings/mod.rs +++ b/uniffi_bindgen/src/bindings/mod.rs @@ -8,16 +8,23 @@ //! along with some helpers for executing foreign language scripts or tests. use anyhow::{bail, Result}; -use camino::Utf8Path; -use serde::{Deserialize, Serialize}; -use std::fmt; -use crate::interface::ComponentInterface; +use std::fmt; -pub mod kotlin; -pub mod python; -pub mod ruby; -pub mod swift; +mod kotlin; +pub use kotlin::{ + run_script as kotlin_run_script, run_test as kotlin_run_test, KotlinBindingGenerator, +}; +mod python; +pub use python::{ + run_script as python_run_script, run_test as python_run_test, PythonBindingGenerator, +}; +mod ruby; +pub use ruby::{run_test as ruby_run_test, RubyBindingGenerator}; +mod swift; +pub use swift::{ + run_script as swift_run_script, run_test as swift_run_test, SwiftBindingGenerator, +}; /// Enumeration of all foreign language targets currently supported by this crate. /// @@ -88,38 +95,3 @@ impl TryFrom for TargetLanguage { TryFrom::try_from(value.as_str()) } } - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Config { - #[serde(default)] - pub(crate) kotlin: kotlin::Config, - #[serde(default)] - pub(crate) swift: swift::Config, - #[serde(default)] - pub(crate) python: python::Config, - #[serde(default)] - pub(crate) ruby: ruby::Config, -} - -/// Generate foreign language bindings from a compiled `uniffi` library. -pub fn write_bindings( - config: &Config, - ci: &ComponentInterface, - out_dir: &Utf8Path, - language: TargetLanguage, - try_format_code: bool, -) -> Result<()> { - match language { - TargetLanguage::Kotlin => { - kotlin::write_bindings(&config.kotlin, ci, out_dir, try_format_code)? - } - TargetLanguage::Swift => { - swift::write_bindings(&config.swift, ci, out_dir, try_format_code)? - } - TargetLanguage::Python => { - python::write_bindings(&config.python, ci, out_dir, try_format_code)? - } - TargetLanguage::Ruby => ruby::write_bindings(&config.ruby, ci, out_dir, try_format_code)?, - } - Ok(()) -} diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 6a10a38e7f..07acb60687 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -2,9 +2,9 @@ * 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 anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use askama::Template; -use camino::Utf8Path; + use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -14,9 +14,9 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use crate::backend::TemplateExpression; -use crate::bindings::python; + use crate::interface::*; -use crate::{BindingGenerator, BindingsConfig}; +use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -28,29 +28,6 @@ mod object; mod primitives; mod record; -pub struct PythonBindingGenerator; - -impl BindingGenerator for PythonBindingGenerator { - type Config = Config; - - fn write_bindings( - &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, - ) -> Result<()> { - python::write_bindings(config, ci, out_dir, try_format_code) - } - - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - bail!("Generate bindings for Python requires a cdylib, but {library_path} was given"); - } - Ok(()) - } -} - /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -181,8 +158,6 @@ impl BindingsConfig for Config { self.cdylib_name .get_or_insert_with(|| cdylib_name.to_string()); } - - fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} } // Generate python bindings for the given ComponentInterface, as a string. diff --git a/uniffi_bindgen/src/bindings/python/mod.rs b/uniffi_bindgen/src/bindings/python/mod.rs index 4b9dc8f609..31bbfa4a43 100644 --- a/uniffi_bindgen/src/bindings/python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/mod.rs @@ -8,30 +8,54 @@ use anyhow::Result; use camino::Utf8Path; use fs_err as fs; -pub mod gen_python; +mod gen_python; mod test; use super::super::interface::ComponentInterface; -pub use gen_python::{generate_python_bindings, Config}; +use gen_python::{generate_python_bindings, Config}; pub use test::{run_script, run_test}; -// Generate python bindings for the given ComponentInterface, in the given output directory. -pub fn write_bindings( - config: &Config, - ci: &ComponentInterface, - out_dir: &Utf8Path, - try_format_code: bool, -) -> 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(), - ) +pub struct PythonBindingGenerator; + +impl crate::BindingGenerator for PythonBindingGenerator { + type Config = Config; + + fn new_config(&self, root_toml: &toml::Value) -> Result { + Ok( + match root_toml.get("bindings").and_then(|b| b.get("python")) { + Some(v) => v.clone().try_into()?, + None => Default::default(), + }, + ) + } + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> 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(), + ) + } } + + Ok(()) } - 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" + ); + } + Ok(()) + } } diff --git a/uniffi_bindgen/src/bindings/python/test.rs b/uniffi_bindgen/src/bindings/python/test.rs index 0c23140b33..0392c324d4 100644 --- a/uniffi_bindgen/src/bindings/python/test.rs +++ b/uniffi_bindgen/src/bindings/python/test.rs @@ -2,8 +2,8 @@ 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::bindings::TargetLanguage; -use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; +use crate::bindings::RunScriptOptions; +use crate::library_mode::generate_bindings; use anyhow::{Context, Result}; use camino::Utf8Path; use std::env; @@ -39,10 +39,7 @@ pub fn run_script( generate_bindings( &cdylib_path, None, - &BindingGeneratorDefault { - target_languages: vec![TargetLanguage::Python], - try_format_code: false, - }, + &super::PythonBindingGenerator, None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index 04841b459c..ba6f69ff3d 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -2,39 +2,15 @@ * 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 anyhow::{bail, Result}; +use anyhow::Result; use askama::Template; -use camino::Utf8Path; + use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; -use std::collections::HashMap; -use crate::bindings::ruby; use crate::interface::*; -use crate::{BindingGenerator, BindingsConfig}; - -pub struct RubyBindingGenerator; -impl BindingGenerator for RubyBindingGenerator { - type Config = Config; - - fn write_bindings( - &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, - ) -> Result<()> { - ruby::write_bindings(config, ci, out_dir, try_format_code) - } - - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - if cdylib_name.is_none() { - bail!("Generate bindings for Ruby requires a cdylib, but {library_path} was given"); - } - Ok(()) - } -} +use crate::BindingsConfig; const RESERVED_WORDS: &[&str] = &[ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", @@ -136,8 +112,6 @@ impl BindingsConfig for Config { self.cdylib_name .get_or_insert_with(|| cdylib_name.to_string()); } - - fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} } #[derive(Template)] diff --git a/uniffi_bindgen/src/bindings/ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/mod.rs index 6db89b9f0b..d7cd43150e 100644 --- a/uniffi_bindgen/src/bindings/ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/mod.rs @@ -4,42 +4,62 @@ use std::process::Command; +use crate::{BindingGenerator, ComponentInterface}; use anyhow::{Context, Result}; use camino::Utf8Path; use fs_err as fs; -pub mod gen_ruby; +mod gen_ruby; mod test; -pub use gen_ruby::{Config, RubyWrapper}; -pub use test::{run_test, test_script_command}; - -use super::super::interface::ComponentInterface; - -// Generate ruby bindings for the given ComponentInterface, in the given output directory. - -pub fn write_bindings( - config: &Config, - ci: &ComponentInterface, - out_dir: &Utf8Path, - try_format_code: bool, -) -> 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(), - ) +use gen_ruby::{Config, RubyWrapper}; +pub use test::run_test; + +pub struct RubyBindingGenerator; +impl BindingGenerator for RubyBindingGenerator { + type Config = Config; + + fn new_config(&self, root_toml: &toml::Value) -> Result { + Ok( + match root_toml.get("bindings").and_then(|b| b.get("ruby")) { + Some(v) => v.clone().try_into()?, + None => Default::default(), + }, + ) + } + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> 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(), + ) + } } + + Ok(()) } - 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" + ); + } + Ok(()) + } } // Generate ruby bindings for the given ComponentInterface, as a string. - pub fn generate_ruby_bindings(config: &Config, ci: &ComponentInterface) -> Result { use askama::Template; RubyWrapper::new(config.clone(), ci) diff --git a/uniffi_bindgen/src/bindings/ruby/test.rs b/uniffi_bindgen/src/bindings/ruby/test.rs index 88f770b9dc..86965fe2ff 100644 --- a/uniffi_bindgen/src/bindings/ruby/test.rs +++ b/uniffi_bindgen/src/bindings/ruby/test.rs @@ -2,9 +2,7 @@ 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::bindings::TargetLanguage; use crate::library_mode::generate_bindings; -use crate::BindingGeneratorDefault; use anyhow::{bail, Context, Result}; use camino::Utf8Path; use std::env; @@ -38,10 +36,7 @@ pub fn test_script_command( generate_bindings( &cdylib_path, None, - &BindingGeneratorDefault { - target_languages: vec![TargetLanguage::Ruby], - try_format_code: false, - }, + &super::RubyBindingGenerator, None, &out_dir, false, diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 16c1625123..533fd884f1 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -10,15 +10,15 @@ use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; -use camino::Utf8Path; + use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; use crate::backend::TemplateExpression; -use crate::bindings::swift; + use crate::interface::*; -use crate::{BindingGenerator, BindingsConfig}; +use crate::BindingsConfig; mod callback_interface; mod compounds; @@ -30,29 +30,6 @@ mod object; mod primitives; mod record; -pub struct SwiftBindingGenerator; -impl BindingGenerator for SwiftBindingGenerator { - type Config = Config; - - fn write_bindings( - &self, - ci: &ComponentInterface, - config: &Config, - out_dir: &Utf8Path, - try_format_code: bool, - ) -> Result<()> { - swift::write_bindings(config, ci, out_dir, try_format_code) - } - - fn check_library_path( - &self, - _library_path: &Utf8Path, - _cdylib_name: Option<&str>, - ) -> Result<()> { - Ok(()) - } -} - /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -311,8 +288,6 @@ impl BindingsConfig for Config { self.cdylib_name .get_or_insert_with(|| cdylib_name.to_string()); } - - fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} } /// Generate UniFFI component bindings for Swift, as strings in memory. @@ -606,10 +581,6 @@ impl SwiftCodeOracle { } } - fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { - self.ffi_type_label(ffi_type) - } - /// Get the name of the protocol and class name for an object. /// /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. @@ -704,10 +675,6 @@ pub mod filters { Ok(oracle().ffi_type_label(ffi_type)) } - pub fn ffi_canonical_name(ffi_type: &FfiType) -> Result { - Ok(oracle().ffi_canonical_name(ffi_type)) - } - pub fn ffi_default_value(return_type: Option) -> Result { Ok(oracle().ffi_default_value(return_type.as_ref())) } @@ -766,11 +733,6 @@ pub mod filters { Ok(quote_general_keyword(oracle().enum_variant_name(nm))) } - /// Get the idiomatic Swift rendering of an individual enum variant, for contexts (for use in non-declaration contexts where quoting is not needed) - pub fn enum_variant_swift(nm: &str) -> Result { - Ok(oracle().enum_variant_name(nm)) - } - /// Get the idiomatic Swift rendering of an FFI callback function name pub fn ffi_callback_name(nm: &str) -> Result { Ok(oracle().ffi_callback_name(nm)) @@ -795,28 +757,6 @@ pub mod filters { Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) } - pub fn error_handler(result: &ResultType) -> Result { - Ok(match &result.throws_type { - Some(t) => format!("{}.lift", ffi_converter_name(t)?), - None => "nil".into(), - }) - } - - /// Name of the callback function to handle an async result - pub fn future_callback(result: &ResultType) -> Result { - Ok(format!( - "uniffiFutureCallbackHandler{}{}", - match &result.return_type { - Some(t) => SwiftCodeOracle.find(t).canonical_name(), - None => "Void".into(), - }, - match &result.throws_type { - Some(t) => SwiftCodeOracle.find(t).canonical_name(), - None => "".into(), - } - )) - } - pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { Ok(SwiftCodeOracle.object_names(obj)) } diff --git a/uniffi_bindgen/src/bindings/swift/mod.rs b/uniffi_bindgen/src/bindings/swift/mod.rs index bf17f38a4e..f30fbbb190 100644 --- a/uniffi_bindgen/src/bindings/swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/mod.rs @@ -29,22 +29,20 @@ //! * How to read from and write into a byte buffer. //! -use std::process::Command; - +use crate::{BindingGenerator, ComponentInterface}; use anyhow::Result; use camino::Utf8Path; use fs_err as fs; +use std::process::Command; -pub mod gen_swift; -pub use gen_swift::{generate_bindings, Config}; +mod gen_swift; +use gen_swift::{generate_bindings, Config}; mod test; - -use super::super::interface::ComponentInterface; pub use test::{run_script, run_test}; /// The Swift bindings generated from a [`ComponentInterface`]. /// -pub struct Bindings { +struct Bindings { /// The contents of the generated `.swift` file, as a string. library: String, /// The contents of the generated `.h` file, as a string. @@ -53,45 +51,66 @@ pub struct Bindings { modulemap: Option, } -/// Write UniFFI component bindings for Swift as files on disk. -/// -/// 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. -pub fn write_bindings( - config: &Config, - ci: &ComponentInterface, - out_dir: &Utf8Path, - try_format_code: bool, -) -> Result<()> { - let Bindings { - header, - library, - modulemap, - } = generate_bindings(config, ci)?; +pub struct SwiftBindingGenerator; +impl BindingGenerator for SwiftBindingGenerator { + type Config = Config; + + fn new_config(&self, root_toml: &toml::Value) -> Result { + Ok( + match root_toml.get("bindings").and_then(|b| b.get("swift")) { + Some(v) => v.clone().try_into()?, + None => Default::default(), + }, + ) + } - let source_file = out_dir.join(format!("{}.swift", config.module_name())); - fs::write(&source_file, library)?; + /// 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, + ) -> Result<()> { + let Bindings { + header, + library, + modulemap, + } = generate_bindings(config, ci)?; - let header_file = out_dir.join(config.header_filename()); - fs::write(header_file, header)?; + let source_file = out_dir.join(format!("{}.swift", config.module_name())); + fs::write(&source_file, library)?; - if let Some(modulemap) = modulemap { - let modulemap_file = out_dir.join(config.modulemap_filename()); - fs::write(modulemap_file, modulemap)?; - } + let header_file = 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 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 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(()) } - 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 195a77696b..d5378253b9 100644 --- a/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi_bindgen/src/bindings/swift/test.rs @@ -2,7 +2,9 @@ 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::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; +use crate::bindings::RunScriptOptions; +use crate::library_mode::generate_bindings; + use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; @@ -12,8 +14,6 @@ use std::io::Write; use std::process::{Command, Stdio}; use uniffi_testing::UniFFITestHelper; -use crate::bindings::TargetLanguage; - /// Run Swift tests for a UniFFI test fixture pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { run_script( @@ -128,10 +128,7 @@ impl GeneratedSources { let sources = generate_bindings( cdylib_path, None, - &BindingGeneratorDefault { - target_languages: vec![TargetLanguage::Swift], - try_format_code: false, - }, + &super::SwiftBindingGenerator, None, out_dir, false, @@ -140,7 +137,7 @@ impl GeneratedSources { .iter() .find(|s| s.package.name == crate_name) .unwrap(); - let main_module = main_source.config.bindings.swift.module_name(); + let main_module = main_source.config.module_name(); let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; let module_map = match modulemap_glob.len() { 0 => bail!("No modulemap files found in {out_dir}"), diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index dfc90b32a6..5df9aade90 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -95,7 +95,7 @@ use anyhow::{anyhow, bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use fs_err::{self as fs, File}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize}; use std::io::prelude::*; use std::io::ErrorKind; use std::{collections::HashMap, process::Command}; @@ -107,7 +107,6 @@ pub mod library_mode; pub mod macro_metadata; pub mod scaffolding; -use bindings::TargetLanguage; pub use interface::ComponentInterface; use scaffolding::RustScaffolding; @@ -128,16 +127,6 @@ pub trait BindingsConfig: DeserializeOwned { /// /// 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>); -} - -/// Binding generator config with no members -#[derive(Clone, Debug, Deserialize, Hash, PartialEq, PartialOrd, Ord, Eq)] -pub struct EmptyBindingsConfig; - -impl BindingsConfig for EmptyBindingsConfig { - fn update_from_ci(&mut self, _ci: &ComponentInterface) {} - fn update_from_cdylib_name(&mut self, _cdylib_name: &str) {} fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} } @@ -149,6 +138,9 @@ pub trait BindingGenerator: Sized { /// Handles configuring the bindings type Config: BindingsConfig; + /// Creates a new config. + fn new_config(&self, root_toml: &toml::Value) -> Result; + /// Writes the bindings to the output directory /// /// # Arguments @@ -167,43 +159,6 @@ pub trait BindingGenerator: Sized { fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>; } -pub struct BindingGeneratorDefault { - pub target_languages: Vec, - pub try_format_code: bool, -} - -impl BindingGenerator for BindingGeneratorDefault { - type Config = Config; - - fn write_bindings( - &self, - ci: &ComponentInterface, - config: &Self::Config, - out_dir: &Utf8Path, - _try_format_code: bool, - ) -> Result<()> { - for &language in &self.target_languages { - bindings::write_bindings( - &config.bindings, - ci, - out_dir, - language, - self.try_format_code, - )?; - } - Ok(()) - } - - fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { - for &language in &self.target_languages { - if cdylib_name.is_none() && language != TargetLanguage::Swift { - bail!("Generate bindings for {language} requires a cdylib, but {library_path} was given"); - } - } - Ok(()) - } -} - /// Generate bindings for an external binding generator /// Ideally, this should replace the [`generate_bindings`] function below /// @@ -241,7 +196,8 @@ pub fn generate_external_bindings( let config_file_override = config_file_override.as_ref().map(|p| p.as_ref()); let config = { - let mut config = load_initial_config::(crate_root, config_file_override)?; + 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()) @@ -435,10 +391,10 @@ fn load_toml_file(source: Option<&Utf8Path>) -> Result( +fn load_initial_config( crate_root: &Utf8Path, config_file_override: Option<&Utf8Path>, -) -> Result { +) -> Result { let mut config = load_toml_file(Some(crate_root.join("uniffi.toml").as_path())) .context("default config")? .unwrap_or(toml::value::Table::default()); @@ -448,7 +404,7 @@ fn load_initial_config( merge_toml(&mut config, override_config); } - Ok(toml::Value::from(config).try_into()?) + Ok(toml::Value::from(config)) } fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) { @@ -467,55 +423,6 @@ fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Config { - #[serde(default)] - bindings: bindings::Config, -} - -impl BindingsConfig for Config { - fn update_from_ci(&mut self, ci: &ComponentInterface) { - self.bindings.kotlin.update_from_ci(ci); - self.bindings.swift.update_from_ci(ci); - self.bindings.python.update_from_ci(ci); - self.bindings.ruby.update_from_ci(ci); - } - - fn update_from_cdylib_name(&mut self, cdylib_name: &str) { - self.bindings.kotlin.update_from_cdylib_name(cdylib_name); - self.bindings.swift.update_from_cdylib_name(cdylib_name); - self.bindings.python.update_from_cdylib_name(cdylib_name); - self.bindings.ruby.update_from_cdylib_name(cdylib_name); - } - - fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) { - self.bindings.kotlin.update_from_dependency_configs( - config_map - .iter() - .map(|(key, config)| (*key, &config.bindings.kotlin)) - .collect(), - ); - self.bindings.swift.update_from_dependency_configs( - config_map - .iter() - .map(|(key, config)| (*key, &config.bindings.swift)) - .collect(), - ); - self.bindings.python.update_from_dependency_configs( - config_map - .iter() - .map(|(key, config)| (*key, &config.bindings.python)) - .collect(), - ); - self.bindings.ruby.update_from_dependency_configs( - config_map - .iter() - .map(|(key, config)| (*key, &config.bindings.ruby)) - .collect(), - ); - } -} - // FIXME(HACK): // Include the askama config file into the build. // That way cargo tracks the file and other tools relying on file tracking see it as well. diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index c460c03d9f..7723d8d5c3 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -69,6 +69,7 @@ pub fn generate_external_bindings( binding_generator.check_library_path(library_path, cdylib_name)?; let mut sources = find_sources( + binding_generator, &cargo_metadata, library_path, cdylib_name, @@ -136,12 +137,13 @@ pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> { None } -fn find_sources( +fn find_sources( + generator: &T, cargo_metadata: &cargo_metadata::Metadata, library_path: &Utf8Path, cdylib_name: Option<&str>, config_file_override: Option<&Utf8Path>, -) -> Result>> { +) -> Result>> { let items = macro_metadata::extract_from_library(library_path)?; let mut metadata_groups = create_metadata_groups(&items); group_metadata(&mut metadata_groups, items)?; @@ -186,7 +188,8 @@ fn find_sources( ci.add_metadata(metadata)?; }; ci.add_metadata(group)?; - let mut config = load_initial_config::(crate_root, config_file_override)?; + let mut config = + 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); }