Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift external type module config #1404

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

[All changes in [[UnreleasedVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.21.0...HEAD).

### What's changed

- Added support for Swift external types

## v0.21.0 - (_2022-10-14_)

[All changes in v0.21.0](https://github.com/mozilla/uniffi-rs/compare/v0.20.0...v0.21.0).
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"fixtures/ext-types/guid",
"fixtures/ext-types/uniffi-one",
"fixtures/ext-types/lib",
"fixtures/ext-types/lib-with-separate-swift-modules/",

# we should roll the above and below up somehow that makes sense...
"fixtures/external-types/crate-one",
Expand Down
44 changes: 44 additions & 0 deletions docs/manual/src/udl/ext_types_external.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,47 @@ include!(concat!(env!("OUT_DIR"), "/consuming_crate.uniffi.rs"));
Your `Cargo.toml` must reference the external crate as normal.

The `External` attribute can be specified on dictionaries, enums and errors.

## Foreign bindings

The foreign bindings will also need to know how to access the external type,
which varies slightly for each language:

### Kotlin

For Kotlin, the generated code needs to import the external types from the
Kotlin module that corresponds to the Rust crate. By default, UniFFI assumes
that the Kotlin module name matches the Rust crate name, but this can be
configured in `uniffi.toml` with an entry like this:

```
[bindings.kotlin.external_packages]
# Map the crate names from [External={name}] into Kotlin package names
rust-crate-name = "kotlin.package.name"
```

### Swift

For Swift, there are 2 ways to use the UniFFI-generated Swift code:
- Compile all source files together in the same module.
- Compile each source file into a different module.

By default, UniFFI assumes that the source files will be compiled together, so
external types will be automatically available and nothing needs to be configured.

If you compile each source file into a different module, then you need to
add configuration to `uniffi.toml`.

```
[bindings.swift.external_types]
in_different_modules = true
```

By default, UniFFI assumes the module name is the same as Rust crate name
specified in the UDL file (with hyphens replaced with underscores). You can
override this with the `bindings.swift.external_types.module_names` config key:

```
[bindings.swift.external_types.module_names]
my-rust-crate-name = "my_swift_module_name"
```
41 changes: 41 additions & 0 deletions fixtures/ext-types/lib-with-separate-swift-modules/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "uniffi-fixture-ext-types-swift-separate-modules"
edition = "2021"
version = "0.21.0"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
license = "MPL-2.0"
publish = false

[package.metadata.uniffi.testing]
external-crates = [
"uniffi-fixture-ext-types-guid",
"uniffi-fixture-ext-types-lib-one",
"uniffi-example-custom-types",
]
swift-use-separate-modules = true

[lib]
crate-type = ["lib", "cdylib"]
name = "uniffi_ext_types_lib"

[dependencies]
anyhow = "1"
bytes = "1.0"
uniffi = {path = "../../../uniffi", features=["builtin-bindgen"]}

uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"}
uniffi-fixture-ext-types-guid = {path = "../guid"}

# Reuse one of our examples.
uniffi-example-custom-types = {path = "../../../examples/custom-types"}

url = "2.2"

[build-dependencies]
uniffi_build = {path = "../../../uniffi_build", features=["builtin-bindgen"]}



[dev-dependencies]
uniffi_bindgen = {path = "../../../uniffi_bindgen"}
uniffi_macros = {path = "../../../uniffi_macros"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is basically the same as the uniffi-fixture-ext-types fixture found in
`../lib`. The only difference is that this crate configures the swift tests to
compile each source file into a separate module so that we can test that mode.
7 changes: 7 additions & 0 deletions fixtures/ext-types/lib-with-separate-swift-modules/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* 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/. */

fn main() {
uniffi_build::generate_scaffolding("./src/ext-types-lib.udl").unwrap();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace imported_types_lib {
CombinedType get_combined_type(optional CombinedType? value);
};

// A type defined in a .udl file in the `uniffi-one` crate (ie, in
// `../../uniffi-one/src/uniffi-one.udl`)
[External="uniffi-one"]
typedef extern UniffiOneType;

// A "wrapped" type defined in the guid crate (ie, defined in `../../guid/src/lib.rs` and
// "declared" in `../../guid/src/guid.udl`). But it's still "external" from our POV,
// So same as the `.udl` type above!
[External="ext-types-guid"]
typedef extern Guid;

// And re-use the `custom-types` example - this exposes `Url` and `Handle`
[External="custom-types"]
typedef extern Url;

[External="custom-types"]
typedef extern Handle;

// And a new type here to tie them all together.
dictionary CombinedType {
UniffiOneType uot;
sequence<UniffiOneType> uots;
UniffiOneType? maybe_uot;

Guid guid;
sequence<Guid> guids;
Guid? maybe_guid;

Url url;
sequence<Url> urls;
Url? maybe_url;

Handle handle;
sequence<Handle> handles;
Handle? maybe_handle;
};
53 changes: 53 additions & 0 deletions fixtures/ext-types/lib-with-separate-swift-modules/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use custom_types::Handle;
use ext_types_guid::Guid;
use uniffi_one::UniffiOneType;
use url::Url;

pub struct CombinedType {
pub uot: UniffiOneType,
pub uots: Vec<UniffiOneType>,
pub maybe_uot: Option<UniffiOneType>,

pub guid: Guid,
pub guids: Vec<Guid>,
pub maybe_guid: Option<Guid>,

pub url: Url,
pub urls: Vec<Url>,
pub maybe_url: Option<Url>,

pub handle: Handle,
pub handles: Vec<Handle>,
pub maybe_handle: Option<Handle>,
}

fn get_combined_type(existing: Option<CombinedType>) -> CombinedType {
existing.unwrap_or_else(|| CombinedType {
uot: UniffiOneType {
sval: "hello".to_string(),
},
uots: vec![
UniffiOneType {
sval: "first of many".to_string(),
},
UniffiOneType {
sval: "second of many".to_string(),
},
],
maybe_uot: None,

guid: Guid("a-guid".into()),
guids: vec![Guid("b-guid".into()), Guid("c-guid".into())],
maybe_guid: None,

url: Url::parse("http://example.com/").unwrap(),
urls: vec![],
maybe_url: None,

handle: Handle(123),
handles: vec![Handle(1), Handle(2), Handle(3)],
maybe_handle: Some(Handle(4)),
})
}

include!(concat!(env!("OUT_DIR"), "/ext-types-lib.uniffi.rs"));
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* 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/. */

import imported_types_lib
import Foundation

let ct = getCombinedType(value: nil)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
assert(ct.url == URL(string: "http://example.com/"))

let ct2 = getCombinedType(value: ct)
assert(ct == ct2)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
uniffi_macros::build_foreign_language_testcases!(
"tests/bindings/test_imported_types.swift",
// No need to test other languages, since this crate only changes the Swift configuration
// (see ../README.md)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bindings.swift.external_types]
in_different_modules = true
14 changes: 14 additions & 0 deletions fixtures/ext-types/lib/tests/bindings/test_imported_types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* 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/. */

import imported_types_lib
import Foundation

let ct = getCombinedType(value: nil)
assert(ct.uot.sval == "hello")
assert(ct.guid == "a-guid")
assert(ct.url == URL(string: "http://example.com/"))

let ct2 = getCombinedType(value: ct)
assert(ct == ct2)
1 change: 1 addition & 0 deletions fixtures/ext-types/lib/tests/test_generated_bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
uniffi_macros::build_foreign_language_testcases!(
"tests/bindings/test_imported_types.kts",
"tests/bindings/test_imported_types.py",
"tests/bindings/test_imported_types.swift",
);
25 changes: 25 additions & 0 deletions uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* 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 crate::backend::{CodeOracle, CodeType};

pub struct ExternalCodeType {
name: String,
}

impl ExternalCodeType {
pub fn new(name: String) -> Self {
ExternalCodeType { name }
}
}

impl CodeType for ExternalCodeType {
fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
self.name.clone()
}

fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
format!("Type{}", self.name)
}
}
40 changes: 38 additions & 2 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};

use anyhow::{Context, Result};
use askama::Template;
use heck::{ToLowerCamelCase, ToUpperCamelCase};
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};

use super::Bindings;
Expand All @@ -21,6 +21,7 @@ mod compounds;
mod custom;
mod enum_;
mod error;
mod external;
mod miscellany;
mod object;
mod primitives;
Expand All @@ -40,6 +41,8 @@ pub struct Config {
omit_argument_labels: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
#[serde(default)]
external_types: ExternalTypesConfig,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
Expand All @@ -50,6 +53,14 @@ pub struct CustomTypeConfig {
from_custom: TemplateExpression,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ExternalTypesConfig {
#[serde(default)]
in_different_modules: bool,
#[serde(default)]
module_names: HashMap<String, String>,
}

impl Config {
/// The name of the Swift module containing the high-level foreign-language bindings.
pub fn module_name(&self) -> String {
Expand Down Expand Up @@ -131,6 +142,16 @@ impl MergeWith for Config {
.omit_argument_labels
.merge_with(&other.omit_argument_labels),
custom_types: self.custom_types.merge_with(&other.custom_types),
external_types: self.external_types.merge_with(&other.external_types),
}
}
}

impl MergeWith for ExternalTypesConfig {
fn merge_with(&self, other: &Self) -> Self {
ExternalTypesConfig {
in_different_modules: self.in_different_modules || other.in_different_modules,
module_names: self.module_names.merge_with(&other.module_names),
}
}
}
Expand Down Expand Up @@ -207,6 +228,21 @@ impl<'a> TypeRenderer<'a> {
self.imports.borrow_mut().insert(name.to_owned());
""
}

/// Get the Swift module name for an external type
fn external_types_in_different_modules(&self) -> bool {
self.config.external_types.in_different_modules
}

/// Get the Swift module name for an external type
fn external_type_module_name(&self, name: &str) -> String {
self.config
.external_types
.module_names
.get(name)
.cloned()
.unwrap_or_else(|| name.to_snake_case())
}
}

/// Template for generating the `.h` file that defines the low-level C FFI.
Expand Down Expand Up @@ -323,7 +359,7 @@ impl SwiftCodeOracle {
Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
Type::External { .. } => panic!("no support for external types yet"),
Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
Type::Unresolved { name } => {
unreachable!("Type `{name}` must be resolved before calling create_code_type")
Expand Down
Loading