# Multi-Platform App Dev in Rust: Systems Challenges
## Building Apps with their Dependencies (incl. UI toolkits)
* Need to handle complex, multi-step builds beyond just Rust/Cargo
* Cargo can't and won't support arbitrary post-build actions
* Security issues, lack of maintainer bandwidth, out of scope for cargo itself
* Pre-compilation build.rs helps, but doesn't suffice
* Good for building native dependencies (Robius does this for Android platform support crates)
* Good for generating code, data, etc that should make it into the final binary
* Not sufficient for
* Doesn't support packaging, signing, or any other post-compilation steps
* Need build systems that have better interop with Rust, Cargo, and rustc
* Configuring a Cargo/rustc build is not easy with existing build tools
* Build tools typically introduce a *third* configuration space -- too many for a dev to manage
* Cargo's configuration space: `features`, cargo flags, profiles, etc
* Rustc's configuration space: `cfg`, rustflags, target spec, etc
* Need to support *easily* building non-Rust (native) code
* Cannot avoid native code on some platforms (primarily Android)
* e.g., must compile custom Java classes needed for passing to/from Android platform APIs
* Usually avoidable or automatic when building on a Linux host, thanks to `pkg-config`
* Frequently needed on macOS/Windows where package managers are nonstandardized
* Must be invokable seamlessly as part of a Rust build process (ideally only when necessary)
* Must be triggerable from third-party crates' build process
* That is, any crate in the dependency tree must be able to build its own native code
* Not buildable only from a top-level crate or first-party app
* Must know about, discover, and support platform-specific toolchains
* `cargo-makepad` handles a lot of this for Makepad apps
* But uses its own custom versions of each tool for a minimal install
* Ideally, UI tool shouldn't require custom build infra
* But many do, mostly out of necessity because nothing else existed when they first started work
* Future goal: UI tools can be built with a common Robius/OSIRIS top-level build tool
* *(✅ partially solved)* Standard configuration of non-Rust builds
* Robius's [`android-build`](https://github.com/project-robius/android-build) crate handles this for Android
* Discovery of installed Android SDK/NDK on Windows, Linux, and macOS hosts
* Clear docs about env vars and how the dev can customize the build
* Similar to what `cc-rs` offers for C/C++ builds
* Need standardized way for each crate in dependency chain to specify any required permissions/metadata
* Right now the top-level app must know about and declare *all* permissions needed by the entire dependency chain.
* This is obviously not feasible long-term
* Potential solution: use cargo metadata
* Define standard format for key-value metadata that a build script or top-level build tool can parse
* Also need tool for merging multiple app manifests into a single one
## Bundling, Packaging, Distributing
* Most app stores and package managers require apps to be:
* Signed, by the app dev with a pre-provided key
* Bundled, using standardized or special packaging tools
* Published, using the app store tooling
* Most of this is done in proprietary ways with special tools
* These tools do not typically offer APIs for external programmatic access
* There's not a lot we can actually do here... 🙁
* Must thoroughly document procedure to correctly direct developer actions
* Mobile platforms, particular Apple, are the most difficult
* Complex workflow with many proprietary, closed tools (Xcode)
* Currently, OSIRIS *does* support bundling and distributing via TestFlight
* But dev must first create a provisioning profile for their app using Xcode
* Our tool can collect package metadata from an app's Cargo.toml
* e.g., app pkg name, icons, version info, *maybe * permissions
* Then, guide developers through the rest of the procedures via docs
* Google Play store requires submitting intermediary build artifacts now
* Can't just submit full APK any more.
* Thus, local development build must differ from a final publishing build
* Build infra must explicitly support various build modes
* Dev/debug, Dev/release, Publish/release
* Bundling/packaging tools have strict requirements
* e.g., no arbitrary executables beyond what your app's manifest specifies
* **Important**: signing/bundling is ***required for certain platform APIs to work***
* Example: Apple does not display system notifications at all if the app code is not packaged into a signed bundle.
## Integration with UI toolkits
* Each UI toolkit is vastly different
* We mostly care about what platform resources the UI toolkit "owns" and controls
* How do they internally represent such states?
* How can external crates interface with or access those states?
* Platform support crates must be easy to integrate with UI system internals
* Example: [`robius-android-env`](https://github.com/project-robius/robius-android-env/) exists to simplify how third-party feature crates can access UI-owned JNI env and current activity context set up by the UI toolkit
## Exposing & Abstracting Platform features / OS services
* Obvious challenge: there are *many* categories of features and *many* platforms
* Many hundreds of unique combinations
* Platforms tend to be *very* different
* Very little continuity across mobile platforms
* Some minor similarities on desktop (especially Unix-like)
* Majority of mobile-specific features have not ever been attempted by the Rust community
* But some desktop features have been tackled
### Goals / guiding philosophy
* Native-first (or close to it as possible); abstractions later
* Don't reimplement something in Rust just "because"
* e.g., context menus, IMEs, file/image pickers
* Keep implementations simple, with no fluff
* We don’t want to impose unavoidable overhead just to provide Rust access to a platform API.
* Developers should never be forced to drop down to platform-specific code
* But, these crates should allow easy access to underlying platform if desired
* Minimize dependencies
* Keeps compile times low
* Live update provided by UI toolkits isn't very useful if compilation takes 30 minutes
* Reduces size of code and binaries
* For ex, early on in our journey we were using `tokio`’s one-shot channels for callbacks in some of the platform crates
* But then we realized just how heavyweight certain deps like that can be (same for things like serde), so we only now use such deps if they’re unavoidable.
* In the case where we actually do need them, we can offer a minimal pared-down version of that crate that can be used by the framework by default
* Then, if an application does need the full crate, it can replace it with a Cargo-level patch statement
### Platform features & OS APIs of interest
> > **Legend**:
> > * <font style="background-color:orange"> *Orange:* In-progress </font>
> > * <font style="background-color:lightgreen"> *Green:* Completed </font>
> > * *Clear:* In backlog (not started)
* <font style="background-color:orange"> System notifications </font>
* Partially handled, only for desktop platforms
* <font style="background-color:lightgreen"> Biometrics & authentication </font>
* ✅ Fully supported on all platforms via [`robius-authentication`](https://github.com/project-robius/robius-authentication)
* File/image picker dialogs
* Context menus (OS-native)
* <font style="background-color:lightgreen"> Menu bar (desktop only) </font>
* Tauri's [`muda` crate](https://github.com/tauri-apps/muda) should suffice
* <font style="background-color:orange"> Rich clipboard </font>
* Plaintext clipboard already supported in Makepad
* <font style="background-color:orange"> Drag & Drop </font>
* Simple cases supported in Makepad
* Default URI/Intent handling
* Will be impl'd by Robius by mid-2024
* <font style="background-color:lightgreen">Content sharing (open URI) in foreign app </font>
* ✅ Fully supported on all platforms via [`robius-open`](https://github.com/project-robius/robius-open)
* Native font access
* Will be handled by Robius by end of 2024
* Input Method Editors (IME)
* Very complex. Needs collab between Robius, Makepad, and Linebender
* Native gesture recognition
* Connectivity management
* WiFi, Bluetooth, NFC, nearby devices
* <font style="background-color:orange"> GPS/Location access </font>
* Work-in-progress by Robius
* Multimedia capture
* Device discovery & configuration
* <font style="background-color:orange"> Camera – snapshots, video, settings </font>
* Partially supported by Makepad
* <font style="background-color:orange"> Audio – input, MIDI, playback </font>
* Partially supported by Makepad
* Sensors
* Native alarms, timers
* Keychain (secret storage)
* Power management/status
* <font style="background-color:orange"> App lifecycle state transitions </font>
* Robius added some of this to Makepad (missing full state transition map for Apple platforms)
* See the [first few variants of the `Event` enum here](https://github.com/makepad/makepad/blob/8180b32d8fd301c8970d91b7f5344dd70fadb581/platform/src/event/event.rs#L29)
* And [this relevant commit](https://github.com/makepad/makepad/commit/c6edd5522f27fc6fd93f0025146d68fc59f8e93d) defining the new Foreground/Background events and how they map to Android's platform-native Activity lifecycle callbacks
* Robius intends to extract this into a separate crates to support external observers of lifecycle transitions
* and many more...
### Platforms of interest
All of the following are fully supported at the moment, except for those listed under `Other`.
* Mobile
* Android
* iOS
* Secondary targets: Apple's watchOS & tvOS
* Desktop
* macOS
* Linux (Debian-based distros)
* Windows
* Web (w/ WASM)
* Other (unsupported)
* OpenHarmony OS
* Microcontrollers, select peripherals (?)
## Managing concurrency: multi-threading, async/sync
* UI & other select workloads must run on “main” thread
* Certain platform APIs only work if called from the same thread or thread group as what draws the UI
* A lot of existing SDKs require async, some don't allow it
* e.g., Matrix Rust SDK only exposes `async fn`s for many key features
* Other system APIs should *not* be invoked from an async context
* If a system API will block the OS-native thread, calling it from an async context is **bad** because it will cause all other async tasks running on that same native OS thread to be blocked until the
* Never want to block the main UI thread --> never invoke async fns on main thread
* Must weave complex web of inter-context communication (e.g., using channels, condvars)
* Rust enums do make this constant message passing fairly easy and safe, but it is still very difficult to debug if something doesn't work
* Opinion: app devs shouldn't have to deal with 3 different types of execution contexts
* Can be hard to figure out what should run where, and what can/cannot run where
* Goal: create an abstraction crate that provides macros/helpers to auto-offload certain workloads to background sync threads or background async tasks
* Future: discover and expose heterogeneous CPU topology
* e.g., ARM's `bigLITTLE` (now `dynamIQ`)
* Allow app logic, UI toolkit, and platform support crates to define which class of CPU a given workload should run on
* Set affinity for background low-priority workloads to prefer LITTLE/low-power CPUs
*