Skip to content

Commit 9ff0554

Browse files
authored
Add dart/flutter bindings to livekit-uniffi (#1183)
### Before you submit your PR Make sure the following is true before submitting your PR: - [X] I have read the [contributing guidelines](https://github.com/livekit/rust-sdks/blob/main/CONTRIBUTING.md) and validated that this PR will be accepted. - [X] I have read and followed the principles regarding breaking changes, testing, and code quality. ### PR description Added Dart bindings with the uniffi-dart bindgen. This generates a `Native Assets` API. Exercise with `cargo make bindgen-dart`. This adds a uniffi-bindgen-dart feature to the `livekit-uniffi` crate: followup would be to separate out the uniffi-tooling binaries in to a workspace centred tooling crate. Added consumable package, with a hook/build.dart. This works both locally, and by downloading a prebuilt cdylib. Added CI which builds the cdylibs per OS/architecture on release. ### Breaking changes No changes breaking changes are made, however: - `uniffi-dart` bindgen needed a bug fix which means the version of uniffi-dart is from main (pinned to the hash). - the version of uniffi is incremented to 0.31. This is unlikely to cause breakage in the Swift, Python, Kotlin bindings, but _may_ cause problems for `uniffi-bindgen-node`. Followup: move `uniffi-bindgen-node` to `uniffi-bindgen-react-native`. ### MSRV No changes. uniffi 0.31 requires >= 1.85 ### Testing - Dart package tests (`support/dart/tests`): a smoke test including a negative case. - CI build & hook/build.dart tested via dry run. ### Async We want the project to be runtime-agnostic, so please reuse what's already in [livekit-runtime](https://github.com/livekit/rust-sdks/blob/main/livekit-runtime/) and feel free to add anything missing. It's ok to use Tokio directly, when writing unit tests, if necessary. When testing, do not use artificial delays for the state to "catch up"; instead, respect the event flow and subscribe properly using channels or other mechanisms.
1 parent 8ead5c3 commit 9ff0554

13 files changed

Lines changed: 862 additions & 202 deletions

.changeset/uniffi-dart-bindings.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
livekit-uniffi: minor
3+
---
4+
5+
Add a Dart bindings target. Bumps the crate's UniFFI dependency from 0.30 to 0.31 to match the bindgen.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
name: UniFFI cdylib builds
2+
3+
# Builds the livekit-uniffi dynamic library for every Dart/Flutter target and
4+
# attaches them to the livekit-uniffi release as `build-<triple>.zip` (+ a
5+
# `.sha256` sidecar). The Dart build hook (support/dart/hook/build.dart.tera)
6+
# downloads these at consumer build time.
7+
#
8+
# Reusable workflow, invoked from uniffi-packages.yml once the release tag has
9+
# been resolved. Mirrors the structure of ffi-builds.yml. Not triggered directly.
10+
11+
on:
12+
workflow_call:
13+
inputs:
14+
version:
15+
description: Release version (e.g. 0.1.1)
16+
required: true
17+
type: string
18+
tag_name:
19+
description: Source release tag (e.g. livekit-uniffi/v0.1.1)
20+
required: true
21+
type: string
22+
dry_run:
23+
description: Build artifacts but skip release upload
24+
type: boolean
25+
default: false
26+
27+
permissions:
28+
contents: write
29+
30+
jobs:
31+
build:
32+
name: Build (${{ matrix.target }})
33+
runs-on: ${{ matrix.os }}
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
include:
38+
- os: macos-latest
39+
target: aarch64-apple-darwin
40+
platform: apple
41+
- os: macos-latest
42+
target: x86_64-apple-darwin
43+
platform: apple
44+
- os: macos-latest
45+
target: aarch64-apple-ios
46+
platform: apple
47+
- os: macos-latest
48+
target: aarch64-apple-ios-sim
49+
platform: apple
50+
- os: macos-latest
51+
target: x86_64-apple-ios
52+
platform: apple
53+
- os: ubuntu-latest
54+
target: x86_64-unknown-linux-gnu
55+
platform: linux
56+
- os: ubuntu-latest
57+
target: aarch64-unknown-linux-gnu
58+
platform: linux
59+
- os: windows-latest
60+
target: x86_64-pc-windows-msvc
61+
platform: windows
62+
- os: windows-latest
63+
target: aarch64-pc-windows-msvc
64+
platform: windows
65+
- os: ubuntu-latest
66+
target: aarch64-linux-android
67+
platform: android
68+
- os: ubuntu-latest
69+
target: armv7-linux-androideabi
70+
platform: android
71+
- os: ubuntu-latest
72+
target: x86_64-linux-android
73+
platform: android
74+
75+
steps:
76+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
77+
with:
78+
submodules: true
79+
80+
- name: Setup Rust toolchain
81+
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
82+
with:
83+
target: ${{ matrix.target }}
84+
cache: false
85+
# This action defaults RUSTFLAGS to "-D warnings"; clear it so
86+
# dependency warnings don't fail the build (matches ffi-builds.yml).
87+
rustflags: ""
88+
89+
- name: Install Protoc
90+
uses: arduino/setup-protoc@a8b67ba40b37d35169e222f3bb352603327985b6 # v2.1.0
91+
with:
92+
version: "25.2"
93+
repo-token: ${{ secrets.GITHUB_TOKEN }}
94+
95+
# Apple, Windows: build the cdylib directly for the target.
96+
- name: Build (Apple / Windows)
97+
if: ${{ matrix.platform == 'apple' || matrix.platform == 'windows' }}
98+
shell: bash
99+
run: cargo build --release --target ${{ matrix.target }} -p livekit-uniffi
100+
101+
# Linux: native build for x64; cross toolchain for aarch64.
102+
- name: Build (Linux)
103+
if: ${{ matrix.platform == 'linux' }}
104+
run: |
105+
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
106+
sudo apt-get update
107+
sudo apt-get install -y gcc-aarch64-linux-gnu
108+
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
109+
# .cargo/config.toml forces -fuse-ld=lld for this target to link
110+
# libwebrtc; livekit-uniffi doesn't use libwebrtc. A non-empty global
111+
# RUSTFLAGS overrides that per-target config, selecting the GNU bfd
112+
# linker the cross binutils provides.
113+
export RUSTFLAGS="-C link-arg=-fuse-ld=bfd"
114+
fi
115+
cargo build --release --target ${{ matrix.target }} -p livekit-uniffi
116+
117+
# Android: build with cargo-ndk.
118+
- name: Build (Android)
119+
if: ${{ matrix.platform == 'android' }}
120+
run: |
121+
cargo install cargo-ndk
122+
cargo ndk --target ${{ matrix.target }} build --release -p livekit-uniffi
123+
124+
# macOS/Linux: zip + shasum are available.
125+
- name: Package artifact (Unix)
126+
if: ${{ matrix.platform != 'windows' }}
127+
shell: bash
128+
run: |
129+
triple="${{ matrix.target }}"
130+
dir="target/${triple}/release"
131+
case "$triple" in
132+
*apple*) lib="liblivekit_uniffi.dylib" ;;
133+
*) lib="liblivekit_uniffi.so" ;;
134+
esac
135+
out="build-${triple}.zip"
136+
# Zip with the bare library name at the archive root.
137+
(cd "$dir" && zip "$GITHUB_WORKSPACE/$out" "$lib")
138+
# SHA-256 sidecar, in `shasum -a 256` format (lowercase hex first).
139+
shasum -a 256 "$out" > "$out.sha256"
140+
141+
# Windows runners lack `zip`/`shasum`; use PowerShell equivalents and
142+
# emit the same `<lowercase-hex> <file>` sidecar format the hook parses.
143+
- name: Package artifact (Windows)
144+
if: ${{ matrix.platform == 'windows' }}
145+
shell: pwsh
146+
run: |
147+
$triple = "${{ matrix.target }}"
148+
$lib = "target/$triple/release/livekit_uniffi.dll"
149+
$out = "build-$triple.zip"
150+
Compress-Archive -Path $lib -DestinationPath $out -Force
151+
$hash = (Get-FileHash -Algorithm SHA256 $out).Hash.ToLower()
152+
"$hash $out" | Out-File -Encoding ascii -NoNewline "$out.sha256"
153+
154+
- name: Upload build artifact
155+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
156+
with:
157+
name: cdylib-${{ matrix.target }}
158+
path: |
159+
build-${{ matrix.target }}.zip
160+
build-${{ matrix.target }}.zip.sha256
161+
162+
release:
163+
name: Attach cdylibs to release
164+
needs: build
165+
if: ${{ !inputs.dry_run }}
166+
runs-on: ubuntu-latest
167+
steps:
168+
- name: Download build artifacts
169+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
170+
with:
171+
pattern: cdylib-*
172+
merge-multiple: true
173+
path: cdylibs
174+
175+
- name: Upload to release
176+
env:
177+
GH_TOKEN: ${{ github.token }}
178+
run: gh release upload "${{ inputs.tag_name }}" cdylibs/* --repo "${{ github.repository }}" --clobber
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: UniFFI Dart tests
2+
3+
# Builds the Dart package (dev mode: host cdylib + generated bindings) and runs
4+
# its test suite, exercising the FFI boundary end-to-end. Scoped to changes that
5+
# can affect the livekit-uniffi Dart bindings.
6+
7+
on:
8+
push:
9+
branches: ["main"]
10+
pull_request:
11+
branches: ["main"]
12+
paths:
13+
- "livekit-uniffi/**"
14+
- "livekit-api/**"
15+
- "livekit-protocol/**"
16+
- "Cargo.lock"
17+
- "Cargo.toml"
18+
- ".cargo/**"
19+
- ".github/workflows/uniffi-dart-test.yml"
20+
21+
permissions:
22+
contents: read
23+
24+
jobs:
25+
dart-test:
26+
name: Dart package tests
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
30+
with:
31+
submodules: true
32+
33+
- name: Setup Rust toolchain
34+
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
35+
with:
36+
cache: false
37+
rustflags: ""
38+
39+
- name: Install Protoc
40+
uses: arduino/setup-protoc@a8b67ba40b37d35169e222f3bb352603327985b6 # v2.1.0
41+
with:
42+
version: "25.2"
43+
repo-token: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Setup Dart
46+
uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
47+
with:
48+
sdk: stable
49+
50+
- name: Cache cargo registry
51+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
52+
with:
53+
path: |
54+
~/.cargo/registry/index/
55+
~/.cargo/registry/cache/
56+
~/.cargo/git/db/
57+
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
58+
restore-keys: |
59+
${{ runner.os }}-cargo-registry-
60+
61+
- name: Cache cargo target
62+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
63+
with:
64+
path: target/
65+
key: ${{ runner.os }}-cargo-target-dart-${{ hashFiles('**/Cargo.lock') }}
66+
restore-keys: |
67+
${{ runner.os }}-cargo-target-dart-
68+
69+
- name: Install cargo-make
70+
run: cargo install --locked cargo-make
71+
72+
- name: Build Dart package
73+
working-directory: livekit-uniffi
74+
run: cargo make dart-package
75+
76+
- name: Run Dart tests
77+
working-directory: livekit-uniffi/packages/dart
78+
run: |
79+
dart pub get
80+
dart test

.github/workflows/uniffi-packages.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,19 @@ jobs:
7676
tag_name: ${{ needs.resolve-tag.outputs.tag_name }}
7777
dry_run: ${{ inputs.dry_run || false }}
7878
secrets: inherit
79+
80+
# Raw cdylibs for the Dart package's build-hook download (build-<triple>.zip).
81+
cdylib:
82+
needs: resolve-tag
83+
if: needs.resolve-tag.outputs.tag_name != ''
84+
# The reusable workflow uploads to this repo's release via github.token; a
85+
# called workflow can't elevate the caller's token, so grant write here (the
86+
# top-level default is read). Swift uses a PAT and Android uses Maven, so
87+
# neither needs this.
88+
permissions:
89+
contents: write
90+
uses: ./.github/workflows/uniffi-cdylib.yml
91+
with:
92+
version: ${{ needs.resolve-tag.outputs.version }}
93+
tag_name: ${{ needs.resolve-tag.outputs.tag_name }}
94+
dry_run: ${{ inputs.dry_run || false }}

0 commit comments

Comments
 (0)