From 703682a41c5f2567daa62fee8fef5727d03fe7d0 Mon Sep 17 00:00:00 2001 From: Trinity Pointard Date: Sun, 16 Jan 2022 23:49:31 +0100 Subject: [PATCH] add iOS documentation --- doc/Android.md | 12 ++-- doc/iOS.md | 159 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 doc/iOS.md diff --git a/doc/Android.md b/doc/Android.md index 27f1b5561..8b03cd764 100644 --- a/doc/Android.md +++ b/doc/Android.md @@ -8,7 +8,7 @@ waiting until Arti is more stable.) There are also rough edges, which will hopefully get polished over time. Most of these should be explained below. -This guide assumes you already have install Cargo and Android Studio (but not that you used both together). +This guide assumes you already have installed Cargo and Android Studio (but not that you used both together). ## Installing the requirements @@ -42,7 +42,7 @@ $ cargo init --lib You'll then need to add some content to the Cargo.toml. First add the subcrates of arti you want to use to the `[dependencies]` section. You'll have to add `features=["static"]` to crates that support this feature -(at the moment tor-rtcompat and tor-dirmgr): otherwise they will fail either to compile or to run. +(at the moment tor-rtcompat, tor-dirmgr and arti-client): otherwise they will fail either to compile or to run. You'll also need to add `jni`, to allow Rust and the Java in your app to work together. ```toml @@ -67,9 +67,9 @@ You should be familiar with this if you used the JNI before. If not, it's proba pub extern "C" fn Java_net_example_MyClass_myMethod( /* parameters omitted */ ) {..} ``` -Once you are satisfied with your code, you can compile it by running this command. (This is a good time for a coffee break.) +Once you are satisfied with your code, you can compile it by running this command. (This is a good time to take a coffee break) ```sh - ## build for 32bit and 64bit, x86 (emulator) and arm (most devices). Warning, this won't work out of the box, see caveats below + ## build for 32bit and 64bit, x86 (emulator) and arm (most devices). $ cargo ndk -t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 -o ./jniLibs build ## build for 64bit arm only (recents devices). $ cargo ndk -t arm64-v8a -o ./jniLibs build @@ -108,7 +108,7 @@ You can now build your application, and test it in an emulator or on your device ## Tips and caveats -You can find a sample project to build a very basic app using Arti [here](https://gitlab.torproject.org/trinity-1686a/arti-android-example/). +You can find a sample project to build a very basic app using Arti [here](https://gitlab.torproject.org/trinity-1686a/arti-mobile-example/). It does not respect most good practices ("don't run long tasks on the UI thread" for instance), but should otherwise be a good starting point. It's also implementing most of the tips below. @@ -135,7 +135,7 @@ use tracing_subscriber::fmt::Subscriber; use tracing_subscriber::prelude::*; Subscriber::new() - .with(tracing_android::layer("rust.ndk.arti")?) + .with(tracing_android::layer("rust.arti")?) .init(); // this must be called only once, otherwise your app will probably crash ``` diff --git a/doc/iOS.md b/doc/iOS.md new file mode 100644 index 000000000..f8959c79c --- /dev/null +++ b/doc/iOS.md @@ -0,0 +1,159 @@ +# Compiling Arti for iOS + +## Limitation +At the moment of writing this guide, Arti does not have a stable Rust API yet. For that reason, no proper bindings are provided. +You'll need to write these bindings yourself by leveraging Rust FFI for C. + +There are also rough edges, which will hopefully get polished over time. Most of these should be explained below. + +This guide assumes you already have installed Cargo and XCode (but not that you used both together). + +Apple requires to have MacOS installed to develop iOS apps, this guide won't work for Linux, Windows or other BSDs. + +## Installing the requiremments + +First install targets so Rust know how to compile to iOS +```sh +$ rustup target add aarch64-apple-ios \ + aarch64-apple-ios-sim \ + x86_64-apple-ios +``` + +## Configuring a Rust project + +To create a new project in the directory you're in. You can do: +```sh +$ cargo init --lib +``` + +You'll then need to add some content to the Cargo.toml. + +First add the subcrates of arti you want to use to the `[dependencies]` section. You'll have to add `features=["static"]` to crates that support this feature +(at the moment tor-rtcompat, tor-dirmgr and arti-client): otherwise they will fail either to compile or to run. + +You'll probably want to add some other dependencies, like futures, but these are not technically requirements. + +You'll also need to specify what kind of lib this is. By default, it's a Rust lib that can only be used in the rust ecosystem. +We want it to be a static library: +```toml +[lib] +name = "arti_mobile" +crate-type = ["staticlib"] +``` + +You are good to start programming in `src/lib.rs`. +To make your functions available to Swift, you need to set certain modifiers on them. +```rust +// defined the function my_function which will be exported without mangling its name, as a C-compatible function. +#[no_mangle] +pub extern "C" fn my_function( /* parameters omitted */ ) {..} +``` + +You also need to add these functions to a C header file which will be imported later in XCode. +```C +// You'll probably need to import stdbool, stdint and stdlib for the type definitions they contain + +void my_function(void); +``` + +There exist a tool to build this header file for you, see in `Tips and caveats` below. + +Once you are satisfied with your code, you can compile it by running these commands. (This is a good time to take a coffee break) +```sh + ## build for 64bit iPhone/iPad (32bit is no longer supported since iOS 11) +$ cargo build --target aarch64-apple-ios + ## build for M1 based Mac (emulator) +$ cargo build --target aarch64-apple-ios-sim + ## build for x86 based Mac (emulator) +$ cargo build --target x86_64-apple-ios +``` + +You can add `--release` to each of this commands to build release libs that are smaller and faster, but take longer to compile. + +## The Swift part + +I'll assume you already have a project setup. This can be a brand new project, or an already existing one. + +First you'll need to add the native library and its header to your project. + +Open your project settings Go in Build Settings and search Objective-C Bridging Header. Set it to the path +to your C header file. + +Now close XCode, and open your project.pbxproj in a text editor. Jump to `LD_RUNPATH_SEARCH_PATHS`. You +should find it two times, in a section named Debug and an other named Release. + +In the Debug section, after `LD_RUNPATH_SEARCH_PATHS`, add the following: +``` +"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = ( + "$(inherited)", + "..//target/aarch64-apple-ios/debug", +); +"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = ( + "$(inherited)", + "..//target/aarch64-apple-ios-sim/debug", +); +"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = ( + "$(inherited)", + "..//target/x86_64-apple-ios/debug", +); +OTHER_LDFLAGS = ( + "$(inherited)", + "-larti_mobile", /* replace arti-mobile with what you put as name in [lib] in Cargo.toml */ +); +``` + +In the Release section, add the same block, but replace `debug` at the end of each path with `release`. + +Now you can start calling your Rust functions from Swift like normal functions. Types are a bit unneasy to +work with, strings get transformed into char\* at the FFI interface, and Swift consider them as +`Optional>` which need unwrapping and conversion before being used. You also +need to free such a pointer by passing it back to Rust and dropping the value there. Otherwise these +functions should work almost as any other. + +You can now build your application, and test it in an emulator or on your device. Hopefully it should work. + +## Tips and caveats + +You can find a sample project to build a very basic app using Arti [here](https://gitlab.torproject.org/trinity-1686a/arti-mobile-example/). +It does not respect most good practices of app developement, but should otherwise be a good starting point. + +## Generating C headers from Rust code +Instead of writing C headers manually and hopping to not make misstakes, it's possible to generate them +automatically by using cbindgen. First install it. +```sh +$ cargo install cbindgen +``` + +Then use bindgen to generate the headers. You should put all functions you want to export in a single rust file. +```sh +$ cbindgen src/lib.rs -l c > arti-mobile.h +``` + +### Debugging and stability +Arti logs events to help debugging. By default these logs are not available on iOS. +You can make Arti export its logs to OSLog by adding a couple dependencies and writing a bit of code: + +```toml +# in [dependencies] in Cargo.toml +tracing-subscriber = "0.3.3" +tracing-oslog = "0.1.2" +``` + +```rust +use tracing_subscriber::fmt::Subscriber; +use tracing_subscriber::prelude::*; + +Subscriber::new() + .with(tracing_oslog::OsLogger::layer("rust.arti")?) + .init(); // this must be called only once, otherwise your app will probably crash +``` + +You should take great care about your rust code not unwinding into Swift Runtime: If it does, it will crash your app with no error message to help you. +If your code can panic, you should use `catch_unwind` to capture it before it reaches Swift. + +## Async and Swift +Arti relies a lot on Rust futures. There is no easy way to use these futures from Swift. The easiest ways is probably to block on futures +if you are okay with it. Otherwise you have to pass callbacks from Swift to Rust, and make so they are called when the future completes. + +Eventually, Arti will provide a set of blocking APIs for use for embedding; +please get in touch if you want to help design them.