Skip to content

Commit

Permalink
Add uniffi-bindgen-swift
Browse files Browse the repository at this point in the history
This is a `uniffi-bindgen` variant dedicated to Swift code, with options
specialized for Swift.  I'm currently thinking that we should consider
doing this for all languages.  In addition to allowing specialized
options, it also makes the in-tree bindings less special-cased compared
to external bindings.
  • Loading branch information
bendk committed Oct 1, 2024
1 parent e73347c commit 5c4409a
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

## [[UnreleasedUniFFIVersion]] (backend crates: [[UnreleasedBackendVersion]]) - (_[[ReleaseDate]]_)

### What's new?

- Added the `uniffi-bindgen-swift` binary. It works like `uniffi-bindgen` but with additional
Swift-specific features. See
https://mozilla.github.io/uniffi-rs/latest/swift/uniffi-bindgen-swift.html for details.

### What's fixed?

- `uniffi.toml` of crates without a `lib` type where ignored in 0.28.1
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/src/swift/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ more likely to change than other configurations.
| `module_name` | `{namespace}`[^1] | The name of the Swift module containing the high-level foreign-language bindings. |
| `ffi_module_name` | `{module_name}FFI` | The name of the lower-level C module containing the FFI declarations. |
| `ffi_module_filename` | `{ffi_module_name}` | The filename stem for the lower-level C module containing the FFI declarations. |
| `generate_module_map` | `true` | Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations. |
| `generate_module_map` | `true` | Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations. (ignored by `uniffi-bindgen-swift`) |
| `omit_argument_labels` | `false` | Whether to omit argument labels in Swift function definitions. |
| `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`let` instead of `var`). |
| `experimental_sendable_value_types` | `false` | Whether to mark value types as `Sendable'. |
Expand Down
20 changes: 5 additions & 15 deletions docs/manual/src/swift/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,10 @@ Concepts from the UDL file map into Swift as follows:
* If this happens inside a non-throwing Swift function, it will be converted
into a fatal Swift error that cannot be caught.

Conceptually, the generated bindings are split into two Swift modules, one for the low-level
C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example"
we generate:
## Generated files

* A C header file `exampleFFI.h` declaring the low-level structs and functions for calling
into Rust, along with a corresponding `exampleFFI.modulemap` to expose them to Swift.
* A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it
to provide the higher-level Swift API.
UniFFI generates several kinds of files for Swift bindings:

Splitting up the bindings in this way gives you flexibility over how both the Rust code
and the Swift code are distributed to consumers. For example, you may choose to compile
and distribute the Rust code for several UniFFI components as a single shared library
in order to reduce the compiled code size, while distributing their Swift wrappers as
individual modules.

For more technical details on how the bindings work internally, please see the
[module documentation](https://docs.rs/uniffi_bindgen/latest/uniffi_bindgen/bindings/swift/index.html)
* C header files declaring the FFI structs/functions used by the Rust scaffolding code
* A modulemap, which defines a Swift module for the C FFI definitions in the header file.
* A Swift source file that defines the Swift API used by consumers. This imports the FFI module.
48 changes: 48 additions & 0 deletions docs/manual/src/swift/uniffi-bindgen-swift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# uniffi-bindgen-swift

Swift bindings can be generated like other languages using `uniffi-bindgen -l swift`. However, you
can also use the `uniffi-bindgen-swift` binary which gives greater control over Swift-specific
features:

* Select which kind of files to generate: headers, modulemaps, and/or Swift sources.
* Generate a single modulemap for a library.
* Generate XCFramework-compatible modulemaps.
* Customize the modulemap module name.
* Customize the modulemap filename.

`uniffi-bindgen-swift` can be added to your project using the same general steps as `uniffi-bindgen`.
See https://mozilla.github.io/uniffi-rs/latest/tutorial/foreign_language_bindings.html#creating-the-bindgen-binary.
The Rust source for the binary should be:

```
fn main() {
uniffi::uniffi_bindgen_swift()
}
```

`uniffi-bindgen-swift` always inputs a library path and runs in "library mode". This means
proc-macro-based bindings generation is always supported.

## Examples:


Generate .swift source files for a library
```
cargo run -p uniffi-bindgen-swift -- target/release/mylibrary.a build/swift --swift-sources
```

Generate .h files for a library
```
cargo run -p uniffi-bindgen-swift -- target/release/mylibrary.a build/swift/Headers --headers
```


Generate a modulemap
```
cargo run -p uniffi-bindgen-swift -- target/release/mylibrary.a build/swift/Modules --modulemap --modulemap-filename mymodule.modulemap
```

Generate a Xcframework-compatible modulemap
```
cargo run -p uniffi-bindgen-swift -- target/release/mylibrary.a build/swift/Modules --xcframework --modulemap --modulemap-filename mymodule.modulemap
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nav:

- 'Swift':
- ./swift/overview.md
- ./swift/uniffi-bindgen-swift.md
- ./swift/configuration.md
- ./swift/module.md
- ./swift/xcode.md
Expand Down
20 changes: 20 additions & 0 deletions uniffi/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* 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/. */

mod swift;
mod uniffi_bindgen;

pub fn uniffi_bindgen_main() {
if let Err(e) = uniffi_bindgen::run_main() {
eprintln!("{e}");
std::process::exit(1);
}
}

pub fn uniffi_bindgen_swift() {
if let Err(e) = swift::run_main() {
eprintln!("{e}");
std::process::exit(1);
}
}
73 changes: 73 additions & 0 deletions uniffi/src/cli/swift.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* 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/. */

use anyhow::Result;
use camino::Utf8PathBuf;
use clap::{Args, Parser};

use uniffi_bindgen::bindings::{generate_swift_bindings, SwiftBindingsOptions};

#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(flatten)]
kinds: Kinds,
/// Library path to generate bindings for
library_path: Utf8PathBuf,
/// Directory to generate files in
out_dir: Utf8PathBuf,
/// Generate a XCFramework-compatible modulemap
#[arg(long)]
xcframework: bool,
/// module name for the generated modulemap
#[arg(long)]
module_name: Option<String>,
/// filename for the generate modulemap
#[arg(long)]
modulemap_filename: Option<String>,
/// 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,
}

#[derive(Debug, Args)]
#[group(required = true, multiple = true)]
struct Kinds {
/// Generate swift files
#[arg(long)]
swift_sources: bool,

/// Generate header files
#[arg(long)]
headers: bool,

/// Generate modulemap
#[arg(long)]
modulemap: bool,
}

pub fn run_main() -> Result<()> {
let cli = Cli::parse();
generate_swift_bindings(cli.into())
}

impl From<Cli> for SwiftBindingsOptions {
fn from(cli: Cli) -> Self {
Self {
generate_swift_sources: cli.kinds.swift_sources,
generate_headers: cli.kinds.headers,
generate_modulemap: cli.kinds.modulemap,
library_path: cli.library_path,
out_dir: cli.out_dir,
xcframework: cli.xcframework,
module_name: cli.module_name,
modulemap_filename: cli.modulemap_filename,
metadata_no_deps: cli.metadata_no_deps,
}
}
}
File renamed without changes.
4 changes: 1 addition & 3 deletions uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ pub use uniffi_build::{generate_scaffolding, generate_scaffolding_for_crate};
pub use uniffi_macros::build_foreign_language_testcases;

#[cfg(feature = "cli")]
pub fn uniffi_bindgen_main() {
cli::run_main().unwrap();
}
pub use cli::*;

#[cfg(test)]
mod test {
Expand Down
2 changes: 1 addition & 1 deletion uniffi_bindgen/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use python::PythonBindingGenerator;
mod ruby;
pub use ruby::RubyBindingGenerator;
mod swift;
pub use swift::SwiftBindingGenerator;
pub use swift::{generate_swift_bindings, SwiftBindingGenerator, SwiftBindingsOptions};

#[cfg(feature = "bindgen-tests")]
pub use self::{
Expand Down
49 changes: 41 additions & 8 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ impl Config {
}

/// Generate UniFFI component bindings for Swift, as strings in memory.
///
pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bindings> {
let header = BridgingHeader::new(config, ci)
.render()
Expand All @@ -277,7 +276,7 @@ pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bin
.context("failed to render Swift library")?;
let modulemap = if config.generate_module_map() {
Some(
ModuleMap::new(config, ci)
ModuleMap::new_for_single_component(config, ci)
.render()
.context("failed to render Swift modulemap")?,
)
Expand All @@ -291,6 +290,35 @@ pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bin
})
}

/// Generate the bridging header for a component
pub fn generate_header(config: &Config, ci: &ComponentInterface) -> Result<String> {
BridgingHeader::new(config, ci)
.render()
.context("failed to render Swift bridging header")
}

/// Generate the swift source for a component
pub fn generate_swift(config: &Config, ci: &ComponentInterface) -> Result<String> {
SwiftWrapper::new(config.clone(), ci)
.render()
.context("failed to render Swift library")
}

/// Generate the modulemap for a set of components
pub fn generate_modulemap(
module_name: String,
header_filenames: Vec<String>,
xcframework: bool,
) -> Result<String> {
ModuleMap {
module_name,
header_filenames,
xcframework,
}
.render()
.context("failed to render Swift library")
}

/// Renders Swift helper code for all types
///
/// This template is a bit different than others in that it stores internal state from the render
Expand Down Expand Up @@ -369,14 +397,19 @@ impl<'config, 'ci> BridgingHeader<'config, 'ci> {
/// so that it can be imported by the higher-level code in from [`SwiftWrapper`].
#[derive(Template)]
#[template(syntax = "c", escape = "none", path = "ModuleMapTemplate.modulemap")]
pub struct ModuleMap<'config, 'ci> {
config: &'config Config,
_ci: &'ci ComponentInterface,
pub struct ModuleMap {
module_name: String,
header_filenames: Vec<String>,
xcframework: bool,
}

impl<'config, 'ci> ModuleMap<'config, 'ci> {
pub fn new(config: &'config Config, _ci: &'ci ComponentInterface) -> Self {
Self { config, _ci }
impl ModuleMap {
pub fn new_for_single_component(config: &Config, _ci: &ComponentInterface) -> Self {
Self {
module_name: config.ffi_module_name(),
header_filenames: vec![config.header_filename()],
xcframework: false,
}
}
}

Expand Down
Loading

0 comments on commit 5c4409a

Please sign in to comment.