diff --git a/.changes/uniffi-rust-core-dev-wiring b/.changes/uniffi-rust-core-dev-wiring index bc9ac8dbc..1ddb01c27 100644 --- a/.changes/uniffi-rust-core-dev-wiring +++ b/.changes/uniffi-rust-core-dev-wiring @@ -1 +1 @@ -minor type="added" "Wire in the livekit_uniffi Rust core behind a native-only facade, delivered as a bundled cdylib via Native Assets" +minor type="added" "Wire in the livekit_ffi Rust core behind a native-only facade, delivered as a bundled cdylib via Native Assets" diff --git a/AGENTS.md b/AGENTS.md index d58fea1e8..da50475ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,27 +32,27 @@ CI (`build.yaml`) runs all of the above plus example-app builds for every platfo Web/native divergence is handled with conditional imports (e.g. `track/processor_native.dart` vs `processor_web.dart`) — new platform-specific code should follow that pattern. -## The Rust core (`livekit_uniffi`) +## The Rust core (`livekit_ffi`) -`lib/src/uniffi/` wraps `livekit_uniffi`, a Dart package generated from the `livekit-uniffi` crate in the sibling `rust-sdks` repo. It reaches Rust through Dart's Native Assets: the package's `hook/build.dart` bundles a `cdylib` into the host app and the generated bindings call into it with `@Native`. This is why the SDK requires Flutter >= 3.38 / Dart >= 3.10. +`lib/src/ffi/` wraps `livekit_ffi`, a Dart package generated from the `livekit-ffi` crate in the sibling `rust-sdks` repo. It reaches Rust through Dart's Native Assets: the package's `hook/build.dart` bundles a `cdylib` into the host app and the generated bindings call into it with `@Native`. This is why the SDK requires Flutter >= 3.38 / Dart >= 3.10. -There is no dynamic library to load on the web, so `uniffi.dart` splits native/web the same way the rest of the SDK does. **`uniffi_io.dart` is the only file allowed to import `package:livekit_uniffi/...`** — importing it from anywhere reachable on web pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Guard calls with `LiveKitUniffi.isAvailable`. +There is no dynamic library to load on the web, so `ffi.dart` splits native/web the same way the rest of the SDK does. **`ffi_io.dart` is the only file allowed to import `package:livekit_ffi/...`** — importing it from anywhere reachable on web pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Guard calls with `LiveKitFFI.isAvailable`. ### Local development loop -`livekit_uniffi` is published on pub.dev and the normal dependency resolves it; its build hook downloads the matching prebuilt library from the `livekit-uniffi` GitHub release, so no Rust toolchain is needed. To develop against unreleased crate changes, point both `pubspec.yaml` and `example/pubspec.yaml` at a sibling `rust-sdks` checkout with a `dependency_overrides` entry (`path: ../rust-sdks/livekit-uniffi/packages/dart`, overrides do not propagate from a dependency, hence both) and produce or refresh the package with: +`livekit_ffi` is published on pub.dev and the normal dependency resolves it; its build hook downloads the matching prebuilt library from the `livekit-ffi` GitHub release, so no Rust toolchain is needed. To develop against unreleased crate changes, point both `pubspec.yaml` and `example/pubspec.yaml` at a sibling `rust-sdks` checkout with a `dependency_overrides` entry (`path: ../rust-sdks/livekit-ffi/packages/dart`, overrides do not propagate from a dependency, hence both) and produce or refresh the package with: ```sh -cd ../rust-sdks/livekit-uniffi +cd ../rust-sdks/livekit-ffi cargo make dart-package # generates packages/dart/: bindings, pubspec, build hook, host cdylib cd - flutter pub get -flutter test test/uniffi/ # smoke test: calls buildVersion() across the FFI boundary +flutter test test/ffi/ # smoke test: calls buildVersion() across the FFI boundary ``` Requires `cargo-make`, `protoc` and `tera`. Re-run `cargo make dart-package` whenever the crate's exported surface changes — the build hook tracks the copied library, so a stale one won't be silently reused. -Two things to know about that hook: it picks the locally built library purely on target *OS*, not architecture, so a host build can be bundled into an iOS or Android build by mistake — verify the desktop target first when debugging. And its download mode (used when no local library is present) fetches `build-.zip` from the `livekit-uniffi` GitHub release matching the package version and verifies its SHA-256, so a version whose release lacks assets fails at build time rather than at runtime. +Two things to know about that hook: it picks the locally built library purely on target *OS*, not architecture, so a host build can be bundled into an iOS or Android build by mistake — verify the desktop target first when debugging. And its download mode (used when no local library is present) fetches `build-.zip` from the `livekit-ffi` GitHub release matching the package version and verifies its SHA-256, so a version whose release lacks assets fails at build time rather than at runtime. ## Common pitfalls (from issue history) diff --git a/example/lib/utils.dart b/example/lib/utils.dart index 2142b23cf..a85f6064a 100644 --- a/example/lib/utils.dart +++ b/example/lib/utils.dart @@ -6,15 +6,15 @@ import 'package:livekit_client/livekit_client.dart'; FutureOr Function()? onWindowShouldClose; -/// One synchronous call into the Rust core through the uniffi facade. The +/// One synchronous call into the Rust core through the ffi facade. The /// example shows the result at startup and on the connect page as a smoke /// test that the native library was bundled and loads on this platform. String rustCoreVersionLabel() { - if (!LiveKitUniffi.isAvailable) { + if (!LiveKitFFI.isAvailable) { return 'Rust core not available on this platform'; } try { - return 'Rust core ${LiveKitUniffi.buildVersion}'; + return 'Rust core ${LiveKitFFI.buildVersion}'; } catch (error) { return 'Rust core failed to load: $error'; } diff --git a/lib/livekit_client.dart b/lib/livekit_client.dart index e529ed165..5d57be5cd 100644 --- a/lib/livekit_client.dart +++ b/lib/livekit_client.dart @@ -90,7 +90,7 @@ export 'src/token_source/custom.dart'; export 'src/token_source/caching.dart'; export 'src/token_source/development.dart'; export 'src/token_source/jwt.dart'; -export 'src/uniffi/uniffi.dart'; +export 'src/ffi/ffi.dart'; /// Misspelled alias for [kRpcVersion]. Kept for backwards compatibility with code /// that referenced the original typo. diff --git a/lib/src/uniffi/uniffi.dart b/lib/src/ffi/ffi.dart similarity index 87% rename from lib/src/uniffi/uniffi.dart rename to lib/src/ffi/ffi.dart index c25367087..9de213e81 100644 --- a/lib/src/uniffi/uniffi.dart +++ b/lib/src/ffi/ffi.dart @@ -14,11 +14,11 @@ import 'package:meta/meta.dart'; -import 'uniffi_io.dart' if (dart.library.js_interop) 'uniffi_web.dart' as impl; +import 'ffi_io.dart' if (dart.library.js_interop) 'ffi_web.dart' as impl; -/// Facade over the Rust core exposed by the `livekit_uniffi` package. +/// Facade over the Rust core exposed by the `livekit_ffi` package. /// -/// `livekit_uniffi` reaches Rust through Dart's Native Assets: its build hook +/// `livekit_ffi` reaches Rust through Dart's Native Assets: its build hook /// bundles a `cdylib` into the host app and the generated bindings call into it /// with `@Native`. None of that exists on the web, where there is no dynamic /// library to load, so every entry point here is split native/web through the @@ -29,7 +29,7 @@ import 'uniffi_io.dart' if (dart.library.js_interop) 'uniffi_web.dart' as impl; /// Callers get [isAvailable] to branch on, and platform-specific code paths /// stay out of the public API surface. @experimental -abstract final class LiveKitUniffi { +abstract final class LiveKitFFI { /// Whether the Rust core can be called on this platform. /// /// False on web. Every other member throws [UnsupportedError] when this is diff --git a/lib/src/uniffi/uniffi_io.dart b/lib/src/ffi/ffi_io.dart similarity index 73% rename from lib/src/uniffi/uniffi_io.dart rename to lib/src/ffi/ffi_io.dart index 983c5aaf2..b486a6f31 100644 --- a/lib/src/uniffi/uniffi_io.dart +++ b/lib/src/ffi/ffi_io.dart @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:livekit_uniffi/livekit_uniffi.dart' as uniffi; +import 'package:livekit_ffi/livekit_ffi.dart' as ffi; -/// Native implementation of [LiveKitUniffi]. See `uniffi.dart`. +/// Native implementation of [LiveKitFFI]. See `ffi.dart`. /// /// This is the only file in the SDK that may import the generated bindings: -/// the conditional import in `uniffi.dart` keeps it out of web builds. +/// the conditional import in `ffi.dart` keeps it out of web builds. const bool isAvailable = true; -String buildVersion() => uniffi.buildVersion(); +String buildVersion() => ffi.buildVersion(); diff --git a/lib/src/uniffi/uniffi_web.dart b/lib/src/ffi/ffi_web.dart similarity index 78% rename from lib/src/uniffi/uniffi_web.dart rename to lib/src/ffi/ffi_web.dart index db2ab1968..3f5e06d7f 100644 --- a/lib/src/uniffi/uniffi_web.dart +++ b/lib/src/ffi/ffi_web.dart @@ -12,16 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Web implementation of [LiveKitUniffi]. See `uniffi.dart`. +/// Web implementation of [LiveKitFFI]. See `ffi.dart`. /// /// Native Assets bundles a `cdylib`, which the web has no way to load, so the /// Rust core is simply absent here. This file deliberately does not import -/// `package:livekit_uniffi/...` -- doing so would pull `dart:ffi` into a web +/// `package:livekit_ffi/...` -- doing so would pull `dart:ffi` into a web /// compile and fail the build. const bool isAvailable = false; Never buildVersion() => throw UnsupportedError( - 'LiveKitUniffi.buildVersion is not available on web: the Rust core is ' + 'LiveKitFFI.buildVersion is not available on web: the Rust core is ' 'delivered as a native library. Guard calls with ' - 'LiveKitUniffi.isAvailable.', + 'LiveKitFFI.isAvailable.', ); diff --git a/pubspec.lock b/pubspec.lock index f477e15ab..2f8c81e36 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -440,14 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" - livekit_uniffi: + livekit_ffi: dependency: "direct main" description: - name: livekit_uniffi - sha256: "8ff1402ed684ce09899d77faeb828676030fff2ebf5449678484e665440c6e14" + name: livekit_ffi + sha256: "7b2a95698497b4c572c8da64588fc295e609aea235faf0552fc7e5c8c013c410" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.13.0" logger: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 85f0d6c57..074180888 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,9 +57,9 @@ dependencies: flutter_webrtc: 1.6.2+hotfix.4 dart_webrtc: ^1.8.0 - # Rust core (livekit-uniffi), delivered as a bundled cdylib via Native Assets. - # Native platforms only, see lib/src/uniffi/. - livekit_uniffi: ^0.2.2 + # Rust core (livekit-ffi), delivered as a bundled cdylib via Native Assets. + # Native platforms only, see lib/src/ffi/. + livekit_ffi: ^0.13.0 dev_dependencies: flutter_test: diff --git a/test/uniffi/uniffi_test.dart b/test/ffi/ffi_test.dart similarity index 83% rename from test/uniffi/uniffi_test.dart rename to test/ffi/ffi_test.dart index b0f6df841..73e42d670 100644 --- a/test/uniffi/uniffi_test.dart +++ b/test/ffi/ffi_test.dart @@ -17,23 +17,23 @@ library; import 'package:flutter_test/flutter_test.dart'; -import 'package:livekit_client/src/uniffi/uniffi.dart'; +import 'package:livekit_client/src/ffi/ffi.dart'; void main() { // Exercises the whole delivery chain rather than any particular API: the // build hook resolved a cdylib for this target, Native Assets bundled it, // `@Native` bound the symbol, and a value crossed back from Rust. If the // bindgen or the hook regresses, this is what fails first. - group('livekit_uniffi', () { + group('livekit_ffi', () { test('is available on native platforms', () { - expect(LiveKitUniffi.isAvailable, isTrue); + expect(LiveKitFFI.isAvailable, isTrue); }); test('buildVersion returns the Rust core version', () { - final version = LiveKitUniffi.buildVersion; + final version = LiveKitFFI.buildVersion; expect(version, isNotEmpty); // The crate stamps its own semver, so assert the shape rather than a - // literal that would need bumping on every livekit-uniffi release. + // literal that would need bumping on every livekit-ffi release. expect(version, matches(RegExp(r'^\d+\.\d+\.\d+'))); }); });