Skip to content

Commit

Permalink
Merge pull request #2248 from bendk/push-uunuklpxuxzo
Browse files Browse the repository at this point in the history
uniffi-bindgen-swift
  • Loading branch information
bendk authored Oct 3, 2024
2 parents 305454d + 5c4409a commit 3662c84
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 119 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

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

### ⚠️ Breaking Changes for external bindings authors ⚠️
### What's new?

- Added the `GenerationSettings::mode` field. This can be ignored in most cases, it's currently only used by Swift.
- 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?

Expand Down
4 changes: 2 additions & 2 deletions docs/manual/src/swift/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ more likely to change than other configurations.

| Configuration name | Default | Description |
| ----------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cdylib_name` | `uniffi_{namespace}`[^1] | The name of the compiled Rust library containing the FFI implementation (not needed when using `--library`). |
| `cdylib_name` | `uniffi_{namespace}`[^1] | The name of the compiled Rust library containing the FFI implementation (not needed when using `generate --library`). |
| `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. Not valid when using `--library`, in library-mode the modulemap is always generated.
| `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
25 changes: 0 additions & 25 deletions docs/manual/src/swift/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,3 @@ UniFFI generates several kinds of files for Swift bindings:
* 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.

The file layout depends on which mode is used to generate the bindings:

### Library mode

`uniffi-bindgen` in [library mode](../tutorial/foreign_language_bindings.html#running-uniffi-bindgen-using-a-library-file) generates:

* A Swift file for each crate (`[crate_name].swift`)
* A header file for each crate (`[crate_name]FFI.h`)
* A single modulemap file for the entire module (`[library_name].modulemap`)

The expectation is that each `.swift` file will be compiled together into a single Swift module that represents the library as a whole.

### Single UDL file

`uniffi-bindgen` in [Single UDL file mode](../tutorial/foreign_language_bindings.html#running-uniffi-bindgen-with-a-single-udl-file) generates:

* A Swift file for the crate (`[crate_name].swift`)
* A header file for the crate (`[crate_name]FFI.h`)
* A modulemap file for the crate (`[crate_name].modulemap`)

The expectation is that the `.swift` will be compiled into a module and this is the only module to generate for the Rust library.

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)
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 3662c84

Please sign in to comment.