diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 43a28d04..0bd6810e 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,3 +1,12 @@ # Summary [Introduction](README.md) + +* [Getting Started](getting-started/README.md) + - [Before you start](getting-started/pre-installation.md) + +* [Reference](api/README.md) + - [Command Line](api/commandline.md) + - [Configuring your project](api/config-yaml.md) + - [Tweaking code generation](api/uniffi-toml.md) + - [Generating a Turbo Module](api/turbo-module-files.md) diff --git a/docs/src/api/README.md b/docs/src/api/README.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/src/api/commandline.md b/docs/src/api/commandline.md new file mode 100644 index 00000000..6e367083 --- /dev/null +++ b/docs/src/api/commandline.md @@ -0,0 +1,221 @@ +`uniffi-bindgen-react-native` the command line utility that ties together much of the building of Rust, and the generating the bindings and turbo-modules. + +Most commands take a `--config FILE` option. This is a YAML file which collects commonly used options together, and is [documented here](config-yaml.md). + +The command can be invoked with `./node_modules/.bin/uniffi-bindgen-react-native`. + +Running the following command from within your project: + +```sh +alias ubrn=$(pwd)/node_modules/.bin/uniffi-bindgen-react-native +``` +allows you to run the command as `ubrn`, which is simpler to type. From hereon, commands will be given as `ubrn` commands. + +# `ubrn` Commands + +Running `ubrn --help` gives the following output: + +```sh +Usage: uniffi-bindgen-react-native + +Commands: + checkout Checkout a given Github repo into `rust_modules` + build Build (and optionally generate code) for Android or iOS + generate Generate bindings or the turbo-module glue code from the Rust + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +``` + +## `checkout` +Checkout a given Git repo into `rust_modules`. + +```sh +Usage: uniffi-bindgen-react-native checkout [OPTIONS] + +Arguments: + The repository where to get the crate + +Options: + --config + --branch The branch or tag which to checkout [default: main] + -h, --help Print help +``` +The checkout command can be operated in two ways, either: +1. with a `REPO` argument and optional `--branch` argument. OR +2. with a config file which may specify a repo and branch, or just a `directory`. + +## `build android` + +Build the crate for use on an Android device or emulator, using `cargo ndk`, which in turn uses Android Native Development Kit. + +``` +Usage: uniffi-bindgen-react-native build android [OPTIONS] --config + +Options: + --config The configuration file for this build + -r, --release Build a release build + -g, --and-generate Optionally generate the bindings and turbo-module code for the crate + -h, --help Print help +``` + +The configuration file refers to [the YAML configuration](config-yaml.md). + +`--release` sets the release profile for `cargo`. + +`--and-generate` is a convenience option to pass the built library file to `generate bindings` and `generate turbo-module`. + +Once the library files (one for each target) are created, they are copied into the `jniLibs` specified by the YAML configuration. + +```admonish note +React Native requires that the Rust library be built as a static library. The CMake based build will combine the C++ with the static library into a shared object. + +To configure Rust to build a static library, you should ensure `staticlib` is in the `crate-type` list in the `[lib]` section of the `Cargo.toml` file. Minimally, this should be in the `Cargo.toml` manifest file: + +
+[lib]
+crate-type = ["staticlib"]
+
+
+``` + + +## `build ios` + +Build the crate for use on an iOS device or simulator. +``` +Usage: uniffi-bindgen-react-native build ios [OPTIONS] --config + +Options: + --config The configuration file for this build + --sim-only Only build for the simulator + --no-sim Exclude builds for the simulator + -r, --release Build a release build + -g, --and-generate Optionally generate the bindings and turbo-module code for the crate + -h, --help Print help +``` +The configuration file refers to [the YAML configuration](config-yaml.md). + +`--sim-only` and `--no-sim` restricts the targets to targets with/without `sim` in the target triple. + +`--and-generate` is a convenience option to pass the built library file to `generate bindings` and `generate turbo-module`. + +Once the target libraries are compiled, and a config file is specified, they are passed to `xcodebuild -create-xcframework` to generate an `xcframework`. + +```admonish note +React Native requires that the Rust library be built as a static library. The `xcodebuild` based build will combine the C++ with the static library `.xcframework` file. + +To configure Rust to build a static library, you should ensure `staticlib` is in the `crate-type` list in the `[lib]` section of the `Cargo.toml` file. Minimally, this should be in the `Cargo.toml` manifest file: + +
+[lib]
+crate-type = ["staticlib"]
+
+
+``` + + +## `generate bindings` +Generate the just the bindings. In most cases, this command should not be called directly, but with the build, with `--and-generate`. + +```admonish info +This command follows the command line format of other `uniffi-bindgen` commands. Most arguments are passed straight to [`uniffi-bindgen::library_mode::generate_bindings`](https://docs.rs/uniffi_bindgen/0.28/uniffi_bindgen/library_mode/fn.generate_bindings.html). + +For more/better documentation, please see the linked docs. +``` + +```admonish warning +Because this mirrors other `uniffi-bindgen`s, the `--config` option here is asking for a [`uniffi.toml`](uniffi-toml) file. +``` + +This command will generate two typescript files and two C++ files per Uniffi namespace. These are: `namespace.ts`, `namespace-ffi.ts`, `namespace.h`, `namespace.cpp`, substituting `namespace` for names derived from the Rust crate. + +The [namespace is defined as](https://docs.rs/uniffi_bindgen/latest/uniffi_bindgen/interface/struct.ComponentInterface.html#method.namespace): + +> The string namespace within which this API should be presented to the caller. +> +> This string would typically be used to prefix function names in the FFI, to build a package or module name for the foreign language, etc. + +It may also be thought of as a crate or sub-crate which exports uniffi API. + +The C++ files will be put into the `--cpp-dir` and the typescript files into the `--ts-dir`. + +The C++ files can register themselves with the Hermes runtime. + +``` +Usage: uniffi-bindgen-react-native generate bindings [OPTIONS] --ts-dir --cpp-dir + +Arguments: + + A UDL file or library file + +Options: + --lib-file + The path to a dynamic library to attempt to extract the definitions from and extend the component interface with + + --crate + Override the default crate name that is guessed from UDL file path. + + In library mode, this + + --config + The location of the uniffi.toml file + + --library + Treat the input file as a library, extracting any Uniffi definitions from that + + --no-format + By default, bindgen will attempt to format the code with prettier and clang-format + + --ts-dir + The directory in which to put the generated Typescript + + --cpp-dir + The directory in which to put the generated C++ + + -h, --help + Print help (see a summary with '-h') +``` +## `generate turbo-module` +Generate the TurboModule code to plug the bindings into the app. + +More details about the files generated is shown [here](turbo-module-files.md). + +``` +Usage: uniffi-bindgen-react-native generate turbo-module --config [NAMESPACES]... + +Arguments: + [NAMESPACES]... The namespaces that are generated by `generate bindings` + +Options: + --config The configuration file for this build + -h, --help Print help +``` + +The namespaces in the commmand line are derived from the crate that has had its bindings created. + +```admonish info +The locations of the files are derived from [the configuration file](config-yaml.md) and the project's package.json` file. + +The relationships between files are preserved-- e.g. where one file points to another via a relative path, the relative path is calculated from these locations. +``` + +## `help` + +Prints the help message. + +``` +Usage: uniffi-bindgen-react-native + +Commands: + checkout Checkout a given Github repo into `rust_modules` + build Build (and optionally generate code) for Android or iOS + generate Generate bindings or the turbo-module glue code from the Rust + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +``` + +You can add `--help` to any command to get more information about that command. diff --git a/docs/src/api/config-yaml.md b/docs/src/api/config-yaml.md new file mode 100644 index 00000000..bb030522 --- /dev/null +++ b/docs/src/api/config-yaml.md @@ -0,0 +1,148 @@ +# Configuration for `uniffi-bindgen-react-native` + +The configuration yaml file is a collection of configuration options used in one or more [commands](commandline.md). + +The file is designed to be easy to start. A **minimal** configuation would be: + +```yaml +crate: + directory: ./rust + manifest-file: Cargo.toml +``` + +Getting started from here would require a command to start the Rust: + +```sh +cargo init --lib ./rust +cd ./rust +cargo add uniffi +``` + +# YAML entries + +## `crate` + +```yaml +crate: + manifest-path: crates/my-api/Cargo.toml + repo: https://github.com/example/my-rust-sdk + branch: main +``` +In this case, the `ubrn checkout` command will clone the given repo with the branch/ref into the `rust_modules` directory of the project. + +If run a second time, no overwriting will occur. + +The `manifest-path` is the path relative to the root of the Rust workspace directory. In this case, the manifest is expected to be, relative to your React Native library project: `./rust_modules/my-rust-sdk/crates/my-api/Cargo.tml`. + +```yaml +crate: + directory: ./rust + manifest-path: crates/my-api/Cargo.toml +``` +In this case, the `./rust` directory tells `ubrn` where the Rust workspace is, relative to your React Native library project. The `manifest-path` is the relative path from the workspace file to the crate which will be used to build bindings. + +## `bindings` + +This section governs the generation of the bindings— the nitty-gritty of the Rust API translated into Typescript. This is mostly the location on disk of where these files will end up, but also has a second configuration file. + +```yaml +bindings: + cpp: cpp/bindings + ts: ts/bindings + uniffiToml: ./uniffi.toml +``` +The [`uniffi.toml` file](uniffi-toml.md) configures custom types, to further customize the conversion into Typescript data-types. + +If missing, the defaults will be used: +``` +bindings: + cpp: cpp/generated + ts: ts/generated +``` +## `android` + +This is to configure the build steps for the Rust, the bindings, and the turbo-module code for Android. + +This section can be completely missed out, as sensible defaults are provided, but can also be customized: + +```yaml +android: + directory: ./android + cargoExtras: [] + targets: + - aarch64-linux-android + - armv7-linux-androideabi + - i686-linux-android + - x86_64-linux-android + apiLevel: 21 + jniLibs: src/main/jniLibs + packageName: +``` + +The `directory` is the location of the Android project, relative to the root of the React Native library project. + +`targets` is a list of targets to build for. The Rust source code is built once per target. + +`cargoExtras` is a list of extra arguments passed directly to the `cargo build` command. + +`apiLevel` is the minimum API level to target: this is passed to the `cargo ndk` command as a `--platform` argument. + +```admonish tip +Reducing the number of targets to build for will speed up the edit-compile-run cycle. +``` + +`packageName` is the name of the Android package that Codegen used to generate the TurboModule. This is derived from the `package.json` file, and can almost always be left. + +To customize the `packageName`, you should edit or add the entry at the path `codegenConfig`/`android`/`javaPackageName` in `package.json`. + +## `ios` + +This is to configure the build steps for the Rust, the bindings, and the turbo-module code for iOS. + +This section can be completely missed out, as sensible defaults are provided, but can also be customized: + +```yaml +ios: + directory: ios + cargoExtras:: [] + targets: + - aarch64-apple-ios + - aarch64-apple-ios-sim + xcodebuildExtras: [] + frameworkName: build/MyFramework +``` + + +The `directory` is the location of the iOS project, relative to the root of the React Native library project. + +`targets` is a list of targets to build for. The Rust source code is built once per target. + +`cargoExtras` is a list of extra arguments passed directly to the `cargo build` command. + +`xcodebuildExtras` is a list of extra arguments passed directly to the `xcodebuild` command. + +## `turboModule` + +This section configures the location of the Typescript and C++ files generated by the `generate turbo-module` command. + +If absent, the defaults will be used: + +```yaml +turboModule: + ts: src + cpp: cpp +``` + +The Typescript files are the `index.ts` file, and the Codegen installer file. + +```admonish info +By default, the `index.ts` file is intended to be the entry point for your library. + +In this case, changing the location of the `ts` directory will require changing the `main` entry in the `package.json` file. +``` + +## `noOverwrite` + +This list of [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)) of file that should not be generated or overwritten by the `--and-generate` flag, and the `generate turbo-module` command. + +This is useful if you want to customize one or more of the generated files, and not losen those changes. diff --git a/docs/src/api/turbo-module-files.md b/docs/src/api/turbo-module-files.md new file mode 100644 index 00000000..33f99016 --- /dev/null +++ b/docs/src/api/turbo-module-files.md @@ -0,0 +1,23 @@ +# Generating Turbo Module files to install the bindings + +The bindings of the Rust library consist of several C++ files and several typescript files. + +There is a host of smaller files that need to be configured with these namespaces, and with configuration from the [config YAML](config-yaml.md) file. + +These include: + +- For Javascript: + - An `index.ts` file, to call into the installation process, initialize the bindings for each namespace, and re-export the generated bindings for client code. + - A Codegen file, to generates install methods from Javascript to Java and Objective C. +- For Android: + - A `Package.java` and `Module.java` file, which receives the codegen'd install method calls, to get the Hermes `JavascriptRuntime` and `CallInvokerHolder` to pass it via JNI to + - A `cpp-adapter.cpp` to receive the JNI calls, and converts those into `jsi::Runtime` and `react::CallInvoker` then calls into generic C++ install code. +- Generic C++ install code: + - A turbo-module installation `.h` and `.cpp` which catches the calls from Android and iOS and registers the bindings C++ with the Hermes `jsi::Runtime`. +- For iOS: + - a `Module.h` and `Module.mm` file which receives the codegen'd install method calls, and digs around to find the `jsi::Runtime` and `react::CallInvoker`. It then calls into the generic C++ install code. +- To build for iOS: + - A podspec file to tell Xcode about the generated files, and the framework name/location. +- To build for Android + - A `CMakeLists.txt` file to configure the Android specific tool chain for all the generated C++ files. + - The `build.gradle` file which tells keeps the codegen package name in-sync and configures `cmake`. (note to self, this could be done from within the `CMakeLists.txt` file). diff --git a/docs/src/api/uniffi-toml.md b/docs/src/api/uniffi-toml.md new file mode 100644 index 00000000..d56f5bc0 --- /dev/null +++ b/docs/src/api/uniffi-toml.md @@ -0,0 +1,67 @@ +The `uniffi.toml` file is a toml file used to customize [the generation of C++ and Typescript](https://mozilla.github.io/uniffi-rs/0.27/bindings.html). + +As of time of writing, only `typescript` bindings generation exposes any options for customization, and only for `customTypes`. + +### Typescript custom types + +From [the uniffi-rs manual](https://mozilla.github.io/uniffi-rs/latest/udl/custom_types.html): + +> Custom types allow you to extend the UniFFI type system to support types from your Rust crate or 3rd party libraries. This works by converting to and from some other UniFFI type to move data across the FFI. + +This table customizes how a type called `MillisSinceEpoch` comes out of Rust. + +We happen to know that it crosses the FFI as a Rust `i64`, which +converts to a JS `bigint`, but we can do better. + +```toml +[bindings.typescript.customTypes.MillisSinceEpoch] +# Name of the type in the Typescript code. +typeName = "Date" +# Expressions to convert between bigint and Date +intoCustom = 'new Date(Number({}))' +fromCustom = "BigInt({}.getTime())" +``` + +This table customizes how a type called `Url` comes out of Rust. +We happen to know that it crosses the FFI as a `string`. + +```toml +[bindings.typescript.customTypes.Url] +# We want to use our own Url class; because it's also called +# Url, we don't need to specify a typeName. +# Import the Url class from ../src/converters +imports = [ [ "Url", "../src/converters" ] ] +# Expressions to convert between strings and URLs. +# The `{}` is substituted for the value. +intoCustom = "new Url({})" +fromCustom = "{}.toString()" +``` +We can provide zero or more imports which are slotted into a JS import statement. This allows us to import `type` and from modules in `node_modules`. + +The next example is a bit contrived, but allows us to see how to customize a generated type that came from Rust. + +The `EnumWrapper` is defined in Rust as: + +```rust +pub struct EnumWrapper(MyEnum); +uniffi::custom_newtype!(EnumWrapper, MyEnum); +``` + +In the `uniffi.toml` file, we want to convert the wrapped `MyEnum` into a `string`. In this case, the `string` is the custom type, and we need to provide code to convert to and from the custom type. +```toml +[bindings.typescript.customTypes.EnumWrapper] +typeName = "string" +# An expression to get from the custom (a string), to the underlying enum. +fromCustom = "{}.indexOf('A') >= 0 ? new MyEnum.A({}) : new MyEnum.B({})" +# An expression to get from the underlying enum to the custom string. +# It has to be an expression, so we use an immediately executing anonymous function. +intoCustom = """((v: MyEnum) => { + switch (v.tag) { + case MyEnum_Tags.A: + return v.inner[0]; + case MyEnum_Tags.B: + return v.inner[0]; + } +})({}) +""" +``` diff --git a/docs/src/getting-started/README.md b/docs/src/getting-started/README.md new file mode 100644 index 00000000..bad55622 --- /dev/null +++ b/docs/src/getting-started/README.md @@ -0,0 +1 @@ +# Getting Started diff --git a/docs/src/getting-started/pre-installation.md b/docs/src/getting-started/pre-installation.md new file mode 100644 index 00000000..58d78a1c --- /dev/null +++ b/docs/src/getting-started/pre-installation.md @@ -0,0 +1,71 @@ +# Before you start + +Better resources are available than this site for installing these dependencies. + +Below are a list of the dependencies, and a non-comprehensive instructions on how to get them onto your system. + +## Install Rust +If Rust isn't already installed on your system, you should install it as per the [rust-lang.org install instructions](https://www.rust-lang.org/tools/install). + +This will add `cargo` and `rustup` to your path, which are the main entry points into Rust. + +## Install C++ tooling + +These commands will add the tooling needed to compile and run the generated C++ code. + +Optionally, `clang-format` can be installed to format the generated C++ code. + +For MacOS, using homebrew: +```sh +brew install cmake ninja clang-format +``` + +For Debian flavoured Linux: +```sh +apt-get install cmake ninja clang-format +``` + +For generared Typescript, the existing `prettier` installation is detected and your configuration is used. + +## Android + +### Add the Android specific targets + +This command adds the backends for the Rust compiler to emit machine code for different Android architectures. + +```sh +rustup target add \ + aarch64-linux-android \ + armv7-linux-androideabi \ + i686-linux-android \ + x86_64-linux-android +``` + +### Install `cargo-ndk` + +> This cargo extension handles all the environment configuration needed for successfully building libraries for Android from a Rust codebase, with support for generating the correct jniLibs directory structure. + +```sh +cargo install cargo-ndk +``` + +## iOS + +### Ensure `xcodebuild` is avaiable + +This command checks if Xcode command line tools are available, and if not, will start the installation process. + +```sh +xcode-select --install +``` + +### Add the iOS specific targets + +This command adds the backends for the Rust compiler to emit machine code for different iOS architectures. + +```sh +rustup target add \ + aarch64-apple-ios \ + aarch64-apple-ios-sim \ + x86_64-apple-ios +```