Bug 1919574 - CLI tool to generate the android/ios directories
I was going to update the adding a new component docs, but while I was reading them that the best way to simplify them would be to automate the process. * Added the `cargo start-bindings` tool. * Removed the "Converting an existing Component to UniFFI" HOWTO. I believe that all our existing components have been converted so there's no need to keep updating this. * Added a section about dependencies and the build.gradle file. * Removed the `pub use` items from the megazord lib.rs files. I don't believe they were needed.
This commit is contained in:
Родитель
7aafd5efa6
Коммит
c35705a06f
|
@ -8,3 +8,4 @@ suggest-bench = ["bench", "-p", "suggest", "--features", "benchmark_api"]
|
||||||
suggest-debug-ingestion-sizes = ["run", "-p", "suggest", "--bin", "debug_ingestion_sizes", "--features", "benchmark_api"]
|
suggest-debug-ingestion-sizes = ["run", "-p", "suggest", "--bin", "debug_ingestion_sizes", "--features", "benchmark_api"]
|
||||||
relevancy = ["run", "-p", "examples-relevancy-cli", "--"]
|
relevancy = ["run", "-p", "examples-relevancy-cli", "--"]
|
||||||
suggest = ["run", "-p", "examples-suggest-cli", "--"]
|
suggest = ["run", "-p", "examples-suggest-cli", "--"]
|
||||||
|
start-bindings = ["run", "-p", "start-bindings", "--"]
|
||||||
|
|
|
@ -4,6 +4,13 @@
|
||||||
|
|
||||||
# v132.0 (_2024-09-30_)
|
# v132.0 (_2024-09-30_)
|
||||||
|
|
||||||
|
## ✨ What's New ✨
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
- Simplified the process of adding a new component by adding a tool that can autogenerate the
|
||||||
|
initial UniFFI/bindings code.
|
||||||
|
|
||||||
## 🦊 What's Changed 🦊
|
## 🦊 What's Changed 🦊
|
||||||
|
|
||||||
### Glean
|
### Glean
|
||||||
|
|
|
@ -278,7 +278,7 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mime",
|
"mime",
|
||||||
|
@ -382,7 +382,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
@ -1768,7 +1768,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.5.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1869,7 +1869,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1931,7 +1931,7 @@ dependencies = [
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2005,9 +2005,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
@ -2087,9 +2087,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jexl-eval"
|
name = "jexl-eval"
|
||||||
|
@ -2157,7 +2157,7 @@ dependencies = [
|
||||||
"fraction",
|
"fraction",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"iso8601",
|
"iso8601",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"memchr",
|
"memchr",
|
||||||
"num-cmp",
|
"num-cmp",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2921,6 +2921,18 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_map"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30c7f82d6d446dd295845094f3a76bcdc5e6183b66667334e169f019cd05e5a0"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"hashbrown 0.14.3",
|
||||||
|
"parking_lot",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
version = "11.1.3"
|
version = "11.1.3"
|
||||||
|
@ -3661,6 +3673,49 @@ dependencies = [
|
||||||
"viaduct",
|
"viaduct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinja"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f28580fecce391f3c0e65a692e5f2b5db258ba2346ee04f355ae56473ab973dc"
|
||||||
|
dependencies = [
|
||||||
|
"humansize",
|
||||||
|
"itoa 1.0.11",
|
||||||
|
"num-traits",
|
||||||
|
"percent-encoding",
|
||||||
|
"rinja_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinja_derive"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f1ae91455a4c82892d9513fcfa1ac8faff6c523602d0041536341882714aede"
|
||||||
|
dependencies = [
|
||||||
|
"basic-toml",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"once_map",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rinja_parser",
|
||||||
|
"rustc-hash 2.0.0",
|
||||||
|
"serde",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rinja_parser"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06ea17639e1f35032e1c67539856e498c04cd65fe2a45f55ec437ec55e4be941"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"nom",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rkv"
|
name = "rkv"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
|
@ -3741,6 +3796,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -3915,7 +3976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 1.9.1",
|
"indexmap 1.9.1",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3945,7 +4006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3969,7 +4030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
|
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 1.9.1",
|
"indexmap 1.9.1",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
|
@ -4095,6 +4156,26 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "start-bindings"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"camino",
|
||||||
|
"cargo_metadata",
|
||||||
|
"clap 4.2.2",
|
||||||
|
"rinja",
|
||||||
|
"serde_yaml 0.8.24",
|
||||||
|
"toml",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -4415,7 +4496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.11",
|
||||||
"libc",
|
"libc",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"num_threads",
|
"num_threads",
|
||||||
|
@ -4528,6 +4609,23 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.22.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.5.0",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
@ -5460,6 +5558,15 @@ version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c52ac009d615e79296318c1bcce2d422aaca15ad08515e344feeda07df67a587"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
|
@ -49,6 +49,7 @@ members = [
|
||||||
"megazords/ios-rust/focus",
|
"megazords/ios-rust/focus",
|
||||||
"tools/protobuf-gen",
|
"tools/protobuf-gen",
|
||||||
"tools/embedded-uniffi-bindgen",
|
"tools/embedded-uniffi-bindgen",
|
||||||
|
"tools/start-bindings",
|
||||||
"automation/swift-components-docs",
|
"automation/swift-components-docs",
|
||||||
|
|
||||||
"examples/*/",
|
"examples/*/",
|
||||||
|
|
|
@ -506,7 +506,7 @@ The following text applies to code linked from these dependencies:
|
||||||
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
||||||
[id-arena](https://github.com/fitzgen/id-arena),
|
[id-arena](https://github.com/fitzgen/id-arena),
|
||||||
[idna](https://github.com/servo/rust-url/),
|
[idna](https://github.com/servo/rust-url/),
|
||||||
[indexmap](https://github.com/bluss/indexmap),
|
[indexmap](https://github.com/indexmap-rs/indexmap),
|
||||||
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
||||||
[ipnet](https://github.com/krisprice/ipnet),
|
[ipnet](https://github.com/krisprice/ipnet),
|
||||||
[itertools](https://github.com/rust-itertools/itertools),
|
[itertools](https://github.com/rust-itertools/itertools),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
# Adding a new component to Application Services
|
# Adding a new component to Application Services
|
||||||
|
|
||||||
Each component in the Application Services repository has three parts (the Rust code,
|
This is a rapid-fire list for adding a component from scratch and generating Kotlin/Swift bindings.
|
||||||
the Kotlin wrapper, and the Swift wrapper) so there are quite a few moving
|
|
||||||
parts involved in adding a new component. This is a rapid-fire list of all
|
|
||||||
the things you'll need to do if adding a new component from scratch.
|
|
||||||
|
|
||||||
## The Rust Code
|
## The Rust Code
|
||||||
|
|
||||||
|
@ -16,14 +13,18 @@ advice on designing and structuring the actual Rust code, and follow the
|
||||||
introduces any new dependencies.
|
introduces any new dependencies.
|
||||||
|
|
||||||
Use [UniFFI](https://mozilla.github.io/uniffi-rs/) to define how your crate's
|
Use [UniFFI](https://mozilla.github.io/uniffi-rs/) to define how your crate's
|
||||||
API will get exposed to foreign-language bindings. Prefer using the
|
API will get exposed to foreign-language bindings. Place the following in your `Cargo.toml`:
|
||||||
[proc-macro](https://mozilla.github.io/uniffi-rs/latest/proc_macro/index.html) approach to creating
|
|
||||||
a UDL file. Place the following entries in your `Cargo.toml`:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
[dependencies]
|
[dependencies]
|
||||||
uniffi = { workspace = true }
|
uniffi = { workspace = true }
|
||||||
|
```
|
||||||
|
|
||||||
|
New components should prefer using the
|
||||||
|
[proc-macro](https://mozilla.github.io/uniffi-rs/latest/proc_macro/index.html) approach rather than
|
||||||
|
a UDL file based approach. If you do use a UDL file, add this to `Cargo.toml` as well.
|
||||||
|
|
||||||
|
```
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
uniffi = { workspace = true }
|
uniffi = { workspace = true }
|
||||||
```
|
```
|
||||||
|
@ -32,60 +33,16 @@ Include your new crate in the `application-services` workspace, by adding
|
||||||
it to the `members` and `default-members` lists in the `Cargo.toml` at
|
it to the `members` and `default-members` lists in the `Cargo.toml` at
|
||||||
the root of the repository.
|
the root of the repository.
|
||||||
|
|
||||||
In order to be published to consumers, your crate must be included in the
|
|
||||||
["megazord"](../design/megazords.md) crate for each target platform:
|
|
||||||
|
|
||||||
* For Android, add it as a dependency in `./megazords/full/Cargo.toml` and
|
|
||||||
add a `pub use <your_crate_name>` to `./megazords/full/src/lib.rs`.
|
|
||||||
* For iOS, add it as a dependency in `./megazords/ios-rust/rust/Cargo.toml` and
|
|
||||||
add a `pub use <your_crate_name>` to `./megazords/ios-rust/src/lib.rs`.
|
|
||||||
|
|
||||||
Run `cargo check -p <your_crate_name>` in the repository root to confirm that
|
Run `cargo check -p <your_crate_name>` in the repository root to confirm that
|
||||||
things are configured properly. This will also have the side-effect of updating
|
things are configured properly. This will also have the side-effect of updating
|
||||||
`Cargo.lock` to contain your new crate and its dependencies.
|
`Cargo.lock` to contain your new crate and its dependencies.
|
||||||
|
|
||||||
|
|
||||||
## The Kotlin Bindings
|
## The Android Bindings
|
||||||
|
|
||||||
Make a `./components/<your_crate_name>/android` subdirectory to contain
|
Run the `start-bindings android <your_crate_name> <component_description>` command to auto-generate the initial code. Follow the directions in the output.
|
||||||
Kotlin- and Android-specific code. This directory will contain a gradle
|
|
||||||
project for building your Kotlin bindings.
|
|
||||||
|
|
||||||
Copy the `build.gradle` file from `./components/crashtest/android/` into
|
You will end up with a directory structure something like this:
|
||||||
your own component's directory. Update the `ext.configureUniFFIBindgen("crashtest")` line,
|
|
||||||
replacing "crashtest" with the crate name of your component.
|
|
||||||
|
|
||||||
Create a file `./components/<your_crate_name>/uniffi.toml` with the
|
|
||||||
following contents:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[bindings.kotlin]
|
|
||||||
package_name = "mozilla.appservices.<your_crate_name>"
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a file `./components/<your_crate_name>/android/src/main/AndroidManifest.xml`
|
|
||||||
with the following contents:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="org.mozilla.appservices.<your_crate_name>" />
|
|
||||||
```
|
|
||||||
|
|
||||||
In the root of the repository, edit `.buildconfig-android.yml`to add
|
|
||||||
your component's metadata. This will cause it to be included in the
|
|
||||||
gradle workspace and in our build and publish pipeline. Check whether
|
|
||||||
it builds correctly by running:
|
|
||||||
* `./gradlew <your_crate_name>:assembleDebug`
|
|
||||||
|
|
||||||
You can include hand-written Kotlin code alongside the automatically
|
|
||||||
generated bindings, by placing `.kt`` files in a directory named:
|
|
||||||
* `./android/src/test/java/mozilla/appservices/<your_crate_name>/`
|
|
||||||
|
|
||||||
You can write Kotlin-level tests that consume your component's API,
|
|
||||||
by placing `.kt`` files in a directory named:
|
|
||||||
* `./android/src/test/java/mozilla/appservices/<your_crate_name>/`.
|
|
||||||
|
|
||||||
So you would end up with a directory structure something like this:
|
|
||||||
|
|
||||||
* `components/<your_crate_name>/`
|
* `components/<your_crate_name>/`
|
||||||
* `Cargo.toml`
|
* `Cargo.toml`
|
||||||
|
@ -97,26 +54,42 @@ So you would end up with a directory structure something like this:
|
||||||
* `src/`
|
* `src/`
|
||||||
* `main/`
|
* `main/`
|
||||||
* `AndroidManifest.xml`
|
* `AndroidManifest.xml`
|
||||||
* `java/mozilla/appservices/<your_crate_name>/`
|
|
||||||
* Hand-written Kotlin code here.
|
|
||||||
* `test/java/mozilla/appservices/<your_crate_name>/`
|
|
||||||
* Kotlin test-cases here.
|
|
||||||
|
|
||||||
Run your component's Kotlin tests with `./gradlew <your_crate_name>:test`
|
### Dependent crates
|
||||||
to confirm that this is all working correctly.
|
|
||||||
|
If your crate uses types from another crate in it's public API, you need to include a dependency for
|
||||||
|
the corresponding project in your `android/build.gradle` file.
|
||||||
|
|
||||||
|
For example, suppose use the `remote_settings::RemoteSettingsServer` type in your public API so that
|
||||||
|
consumers can select which server they want. In that case, you need to a dependency on the
|
||||||
|
remotesettings project:
|
||||||
|
|
||||||
|
```
|
||||||
|
dependencies {
|
||||||
|
api project(":remotesettings")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hand-written code
|
||||||
|
|
||||||
|
You can include hand-written Kotlin code alongside the automatically
|
||||||
|
generated bindings, by placing `.kt`` files in a directory named:
|
||||||
|
* `./android/src/test/java/mozilla/appservices/<your_crate_name>/`
|
||||||
|
|
||||||
|
You can write Kotlin-level tests that consume your component's API,
|
||||||
|
by placing `.kt`` files in a directory named:
|
||||||
|
* `./android/src/test/java/mozilla/appservices/<your_crate_name>/`.
|
||||||
|
|
||||||
|
You can run the tests with `./gradlew <your_crate_name>:test`
|
||||||
|
|
||||||
|
## The iOS Bindings
|
||||||
|
|
||||||
|
* Run the `start-bindings ios <your_crate_name>` command to auto-generate the initial code
|
||||||
|
* Run `start-bindings ios-focus <your_crate_name>` if you also want to expose your component to Focus.
|
||||||
|
* Follow the directions in the output.
|
||||||
|
|
||||||
|
|
||||||
## The Swift Bindings
|
You will end up with a directory structure something like this:
|
||||||
### Creating the directory structure
|
|
||||||
Make a `./components/<your_crate_name>/ios` subdirectory to contain
|
|
||||||
Swift- and iOS-specific code. The UniFFI-generated swift bindings will
|
|
||||||
be written to a subdirectory named `Generated`.
|
|
||||||
|
|
||||||
You can include hand-written Swift code alongside the automatically
|
|
||||||
generated bindings, by placing `.swift` files in a directory named:
|
|
||||||
`./ios/<your_crate_name>/`.
|
|
||||||
|
|
||||||
So you would end up with a directory structure something like this:
|
|
||||||
|
|
||||||
* `components/<your_crate_name>/`
|
* `components/<your_crate_name>/`
|
||||||
* `Cargo.toml`
|
* `Cargo.toml`
|
||||||
|
@ -124,31 +97,20 @@ So you would end up with a directory structure something like this:
|
||||||
* `src/`
|
* `src/`
|
||||||
* Rust code here.
|
* Rust code here.
|
||||||
* `ios/`
|
* `ios/`
|
||||||
* `<your_crate_name>/`
|
|
||||||
* Hand-written Swift code here.
|
|
||||||
* `Generated/`
|
* `Generated/`
|
||||||
* Generated Swift code will be written into this directory.
|
* Generated Swift code will be written into this directory.
|
||||||
|
|
||||||
### Adding your component to the Swift Package Manager Megazord
|
### Adding your component to the Swift Package Manager Megazord
|
||||||
|
|
||||||
> *For more information on our how we ship components using the Swift Package Manager, check the [ADR that introduced the Swift Package Manager](../adr/0003-swift-packaging.md)*
|
> *For more information on our how we ship components using the Swift Package Manager, check the [ADR that introduced the Swift Package Manager](../adr/0003-swift-packaging.md)*
|
||||||
|
|
||||||
You will need to do the following steps to include the component in the megazord:
|
Add your component into the iOS ["megazord"](../design/megazords.md) through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac.
|
||||||
1. Update its `uniffi.toml` to include the following settings:
|
|
||||||
```toml
|
|
||||||
[bindings.swift]
|
|
||||||
ffi_module_name = "MozillaRustComponents"
|
|
||||||
ffi_module_filename = "<crate_name>FFI"
|
|
||||||
```
|
|
||||||
1. Add the component as a dependency to the `Cargo.toml` in [`megazords/ios-rust/`](https://github.com/mozilla/application-services/blob/main/megazords/ios-rust/Cargo.toml)
|
|
||||||
1. Add a `pub use` declaration for the component in [`megazords/ios-rust/src/lib.rs`](https://github.com/mozilla/application-services/blob/main/megazords/ios-rust/src/lib.rs)
|
|
||||||
1. Add an `#import` for its header file to [`megazords/ios-rust/MozillaRustComponents.h`](https://github.com/mozilla/application-services/blob/main/megazords/ios-rust/MozillaRustComponents.h)
|
|
||||||
1. Add your component into the iOS ["megazord"](../design/megazords.md) through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac.
|
|
||||||
|
|
||||||
1. Open `megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj` in Xcode.
|
1. Open `megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj` in Xcode.
|
||||||
|
|
||||||
1. In the Project navigator, add a new Group for your new component, pointing to
|
1. In the Project navigator, add a new Group for your new component, pointing to
|
||||||
the `./ios/` directory you created above. Add the following entries to the Group:
|
the `./ios/` directory you created above. Add the following entries to the Group:
|
||||||
* Any hand-written `.swift `files for your component
|
* Any hand-written `.swift `files for your component
|
||||||
|
|
||||||
> Make sure that the "Copy items if needed" option is **unchecked**, and that
|
> Make sure that the "Copy items if needed" option is **unchecked**, and that
|
||||||
nothing is checked in the "Add to targets" list.
|
nothing is checked in the "Add to targets" list.
|
||||||
|
@ -191,11 +153,19 @@ The result should look something like this:
|
||||||
Use the Xcode Test Navigator to run your tests and check whether
|
Use the Xcode Test Navigator to run your tests and check whether
|
||||||
they're passing.
|
they're passing.
|
||||||
|
|
||||||
## Distribute your component with `rust-components-swift`
|
### Hand-written code
|
||||||
|
|
||||||
|
You can include hand-written Swift code alongside the automatically
|
||||||
|
generated bindings, by placing `.swift` files in a directory named:
|
||||||
|
`./ios/<your_crate_name>/`.
|
||||||
|
|
||||||
|
Make sure that this code gets distributed. Edit `taskcluster/scripts/build-and-test-swift.py` and:
|
||||||
|
|
||||||
|
- Add the path to the directory containing any hand-written swift code to `SOURCE_TO_COPY`
|
||||||
|
- Optionally also to `FOCUS_SOURCE_TO_COPY` if your component is also targeting Firefox Focus
|
||||||
|
|
||||||
|
|
||||||
|
### Distribute your component with `rust-components-swift`
|
||||||
The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through [`rust-components-swift`](https://github.com/mozilla/rust-components-swift).
|
The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through [`rust-components-swift`](https://github.com/mozilla/rust-components-swift).
|
||||||
|
|
||||||
A nightly taskcluster job prepares the `rust-component-swift` packages from the source code in the application-services repository. To distribute your component with `rust-component-swift`, add the following to the taskcluster script in `taskcluster/scripts/build-and-test-swift.py`:
|
|
||||||
- Add the path to the directory containing any hand-written swift code to `SOURCE_TO_COPY`
|
|
||||||
- Optionally also to `FOCUS_SOURCE_TO_COPY` if your component is also targeting Firefox Focus
|
|
||||||
|
|
||||||
Your component should now automatically get included in the next `rust-component-swift` nightly release.
|
Your component should now automatically get included in the next `rust-component-swift` nightly release.
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
# Converting an existing Component to use UniFFI
|
|
||||||
|
|
||||||
When we started building the components in this repo, exposing Rust code to
|
|
||||||
Kotlin and Swift was a manual process and each component had its own
|
|
||||||
hand-written FFI layer and foreign-language bindings.
|
|
||||||
|
|
||||||
As we've gained more experience with building components in this way, we've
|
|
||||||
started to automate bindings generation and capture best practices in a
|
|
||||||
tool called [UniFFI](https://mozilla.github.io/uniffi-rs/), which is the
|
|
||||||
currently recommended approach when [adding a new component from scratch](
|
|
||||||
./adding-a-new-component.md).
|
|
||||||
|
|
||||||
We expect that existing components will gradually be ported over to use
|
|
||||||
UniFFI, and this document is a guide to doing that port.
|
|
||||||
|
|
||||||
## First, get familiar with UniFFI
|
|
||||||
|
|
||||||
First, make sure you've perused the [UniFFI guide](https://mozilla.github.io/uniffi-rs/)
|
|
||||||
to understand the overall architecture of a UniFFI component, and take a look
|
|
||||||
at the [guide to adding a new component](./adding-a-new-component.md) to understand
|
|
||||||
how such components fit in to this repo. The aim of porting will be to have a component
|
|
||||||
that looks like it was added by the process described therein.
|
|
||||||
|
|
||||||
## Next, get familiar with the target component
|
|
||||||
|
|
||||||
Pre-UniFFI components typically consist of four main parts:
|
|
||||||
|
|
||||||
* A Rust crate implementing the core functionality of the component
|
|
||||||
* A separate Rust crate that exposes the core functionality over a C-style FFI.
|
|
||||||
* An Android package that imports the C-style FFI into idiomatic Kotlin.
|
|
||||||
* A Swift module that imports the C-style FFI into idiomatic Swift.
|
|
||||||
|
|
||||||
The code for these parts will be laid out something like this:
|
|
||||||
|
|
||||||
* `components/<component_name>/`
|
|
||||||
* `Cargo.toml`
|
|
||||||
* `src/`
|
|
||||||
* Rust code for the core functionality of the component goes here.
|
|
||||||
* `ffi/`
|
|
||||||
* `Cargo.toml`
|
|
||||||
* `src/`
|
|
||||||
* Rust code specifically for exposing the C-style FFI goes here.
|
|
||||||
* `android/`
|
|
||||||
* `build.gradle`
|
|
||||||
* `src/`
|
|
||||||
* `main/`
|
|
||||||
* `AndroidManifest.xml`
|
|
||||||
* `java/mozilla/appservices/<component_name>/`
|
|
||||||
* `Lib<ComponentName>FFI.kt` (low-level bindings to the C-style FFI)
|
|
||||||
* Higher-level hand-written Kotlin that wraps the FFI.
|
|
||||||
* `ios/`
|
|
||||||
* `<component_name>/`
|
|
||||||
* `Rust<ComponentName>API.h` (low-level bindings to the C-style FFI)
|
|
||||||
* Higher-level hand-written Swift that wraps the FFI.
|
|
||||||
|
|
||||||
The goal here is to replace much of the hand-written wrapper layers with autogenerated
|
|
||||||
code:
|
|
||||||
|
|
||||||
* The `./ffi/` crate will disappear entirely, its work is automated by UniFFI
|
|
||||||
* If you still need some hand-written `pub extern "C"` functions, perhaps to
|
|
||||||
implement features not currently supported by UniFFI, then they should move
|
|
||||||
into `lib.rs` of the main component crate.
|
|
||||||
* The low-level `Lib<ComponentName>FFI.kt` file will disappear entirely, as will some of the
|
|
||||||
code that converts it back into nice high-level Kotlin classes and interfaces.
|
|
||||||
* Some of the hand-written Kotlin code may remain, if it provides functionality that
|
|
||||||
cannot be implemented in Rust.
|
|
||||||
* The low-level `Rust<ComponentName>API.h` file will disappear entirely, as will some of the
|
|
||||||
code that converts it back into nice high-level Swift classes and interfaces.
|
|
||||||
* Some of the hand-written Swift code may remain, if it provides functionality that
|
|
||||||
cannot be implemented in Rust.
|
|
||||||
|
|
||||||
You'll aim to end up with a simplified file structure that looks like this:
|
|
||||||
|
|
||||||
|
|
||||||
* `components/<component_name>/`
|
|
||||||
* `Cargo.toml`
|
|
||||||
* `uniffi.toml`
|
|
||||||
* `src/`
|
|
||||||
* `<component_name>.udl` (abstract interface definition)
|
|
||||||
* Rust code here.
|
|
||||||
* `android/`
|
|
||||||
* `build.gradle`
|
|
||||||
* `src/`
|
|
||||||
* `main/`
|
|
||||||
* `AndroidManifest.xml`
|
|
||||||
* `java/mozilla/appservices/<component_name>/`
|
|
||||||
* Optional hand-written Kotlin code here.
|
|
||||||
* `ios/`
|
|
||||||
* `<component_name>/`
|
|
||||||
* Optional hand-written Swift code here.
|
|
||||||
|
|
||||||
## Write a first draft of the `.udl` file for the component's interface
|
|
||||||
|
|
||||||
Make sure you've got the `uniffi-bindgen` command available; `cargo install uniffi_bindgen` will
|
|
||||||
ensure you have the latest version.
|
|
||||||
|
|
||||||
Create `./src/<component_name>.udl` and try to describe the intended interface for the component
|
|
||||||
using [UniFFI's interface definition language](https://mozilla.github.io/uniffi-rs/udl_file_spec.html).
|
|
||||||
You'll probably need to reverse-engineer it a little bit from the existing hand-written Kotlin and/or
|
|
||||||
Swift code.
|
|
||||||
|
|
||||||
Don't spend too much time on trying to match every minute detail of the existing hand-written API.
|
|
||||||
There are likely to be small differences between how UniFFI likes to do things and how the hand-written
|
|
||||||
APIs were structured, and it's in everyone's best long-term interests to just push ahead and update
|
|
||||||
consumers to accommodate any breaking API changes, rather than e.g. trying to convince UniFFI to
|
|
||||||
capitalize enum variant names in the same style that the hand-written code was using.
|
|
||||||
|
|
||||||
To check whether the `.udl` file is syntactically valid, you can use `uniffi-bindgen` to generate
|
|
||||||
the Rust FFI scaffolding like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
uniffi-bindgen scaffolding ./src/<component_name>.udl
|
|
||||||
```
|
|
||||||
|
|
||||||
If this succeeds, it will generate a file `./src/<component_name>.uniffi.rs` with a bunch of
|
|
||||||
thorny auto-generated Rust code. If it fails, it will likely fail with an inscrutable error message.
|
|
||||||
Unfortunately the error reporting in UniFFI is currently a known pain point, and it can take a
|
|
||||||
bit of trial-and-error to identify what part of the file is causing the issue. Sorry :-(
|
|
||||||
|
|
||||||
The aim at this point is to ensure that the intended interface of the component can be expressed
|
|
||||||
in terms that UniFFI understands. Most cases should be supported, but you may find some aspect of
|
|
||||||
the existing component that is hard to express in UniFFI, perhaps even uncovering new functionality
|
|
||||||
that needs to be added to UniFFI itself!
|
|
||||||
|
|
||||||
The `.udl` file is definitely a first draft at this point. It is normal and expected to need
|
|
||||||
to iterate on this file as you port over the underlying Rust code.
|
|
||||||
|
|
||||||
|
|
||||||
## Restructure the Rust code to introduce UniFFI
|
|
||||||
|
|
||||||
You will now restructure the existing Rust crate so that its public API surface
|
|
||||||
and overall "shape" match what you defined in the `.udl` file.
|
|
||||||
|
|
||||||
Start by deleting the `./ffi` sub-crate, because you're going to use UniFFI to generate
|
|
||||||
all of that code. You'll also need to remove it from the workspace in the top-level
|
|
||||||
`Cargo.toml` file, as well as change the crates under `/megazords` to import the core
|
|
||||||
Rust crate for the component rather than importing the FFI sub-crate.
|
|
||||||
|
|
||||||
Add UniFFI to the crate's dependencies and configure its `build.rs` script to invoke the
|
|
||||||
UniFFI scaffolding generator, as described in ["adding a new component"](adding-a-new-component.md).
|
|
||||||
|
|
||||||
Now, edit `./lib.rs` so that it matches the interface defined in the `.udl` file as closely
|
|
||||||
as possible. If the `.udl` has an `interface Example` then `lib.rs` should contain a
|
|
||||||
`pub struct Example`, if the `.udl` contains an `enum ExampleItem` then `lib.rs` should
|
|
||||||
contain a `pub enum ExampleItem`, and so-on.
|
|
||||||
|
|
||||||
The details of this step will depend heavily on the specific crate, but some tips include:
|
|
||||||
|
|
||||||
* You may find it useful to move all of the existing code into a sub-module named `internal`,
|
|
||||||
and then make a brand new `lib.rs` that imports or re-defines just the pieces it needs
|
|
||||||
in order to implement the interface from the `.udl` file. The `fxa-client` crate is an
|
|
||||||
example of a case where this worked out well, though of course your mileage may vary.
|
|
||||||
|
|
||||||
* If the existing crate contains a file named like `<component_name>_msg_types.proto`, then
|
|
||||||
it was using Protocol Buffers to serialize data to pass over the FFI. The message types
|
|
||||||
defined in the `.proto` file will need to be converted into `dictionary` or `enum` definitions
|
|
||||||
in your `.udl` file. See the section below for more details.
|
|
||||||
|
|
||||||
As noted above, don't be afraid to accept some API churn during the conversion process.
|
|
||||||
We're willing to accept some breaking API changes as the cost of getting bindings generated
|
|
||||||
for free, as long as the core functionality and mental model of the component remain intact.
|
|
||||||
|
|
||||||
At this point, in theory the crate should be buildable with UniFFI, although it's likely
|
|
||||||
to require some iteration to get it all working! Run `cargo check` to check for any
|
|
||||||
compilation errors without having to do a full build.
|
|
||||||
|
|
||||||
### Removing Protobuf Messages
|
|
||||||
|
|
||||||
Passing rich structured data over the FFI is the most complex part of our hand-written bindings,
|
|
||||||
and was previously done by [serializing data via Protocol Buffers](
|
|
||||||
https://hacks.mozilla.org/2019/04/crossing-the-rust-ffi-frontier-with-protocol-buffers/).
|
|
||||||
This is something that UniFFI tries to make as simple as possible.
|
|
||||||
|
|
||||||
Start by locating the `<component_name>_msg_types.proto` file for the component. This file defines
|
|
||||||
the structured messages that can be passed over the FFI, and you should see that they correspond
|
|
||||||
to various types of structured data that the component wants to receive from, or return to,
|
|
||||||
the foreign-language code.
|
|
||||||
|
|
||||||
Find the places in your `.udl` interface that correspond to these message types and make sure
|
|
||||||
that you've got a similarly-shaped `dictionary` or `enum` for each one. You should find that
|
|
||||||
representing this structured data in UDL is simpler than protobuf in many cases - for example
|
|
||||||
many of our `.protobuf` files need to use a separate `ExampleStructs` message in order to
|
|
||||||
pass a list of `ExampleStruct` messages over the FFI, but in UniFFI this is represented
|
|
||||||
directly as `sequence<ExampleStruct>`.
|
|
||||||
|
|
||||||
Find the places in the Rust code that are using these message types to return structured data.
|
|
||||||
In simple cases, you may be able to directly replace uses of `msg_types::ExampleStruct` with
|
|
||||||
the corresponding `crate::ExampleStruct` from your public API.
|
|
||||||
For more complex cases, you may find it helpful to define an `Into` mapping between the
|
|
||||||
UniFFI dictionary/enum in the crate's public interface, and a more complex struct designed
|
|
||||||
for internal use.
|
|
||||||
|
|
||||||
As noted above, don't be afraid to accept some API churn during this conversion process.
|
|
||||||
|
|
||||||
Once you have replaced all uses of the `msg_types` structs in the Rust code:
|
|
||||||
|
|
||||||
* Delete `./src/<component_name>_msg_types.proto`.
|
|
||||||
* Delete `./src/mozilla.appservices.<component_name>.protobuf.rs`, which is generated from the `.proto` file.
|
|
||||||
* Remote `prost` and `prost-derive` from the crate's dependencies.
|
|
||||||
* Delete the crate from the list in `/tools/protobuf_files.toml`.
|
|
||||||
|
|
||||||
If you happen to find that you've deleted the last crate from the list in `protobuf_files.toml`,
|
|
||||||
congratulations! You've successfully removed protocol buffers from this repo entirely, and should
|
|
||||||
file a bug to track the complete removal of protobuf from our tooling and dependency chain.
|
|
||||||
|
|
||||||
## Document the Public API in the Rust code
|
|
||||||
|
|
||||||
Write consumer-facing documentation on the public API in `lib.rs` using Rust's standard
|
|
||||||
[rustdoc](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html) conventions
|
|
||||||
and tools. The `fxa-client` crate may serve as a good example.
|
|
||||||
|
|
||||||
You can view the generated documentation by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo doc --no-deps --open
|
|
||||||
```
|
|
||||||
|
|
||||||
In future, we intend to automatically extract documentation from the Rust code
|
|
||||||
and make it easily available to consumers of the generated bindings.
|
|
||||||
|
|
||||||
(In fact there is some work-in-progress code in [uniffi-rs#416](https://github.com/mozilla/uniffi-rs/pull/416)
|
|
||||||
that can read docs from the Rust code and write them back into the `.udl` file, which you're
|
|
||||||
welcome to try out if you're feeling adventurous. But it's just a very hacky prototype.)
|
|
||||||
|
|
||||||
## Set up the Kotlin wrapper
|
|
||||||
|
|
||||||
It's easiest to start by removing all of the hand-written Kotlin code under `android/src/main/java`
|
|
||||||
and then restoring parts of it later if necessary. Leave the `AndroidManifest.xml` file and any tests
|
|
||||||
in place.
|
|
||||||
|
|
||||||
Delete the `android/build.gradle` file and then follow the instructions for [adding Kotlin bindings
|
|
||||||
for a new component](adding-a-new-component.md#the-kotlin-bindings) to create a new `build.gradle`
|
|
||||||
file and a corresponding `uniffi.toml`.
|
|
||||||
|
|
||||||
This should be all that's required to set up UniFFI to build the Kotlin bindings. Try building
|
|
||||||
the Android package to confirm:
|
|
||||||
|
|
||||||
* `./gradlew <component_name>:assembleDebug`
|
|
||||||
|
|
||||||
The UniFFI-generated Kotlin code will be under `./android/build/generated/source/uniffi/` and
|
|
||||||
may be useful for debugging.
|
|
||||||
|
|
||||||
If there are existing Kotlin tests for the component, the next step is to get those passing:
|
|
||||||
|
|
||||||
* `./gradlew <component_name>:test`
|
|
||||||
|
|
||||||
As noted above, it is normal and expected for the autogenerated bindings to be subtly different
|
|
||||||
from the previous hand-written ones. For example, UniFFI insists on using SHOUTY_SNAKE_CASE
|
|
||||||
variant names in Kotlin enums while the hand-written code may have used CamelCase. Some components
|
|
||||||
also have small naming differences between the Rust code and the hand-written Kotlin bindings,
|
|
||||||
which UniFFI will not allow.
|
|
||||||
|
|
||||||
If the component had functionality in its Kotlin layer that was not part of the Rust API,
|
|
||||||
then you'll need to add some hand-written Kotlin code under `android/src/main/java` to
|
|
||||||
implement it. The `fxa-client` component may be a good example here: its Rust layer exposes
|
|
||||||
a `FirefoxAccount` struct that the Kotlin code wraps into a `PersistedFirefoxAccount` class,
|
|
||||||
adding the ability to set a persistence callback.
|
|
||||||
|
|
||||||
Finally, you will need to try out the new bindings with a consuming app. For Kotlin code you should
|
|
||||||
[make a local build of android-components and Fenix](locally-published-components-in-fenix.md),
|
|
||||||
updating them to accommodate any changes in the component's public API.
|
|
||||||
|
|
||||||
|
|
||||||
## Set up the Swift wrapper
|
|
||||||
|
|
||||||
It's easiest to start by removing all of the hand-written Swift code under `./ios` and then
|
|
||||||
restoring parts of it later if necessary.
|
|
||||||
|
|
||||||
Edit `/megazords/ios-rust/MozillaTestServices.h` to remove any references to `Rust<ComponentName>API.h`,
|
|
||||||
replacing them with the UniFFI-generated header file name `<component_name>FFI.h`.
|
|
||||||
|
|
||||||
Open `/megazords/ios-rust/MozillaTestServices.xcodeproj` in Xcode and follow the instructions for
|
|
||||||
[adding Swift bindings for a new component](adding-a-new-component.md#the-swift-bindings) to
|
|
||||||
configure Xcode to build your UniFFI-generated bindings.
|
|
||||||
|
|
||||||
While you are in the Xcode Project Navigator, you should also delete any references to
|
|
||||||
`Rust<ComponentName>API.h` or to the old hand-written Swift wrappers. (They should be highlighted
|
|
||||||
in red in the Project Navigator, because the files will be missing from disk after you
|
|
||||||
deleted them above).
|
|
||||||
|
|
||||||
This should be all that's required to set up UniFFI to build the Swift bindings. Try building
|
|
||||||
the project in Xcode to confirm.
|
|
||||||
|
|
||||||
The UniFFI-generated Swift code will be under `ios/Generated` and may be useful for debugging.
|
|
||||||
|
|
||||||
If there are existing Swift tests for the component, the next step is to get those passing:
|
|
||||||
|
|
||||||
* `./automation/run_ios_tests.sh`
|
|
||||||
* (or run them from the Xcode GUI)
|
|
||||||
|
|
||||||
As noted above, it is normal and expected for the autogenerated bindings to be subtly different
|
|
||||||
from the previous hand-written ones. Many existing components have small naming differences
|
|
||||||
between the Rust code and the hand-written Swift bindings, which UniFFI will not allow.
|
|
||||||
|
|
||||||
If the component had functionality in its Swift layer that was not part of the Rust API,
|
|
||||||
then you'll need to add some hand-written Swift code under `./ios/<ComponentName>` to
|
|
||||||
implement it. The `fxa-client` component may be a good example here: its Rust layer exposes
|
|
||||||
a `FirefoxAccount` struct that the Swift code wraps into a `PersistedFirefoxAccount` class,
|
|
||||||
adding the ability to set a persistence callback.
|
|
||||||
|
|
||||||
You will need to add any such file to the "Compile Sources" list in Xcode, in the same way
|
|
||||||
that you added the `.udl` file.
|
|
||||||
|
|
||||||
Finally, you will need to try out the new bindings with a consuming app. For Swift code you should make a local build of Firefox iOS, you can do that by following the steps in [this document](./locally-published-components-in-firefox-ios.md)
|
|
|
@ -497,7 +497,7 @@ The following text applies to code linked from these dependencies:
|
||||||
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
||||||
[id-arena](https://github.com/fitzgen/id-arena),
|
[id-arena](https://github.com/fitzgen/id-arena),
|
||||||
[idna](https://github.com/servo/rust-url/),
|
[idna](https://github.com/servo/rust-url/),
|
||||||
[indexmap](https://github.com/bluss/indexmap),
|
[indexmap](https://github.com/indexmap-rs/indexmap),
|
||||||
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
||||||
[ipnet](https://github.com/krisprice/ipnet),
|
[ipnet](https://github.com/krisprice/ipnet),
|
||||||
[itertools](https://github.com/rust-itertools/itertools),
|
[itertools](https://github.com/rust-itertools/itertools),
|
||||||
|
|
|
@ -475,7 +475,7 @@ The following text applies to code linked from these dependencies:
|
||||||
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
[iana-time-zone](https://github.com/strawlab/iana-time-zone),
|
||||||
[id-arena](https://github.com/fitzgen/id-arena),
|
[id-arena](https://github.com/fitzgen/id-arena),
|
||||||
[idna](https://github.com/servo/rust-url/),
|
[idna](https://github.com/servo/rust-url/),
|
||||||
[indexmap](https://github.com/bluss/indexmap),
|
[indexmap](https://github.com/indexmap-rs/indexmap),
|
||||||
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
[io-lifetimes](https://github.com/sunfishcode/io-lifetimes),
|
||||||
[ipnet](https://github.com/krisprice/ipnet),
|
[ipnet](https://github.com/krisprice/ipnet),
|
||||||
[itertools](https://github.com/rust-itertools/itertools),
|
[itertools](https://github.com/rust-itertools/itertools),
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "start-bindings"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
camino = "1"
|
||||||
|
cargo_metadata = "0.15"
|
||||||
|
clap = { version = "4.2", features = ["derive"] }
|
||||||
|
rinja = "0.3.3"
|
||||||
|
serde_yaml = "0.8"
|
||||||
|
toml = "0.5"
|
||||||
|
toml_edit = "0.22.21"
|
|
@ -0,0 +1,194 @@
|
||||||
|
/* 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 std::{
|
||||||
|
fs::{create_dir_all, read_to_string, File},
|
||||||
|
io::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use camino::Utf8Path;
|
||||||
|
use rinja::Template;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cargo_metadata::CargoMetadataInfo,
|
||||||
|
toml::{add_cargo_toml_dependency, update_uniffi_toml},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_android(crate_name: String, description: String) -> Result<()> {
|
||||||
|
let metadata = CargoMetadataInfo::new(&crate_name)?;
|
||||||
|
let android_root = metadata.crate_root.join("android");
|
||||||
|
|
||||||
|
println!();
|
||||||
|
write_file(
|
||||||
|
BuildGradle {
|
||||||
|
crate_name: crate_name.clone(),
|
||||||
|
}
|
||||||
|
.render()?,
|
||||||
|
&android_root.join("build.gradle"),
|
||||||
|
)?;
|
||||||
|
write_file(
|
||||||
|
ANDROID_MANIFEST,
|
||||||
|
&android_root
|
||||||
|
.join("src")
|
||||||
|
.join("main")
|
||||||
|
.join("AndroidManifest.xml"),
|
||||||
|
)?;
|
||||||
|
write_file(PROGUARD_RULES, &android_root.join("proguard-rules.pro"))?;
|
||||||
|
update_uniffi_toml(
|
||||||
|
&metadata.crate_root,
|
||||||
|
"kotlin",
|
||||||
|
[(
|
||||||
|
"package_name",
|
||||||
|
format!("mozilla.appservices.{crate_name}").into(),
|
||||||
|
)],
|
||||||
|
)?;
|
||||||
|
add_cargo_toml_dependency(
|
||||||
|
&metadata.android_megazord_root,
|
||||||
|
&metadata.crate_root,
|
||||||
|
&crate_name,
|
||||||
|
)?;
|
||||||
|
update_buildconfig(
|
||||||
|
&metadata.workspace_root,
|
||||||
|
&crate_name,
|
||||||
|
&android_root,
|
||||||
|
&description,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!("Android bindings successfully started!");
|
||||||
|
println!();
|
||||||
|
println!("Run `./gradlew <your_crate_name>:assembleDebug` from the app-services root directory to test that this is working");
|
||||||
|
println!();
|
||||||
|
println!("Does crate use types from another crate in it's public API? If so, you'll need to tweak the `android/build.gradle` file:");
|
||||||
|
println!("https://mozilla.github.io/application-services/book/howtos/adding-a-new-component.html#dependent-crates");
|
||||||
|
println!();
|
||||||
|
println!("Optional steps:");
|
||||||
|
println!(
|
||||||
|
" - Add hand-written Android code in {}",
|
||||||
|
metadata
|
||||||
|
.crate_root
|
||||||
|
.join("android")
|
||||||
|
.join("src")
|
||||||
|
.join("main")
|
||||||
|
.join("java")
|
||||||
|
.join("mozilla")
|
||||||
|
.join("appservices")
|
||||||
|
.join(&crate_name)
|
||||||
|
.strip_prefix(&metadata.workspace_root)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" - Add tests in {}",
|
||||||
|
metadata
|
||||||
|
.crate_root
|
||||||
|
.join("android")
|
||||||
|
.join("src")
|
||||||
|
.join("test")
|
||||||
|
.join("java")
|
||||||
|
.join("mozilla")
|
||||||
|
.join("appservices")
|
||||||
|
.join(&crate_name)
|
||||||
|
.strip_prefix(&metadata.workspace_root)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_file(contents: impl AsRef<str>, path: &Utf8Path) -> Result<()> {
|
||||||
|
let contents = contents.as_ref();
|
||||||
|
create_dir_all(path.parent().unwrap())?;
|
||||||
|
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
write!(file, "{contents}")?;
|
||||||
|
println!("{path} generated");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update .buildconfig-android.yml
|
||||||
|
//
|
||||||
|
// We don't have anything like toml-edit that can edit YAML files while maintaining the formatting.
|
||||||
|
// Instead, if we need to update the file, append a manually constructed YAML fragment.
|
||||||
|
fn update_buildconfig(
|
||||||
|
workspace_root: &Utf8Path,
|
||||||
|
crate_name: &str,
|
||||||
|
android_root: &Utf8Path,
|
||||||
|
description: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let path = workspace_root.join(".buildconfig-android.yml");
|
||||||
|
if !buildconfig_needs_update(&path, crate_name)? {
|
||||||
|
println!("{path} skipped ([projects.{crate_name}] key already exists)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fragment = BuildConfigFragementTemplate {
|
||||||
|
crate_name: crate_name.to_owned(),
|
||||||
|
android_root: android_root
|
||||||
|
.strip_prefix(workspace_root)
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
description: description.to_owned(),
|
||||||
|
}
|
||||||
|
.render()?;
|
||||||
|
|
||||||
|
let mut file = File::options().append(true).open(&path)?;
|
||||||
|
write!(file, "{fragment}")?;
|
||||||
|
println!("{path} updated");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildconfig_needs_update(path: &Utf8Path, crate_name: &str) -> Result<bool> {
|
||||||
|
let config: serde_yaml::Value = serde_yaml::from_str(&read_to_string(path)?)?;
|
||||||
|
let projects = config
|
||||||
|
.as_mapping()
|
||||||
|
.and_then(|m| m.get(&"projects".into()))
|
||||||
|
.and_then(|v| v.as_mapping());
|
||||||
|
match projects {
|
||||||
|
None => bail!("buildconfig.yaml does not have projects key"),
|
||||||
|
Some(projects) => Ok(!projects.contains_key(&crate_name.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "build.gradle", escape = "none")]
|
||||||
|
struct BuildGradle {
|
||||||
|
crate_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ANDROID_MANIFEST: &str =
|
||||||
|
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"/>\n";
|
||||||
|
const PROGUARD_RULES: &str = "\
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
";
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "buildconfig.android.fragment", escape = "none")]
|
||||||
|
struct BuildConfigFragementTemplate {
|
||||||
|
crate_name: String,
|
||||||
|
android_root: String,
|
||||||
|
description: String,
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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::{bail, Result};
|
||||||
|
use camino::Utf8PathBuf;
|
||||||
|
use cargo_metadata::{Metadata, MetadataCommand};
|
||||||
|
|
||||||
|
pub struct CargoMetadataInfo {
|
||||||
|
pub workspace_root: Utf8PathBuf,
|
||||||
|
pub crate_root: Utf8PathBuf,
|
||||||
|
pub android_megazord_root: Utf8PathBuf,
|
||||||
|
pub ios_megazord_root: Utf8PathBuf,
|
||||||
|
pub ios_focus_megazord_root: Utf8PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CargoMetadataInfo {
|
||||||
|
pub fn new(crate_name: &str) -> Result<Self> {
|
||||||
|
let metadata = MetadataCommand::new().exec().unwrap();
|
||||||
|
Ok(Self {
|
||||||
|
crate_root: find_crate_root(&metadata, crate_name)?,
|
||||||
|
android_megazord_root: find_crate_root(&metadata, "megazord")?,
|
||||||
|
ios_megazord_root: find_crate_root(&metadata, "megazord_ios")?,
|
||||||
|
ios_focus_megazord_root: find_crate_root(&metadata, "megazord_focus")?,
|
||||||
|
workspace_root: metadata.workspace_root,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_crate_root(metadata: &Metadata, crate_name: &str) -> Result<Utf8PathBuf> {
|
||||||
|
let package = metadata.packages.iter().find(|pkg| pkg.name == crate_name);
|
||||||
|
match package {
|
||||||
|
Some(pkg) => Ok(pkg.manifest_path.parent().unwrap().to_owned()),
|
||||||
|
None => bail!("Crate not found: {crate_name}"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
/* 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 std::{
|
||||||
|
fs::{read_to_string, File},
|
||||||
|
io::Write,
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use camino::Utf8Path;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cargo_metadata::CargoMetadataInfo,
|
||||||
|
toml::{add_cargo_toml_dependency, update_uniffi_toml},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_ios(crate_name: String) -> Result<()> {
|
||||||
|
generate(crate_name, IosMegazord::Ios)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_ios_focus(crate_name: String) -> Result<()> {
|
||||||
|
generate(crate_name, IosMegazord::Focus)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IosMegazord {
|
||||||
|
Ios,
|
||||||
|
Focus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IosMegazord {
|
||||||
|
fn root_dir<'a>(&self, metadata_info: &'a CargoMetadataInfo) -> &'a Utf8Path {
|
||||||
|
match self {
|
||||||
|
Self::Ios => &metadata_info.ios_megazord_root,
|
||||||
|
Self::Focus => &metadata_info.ios_focus_megazord_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Ios => "Ios",
|
||||||
|
Self::Focus => "Ios Focus",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crate_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Ios => "megazord_ios",
|
||||||
|
Self::Focus => "megazord_focus",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate(crate_name: String, megazord: IosMegazord) -> Result<()> {
|
||||||
|
let metadata = CargoMetadataInfo::new(&crate_name)?;
|
||||||
|
add_cargo_toml_dependency(
|
||||||
|
megazord.root_dir(&metadata),
|
||||||
|
&metadata.crate_root,
|
||||||
|
&crate_name,
|
||||||
|
)?;
|
||||||
|
update_uniffi_toml(
|
||||||
|
&metadata.crate_root,
|
||||||
|
"swift",
|
||||||
|
[
|
||||||
|
("ffi_module_name", "MozillaRustComponents".into()),
|
||||||
|
("ffi_module_filename", format!("{crate_name}FFI").into()),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
update_megazord_lib_rs(
|
||||||
|
megazord.root_dir(&metadata),
|
||||||
|
megazord.crate_name(),
|
||||||
|
&crate_name,
|
||||||
|
)?;
|
||||||
|
println!();
|
||||||
|
println!("{} bindings successfully started!", megazord.name());
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"The next step is to update the iOS Xcode project. See the application-services docs:"
|
||||||
|
);
|
||||||
|
println!("https://mozilla.github.io/application-services/book/howtos/adding-a-new-component.html#adding-your-component-to-the-swift-package-manager-megazord");
|
||||||
|
println!();
|
||||||
|
println!("Optional steps:");
|
||||||
|
println!(
|
||||||
|
" - Add hand-written code in {}",
|
||||||
|
metadata
|
||||||
|
.crate_root
|
||||||
|
.join("ios")
|
||||||
|
.strip_prefix(&metadata.workspace_root)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add `pub use <crate>` to lib.rs for the megazord.
|
||||||
|
///
|
||||||
|
/// This is needed for iOS, but not for Android. Maybe because iOS uses a static lib.
|
||||||
|
fn update_megazord_lib_rs(
|
||||||
|
crate_root: &Utf8Path,
|
||||||
|
megazord_crate_name: &str,
|
||||||
|
crate_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let path = crate_root.join("src").join("lib.rs");
|
||||||
|
let contents = read_to_string(&path)?;
|
||||||
|
let mut lines: Vec<_> = contents.split('\n').collect();
|
||||||
|
let new_use_statement = format!("pub use {crate_name};");
|
||||||
|
|
||||||
|
let mut last_pub_use = None;
|
||||||
|
for (i, line) in lines.iter().enumerate() {
|
||||||
|
if line.trim() == new_use_statement {
|
||||||
|
// The use statement is already present, don't change anything
|
||||||
|
return Ok(());
|
||||||
|
} else if line.trim().starts_with("pub use") {
|
||||||
|
last_pub_use = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let insert_pos = match last_pub_use {
|
||||||
|
Some(i) => i + 1,
|
||||||
|
None => lines.len(),
|
||||||
|
};
|
||||||
|
lines.insert(insert_pos, &new_use_statement);
|
||||||
|
let mut file = File::create(&path)?;
|
||||||
|
write!(file, "{}", lines.join("\n"))?;
|
||||||
|
println!("{path} generated");
|
||||||
|
|
||||||
|
// Run cargo fmt to ensure the imports are sorted in the correct order.
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(["fmt", "-p", megazord_crate_name])
|
||||||
|
.spawn()?
|
||||||
|
.wait()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
/* 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 android;
|
||||||
|
mod cargo_metadata;
|
||||||
|
mod ios;
|
||||||
|
mod toml;
|
||||||
|
|
||||||
|
pub use android::generate_android;
|
||||||
|
pub use ios::{generate_ios, generate_ios_focus};
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* 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 clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
/// Simple program to greet a person
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Command {
|
||||||
|
Android {
|
||||||
|
crate_name: String,
|
||||||
|
description: String,
|
||||||
|
},
|
||||||
|
Ios {
|
||||||
|
crate_name: String,
|
||||||
|
},
|
||||||
|
IosFocus {
|
||||||
|
crate_name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let result = match args.command {
|
||||||
|
Command::Android {
|
||||||
|
crate_name,
|
||||||
|
description,
|
||||||
|
} => start_bindings::generate_android(crate_name, description),
|
||||||
|
Command::Ios { crate_name } => start_bindings::generate_ios(crate_name),
|
||||||
|
Command::IosFocus { crate_name } => start_bindings::generate_ios_focus(crate_name),
|
||||||
|
};
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("{e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* 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 std::fs::{read_to_string, File};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
use toml_edit::{DocumentMut, Table, Value};
|
||||||
|
|
||||||
|
/// A toml file that we're editing
|
||||||
|
///
|
||||||
|
/// This wraps toml_edit's DocumentMut for a particular file path
|
||||||
|
pub struct TomlFile {
|
||||||
|
path: Utf8PathBuf,
|
||||||
|
doc: DocumentMut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TomlFile {
|
||||||
|
pub fn open(path: &Utf8Path) -> Result<Self> {
|
||||||
|
let doc = if path.exists() {
|
||||||
|
read_to_string(path)?.parse()?
|
||||||
|
} else {
|
||||||
|
DocumentMut::new()
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
path: path.to_owned(),
|
||||||
|
doc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self) -> Result<()> {
|
||||||
|
let mut file = File::create(&self.path)?;
|
||||||
|
write!(file, "{}", self.doc)?;
|
||||||
|
println!("{} updated", self.path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for TomlFile {
|
||||||
|
type Target = DocumentMut;
|
||||||
|
|
||||||
|
fn deref(&self) -> &DocumentMut {
|
||||||
|
&self.doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for TomlFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut DocumentMut {
|
||||||
|
&mut self.doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_cargo_toml_dependency(
|
||||||
|
megazord_root: &Utf8Path,
|
||||||
|
crate_root: &Utf8Path,
|
||||||
|
crate_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Find the relative path from the megazord to the crate root
|
||||||
|
let megazord_components: Vec<_> = megazord_root.components().collect();
|
||||||
|
let crate_root_components: Vec<_> = crate_root.components().collect();
|
||||||
|
let mut i = 0;
|
||||||
|
while i < megazord_components.len()
|
||||||
|
&& i < crate_root_components.len()
|
||||||
|
&& megazord_components[i] == crate_root_components[i]
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
let mut relpath_components = vec![".."; megazord_components.len() - i];
|
||||||
|
for component in crate_root_components.iter().skip(i) {
|
||||||
|
relpath_components.push(component.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut toml = TomlFile::open(&megazord_root.join("Cargo.toml"))?;
|
||||||
|
toml["dependencies"][crate_name]["path"] = relpath_components.join("/").into();
|
||||||
|
toml.write()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_uniffi_toml<const N: usize>(
|
||||||
|
crate_root: &Utf8Path,
|
||||||
|
bindings_name: &str,
|
||||||
|
values: [(&str, Value); N],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut toml = TomlFile::open(&crate_root.join("uniffi.toml"))?;
|
||||||
|
if !toml.contains_key("bindings") {
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.set_implicit(true);
|
||||||
|
toml["bindings"] = toml_edit::Item::Table(table);
|
||||||
|
}
|
||||||
|
toml["bindings"][bindings_name] = toml_edit::Item::Table(Table::from_iter(values));
|
||||||
|
toml.write()
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
apply from: "$rootDir/build-scripts/component-common.gradle"
|
||||||
|
apply from: "$rootDir/publish.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'org.mozilla.appservices.{{ crate_name }}'
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.configureUniFFIBindgen("{{ crate_name }}")
|
||||||
|
ext.dependsOnTheMegazord()
|
||||||
|
ext.configurePublish()
|
|
@ -0,0 +1,7 @@
|
||||||
|
{{ crate_name }}:
|
||||||
|
path: {{ android_root }}
|
||||||
|
artifactId: {{ crate_name }}
|
||||||
|
publications:
|
||||||
|
- name: {{ crate_name }}
|
||||||
|
type: aar
|
||||||
|
description: {{ description }}
|
Загрузка…
Ссылка в новой задаче