From 4026257b32427da22feded69280b1cb1315544b8 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:06:14 -0400 Subject: [PATCH 01/42] refactor(livekit-ffi): move the FFI surface into a room_apis module The crate will soon hold two different FFI surfaces. Put the current surface in its own module first, before the second surface comes in. This commit moves four items into `src/room_apis/`: the C ABI entry points, the generated protobuf module, the request server and the type conversions. The content of these files does not change. The new `room_apis/mod.rs` holds the module declarations that were in `lib.rs`. A glob re-export in `lib.rs` keeps the public API the same. The crate-internal paths `crate::proto` and `crate::server` also stay correct. Thus no other file changes. --- livekit-ffi/src/lib.rs | 10 ++++--- livekit-ffi/src/{ => room_apis}/cabi.rs | 0 .../{ => room_apis}/conversion/audio_frame.rs | 0 .../src/{ => room_apis}/conversion/capture.rs | 0 .../{ => room_apis}/conversion/data_stream.rs | 0 .../{ => room_apis}/conversion/data_track.rs | 0 .../src/{ => room_apis}/conversion/mod.rs | 0 .../{ => room_apis}/conversion/participant.rs | 0 .../{ => room_apis}/conversion/resampler.rs | 0 .../src/{ => room_apis}/conversion/room.rs | 0 .../src/{ => room_apis}/conversion/stats.rs | 0 .../src/{ => room_apis}/conversion/track.rs | 0 .../{ => room_apis}/conversion/video_frame.rs | 0 livekit-ffi/src/room_apis/mod.rs | 26 +++++++++++++++++++ livekit-ffi/src/{ => room_apis}/proto.rs | 0 .../server/audio_filter_tests.rs | 0 .../{ => room_apis}/server/audio_plugin.rs | 0 .../{ => room_apis}/server/audio_source.rs | 0 .../{ => room_apis}/server/audio_stream.rs | 0 .../src/{ => room_apis}/server/capture.rs | 0 .../server/colorcvt/cvtimpl.rs | 0 .../{ => room_apis}/server/colorcvt/mod.rs | 0 .../src/{ => room_apis}/server/data_stream.rs | 0 .../src/{ => room_apis}/server/data_track.rs | 0 .../src/{ => room_apis}/server/logger.rs | 0 livekit-ffi/src/{ => room_apis}/server/mod.rs | 0 .../src/{ => room_apis}/server/participant.rs | 0 .../{ => room_apis}/server/platform_audio.rs | 0 .../src/{ => room_apis}/server/requests.rs | 0 .../src/{ => room_apis}/server/resampler.rs | 0 .../src/{ => room_apis}/server/room.rs | 0 .../src/{ => room_apis}/server/tests.rs | 0 .../src/{ => room_apis}/server/utils.rs | 0 .../{ => room_apis}/server/video_source.rs | 0 .../{ => room_apis}/server/video_stream.rs | 0 35 files changed, 32 insertions(+), 4 deletions(-) rename livekit-ffi/src/{ => room_apis}/cabi.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/audio_frame.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/capture.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/data_stream.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/data_track.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/mod.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/participant.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/resampler.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/room.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/stats.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/track.rs (100%) rename livekit-ffi/src/{ => room_apis}/conversion/video_frame.rs (100%) create mode 100644 livekit-ffi/src/room_apis/mod.rs rename livekit-ffi/src/{ => room_apis}/proto.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/audio_filter_tests.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/audio_plugin.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/audio_source.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/audio_stream.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/capture.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/colorcvt/cvtimpl.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/colorcvt/mod.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/data_stream.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/data_track.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/logger.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/mod.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/participant.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/platform_audio.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/requests.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/resampler.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/room.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/tests.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/utils.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/video_source.rs (100%) rename livekit-ffi/src/{ => room_apis}/server/video_stream.rs (100%) diff --git a/livekit-ffi/src/lib.rs b/livekit-ffi/src/lib.rs index 68b400aa5..ee73be718 100644 --- a/livekit-ffi/src/lib.rs +++ b/livekit-ffi/src/lib.rs @@ -18,15 +18,17 @@ use lazy_static::lazy_static; use livekit::prelude::*; use thiserror::Error; -mod conversion; mod migration; #[cfg(test)] mod migration_tests; +mod room_apis; + +// Republishes the surface at the crate root, so `livekit_ffi::{cabi, proto, +// server}` and the crate-internal `crate::` paths are unchanged by the move. +pub use room_apis::*; + pub mod build_info; -pub mod cabi; -pub mod proto; -pub mod server; uniffi::setup_scaffolding!(); diff --git a/livekit-ffi/src/cabi.rs b/livekit-ffi/src/room_apis/cabi.rs similarity index 100% rename from livekit-ffi/src/cabi.rs rename to livekit-ffi/src/room_apis/cabi.rs diff --git a/livekit-ffi/src/conversion/audio_frame.rs b/livekit-ffi/src/room_apis/conversion/audio_frame.rs similarity index 100% rename from livekit-ffi/src/conversion/audio_frame.rs rename to livekit-ffi/src/room_apis/conversion/audio_frame.rs diff --git a/livekit-ffi/src/conversion/capture.rs b/livekit-ffi/src/room_apis/conversion/capture.rs similarity index 100% rename from livekit-ffi/src/conversion/capture.rs rename to livekit-ffi/src/room_apis/conversion/capture.rs diff --git a/livekit-ffi/src/conversion/data_stream.rs b/livekit-ffi/src/room_apis/conversion/data_stream.rs similarity index 100% rename from livekit-ffi/src/conversion/data_stream.rs rename to livekit-ffi/src/room_apis/conversion/data_stream.rs diff --git a/livekit-ffi/src/conversion/data_track.rs b/livekit-ffi/src/room_apis/conversion/data_track.rs similarity index 100% rename from livekit-ffi/src/conversion/data_track.rs rename to livekit-ffi/src/room_apis/conversion/data_track.rs diff --git a/livekit-ffi/src/conversion/mod.rs b/livekit-ffi/src/room_apis/conversion/mod.rs similarity index 100% rename from livekit-ffi/src/conversion/mod.rs rename to livekit-ffi/src/room_apis/conversion/mod.rs diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/room_apis/conversion/participant.rs similarity index 100% rename from livekit-ffi/src/conversion/participant.rs rename to livekit-ffi/src/room_apis/conversion/participant.rs diff --git a/livekit-ffi/src/conversion/resampler.rs b/livekit-ffi/src/room_apis/conversion/resampler.rs similarity index 100% rename from livekit-ffi/src/conversion/resampler.rs rename to livekit-ffi/src/room_apis/conversion/resampler.rs diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/room_apis/conversion/room.rs similarity index 100% rename from livekit-ffi/src/conversion/room.rs rename to livekit-ffi/src/room_apis/conversion/room.rs diff --git a/livekit-ffi/src/conversion/stats.rs b/livekit-ffi/src/room_apis/conversion/stats.rs similarity index 100% rename from livekit-ffi/src/conversion/stats.rs rename to livekit-ffi/src/room_apis/conversion/stats.rs diff --git a/livekit-ffi/src/conversion/track.rs b/livekit-ffi/src/room_apis/conversion/track.rs similarity index 100% rename from livekit-ffi/src/conversion/track.rs rename to livekit-ffi/src/room_apis/conversion/track.rs diff --git a/livekit-ffi/src/conversion/video_frame.rs b/livekit-ffi/src/room_apis/conversion/video_frame.rs similarity index 100% rename from livekit-ffi/src/conversion/video_frame.rs rename to livekit-ffi/src/room_apis/conversion/video_frame.rs diff --git a/livekit-ffi/src/room_apis/mod.rs b/livekit-ffi/src/room_apis/mod.rs new file mode 100644 index 000000000..77a7783e0 --- /dev/null +++ b/livekit-ffi/src/room_apis/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The protobuf / C-ABI room surface. +//! +//! This is the FFI interface used by the Python, Node and Unity SDKs, which +//! have no WebRTC stack of their own: requests arrive as encoded +//! [`proto::FfiRequest`] messages through [`cabi`] and are dispatched by +//! [`server`]. It depends on `livekit` and, through it, on libwebrtc. + +mod conversion; + +pub mod cabi; +pub mod proto; +pub mod server; diff --git a/livekit-ffi/src/proto.rs b/livekit-ffi/src/room_apis/proto.rs similarity index 100% rename from livekit-ffi/src/proto.rs rename to livekit-ffi/src/room_apis/proto.rs diff --git a/livekit-ffi/src/server/audio_filter_tests.rs b/livekit-ffi/src/room_apis/server/audio_filter_tests.rs similarity index 100% rename from livekit-ffi/src/server/audio_filter_tests.rs rename to livekit-ffi/src/room_apis/server/audio_filter_tests.rs diff --git a/livekit-ffi/src/server/audio_plugin.rs b/livekit-ffi/src/room_apis/server/audio_plugin.rs similarity index 100% rename from livekit-ffi/src/server/audio_plugin.rs rename to livekit-ffi/src/room_apis/server/audio_plugin.rs diff --git a/livekit-ffi/src/server/audio_source.rs b/livekit-ffi/src/room_apis/server/audio_source.rs similarity index 100% rename from livekit-ffi/src/server/audio_source.rs rename to livekit-ffi/src/room_apis/server/audio_source.rs diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/room_apis/server/audio_stream.rs similarity index 100% rename from livekit-ffi/src/server/audio_stream.rs rename to livekit-ffi/src/room_apis/server/audio_stream.rs diff --git a/livekit-ffi/src/server/capture.rs b/livekit-ffi/src/room_apis/server/capture.rs similarity index 100% rename from livekit-ffi/src/server/capture.rs rename to livekit-ffi/src/room_apis/server/capture.rs diff --git a/livekit-ffi/src/server/colorcvt/cvtimpl.rs b/livekit-ffi/src/room_apis/server/colorcvt/cvtimpl.rs similarity index 100% rename from livekit-ffi/src/server/colorcvt/cvtimpl.rs rename to livekit-ffi/src/room_apis/server/colorcvt/cvtimpl.rs diff --git a/livekit-ffi/src/server/colorcvt/mod.rs b/livekit-ffi/src/room_apis/server/colorcvt/mod.rs similarity index 100% rename from livekit-ffi/src/server/colorcvt/mod.rs rename to livekit-ffi/src/room_apis/server/colorcvt/mod.rs diff --git a/livekit-ffi/src/server/data_stream.rs b/livekit-ffi/src/room_apis/server/data_stream.rs similarity index 100% rename from livekit-ffi/src/server/data_stream.rs rename to livekit-ffi/src/room_apis/server/data_stream.rs diff --git a/livekit-ffi/src/server/data_track.rs b/livekit-ffi/src/room_apis/server/data_track.rs similarity index 100% rename from livekit-ffi/src/server/data_track.rs rename to livekit-ffi/src/room_apis/server/data_track.rs diff --git a/livekit-ffi/src/server/logger.rs b/livekit-ffi/src/room_apis/server/logger.rs similarity index 100% rename from livekit-ffi/src/server/logger.rs rename to livekit-ffi/src/room_apis/server/logger.rs diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/room_apis/server/mod.rs similarity index 100% rename from livekit-ffi/src/server/mod.rs rename to livekit-ffi/src/room_apis/server/mod.rs diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/room_apis/server/participant.rs similarity index 100% rename from livekit-ffi/src/server/participant.rs rename to livekit-ffi/src/room_apis/server/participant.rs diff --git a/livekit-ffi/src/server/platform_audio.rs b/livekit-ffi/src/room_apis/server/platform_audio.rs similarity index 100% rename from livekit-ffi/src/server/platform_audio.rs rename to livekit-ffi/src/room_apis/server/platform_audio.rs diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/room_apis/server/requests.rs similarity index 100% rename from livekit-ffi/src/server/requests.rs rename to livekit-ffi/src/room_apis/server/requests.rs diff --git a/livekit-ffi/src/server/resampler.rs b/livekit-ffi/src/room_apis/server/resampler.rs similarity index 100% rename from livekit-ffi/src/server/resampler.rs rename to livekit-ffi/src/room_apis/server/resampler.rs diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/room_apis/server/room.rs similarity index 100% rename from livekit-ffi/src/server/room.rs rename to livekit-ffi/src/room_apis/server/room.rs diff --git a/livekit-ffi/src/server/tests.rs b/livekit-ffi/src/room_apis/server/tests.rs similarity index 100% rename from livekit-ffi/src/server/tests.rs rename to livekit-ffi/src/room_apis/server/tests.rs diff --git a/livekit-ffi/src/server/utils.rs b/livekit-ffi/src/room_apis/server/utils.rs similarity index 100% rename from livekit-ffi/src/server/utils.rs rename to livekit-ffi/src/room_apis/server/utils.rs diff --git a/livekit-ffi/src/server/video_source.rs b/livekit-ffi/src/room_apis/server/video_source.rs similarity index 100% rename from livekit-ffi/src/server/video_source.rs rename to livekit-ffi/src/room_apis/server/video_source.rs diff --git a/livekit-ffi/src/server/video_stream.rs b/livekit-ffi/src/room_apis/server/video_stream.rs similarity index 100% rename from livekit-ffi/src/server/video_stream.rs rename to livekit-ffi/src/room_apis/server/video_stream.rs From ddb1583495e630aa1b2b44572bf28e5b9dfbdf96 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:06:43 -0400 Subject: [PATCH 02/42] refactor(livekit-ffi): move the crate-root FFI types into room_apis The crate root still held six items that belong to one surface only: `FfiError`, `FfiResult`, `FfiCallbackFn`, `FfiHandleId`, `INVALID_HANDLE` and `FFI_SERVER`. Move them to `room_apis/mod.rs`, with the four `use` statements that they need. The glob re-export from the previous commit republishes these items at the crate root. Their paths thus do not change, for other crates or for the code in this crate. This commit adds no feature gates. The gates come in the next commit. --- livekit-ffi/src/lib.rs | 29 ----------------------------- livekit-ffi/src/room_apis/mod.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/livekit-ffi/src/lib.rs b/livekit-ffi/src/lib.rs index ee73be718..804378b67 100644 --- a/livekit-ffi/src/lib.rs +++ b/livekit-ffi/src/lib.rs @@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::borrow::Cow; - -use lazy_static::lazy_static; -use livekit::prelude::*; -use thiserror::Error; - mod migration; #[cfg(test)] mod migration_tests; @@ -31,26 +25,3 @@ pub use room_apis::*; pub mod build_info; uniffi::setup_scaffolding!(); - -#[derive(Error, Debug)] -pub enum FfiError { - #[error("the server is not configured")] - NotConfigured, - #[error("the server is already initialized")] - AlreadyInitialized, - #[error("room error {0}")] - Room(#[from] RoomError), - #[error("invalid request: {0}")] - InvalidRequest(Cow<'static, str>), -} - -/// # SAFTEY: The "C" callback must be threadsafe and not block -pub type FfiCallbackFn = unsafe extern "C" fn(*const u8, usize); -pub type FfiResult = Result; -pub type FfiHandleId = u64; - -pub const INVALID_HANDLE: FfiHandleId = 0; - -lazy_static! { - pub static ref FFI_SERVER: server::FfiServer = server::FfiServer::default(); -} diff --git a/livekit-ffi/src/room_apis/mod.rs b/livekit-ffi/src/room_apis/mod.rs index 77a7783e0..8751a52ea 100644 --- a/livekit-ffi/src/room_apis/mod.rs +++ b/livekit-ffi/src/room_apis/mod.rs @@ -19,8 +19,37 @@ //! [`proto::FfiRequest`] messages through [`cabi`] and are dispatched by //! [`server`]. It depends on `livekit` and, through it, on libwebrtc. +use std::borrow::Cow; + +use lazy_static::lazy_static; +use livekit::prelude::*; +use thiserror::Error; + mod conversion; pub mod cabi; pub mod proto; pub mod server; + +#[derive(Error, Debug)] +pub enum FfiError { + #[error("the server is not configured")] + NotConfigured, + #[error("the server is already initialized")] + AlreadyInitialized, + #[error("room error {0}")] + Room(#[from] RoomError), + #[error("invalid request: {0}")] + InvalidRequest(Cow<'static, str>), +} + +/// # SAFTEY: The "C" callback must be threadsafe and not block +pub type FfiCallbackFn = unsafe extern "C" fn(*const u8, usize); +pub type FfiResult = Result; +pub type FfiHandleId = u64; + +pub const INVALID_HANDLE: FfiHandleId = 0; + +lazy_static! { + pub static ref FFI_SERVER: server::FfiServer = server::FfiServer::default(); +} From 6b83f684f4a354b3c2d13187b400faf18fd7983b Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:08:28 -0400 Subject: [PATCH 03/42] IMPORTANT(livekit-ffi): gate the existing surface behind a room-apis feature A second surface will follow. It must not pull in libwebrtc. Make the current surface optional first, so that a build can leave it out. Each dependency that only the `room_apis` module tree uses becomes optional. The new `room-apis` feature selects them. No source file changes, because each of these dependencies is already isolated in that module tree. The base `tokio` feature set becomes `sync` and `rt-multi-thread`. The `room-apis` feature adds `full` and `parking_lot` again. `default` keeps `room-apis` on. A build that does not give feature flags thus gets the same dependency tree as before this commit. IMPORTANT: the TLS features must select `room-apis`, because they only configure `livekit`. The file .github/workflows/ffi-builds.yml builds three targets with `--no-default-features --features ""`. Without this rule, those targets would build a crate with no surface in it. --- livekit-ffi/Cargo.toml | 83 ++++++++++++++++++++++---------- livekit-ffi/src/lib.rs | 5 +- livekit-ffi/src/room_apis/mod.rs | 2 + 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 532c07b01..276fa052b 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -9,45 +9,76 @@ readme = "README.md" publish = false [features] -default = ["rustls-tls-native-roots"] -native-tls = ["livekit/native-tls"] -native-tls-vendored = ["livekit/native-tls-vendored"] -rustls-tls-native-roots = ["livekit/rustls-tls-native-roots"] -rustls-tls-webpki-roots = ["livekit/rustls-tls-webpki-roots"] -__rustls-tls = ["livekit/__rustls-tls"] +default = ["room-apis", "rustls-tls-native-roots"] + +# `room-apis` implements a full, rust owned and managed webrtc stack. This is used by more modern +# client sdks like python, node, unity, and c++. +room-apis = [ + "dep:livekit", + "dep:webrtc-sys", + "dep:soxr-sys", + "dep:imgproc", + "dep:prost", + "dep:dashmap", + "dep:downcast-rs", + "dep:env_logger", + "dep:from_variants", + "dep:lazy_static", + "dep:parking_lot", + "dep:prost-build", + "dep:webrtc-sys-build", + "tokio/full", + "tokio/parking_lot", +] + +# TLS selection. These imply `room-apis` because they only configure `livekit`: +# .github/workflows/ffi-builds.yml builds several targets with +# `--no-default-features --features ""`, which would otherwise select +# neither surface and produce an empty crate. +native-tls = ["room-apis", "livekit/native-tls"] +native-tls-vendored = ["room-apis", "livekit/native-tls-vendored"] +rustls-tls-native-roots = ["room-apis", "livekit/rustls-tls-native-roots"] +rustls-tls-webpki-roots = ["room-apis", "livekit/rustls-tls-webpki-roots"] +__rustls-tls = ["room-apis", "livekit/__rustls-tls"] # Enable tokio-console to debug tasks -tracing = ["tokio/tracing", "console-subscriber"] +tracing = ["room-apis", "tokio/tracing", "dep:console-subscriber"] # Capture sources (livekit-capture): publish tracks from server-side # producers. This enables the capture scaffolding only; enable a # `capture-*` feature to include a specific source. -capture = ["dep:livekit-capture"] +capture = ["room-apis", "dep:livekit-capture"] capture-clock = ["capture", "livekit-capture/source-clock"] capture-gstreamer = ["capture", "livekit-capture/source-gstreamer"] capture-pattern = ["capture", "livekit-capture/source-pattern"] [dependencies] -livekit = { workspace = true } -livekit-capture = { workspace = true, optional = true, default-features = false } -webrtc-sys = { workspace = true } -soxr-sys = { workspace = true } -imgproc = { workspace = true } livekit-protocol = { workspace = true } -tokio = { workspace = true, features = ["full", "parking_lot"] } +uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] } +tokio = { workspace = true, features = ["sync", "rt-multi-thread"] } futures-util = { workspace = true, default-features = false, features = ["sink"] } -parking_lot = { workspace = true, features = ["deadlock_detection"] } -prost = { workspace = true } -lazy_static = { workspace = true } -thiserror = { workspace = true } +bytes = { workspace = true } log = { workspace = true } -dashmap = "6.2" -env_logger = { workspace = true } -downcast-rs = "1.2" +thiserror = { workspace = true } + +# room-apis only. Each of these is referenced solely from the `room_apis` module +# tree, so gating them costs no source changes. +livekit = { workspace = true, optional = true } +livekit-capture = { workspace = true, optional = true, default-features = false } + +# Referenced from no source file at all: webrtc-sys exists to link static +# libwebrtc, which build.rs wires up under the same feature. +webrtc-sys = { workspace = true, optional = true } +soxr-sys = { workspace = true, optional = true } +imgproc = { workspace = true, optional = true } +prost = { workspace = true, optional = true } +parking_lot = { workspace = true, features = ["deadlock_detection"], optional = true } +lazy_static = { workspace = true, optional = true } +env_logger = { workspace = true, optional = true } +dashmap = { version = "6.2", optional = true } +downcast-rs = { version = "1.2", optional = true } +from_variants = { version = "1.0.2", optional = true } console-subscriber = { workspace = true, features = ["parking_lot"], optional = true } -bytes = { workspace = true } -from_variants = "1.0.2" -uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] } [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" @@ -55,8 +86,8 @@ link-cplusplus = { version = "1", features = ["nothing"] } device-info = { workspace = true } [build-dependencies] -prost-build = { workspace = true } -webrtc-sys-build = { workspace = true } +prost-build = { workspace = true, optional = true } +webrtc-sys-build = { workspace = true, optional = true } [dev-dependencies] livekit-token = { workspace = true } diff --git a/livekit-ffi/src/lib.rs b/livekit-ffi/src/lib.rs index 804378b67..608fa361a 100644 --- a/livekit-ffi/src/lib.rs +++ b/livekit-ffi/src/lib.rs @@ -12,14 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "room-apis")] mod migration; -#[cfg(test)] +#[cfg(all(test, feature = "room-apis"))] mod migration_tests; +#[cfg(feature = "room-apis")] mod room_apis; // Republishes the surface at the crate root, so `livekit_ffi::{cabi, proto, // server}` and the crate-internal `crate::` paths are unchanged by the move. +#[cfg(feature = "room-apis")] pub use room_apis::*; pub mod build_info; diff --git a/livekit-ffi/src/room_apis/mod.rs b/livekit-ffi/src/room_apis/mod.rs index 8751a52ea..f0dc30d78 100644 --- a/livekit-ffi/src/room_apis/mod.rs +++ b/livekit-ffi/src/room_apis/mod.rs @@ -18,6 +18,8 @@ //! have no WebRTC stack of their own: requests arrive as encoded //! [`proto::FfiRequest`] messages through [`cabi`] and are dispatched by //! [`server`]. It depends on `livekit` and, through it, on libwebrtc. +//! +//! Gated behind the `room-apis` feature. use std::borrow::Cow; From 89d1f568dfa31e12304dcb381fed0d6c1cb4ab05 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:08:38 -0400 Subject: [PATCH 04/42] IMPORTANT(livekit-ffi): skip the libwebrtc build steps without room-apis The build script always downloaded libwebrtc, configured the linker and generated the protobuf code. The second surface needs none of these steps. Put the four steps behind the `room-apis` feature. The `DOCS_RS` check stays before the gate, so the behaviour on docs.rs does not change. IMPORTANT: this must be a `cfg` attribute and not a `CARGO_FEATURE_ROOM_APIS` lookup. The crates `prost-build` and `webrtc-sys-build` are now optional build dependencies with the same gate. Their paths do not resolve when the feature is off, so a check at run time is too late. This gate is also what makes the Apple targets possible. The function `configure_linker` stops with a panic on an unknown target operating system. visionOS, tvOS and Mac Catalyst are all unknown to it, and the Swift package builds all three. --- livekit-ffi/build.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/livekit-ffi/build.rs b/livekit-ffi/build.rs index 29158f322..5b08f0fc6 100644 --- a/livekit-ffi/build.rs +++ b/livekit-ffi/build.rs @@ -12,20 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{env, path::Path}; +use std::env; +#[cfg(feature = "room-apis")] +use std::path::Path; +#[cfg(feature = "room-apis")] const PROTO_SRC_DIR: &str = "protocol"; fn main() { if env::var("DOCS_RS").is_ok() { return; } - download_webrtc(); - copy_webrtc_license(); - configure_linker(); - generate_protobuf(); + // livekit-ffi/core-modules is pure Rust: no libwebrtc download, no linker + // configuration (`configure_linker` panics on visionOS/tvOS/Mac Catalyst, + // which the Swift package builds) and no protobuf generation. + // + // This has to be a `cfg` rather than a `CARGO_FEATURE_ROOM_APIS` lookup: + // `prost-build` and `webrtc-sys-build` are optional build-dependencies + // gated on the same feature, so under core-modules those paths do not + // resolve at all. + #[cfg(feature = "room-apis")] + { + download_webrtc(); + copy_webrtc_license(); + configure_linker(); + generate_protobuf(); + } } +#[cfg(feature = "room-apis")] fn download_webrtc() { let webrtc_dir = webrtc_sys_build::webrtc_dir(); if !webrtc_dir.exists() { @@ -33,6 +48,7 @@ fn download_webrtc() { } } +#[cfg(feature = "room-apis")] /// Copy the webrtc license to `CARGO_MANIFEST_DIR`, used by the FFI release action. fn copy_webrtc_license() { let webrtc_dir = webrtc_sys_build::webrtc_dir(); @@ -42,6 +58,7 @@ fn copy_webrtc_license() { std::fs::copy(license, out_file).unwrap(); } +#[cfg(feature = "room-apis")] fn configure_linker() { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); match target_os.as_str() { @@ -61,6 +78,7 @@ fn configure_linker() { } } +#[cfg(feature = "room-apis")] fn generate_protobuf() { let paths: Vec<_> = std::fs::read_dir(PROTO_SRC_DIR) .expect("Failed to read protobuf source directory") @@ -85,6 +103,7 @@ fn generate_protobuf() { .expect("Protobuf generation failed"); } +#[cfg(feature = "room-apis")] trait ProstConfigExt { /// Derive [`from_variants::FromVariants`] on a oneof field's generated enum. fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self; @@ -94,6 +113,7 @@ trait ProstConfigExt { fn from_variants_skip_field(&mut self, path: impl AsRef) -> &mut Self; } +#[cfg(feature = "room-apis")] impl ProstConfigExt for prost_build::Config { fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self { self.enum_attribute(path, "#[derive(from_variants::FromVariants)]") From 7b930a1e9e71af40f6effe5796137199bb19b1b0 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:08:48 -0400 Subject: [PATCH 05/42] chore(bindgens): move the Dart bindgen CLI into tools/bindgens The crate tools/bindgens already holds the shared uniffi-bindgen CLI. The Dart generator belongs with it. Move `bindgen-dart.rs` there without a change to its content. Put it behind a new `dart-bindgen` feature, which is off by default, so that a plain `cargo build --workspace` does not fetch the git dependency it needs. Delete `bindgen.rs`, because tools/bindgens has the same program already. Do this before the merge of the two crates. If these binaries stay in livekit-ffi, then `cargo run` in that crate turns on its default features and builds all of libwebrtc to generate bindings. --- livekit-uniffi/bindgen.rs | 19 -------------- tools/bindgens/Cargo.toml | 26 +++++++++++++++++-- .../bindgens/src/bin/uniffi-bindgen-dart.rs | 0 3 files changed, 24 insertions(+), 21 deletions(-) delete mode 100644 livekit-uniffi/bindgen.rs rename livekit-uniffi/bindgen-dart.rs => tools/bindgens/src/bin/uniffi-bindgen-dart.rs (100%) diff --git a/livekit-uniffi/bindgen.rs b/livekit-uniffi/bindgen.rs deleted file mode 100644 index 7eb9d1172..000000000 --- a/livekit-uniffi/bindgen.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2025 LiveKit, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// This binary is included to allow invoking the UniFFI bindgen CLI from Cargo: -/// `cargo run --bin uniffi-bindgen generate ...` -fn main() { - uniffi::uniffi_bindgen_main() -} diff --git a/tools/bindgens/Cargo.toml b/tools/bindgens/Cargo.toml index 592a91066..4821bd9bc 100644 --- a/tools/bindgens/Cargo.toml +++ b/tools/bindgens/Cargo.toml @@ -8,8 +8,30 @@ description = "Foreign-binding generator CLIs for the LiveKit Rust crates" publish = false # Tooling crate: each bin is a code-generation CLI shared across the workspace's -# uniffi-exposing crates (livekit-ffi, livekit-uniffi, ...). It has no library -# target — the bindgens read metadata from an already-compiled cdylib via +# uniffi-exposing crates (livekit-ffi, ...). It has no library target beyond +# these bins — they read metadata from an already-compiled cdylib via # `--library`, so they never depend on the crates they generate for. [dependencies] uniffi = { workspace = true, features = ["cli"] } +# Dart binding generator. Not published to crates.io, so pinned by git rev. The +# rev must target the same uniffi-rs release (0.31) as the `uniffi` dependency +# above, or it cannot read the compiled metadata. +# +# Pinned to upstream main rather than a release: the newest release +# (v0.2.1+v0.31.2) predates the three fixes this needs -- custom types leaking +# into FFI signatures (#154), object converter statics colliding with Rust +# methods named `write` (#156), and each crate re-declaring the runtime +# scaffolding, which kept livekit-datatrack's types from crossing over (#150). +# Move to a version requirement once a release carries them. +uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart", rev = "67547701d849c9ef6302f79bb2189bb6075f69fa", optional = true } +camino = { version = "1", optional = true } + +[features] +# Enables the `uniffi-bindgen-dart` bin. Off by default so a plain +# `cargo build --workspace` does not fetch or compile the git dependency. +dart-bindgen = ["dep:uniffi-dart", "dep:camino"] + +[[bin]] +name = "uniffi-bindgen-dart" +path = "src/bin/uniffi-bindgen-dart.rs" +required-features = ["dart-bindgen"] diff --git a/livekit-uniffi/bindgen-dart.rs b/tools/bindgens/src/bin/uniffi-bindgen-dart.rs similarity index 100% rename from livekit-uniffi/bindgen-dart.rs rename to tools/bindgens/src/bin/uniffi-bindgen-dart.rs From 8605f2e99431626a8388912ecf92e961e843ee51 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:09:22 -0400 Subject: [PATCH 06/42] refactor(livekit-ffi): move the livekit-uniffi sources into a core_modules module Move the six modules of livekit-uniffi into `livekit-ffi/src/core_modules/`: access tokens, log forwarding, data tracks, data streams and the shared runtime. The content of these files does not change. The crate root of livekit-uniffi becomes the module root `core_modules/mod.rs`, also without a change to its content. The next commit removes the items in it that only a crate root can hold. Delete `build_info.rs`. The file livekit-ffi/src/build_info.rs holds the same function, and both surfaces share it. Nothing declares the new module yet, so no build uses this code. --- .../src/core_modules}/access_token.rs | 0 .../src/core_modules}/common.rs | 0 .../src/core_modules}/data_stream/common.rs | 0 .../src/core_modules}/data_stream/incoming.rs | 0 .../src/core_modules}/data_stream/mod.rs | 0 .../src/core_modules}/data_stream/outgoing.rs | 0 .../src/core_modules}/data_stream/polled.rs | 0 .../src/core_modules}/data_stream/tests.rs | 0 .../src/core_modules}/data_track/common.rs | 0 .../src/core_modules}/data_track/local.rs | 0 .../src/core_modules}/data_track/mod.rs | 0 .../src/core_modules}/data_track/remote.rs | 0 .../src/core_modules}/log_forward.rs | 0 .../src/core_modules/mod.rs | 0 .../src/core_modules}/runtime.rs | 0 livekit-uniffi/src/build_info.rs | 19 ------------------- 16 files changed, 19 deletions(-) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/access_token.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/common.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/common.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/incoming.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/mod.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/outgoing.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/polled.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_stream/tests.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_track/common.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_track/local.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_track/mod.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/data_track/remote.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/log_forward.rs (100%) rename livekit-uniffi/src/lib.rs => livekit-ffi/src/core_modules/mod.rs (100%) rename {livekit-uniffi/src => livekit-ffi/src/core_modules}/runtime.rs (100%) delete mode 100644 livekit-uniffi/src/build_info.rs diff --git a/livekit-uniffi/src/access_token.rs b/livekit-ffi/src/core_modules/access_token.rs similarity index 100% rename from livekit-uniffi/src/access_token.rs rename to livekit-ffi/src/core_modules/access_token.rs diff --git a/livekit-uniffi/src/common.rs b/livekit-ffi/src/core_modules/common.rs similarity index 100% rename from livekit-uniffi/src/common.rs rename to livekit-ffi/src/core_modules/common.rs diff --git a/livekit-uniffi/src/data_stream/common.rs b/livekit-ffi/src/core_modules/data_stream/common.rs similarity index 100% rename from livekit-uniffi/src/data_stream/common.rs rename to livekit-ffi/src/core_modules/data_stream/common.rs diff --git a/livekit-uniffi/src/data_stream/incoming.rs b/livekit-ffi/src/core_modules/data_stream/incoming.rs similarity index 100% rename from livekit-uniffi/src/data_stream/incoming.rs rename to livekit-ffi/src/core_modules/data_stream/incoming.rs diff --git a/livekit-uniffi/src/data_stream/mod.rs b/livekit-ffi/src/core_modules/data_stream/mod.rs similarity index 100% rename from livekit-uniffi/src/data_stream/mod.rs rename to livekit-ffi/src/core_modules/data_stream/mod.rs diff --git a/livekit-uniffi/src/data_stream/outgoing.rs b/livekit-ffi/src/core_modules/data_stream/outgoing.rs similarity index 100% rename from livekit-uniffi/src/data_stream/outgoing.rs rename to livekit-ffi/src/core_modules/data_stream/outgoing.rs diff --git a/livekit-uniffi/src/data_stream/polled.rs b/livekit-ffi/src/core_modules/data_stream/polled.rs similarity index 100% rename from livekit-uniffi/src/data_stream/polled.rs rename to livekit-ffi/src/core_modules/data_stream/polled.rs diff --git a/livekit-uniffi/src/data_stream/tests.rs b/livekit-ffi/src/core_modules/data_stream/tests.rs similarity index 100% rename from livekit-uniffi/src/data_stream/tests.rs rename to livekit-ffi/src/core_modules/data_stream/tests.rs diff --git a/livekit-uniffi/src/data_track/common.rs b/livekit-ffi/src/core_modules/data_track/common.rs similarity index 100% rename from livekit-uniffi/src/data_track/common.rs rename to livekit-ffi/src/core_modules/data_track/common.rs diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-ffi/src/core_modules/data_track/local.rs similarity index 100% rename from livekit-uniffi/src/data_track/local.rs rename to livekit-ffi/src/core_modules/data_track/local.rs diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-ffi/src/core_modules/data_track/mod.rs similarity index 100% rename from livekit-uniffi/src/data_track/mod.rs rename to livekit-ffi/src/core_modules/data_track/mod.rs diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-ffi/src/core_modules/data_track/remote.rs similarity index 100% rename from livekit-uniffi/src/data_track/remote.rs rename to livekit-ffi/src/core_modules/data_track/remote.rs diff --git a/livekit-uniffi/src/log_forward.rs b/livekit-ffi/src/core_modules/log_forward.rs similarity index 100% rename from livekit-uniffi/src/log_forward.rs rename to livekit-ffi/src/core_modules/log_forward.rs diff --git a/livekit-uniffi/src/lib.rs b/livekit-ffi/src/core_modules/mod.rs similarity index 100% rename from livekit-uniffi/src/lib.rs rename to livekit-ffi/src/core_modules/mod.rs diff --git a/livekit-uniffi/src/runtime.rs b/livekit-ffi/src/core_modules/runtime.rs similarity index 100% rename from livekit-uniffi/src/runtime.rs rename to livekit-ffi/src/core_modules/runtime.rs diff --git a/livekit-uniffi/src/build_info.rs b/livekit-uniffi/src/build_info.rs deleted file mode 100644 index 9ce3697e2..000000000 --- a/livekit-uniffi/src/build_info.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2025 LiveKit, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// Returns the version specified in the crate's Cargo.toml. -#[uniffi::export] -pub fn build_version() -> String { - env!("CARGO_PKG_VERSION").to_string() -} From dc8aa81a8d6e196cd2cdff9e39cbcba9b9735b59 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:09:56 -0400 Subject: [PATCH 07/42] IMPORTANT(livekit-ffi): add the core-modules feature Declare the moved modules and add the dependencies that they need. The new `core-modules` feature selects all of them. This feature does not use `livekit`, `libwebrtc`, `soxr-sys` or `imgproc`. Remove from `core_modules/mod.rs` the two items that only a crate root can hold: the `build_info` module and the scaffolding call. Keep the statement `extern crate livekit_net`, which makes the linker put the UniFFI scaffolding of livekit-net into the shared library. IMPORTANT: the two features are mutually exclusive, and a `compile_error` stops a build that selects both. The cause is the UniFFI scaffolding: a crate can have only one such call, and it must be at the crate root. A second `compile_error` stops a build that selects neither feature. There is one scaffolding call for each surface. The core-modules call keeps the namespace `livekit_uniffi`. The generated Swift, Kotlin, Dart and Python modules thus keep their current names, and no downstream SDK must change. A later commit renames them. --- livekit-ffi/Cargo.toml | 35 ++++++++++++++++++++++ livekit-ffi/src/core_modules/mod.rs | 18 ++++++++---- livekit-ffi/src/lib.rs | 45 ++++++++++++++++++++++++++--- livekit-ffi/src/room_apis/mod.rs | 3 +- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 276fa052b..07b1bdb18 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -11,6 +11,9 @@ publish = false [features] default = ["room-apis", "rustls-tls-native-roots"] +# There are two mutually exclusive modes that this crate can be configured +# in, `room-apis` and `core-modules`. + # `room-apis` implements a full, rust owned and managed webrtc stack. This is used by more modern # client sdks like python, node, unity, and c++. room-apis = [ @@ -31,6 +34,27 @@ room-apis = [ "tokio/parking_lot", ] +# `core-modules` implements a set of smaller utility modules in rust to abstract away business +# logic. This does NOT include `libwebrtc` or `livekit`. +# +# This mode is used by older client sdks which historically were fully implemented in each +# downstream language and is a transitional step to migrating eventually someday to the full +# room-apis mode - swift, android, flutter, etc. +core-modules = [ + "dep:livekit-common", + "dep:livekit-data-stream", + "dep:livekit-datatrack", + "dep:livekit-net", + "dep:livekit-token", + "dep:async-trait", + "dep:once_cell", + "dep:tokio-util", + "livekit-common/uniffi", + "livekit-datatrack/uniffi", + "livekit-net/uniffi", + "uniffi/tokio", +] + # TLS selection. These imply `room-apis` because they only configure `livekit`: # .github/workflows/ffi-builds.yml builds several targets with # `--no-default-features --features ""`, which would otherwise select @@ -53,6 +77,7 @@ capture-gstreamer = ["capture", "livekit-capture/source-gstreamer"] capture-pattern = ["capture", "livekit-capture/source-pattern"] [dependencies] +# Shared by both surfaces. livekit-protocol = { workspace = true } uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] } tokio = { workspace = true, features = ["sync", "rt-multi-thread"] } @@ -66,6 +91,16 @@ thiserror = { workspace = true } livekit = { workspace = true, optional = true } livekit-capture = { workspace = true, optional = true, default-features = false } +# core-modules only. +livekit-common = { workspace = true, optional = true } +livekit-token = { workspace = true, optional = true } +livekit-datatrack = { workspace = true, optional = true } +livekit-net = { workspace = true, optional = true } +livekit-data-stream = { workspace = true, optional = true } +tokio-util = { version = "0.7.18", optional = true } +once_cell = { version = "1.21.3", optional = true } +async-trait = { version = "0.1", optional = true } + # Referenced from no source file at all: webrtc-sys exists to link static # libwebrtc, which build.rs wires up under the same feature. webrtc-sys = { workspace = true, optional = true } diff --git a/livekit-ffi/src/core_modules/mod.rs b/livekit-ffi/src/core_modules/mod.rs index c93e40cdc..a0b7cf857 100644 --- a/livekit-ffi/src/core_modules/mod.rs +++ b/livekit-ffi/src/core_modules/mod.rs @@ -12,6 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! The UniFFI core surface. +//! +//! Business logic that client SDKs adopt incrementally over UniFFI: the Swift, +//! Android and Flutter SDKs already have their own WebRTC stack, so this +//! surface deliberately depends on none of `livekit`, `libwebrtc`, `soxr-sys` +//! or `imgproc` -- which is what lets it build for visionOS, tvOS and Mac +//! Catalyst. +//! +//! Gated behind the `core-modules` feature, which is mutually exclusive with +//! `room-apis`. + /// Data tracks core from [`livekit-datatrack`]. pub mod data_track; @@ -24,15 +35,12 @@ pub mod access_token; /// Forward log messages from Rust. pub mod log_forward; -/// Information about the build such as version. -pub mod build_info; - /// Shared exports and utilities. pub mod common; /// Global async runtime. pub mod runtime; +// Forces livekit-net's UniFFI scaffolding into the cdylib. Nothing in this +// crate names livekit-net by path, so without this its exports go missing. extern crate livekit_net; - -uniffi::setup_scaffolding!(); diff --git a/livekit-ffi/src/lib.rs b/livekit-ffi/src/lib.rs index 608fa361a..3f1e49735 100644 --- a/livekit-ffi/src/lib.rs +++ b/livekit-ffi/src/lib.rs @@ -12,6 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +// `room-apis` and `core-modules` are two distinct FFI surfaces that ship as +// separate artifacts to different SDKs, and each sets up its own UniFFI +// scaffolding below. Selecting both is always a build-configuration mistake. +#[cfg(all(feature = "room-apis", feature = "core-modules"))] +compile_error!( + "the `room-apis` and `core-modules` features are mutually exclusive. \ + Build livekit-ffi/core-modules with \ + `--no-default-features --features core-modules`." +); + +// Both surfaces export `build_version` through the scaffolding below, which +// only exists when a surface is selected. Failing here beats failing inside +// the uniffi macro expansion. +#[cfg(not(any(feature = "room-apis", feature = "core-modules")))] +compile_error!( + "exactly one FFI surface must be selected: enable either `room-apis` \ + (the default) or `core-modules`." +); + +// Each surface owns everything specific to it, including its own types and +// statics; the globs republish it at the crate root. That keeps the public API +// (`livekit_ffi::{cabi, proto, server, FfiError, FFI_SERVER, ...}`) and the +// crate-internal `crate::` paths identical to before the modules existed. #[cfg(feature = "room-apis")] mod migration; #[cfg(all(test, feature = "room-apis"))] @@ -19,12 +42,26 @@ mod migration_tests; #[cfg(feature = "room-apis")] mod room_apis; - -// Republishes the surface at the crate root, so `livekit_ffi::{cabi, proto, -// server}` and the crate-internal `crate::` paths are unchanged by the move. #[cfg(feature = "room-apis")] pub use room_apis::*; +#[cfg(feature = "core-modules")] +mod core_modules; +#[cfg(feature = "core-modules")] +pub use core_modules::*; + +/// Information about the build such as version. +/// +/// Shared by both surfaces, and unconditional for that reason: a +/// room-apis-only build would otherwise ship a UniFFI component with no +/// exports at all (ffi-builds.yml greps the generated Python for it). pub mod build_info; -uniffi::setup_scaffolding!(); +// Must sit at the crate root: this defines `crate::UniFfiTag`, which every +// `#[uniffi::export]` in the surface modules resolves against. Only one call +// may exist per crate, which is why the two surfaces are mutually exclusive; +// they are kept separate so either can pin its own namespace later. +#[cfg(feature = "room-apis")] +uniffi::setup_scaffolding!("livekit_ffi"); +#[cfg(feature = "core-modules")] +uniffi::setup_scaffolding!("livekit_uniffi"); diff --git a/livekit-ffi/src/room_apis/mod.rs b/livekit-ffi/src/room_apis/mod.rs index f0dc30d78..bcd294a53 100644 --- a/livekit-ffi/src/room_apis/mod.rs +++ b/livekit-ffi/src/room_apis/mod.rs @@ -19,7 +19,8 @@ //! [`proto::FfiRequest`] messages through [`cabi`] and are dispatched by //! [`server`]. It depends on `livekit` and, through it, on libwebrtc. //! -//! Gated behind the `room-apis` feature. +//! Gated behind the `room-apis` feature, which is mutually exclusive with +//! `core-modules`. use std::borrow::Cow; From ba5eb68f0f04e5413020e0a41aeef5b3b73e0213 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:10:31 -0400 Subject: [PATCH 08/42] IMPORTANT(livekit-ffi): bind the core modules to prost 0.12 The two surfaces need different versions of prost. The crates livekit-protocol and livekit-data-stream use prost 0.12, and the core modules call the `Message` trait on the types that these crates generate. The protobuf code of livekit-ffi comes from prost-build 0.14 and writes `::prost::` paths, so the name `prost` must stay on 0.14. Add prost 0.12 a second time under the name `prost_012` and change the seven imports in the core modules. All seven are in the moved code. No file in the room-apis surface changes. IMPORTANT: this is safe only because the two surfaces are mutually exclusive. The two versions of prost never go into the same build. No existing code calls a prost trait on a livekit-protocol type, thus the two versions did not meet before this change either. Remove this when the workspace uses one version of prost. --- livekit-ffi/Cargo.toml | 10 ++++++++++ livekit-ffi/src/core_modules/data_stream/common.rs | 2 +- livekit-ffi/src/core_modules/data_stream/outgoing.rs | 2 +- livekit-ffi/src/core_modules/data_stream/tests.rs | 2 +- livekit-ffi/src/core_modules/data_track/common.rs | 4 ++-- livekit-ffi/src/core_modules/data_track/local.rs | 2 +- livekit-ffi/src/core_modules/data_track/remote.rs | 2 +- 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 07b1bdb18..303b86d27 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -46,6 +46,7 @@ core-modules = [ "dep:livekit-datatrack", "dep:livekit-net", "dep:livekit-token", + "dep:prost_012", "dep:async-trait", "dep:once_cell", "dep:tokio-util", @@ -115,6 +116,15 @@ downcast-rs = { version = "1.2", optional = true } from_variants = { version = "1.0.2", optional = true } console-subscriber = { workspace = true, features = ["parking_lot"], optional = true } +# prost 0.12, alongside the 0.14 above. livekit-protocol and livekit-data-stream +# are on 0.12 and the core modules call `Message` on their generated types, +# while livekit-ffi's own protos are generated by prost-build 0.14 and emit +# `::prost::` paths, so that name has to stay bound to 0.14. The two surfaces +# are mutually exclusive, so the two versions never compile together. +# +# TODO: drop this once the workspace unifies on a single prost. +prost_012 = { package = "prost", version = "0.12", optional = true } + [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" link-cplusplus = { version = "1", features = ["nothing"] } diff --git a/livekit-ffi/src/core_modules/data_stream/common.rs b/livekit-ffi/src/core_modules/data_stream/common.rs index 4a92a8598..12772af4f 100644 --- a/livekit-ffi/src/core_modules/data_stream/common.rs +++ b/livekit-ffi/src/core_modules/data_stream/common.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use livekit_common as common; use livekit_data_stream::{api as ds_api, backend as ds}; use livekit_protocol as proto; -use prost::Message; +use prost_012::Message; // MARK: - Enums diff --git a/livekit-ffi/src/core_modules/data_stream/outgoing.rs b/livekit-ffi/src/core_modules/data_stream/outgoing.rs index ac6344b84..b3660a545 100644 --- a/livekit-ffi/src/core_modules/data_stream/outgoing.rs +++ b/livekit-ffi/src/core_modules/data_stream/outgoing.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use bytes::Bytes; use livekit_common as lk_common; use livekit_data_stream::{api as ds_api, backend as ds}; -use prost::Message as _; +use prost_012::Message as _; use tokio_util::sync::{CancellationToken, DropGuard}; use super::common::{ diff --git a/livekit-ffi/src/core_modules/data_stream/tests.rs b/livekit-ffi/src/core_modules/data_stream/tests.rs index 2498c5e2c..3270c00e8 100644 --- a/livekit-ffi/src/core_modules/data_stream/tests.rs +++ b/livekit-ffi/src/core_modules/data_stream/tests.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex}; use bytes::Bytes; use livekit_protocol as proto; -use prost::Message as _; +use prost_012::Message as _; use tokio::sync::oneshot; use super::common::{ diff --git a/livekit-ffi/src/core_modules/data_track/common.rs b/livekit-ffi/src/core_modules/data_track/common.rs index f7357418d..52462b358 100644 --- a/livekit-ffi/src/core_modules/data_track/common.rs +++ b/livekit-ffi/src/core_modules/data_track/common.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use livekit_datatrack::api::{DataTrackFrameEncoding, DataTrackSchemaEncoding, DataTrackSid}; use livekit_protocol as proto; -use prost::Message; +use prost_012::Message; uniffi::custom_type!(DataTrackSid, String, { remote, @@ -98,7 +98,7 @@ impl From for DataTrackSchemaId { #[uniffi(flat_error)] pub enum HandleSignalResponseError { #[error("Response decoding failed: {0}")] - Decode(prost::DecodeError), + Decode(prost_012::DecodeError), #[error("Response container has no message")] EmptyMessage, #[error("Unsupported response type in this context")] diff --git a/livekit-ffi/src/core_modules/data_track/local.rs b/livekit-ffi/src/core_modules/data_track/local.rs index ebee263f7..95d92a89f 100644 --- a/livekit-ffi/src/core_modules/data_track/local.rs +++ b/livekit-ffi/src/core_modules/data_track/local.rs @@ -23,7 +23,7 @@ use livekit_datatrack::{ backend::{local, EncryptionProvider}, }; use livekit_protocol as proto; -use prost::Message; +use prost_012::Message; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; diff --git a/livekit-ffi/src/core_modules/data_track/remote.rs b/livekit-ffi/src/core_modules/data_track/remote.rs index 65a95202a..13b1ae882 100644 --- a/livekit-ffi/src/core_modules/data_track/remote.rs +++ b/livekit-ffi/src/core_modules/data_track/remote.rs @@ -22,7 +22,7 @@ use livekit_datatrack::{ backend::{remote, DecryptionProvider}, }; use livekit_protocol as proto; -use prost::Message; +use prost_012::Message; use std::sync::Arc; use tokio::sync::Mutex; use tokio_util::sync::{CancellationToken, DropGuard}; From 9a01d686483187500e8a230d6e502ba87f841f57 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:11:17 -0400 Subject: [PATCH 09/42] chore(livekit-ffi): move the livekit-uniffi packaging assets in Move the files that build and package the UniFFI bindings: the cargo-make tasks, the UniFFI configuration, the Swift, Android and Dart support files, the Python test script and the agent notes. The content of these files does not change. Later commits change them. Move the ignore rules for the `generated/` and `packages/` directories into the livekit-ffi ignore file. Both directories hold build output. Do not move the Node support files. The next commit deletes them. --- livekit-ffi/.gitignore | 4 +++- {livekit-uniffi => livekit-ffi}/AGENTS.md | 0 {livekit-uniffi => livekit-ffi}/Makefile.toml | 0 {livekit-uniffi => livekit-ffi}/python_test/main.py | 0 .../support/android/.gitignore | 0 .../support/android/README.md | 0 .../support/android/build.gradle.kts | 0 .../support/android/consumer-rules.pro | 0 .../support/android/gradle.properties | 0 .../android/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../support/android/gradlew | 0 .../support/android/gradlew.bat | 0 .../support/android/proguard-rules.pro | 0 .../support/android/settings.gradle.kts | 0 .../support/android/src/main/AndroidManifest.xml | 0 .../android/src/main/jniLibs/arm64-v8a/.gitkeep | 0 .../android/src/main/jniLibs/armeabi-v7a/.gitkeep | 0 .../android/src/main/jniLibs/x86_64/.gitkeep | 0 .../support/dart/README.md | 0 .../support/dart/analysis_options.yaml | 0 .../support/dart/hook/build.dart.tera | 0 .../support/dart/pubspec.yaml.tera | 0 .../support/dart/test/access_token_test.dart | 0 .../support/dart/test/build_version_test.dart | 0 .../support/swift/DEBUGGING.md | 0 .../support/swift/LiveKitUniFFI.podspec.tera | 0 .../support/swift/Package.swift.tera | 0 .../support/swift/Package@swift-6.2.swift.tera | 0 .../support/swift/PrivacyInfo.xcprivacy | 0 .../support/swift/README.md | 0 {livekit-uniffi => livekit-ffi}/uniffi.toml | 0 livekit-uniffi/.gitignore | 2 -- 33 files changed, 3 insertions(+), 3 deletions(-) rename {livekit-uniffi => livekit-ffi}/AGENTS.md (100%) rename {livekit-uniffi => livekit-ffi}/Makefile.toml (100%) rename {livekit-uniffi => livekit-ffi}/python_test/main.py (100%) rename {livekit-uniffi => livekit-ffi}/support/android/.gitignore (100%) rename {livekit-uniffi => livekit-ffi}/support/android/README.md (100%) rename {livekit-uniffi => livekit-ffi}/support/android/build.gradle.kts (100%) rename {livekit-uniffi => livekit-ffi}/support/android/consumer-rules.pro (100%) rename {livekit-uniffi => livekit-ffi}/support/android/gradle.properties (100%) rename {livekit-uniffi => livekit-ffi}/support/android/gradle/wrapper/gradle-wrapper.jar (100%) rename {livekit-uniffi => livekit-ffi}/support/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {livekit-uniffi => livekit-ffi}/support/android/gradlew (100%) rename {livekit-uniffi => livekit-ffi}/support/android/gradlew.bat (100%) rename {livekit-uniffi => livekit-ffi}/support/android/proguard-rules.pro (100%) rename {livekit-uniffi => livekit-ffi}/support/android/settings.gradle.kts (100%) rename {livekit-uniffi => livekit-ffi}/support/android/src/main/AndroidManifest.xml (100%) rename {livekit-uniffi => livekit-ffi}/support/android/src/main/jniLibs/arm64-v8a/.gitkeep (100%) rename {livekit-uniffi => livekit-ffi}/support/android/src/main/jniLibs/armeabi-v7a/.gitkeep (100%) rename {livekit-uniffi => livekit-ffi}/support/android/src/main/jniLibs/x86_64/.gitkeep (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/README.md (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/analysis_options.yaml (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/hook/build.dart.tera (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/pubspec.yaml.tera (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/test/access_token_test.dart (100%) rename {livekit-uniffi => livekit-ffi}/support/dart/test/build_version_test.dart (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/DEBUGGING.md (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/LiveKitUniFFI.podspec.tera (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/Package.swift.tera (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/Package@swift-6.2.swift.tera (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/PrivacyInfo.xcprivacy (100%) rename {livekit-uniffi => livekit-ffi}/support/swift/README.md (100%) rename {livekit-uniffi => livekit-ffi}/uniffi.toml (100%) delete mode 100644 livekit-uniffi/.gitignore diff --git a/livekit-ffi/.gitignore b/livekit-ffi/.gitignore index 7c18ec7e5..dfb0ef9b3 100644 --- a/livekit-ffi/.gitignore +++ b/livekit-ffi/.gitignore @@ -1 +1,3 @@ -WEBRTC_LICENSE.md \ No newline at end of file +WEBRTC_LICENSE.md +generated/ +packages/ diff --git a/livekit-uniffi/AGENTS.md b/livekit-ffi/AGENTS.md similarity index 100% rename from livekit-uniffi/AGENTS.md rename to livekit-ffi/AGENTS.md diff --git a/livekit-uniffi/Makefile.toml b/livekit-ffi/Makefile.toml similarity index 100% rename from livekit-uniffi/Makefile.toml rename to livekit-ffi/Makefile.toml diff --git a/livekit-uniffi/python_test/main.py b/livekit-ffi/python_test/main.py similarity index 100% rename from livekit-uniffi/python_test/main.py rename to livekit-ffi/python_test/main.py diff --git a/livekit-uniffi/support/android/.gitignore b/livekit-ffi/support/android/.gitignore similarity index 100% rename from livekit-uniffi/support/android/.gitignore rename to livekit-ffi/support/android/.gitignore diff --git a/livekit-uniffi/support/android/README.md b/livekit-ffi/support/android/README.md similarity index 100% rename from livekit-uniffi/support/android/README.md rename to livekit-ffi/support/android/README.md diff --git a/livekit-uniffi/support/android/build.gradle.kts b/livekit-ffi/support/android/build.gradle.kts similarity index 100% rename from livekit-uniffi/support/android/build.gradle.kts rename to livekit-ffi/support/android/build.gradle.kts diff --git a/livekit-uniffi/support/android/consumer-rules.pro b/livekit-ffi/support/android/consumer-rules.pro similarity index 100% rename from livekit-uniffi/support/android/consumer-rules.pro rename to livekit-ffi/support/android/consumer-rules.pro diff --git a/livekit-uniffi/support/android/gradle.properties b/livekit-ffi/support/android/gradle.properties similarity index 100% rename from livekit-uniffi/support/android/gradle.properties rename to livekit-ffi/support/android/gradle.properties diff --git a/livekit-uniffi/support/android/gradle/wrapper/gradle-wrapper.jar b/livekit-ffi/support/android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from livekit-uniffi/support/android/gradle/wrapper/gradle-wrapper.jar rename to livekit-ffi/support/android/gradle/wrapper/gradle-wrapper.jar diff --git a/livekit-uniffi/support/android/gradle/wrapper/gradle-wrapper.properties b/livekit-ffi/support/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from livekit-uniffi/support/android/gradle/wrapper/gradle-wrapper.properties rename to livekit-ffi/support/android/gradle/wrapper/gradle-wrapper.properties diff --git a/livekit-uniffi/support/android/gradlew b/livekit-ffi/support/android/gradlew similarity index 100% rename from livekit-uniffi/support/android/gradlew rename to livekit-ffi/support/android/gradlew diff --git a/livekit-uniffi/support/android/gradlew.bat b/livekit-ffi/support/android/gradlew.bat similarity index 100% rename from livekit-uniffi/support/android/gradlew.bat rename to livekit-ffi/support/android/gradlew.bat diff --git a/livekit-uniffi/support/android/proguard-rules.pro b/livekit-ffi/support/android/proguard-rules.pro similarity index 100% rename from livekit-uniffi/support/android/proguard-rules.pro rename to livekit-ffi/support/android/proguard-rules.pro diff --git a/livekit-uniffi/support/android/settings.gradle.kts b/livekit-ffi/support/android/settings.gradle.kts similarity index 100% rename from livekit-uniffi/support/android/settings.gradle.kts rename to livekit-ffi/support/android/settings.gradle.kts diff --git a/livekit-uniffi/support/android/src/main/AndroidManifest.xml b/livekit-ffi/support/android/src/main/AndroidManifest.xml similarity index 100% rename from livekit-uniffi/support/android/src/main/AndroidManifest.xml rename to livekit-ffi/support/android/src/main/AndroidManifest.xml diff --git a/livekit-uniffi/support/android/src/main/jniLibs/arm64-v8a/.gitkeep b/livekit-ffi/support/android/src/main/jniLibs/arm64-v8a/.gitkeep similarity index 100% rename from livekit-uniffi/support/android/src/main/jniLibs/arm64-v8a/.gitkeep rename to livekit-ffi/support/android/src/main/jniLibs/arm64-v8a/.gitkeep diff --git a/livekit-uniffi/support/android/src/main/jniLibs/armeabi-v7a/.gitkeep b/livekit-ffi/support/android/src/main/jniLibs/armeabi-v7a/.gitkeep similarity index 100% rename from livekit-uniffi/support/android/src/main/jniLibs/armeabi-v7a/.gitkeep rename to livekit-ffi/support/android/src/main/jniLibs/armeabi-v7a/.gitkeep diff --git a/livekit-uniffi/support/android/src/main/jniLibs/x86_64/.gitkeep b/livekit-ffi/support/android/src/main/jniLibs/x86_64/.gitkeep similarity index 100% rename from livekit-uniffi/support/android/src/main/jniLibs/x86_64/.gitkeep rename to livekit-ffi/support/android/src/main/jniLibs/x86_64/.gitkeep diff --git a/livekit-uniffi/support/dart/README.md b/livekit-ffi/support/dart/README.md similarity index 100% rename from livekit-uniffi/support/dart/README.md rename to livekit-ffi/support/dart/README.md diff --git a/livekit-uniffi/support/dart/analysis_options.yaml b/livekit-ffi/support/dart/analysis_options.yaml similarity index 100% rename from livekit-uniffi/support/dart/analysis_options.yaml rename to livekit-ffi/support/dart/analysis_options.yaml diff --git a/livekit-uniffi/support/dart/hook/build.dart.tera b/livekit-ffi/support/dart/hook/build.dart.tera similarity index 100% rename from livekit-uniffi/support/dart/hook/build.dart.tera rename to livekit-ffi/support/dart/hook/build.dart.tera diff --git a/livekit-uniffi/support/dart/pubspec.yaml.tera b/livekit-ffi/support/dart/pubspec.yaml.tera similarity index 100% rename from livekit-uniffi/support/dart/pubspec.yaml.tera rename to livekit-ffi/support/dart/pubspec.yaml.tera diff --git a/livekit-uniffi/support/dart/test/access_token_test.dart b/livekit-ffi/support/dart/test/access_token_test.dart similarity index 100% rename from livekit-uniffi/support/dart/test/access_token_test.dart rename to livekit-ffi/support/dart/test/access_token_test.dart diff --git a/livekit-uniffi/support/dart/test/build_version_test.dart b/livekit-ffi/support/dart/test/build_version_test.dart similarity index 100% rename from livekit-uniffi/support/dart/test/build_version_test.dart rename to livekit-ffi/support/dart/test/build_version_test.dart diff --git a/livekit-uniffi/support/swift/DEBUGGING.md b/livekit-ffi/support/swift/DEBUGGING.md similarity index 100% rename from livekit-uniffi/support/swift/DEBUGGING.md rename to livekit-ffi/support/swift/DEBUGGING.md diff --git a/livekit-uniffi/support/swift/LiveKitUniFFI.podspec.tera b/livekit-ffi/support/swift/LiveKitUniFFI.podspec.tera similarity index 100% rename from livekit-uniffi/support/swift/LiveKitUniFFI.podspec.tera rename to livekit-ffi/support/swift/LiveKitUniFFI.podspec.tera diff --git a/livekit-uniffi/support/swift/Package.swift.tera b/livekit-ffi/support/swift/Package.swift.tera similarity index 100% rename from livekit-uniffi/support/swift/Package.swift.tera rename to livekit-ffi/support/swift/Package.swift.tera diff --git a/livekit-uniffi/support/swift/Package@swift-6.2.swift.tera b/livekit-ffi/support/swift/Package@swift-6.2.swift.tera similarity index 100% rename from livekit-uniffi/support/swift/Package@swift-6.2.swift.tera rename to livekit-ffi/support/swift/Package@swift-6.2.swift.tera diff --git a/livekit-uniffi/support/swift/PrivacyInfo.xcprivacy b/livekit-ffi/support/swift/PrivacyInfo.xcprivacy similarity index 100% rename from livekit-uniffi/support/swift/PrivacyInfo.xcprivacy rename to livekit-ffi/support/swift/PrivacyInfo.xcprivacy diff --git a/livekit-uniffi/support/swift/README.md b/livekit-ffi/support/swift/README.md similarity index 100% rename from livekit-uniffi/support/swift/README.md rename to livekit-ffi/support/swift/README.md diff --git a/livekit-uniffi/uniffi.toml b/livekit-ffi/uniffi.toml similarity index 100% rename from livekit-uniffi/uniffi.toml rename to livekit-ffi/uniffi.toml diff --git a/livekit-uniffi/.gitignore b/livekit-uniffi/.gitignore deleted file mode 100644 index 507686d02..000000000 --- a/livekit-uniffi/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -generated/ -packages/ \ No newline at end of file From 15617862c548e44977791988974962db898d7bc4 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:11:32 -0400 Subject: [PATCH 10/42] chore(livekit-ffi): drop the UniFFI node build Delete the Node package of the UniFFI bindings: the support templates, the test project and the cargo-make tasks that build them. The crate livekit-ffi-node-bindings gives the Node bindings. It builds the room-apis surface with napi-rs. Two Node packages from one crate is more than the project needs now. This commit only deletes. Nothing else uses these files. A later change can add the package again. --- livekit-ffi/Makefile.toml | 85 ----- livekit-uniffi/node_test/.gitignore | 1 - livekit-uniffi/node_test/index.ts | 69 ---- livekit-uniffi/node_test/package.json | 19 - livekit-uniffi/node_test/pnpm-lock.yaml | 333 ------------------ .../support/node/download-lib.ts.tera | 127 ------- livekit-uniffi/support/node/package.json.tera | 45 --- livekit-uniffi/support/node/tsconfig.json | 15 - livekit-uniffi/support/node/tsup.config.ts | 15 - 9 files changed, 709 deletions(-) delete mode 100644 livekit-uniffi/node_test/.gitignore delete mode 100644 livekit-uniffi/node_test/index.ts delete mode 100644 livekit-uniffi/node_test/package.json delete mode 100644 livekit-uniffi/node_test/pnpm-lock.yaml delete mode 100644 livekit-uniffi/support/node/download-lib.ts.tera delete mode 100644 livekit-uniffi/support/node/package.json.tera delete mode 100644 livekit-uniffi/support/node/tsconfig.json delete mode 100644 livekit-uniffi/support/node/tsup.config.ts diff --git a/livekit-ffi/Makefile.toml b/livekit-ffi/Makefile.toml index 9e368c3ef..c922e851c 100644 --- a/livekit-ffi/Makefile.toml +++ b/livekit-ffi/Makefile.toml @@ -18,7 +18,6 @@ SUPPORT_DIR = "./support" LIB_NAME = "lib${CARGO_MAKE_CRATE_FS_NAME}" TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" SPM_NAME = "LiveKitUniFFI" -NPM_NAME = "@livekit/uniffi" DART_PACKAGE_NAME = "livekit_uniffi" # Rendered into pubspec.yaml by dart-generate-manifest. Fail closed: every # profile gets `publish_to: none` (dev packages embed a host dylib and must @@ -207,90 +206,6 @@ private = false extend = "uniffi-bindgen" env = { LANG = "python" } -# MARK: - Node - -[tasks.node-bindgen] -private = true -dependencies = ["build", "locate-lib"] -env = { CARGO_NET_GIT_FETCH_WITH_CLI = true } -install_crate = { crate_name = "uniffi-bindgen-node", binary = "uniffi-bindgen-node", test_arg = "--help" } -install_crate_args = ["--git", "https://github.com/livekit/uniffi-bindgen-node.git"] -command = "uniffi-bindgen-node" -args = [ - "--out-dir", "${PACKAGES_DIR}/${LANG}", - "--out-dirname-api", "import-meta-url", - "--out-node-version", "^18", - "--out-verbose-logs", - "--crate-name", "${CARGO_MAKE_CRATE_FS_NAME}", - "${LIB_PATH}" -] - -[tasks.node-format] -private = true -command = "npx" -args = [ - "--yes", - "prettier", - "--write", "${PACKAGES_DIR}/${LANG}" -] - -[tasks.node-move-code-into-src] -private = true -script_runner = "@shell" -script = """ -mkdir -p ${PACKAGES_DIR}/${LANG}/src -mv ${PACKAGES_DIR}/${LANG}/*.ts ${PACKAGES_DIR}/${LANG}/src -""" - -[tasks.node-generate-manifest] -private = true -extend = "tera" -args = [ - "--env", - "-t", "${SUPPORT_DIR}/${LANG}/package.json.tera", - "-o", "${PACKAGES_DIR}/${LANG}/package.json", - "${PACKAGES_DIR}/${LANG}/package.json" - # Provide original package.json as input -] - -[tasks.node-copy-support-files] -private = true -script_runner = "@shell" -script = """ -cp ${SUPPORT_DIR}/${LANG}/tsconfig.json ${PACKAGES_DIR}/${LANG}/tsconfig.json -cp ${SUPPORT_DIR}/${LANG}/tsup.config.ts ${PACKAGES_DIR}/${LANG}/tsup.config.ts -""" - -[tasks.node-generate-downloader] -private = true -condition = { profiles = ["release"] } -extend = "tera" -args = ["--env-only", "-t", "${SUPPORT_DIR}/${LANG}/download-lib.ts.tera", "-o", "${PACKAGES_DIR}/${LANG}/download-lib.ts"] - -[tasks.node-link-lib] -private = true -condition = { profiles = ["development"] } -script_runner = "@shell" -script = """ -ln -sf ${LIB_PATH} ${PACKAGES_DIR}/${LANG}/src -""" - -[tasks.node-package-flow] -private = true -dependencies = [ - "node-bindgen", - "node-move-code-into-src", - "node-generate-manifest", - "node-generate-downloader", - "node-copy-support-files", - "node-format", - "node-link-lib" -] - -[tasks.node-package] -env = { LANG = "node" } -run_task = "node-package-flow" - # MARK: - Swift # # Output: ${PACKAGES_DIR}/swift/${SPM_NAME}/ ready to publish. diff --git a/livekit-uniffi/node_test/.gitignore b/livekit-uniffi/node_test/.gitignore deleted file mode 100644 index 3c3629e64..000000000 --- a/livekit-uniffi/node_test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/livekit-uniffi/node_test/index.ts b/livekit-uniffi/node_test/index.ts deleted file mode 100644 index fa973dd67..000000000 --- a/livekit-uniffi/node_test/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2025 LiveKit, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - buildVersion, - logForwardBootstrap, - type ApiCredentials, - tokenGenerate, - tokenVerify, - logForwardReceive, -} from '@livekit/uniffi'; - -async function main() { - // Receive log messages from Rust - logForwardBootstrap("debug"); - - // Print FFI version - console.log(`FFI version: v${buildVersion()}`); - - const credentials: ApiCredentials = { key: "devkey", secret: "secret" }; - - const jwt = tokenGenerate( - { - identity: "some_participant", - roomConfiguration: { - name: "test", - emptyTimeout: 1000, - departureTimeout: 1000, - maxParticipants: 1000, - metadata: "", - minPlayoutDelay: 1000, - maxPlayoutDelay: 1000, - syncStreams: false, - agents: [], - }, - }, - credentials, - ); - console.log("Generated JWT:", jwt); - - const decodedGrants = tokenVerify(jwt, credentials); - console.log("Verified generated JWT:", decodedGrants); - - while (true) { - const message = await logForwardReceive(); - if (!message) { - console.log('Log forwarding ended'); - break; - } - console.log('Log from Rust:', message); - } -} - -if (require.main === module) { - main(); -} diff --git a/livekit-uniffi/node_test/package.json b/livekit-uniffi/node_test/package.json deleted file mode 100644 index bceb4c37b..000000000 --- a/livekit-uniffi/node_test/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "node_test", - "version": "1.0.0", - "main": "index.ts", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "description": "", - "dependencies": { - "@livekit/uniffi": "link:../packages/node" - }, - "devDependencies": { - "@types/node": "^25.0.1", - "tsx": "^4.21.0", - "typescript": "^5.9.3" - } -} diff --git a/livekit-uniffi/node_test/pnpm-lock.yaml b/livekit-uniffi/node_test/pnpm-lock.yaml deleted file mode 100644 index 7e9f6e1cc..000000000 --- a/livekit-uniffi/node_test/pnpm-lock.yaml +++ /dev/null @@ -1,333 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@livekit/uniffi': - specifier: link:../packages/node - version: link:../packages/node - devDependencies: - '@types/node': - specifier: ^25.0.1 - version: 25.9.3 - tsx: - specifier: ^4.21.0 - version: 4.22.4 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - -packages: - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} - - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - -snapshots: - - '@esbuild/aix-ppc64@0.28.1': - optional: true - - '@esbuild/android-arm64@0.28.1': - optional: true - - '@esbuild/android-arm@0.28.1': - optional: true - - '@esbuild/android-x64@0.28.1': - optional: true - - '@esbuild/darwin-arm64@0.28.1': - optional: true - - '@esbuild/darwin-x64@0.28.1': - optional: true - - '@esbuild/freebsd-arm64@0.28.1': - optional: true - - '@esbuild/freebsd-x64@0.28.1': - optional: true - - '@esbuild/linux-arm64@0.28.1': - optional: true - - '@esbuild/linux-arm@0.28.1': - optional: true - - '@esbuild/linux-ia32@0.28.1': - optional: true - - '@esbuild/linux-loong64@0.28.1': - optional: true - - '@esbuild/linux-mips64el@0.28.1': - optional: true - - '@esbuild/linux-ppc64@0.28.1': - optional: true - - '@esbuild/linux-riscv64@0.28.1': - optional: true - - '@esbuild/linux-s390x@0.28.1': - optional: true - - '@esbuild/linux-x64@0.28.1': - optional: true - - '@esbuild/netbsd-arm64@0.28.1': - optional: true - - '@esbuild/netbsd-x64@0.28.1': - optional: true - - '@esbuild/openbsd-arm64@0.28.1': - optional: true - - '@esbuild/openbsd-x64@0.28.1': - optional: true - - '@esbuild/openharmony-arm64@0.28.1': - optional: true - - '@esbuild/sunos-x64@0.28.1': - optional: true - - '@esbuild/win32-arm64@0.28.1': - optional: true - - '@esbuild/win32-ia32@0.28.1': - optional: true - - '@esbuild/win32-x64@0.28.1': - optional: true - - '@types/node@25.9.3': - dependencies: - undici-types: 7.24.6 - - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - - fsevents@2.3.3: - optional: true - - tsx@4.22.4: - dependencies: - esbuild: 0.28.1 - optionalDependencies: - fsevents: 2.3.3 - - typescript@5.9.3: {} - - undici-types@7.24.6: {} diff --git a/livekit-uniffi/support/node/download-lib.ts.tera b/livekit-uniffi/support/node/download-lib.ts.tera deleted file mode 100644 index e725a7cfb..000000000 --- a/livekit-uniffi/support/node/download-lib.ts.tera +++ /dev/null @@ -1,127 +0,0 @@ -// This file is automatically generated. Do not edit manually. - -import * as fs from 'node:fs/promises'; -import { join, resolve } from 'node:path'; -import { parseArgs, type ParseArgsOptionsConfig } from 'node:util'; -import { Open } from 'unzipper'; -import fetch from 'node-fetch'; - -const PACKAGE_NAME = "{{ CARGO_MAKE_CRATE_FS_NAME }}"; -const PACKAGE_VERSION = "{{ CARGO_MAKE_CRATE_VERSION }}"; -const DOWNLOAD_BASE_URL = "{{ CDYLIB_DOWNLOAD_BASE_URL }}"; -const OUTPUT_DIR_PATH = resolve("dist/"); - -type TargetPlatform = ReturnType; -function targetPlatform() { - // ref: https://nodejs.org/api/process.html#processplatform - switch (process.platform) { - case 'win32': - return 'pc-windows-msvc'; - case 'darwin': - return 'apple-darwin'; - case 'linux': - return 'unknown-linux-gnu'; - default: - throw new Error(`Platform ${process.platform} is unsupported.`); - } -} - -type TargetArch = ReturnType; - -function targetArch() { - // ref: https://nodejs.org/api/process.html#processarch - switch (process.arch) { - case "arm": - throw new Error(`Arch arm is unsupported. Build for arm64 / aarch64 instead!`); - case 'arm64': - return "aarch64"; - case 'x64': - return "x86_64"; - default: - throw new Error(`Arch ${process.arch} is unsupported.`); - } -} - -function generateLibraryName(platform: TargetPlatform) { - switch (platform) { - case "pc-windows-msvc": - return `${PACKAGE_NAME}.dll`; - case "apple-darwin": - return `lib${PACKAGE_NAME}.dylib`; - case "unknown-linux-gnu": - return `lib${PACKAGE_NAME}.so`; - } -} - -function generateLibraryDownloadUrl( - platform: TargetPlatform, - arch: TargetArch, - version: string, -) { - // ie, https://github.com/livekit/rust-sdks/releases/download/v0.1.1/build-aarch64-apple-darwin.zip - const filename = `build-${arch}-${platform}.zip`; - return `${DOWNLOAD_BASE_URL}/v${version}/${filename}`; -} - -async function doesFileExist(path: string) { - try { - await fs.access(path, fs.constants.F_OK); - return true; - } catch { - return false; - } -} - -async function downloadLib( - platform: TargetPlatform = targetPlatform(), - arch: TargetArch = targetArch(), - version = PACKAGE_VERSION, - output = OUTPUT_DIR_PATH, - skipIfExists = false, -) { - const libName = generateLibraryName(platform); - const libPath = join(output, libName); - - if (skipIfExists && await doesFileExist(libPath)) { - console.log("Lib file exists, skipping download."); - return; - } - const downloadUrl = generateLibraryDownloadUrl(platform, arch, version); - - const response = await fetch(downloadUrl); - if (!response.ok) { - throw new Error( - `Error fetching ${downloadUrl}: ${response.status} ${await response.text()}`, - ); - } - if (!response.body) { - throw new Error("Response body was null"); - } - - const zipFile = await Open.buffer(Buffer.from(await response.arrayBuffer())); - await zipFile.extract({ path: output }); - - if (!(await doesFileExist(libPath))) { - throw new Error( - `Lib file ${libPath} not found after downloading ${downloadUrl} and unzipping the results!`, - ); - } -} - -if (require.main === module && process.env.SKIP_DOWNLOAD_LIB !== 'true') { - const options: ParseArgsOptionsConfig = { - platform: { type: 'string', default: targetPlatform() }, - arch: { type: 'string', default: targetArch() }, - version: { type: 'string', default: PACKAGE_VERSION }, - output: { type: 'string', default: OUTPUT_DIR_PATH }, - "skip-if-exists": { type: 'boolean', short: 's', default: false }, - }; - const { values } = parseArgs({ args: process.argv.slice(2), options }); - downloadLib( - values.platform, - values.arch, - values.version, - values.output, - values["skip-if-exists"], - ); -} diff --git a/livekit-uniffi/support/node/package.json.tera b/livekit-uniffi/support/node/package.json.tera deleted file mode 100644 index 79f31dea3..000000000 --- a/livekit-uniffi/support/node/package.json.tera +++ /dev/null @@ -1,45 +0,0 @@ -{%- if CARGO_MAKE_PROFILE == "development" -%} - {%- set postinstall = "" -%} - {%- set tsup_args = " --onSuccess 'cp -P src/" ~ LIB_NAME ~ "." ~ LIB_EXT ~ " dist/'" -%} -{%- elif CARGO_MAKE_PROFILE == "release" -%} - {%- set postinstall = "tsx download-lib.ts --skip" -%} - {%- set tsup_args = "" -%} -{%- endif -%} - -{ - "name": "{{ NPM_NAME }}", - "version": "{{ CARGO_MAKE_CRATE_VERSION }}", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "files": ["dist"], - "scripts": { - "prepublishOnly": "npm run build", - "build": "tsup{{tsup_args}}", - "dev": "tsup --watch{{tsup_args}}", - "ts:check": "tsc --noEmit" - {%- if postinstall -%}, - "postinstall": "{{postinstall}}" - {%- endif -%} - }, - "engines": { - "node": ">= 18" - }, - - {#- Include dependencies specified by the bindgen -#} - "dependencies": {{ dependencies | json_encode | safe }}, - "devDependencies": { - {% for package_name, package_version in devDependencies %} - "{{package_name | addslashes}}": "{{package_version | addslashes}}", - {%- endfor %} - "tsup": "^8.4.0", - "typescript": "5.9.3", - "node-fetch": "3.3.2", - "unzipper": "0.10.11", - "@types/unzipper": "0.10.11", - "tsx": "4.21.0" - } -} diff --git a/livekit-uniffi/support/node/tsconfig.json b/livekit-uniffi/support/node/tsconfig.json deleted file mode 100644 index 04172bd5f..000000000 --- a/livekit-uniffi/support/node/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, - "module": "ES2020" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - "outDir": "dist", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "strict": true /* Enable all strict type-checking options. */, - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, - "moduleResolution": "node" - } -} diff --git a/livekit-uniffi/support/node/tsup.config.ts b/livekit-uniffi/support/node/tsup.config.ts deleted file mode 100644 index 8de8bc46c..000000000 --- a/livekit-uniffi/support/node/tsup.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig({ - entry: ['src/index.ts'], - format: ['cjs', 'esm'], - target: 'node18', - dts: true, - clean: true, - - // Inline ubrn into the built bundle since it doesn't publish a commonjs compatible build - noExternal: ["uniffi-bindgen-react-native"], - - // ref: https://stackoverflow.com/a/75868407 - shims: true, -}); From 991b2b50c9fca839b6acb0f1ad30769a42a6d001 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:11:49 -0400 Subject: [PATCH 11/42] chore: remove the livekit-uniffi crate All the code and all the packaging of livekit-uniffi are now in livekit-ffi. Delete what is left: the manifest, the changelog and the readme. Remove the crate from the workspace members and regenerate the lock file. Give the changeset scopes of livekit-uniffi to livekit-ffi, so that a changeset with an old scope still finds a package. IMPORTANT: remove the workspace member and the knope package in one commit. The script .github/scripts/changeset_detect.py compares the two lists. A knope package with no crate, or a crate with no knope package, stops the build. The changelog of livekit-uniffi is in the git history. The changelog of livekit-ffi continues from its own version. --- Cargo.lock | 61 +++++---- Cargo.toml | 3 +- knope.toml | 17 +-- livekit-uniffi/CHANGELOG.md | 245 ------------------------------------ livekit-uniffi/Cargo.toml | 73 ----------- livekit-uniffi/README.md | 46 ------- 6 files changed, 37 insertions(+), 408 deletions(-) delete mode 100644 livekit-uniffi/CHANGELOG.md delete mode 100644 livekit-uniffi/Cargo.toml delete mode 100644 livekit-uniffi/README.md diff --git a/Cargo.lock b/Cargo.lock index f06645337..72af7c10e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,7 +635,9 @@ dependencies = [ name = "bindgens" version = "0.1.0" dependencies = [ + "camino", "uniffi", + "uniffi-dart", ] [[package]] @@ -3897,6 +3899,7 @@ dependencies = [ name = "livekit-ffi" version = "0.12.83" dependencies = [ + "async-trait", "bytes", "console-subscriber", "dashmap", @@ -3911,16 +3914,23 @@ dependencies = [ "link-cplusplus", "livekit", "livekit-capture", + "livekit-common", + "livekit-data-stream", + "livekit-datatrack", + "livekit-net", "livekit-protocol", "livekit-token", "log", + "once_cell", "parking_lot", + "prost 0.12.6", "prost 0.14.4", "prost-build", "serial_test", "soxr-sys", "thiserror 2.0.19", "tokio", + "tokio-util", "uniffi", "webrtc-sys", "webrtc-sys-build", @@ -4030,30 +4040,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "livekit-uniffi" -version = "0.3.0" -dependencies = [ - "async-trait", - "bytes", - "camino", - "futures-util", - "livekit-common", - "livekit-data-stream", - "livekit-datatrack", - "livekit-net", - "livekit-protocol", - "livekit-token", - "log", - "once_cell", - "prost 0.14.4", - "thiserror 2.0.19", - "tokio", - "tokio-util", - "uniffi", - "uniffi-dart", -] - [[package]] name = "livekit-wakeword" version = "0.1.3" @@ -5701,6 +5687,16 @@ dependencies = [ "prost-derive 0.11.9", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + [[package]] name = "prost" version = "0.14.4" @@ -5743,6 +5739,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "prost-derive" version = "0.14.4" @@ -6493,7 +6502,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7217,7 +7226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index fbf37434c..c3fad36ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ "livekit-data-stream", "livekit-rpc", "livekit-ffi", - "livekit-uniffi", "livekit-datatrack", "livekit-token", "livekit-token-source", @@ -105,7 +104,7 @@ tokio-stream = "0.1" # build here, because uniffi-dart requires 0.31.2 or later. mozilla/uniffi-rs#2935 corrects both # defects, but no release (as of mid august 2026) contains that change. # -# For this reason, livekit-uniffi sets `omit_checksums` for Kotlin. See livekit-uniffi/uniffi.toml. +# For this reason, livekit-ffi sets `omit_checksums` for Kotlin. See livekit-ffi/uniffi.toml. uniffi = "0.31" # For examples diff --git a/knope.toml b/knope.toml index bf337b338..c9833ce31 100644 --- a/knope.toml +++ b/knope.toml @@ -145,22 +145,7 @@ versioned_files = [ changelog = "livekit-ffi/CHANGELOG.md" # Assets makes knope create a draft release; CI uploads the actual binaries and publishes. assets = "marker" -scopes = ["ffi", "livekit-ffi"] - -[packages.livekit-uniffi] -# Not in workspace.dependencies (not consumed by other crates here), so no -# root Cargo.toml entry is needed. -versioned_files = [ - "livekit-uniffi/Cargo.toml", - "Cargo.lock", -] -changelog = "livekit-uniffi/CHANGELOG.md" -# No `assets` marker: knope publishes the release + tag directly, and -# uniffi-packages.yml reacts to the published release to build/publish the Swift -# and Android wrapper packages. The Dart/Flutter cdylibs are attached to the -# already-published release by its cdylib job (gh release upload --clobber), so -# the marker stays off; a draft-based flow stranded releases before (#1256). -scopes = ["uniffi", "livekit-uniffi"] +scopes = ["ffi", "livekit-ffi", "uniffi", "livekit-uniffi"] [packages.livekit-wakeword] versioned_files = [ diff --git a/livekit-uniffi/CHANGELOG.md b/livekit-uniffi/CHANGELOG.md deleted file mode 100644 index 70cc6dbe1..000000000 --- a/livekit-uniffi/CHANGELOG.md +++ /dev/null @@ -1,245 +0,0 @@ -## 0.3.0 (2026-10-12) - -### Breaking Changes - -#### `VideoGrants` gains the `agent` grant and `Claims` the `kind` claim (with - -`AccessToken::with_kind`), which the Go, Python and JS SDKs already carry. An -agent worker's token is `VideoGrants { agent: true }` and a simulated job's -participant token is `kind: "agent"`; neither could be minted from Rust before. -`livekit-uniffi` exposes both: `TokenOptions.kind`, `Claims.kind`, and `agent` -on its `VideoGrants` record. - -**Breaking:** the four grants the server infers when absent -- `can_publish`, -`can_subscribe`, `can_publish_data`, `can_update_own_metadata` -- are now -`Option`, as in the Go and JS SDKs. `None` leaves the decision to the -server, and the new getters (`can_publish()`, `can_subscribe()`, -`can_publish_data()`, `can_update_own_metadata()`) read a token the way the -server does, `can_publish_data` falling back to `can_publish` included. Code -that set these fields writes `Some(..)`; code that read them uses the getters. -The same fields are optional on the `livekit-uniffi` record, and -`livekit-api` re-exports the crate as `livekit_api::access_token`, so both -carry the change. - -Nothing at its default is written into the token any more: unset claims and -grants are omitted, as the server's own `omitempty` grants are. Verification -of existing tokens is unchanged. - -### Fixes - -- In-flight incoming data streams now error with `AbnormalEnd` when the room closes. Before, the stream readers ended cleanly, so `read_all()` returned truncated content as if it were complete. - -## 0.2.2 (2026-10-07) - -### Fixes - -- Increase the local and remote data track event buffers so a burst of track lifecycle events cannot fill the channel and deadlock room disconnect. - -## 0.2.1 (2026-09-24) - -### Fixes - -- Format the generated Swift bindings with swiftformat when building the Swift package. -- Upgrade to prost 0.14 across the whole project - #1447 (@1egoman) - -## 0.2.0 (2026-09-22) - -### Breaking Changes - -#### `VideoGrants` gains the `agent` grant and `Claims` the `kind` claim (with - -`AccessToken::with_kind`), which the Go, Python and JS SDKs already carry. An -agent worker's token is `VideoGrants { agent: true }` and a simulated job's -participant token is `kind: "agent"`; neither could be minted from Rust before. -`livekit-uniffi` exposes both: `TokenOptions.kind`, `Claims.kind`, and `agent` -on its `VideoGrants` record. - -**Breaking:** the four grants the server infers when absent -- `can_publish`, -`can_subscribe`, `can_publish_data`, `can_update_own_metadata` -- are now -`Option`, as in the Go and JS SDKs. `None` leaves the decision to the -server, and the new getters (`can_publish()`, `can_subscribe()`, -`can_publish_data()`, `can_update_own_metadata()`) read a token the way the -server does, `can_publish_data` falling back to `can_publish` included. Code -that set these fields writes `Some(..)`; code that read them uses the getters. -The same fields are optional on the `livekit-uniffi` record, and -`livekit-api` re-exports the crate as `livekit_api::access_token`, so both -carry the change. - -Nothing at its default is written into the token any more: unset claims and -grants are omitted, as the server's own `omitempty` grants are. Verification -of existing tokens is unchanged. - -### Features - -#### `EncryptionError::Failed` and `DecryptionError::Failed` carry a `reason` string and are no longer `flat_error`, - -so a foreign `EncryptionProvider` or `DecryptionProvider` returning an error no longer aborts the process with -"Can't lift flat errors" -- a failed data track decrypt (no E2EE manager, key mismatch, corrupt frame) now -drops the frame and leaves the room connected. - -### Fixes - -- Cleanup unused dependencies -- Fix reliable data channel replay: keep the full retry buffer across resumes, drop duplicate reliable packets, and ignore replayed chunks on uncompressed streams instead of failing with `MissedChunk`. - -#### Report the reconnect reason to the server when resuming. - -Resumes previously sent no reason, so server-side telemetry could not attribute why Rust -clients reconnect — every resume looked like `RR_UNKNOWN`. The engine now records what caused -the episode (signal disconnected, publisher failed, subscriber failed) and reports it on each -resume attempt. The v0 signalling path was also missing the `reconnect_reason` query parameter -entirely, so it would not have been reported even if a reason had been supplied. - -## 0.1.12 (2026-09-10) - -### Fixes - -#### Allow code_assets 2.x in the Dart package and stop re-running its build hook - -The `livekit_uniffi` Dart package capped `code_assets` below 2.0.0, which has since -shipped. Its only breaking change (equality on `OS` and `Architecture`) does not -affect the hook, and the cap would make the package unresolvable next to any -dependency that already requires 2.x. The constraint now allows it, and the hook -was verified against `code_assets 2.0.0` / `hooks 2.2.0`. - -The hook also registered the downloaded library as a dependency. Dependencies are -inputs, so the hooks runner saw a file modified during the build and re-ran the -hook, and the download, once on every fresh build. The registration is removed. - -The hook also wrote every target's library to the same shared path. A universal -macOS build runs the hook once per architecture and then merges the results with -`lipo`, which failed because the second download had overwritten the first. Each -target now gets its own subdirectory. - -## 0.1.11 (2026-09-09) - -### Fixes - -- Re-baseline the iOS and Android binary size budgets, which had fallen behind the shipping binaries and blocked the 0.1.10 release. - -#### Moves the RPC implementation into a new `livekit-rpc` crate, alongside the existing - -`livekit-data-stream` and `livekit-datatrack` crates. - -**Breaking:** the `livekit::rpc` module is gone. The RPC types it held are unchanged and -still re-exported from `livekit::participant` and the prelude, so most code needs no edit; -code that spelled the module out (`use livekit::rpc::RpcError;`) should import from -`livekit::participant` or the prelude instead. `RpcClientManager`, `RpcServerManager` and -`HandleRequestOptions` remain reachable under `livekit::participant` but are now -`#[doc(hidden)]`: they are internal SDK API and were never usable without the (private) -transport trait. - -Within the `livekit` crate itself, RPC types are now imported from `livekit-rpc` directly -rather than through those re-exports. - -The new crate does not depend on `libwebrtc`, so its unit tests run without building WebRTC. -The transport seam that made this possible was already in place; the only change to it is -that `RpcTransport::publish_data` now returns a message-only `RpcTransportError` instead of -`livekit::RoomError`, mirroring `livekit_data_stream::api::SendError`. - -Also fixes four latent bugs found while moving the code: - -- An RPC call to a participant who disconnects mid-call now fails promptly with - `RecipientDisconnected`. Pending calls were never purged on disconnect, so the caller - waited out its full response timeout (15s by default) and got `ResponseTimeout` instead. -- A server reporting a version that is not valid semver no longer panics the calling task. - An unparseable version is no longer treated as evidence that the server is too old. -- A v1 `RpcResponse` carrying a compressed payload, or no value at all, now fails with an - `ApplicationError` instead of resolving the caller with an empty successful response. -- Removed an unguarded `unwrap` when building a v1 response packet, by giving the function - a signature that cannot represent the invalid state. - -Also drops the `semver` dependency from `livekit`, which was only used by the RPC client. - -## 0.1.10 (2026-09-08) - -### Features - -- Removes livekit-runtime and converts this package to be tokio only again - #1375 (@1egoman) - -### Fixes - -- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman) -- Lower the Android UniFFI AAR minSdk from 24 to 21 -- Add the `PASSTHROUGH` encoding preset and remove the unused `UpdateEgressRequest` from the generated protocol -- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` / `has_ws_client` UniFFI exports so foreign hosts can exercise the transport seam end-to-end. -- Attach Dart/Flutter cdylib assets to releases and prepare livekit_uniffi for pub.dev publishing -- Update Android's JNA dependency version to 5.19.1 to support 16KB page sizes - -## 0.1.9 (2026-08-25) - -### Fixes - -- differentiate signal connection errors correctly from timeouts - #1234 (@lukasIO) -- Expose `RemoteDataTrack.set_pipeline_options` and `RemoteDataTrackPipelineOptions` (`max_partial_frames`) over UniFFI, matching the JS and Rust SDKs -- Register the `Bytes` UniFFI custom type once in `livekit-common` and borrow it from each component with `uniffi::use_remote_type!`, so the converter is emitted once and the post-generation Swift workaround is no longer needed - -#### Moves access-token generation and verification into a new `livekit-token` crate. - -`livekit_api::access_token::*` continues to resolve to the same types via a -re-export, so no consumer changes are needed. - -Also fixes the `services-tokio` and `services-async` features, which used the -access-token types without declaring the `access-token` feature. Building with -`--no-default-features --features services-tokio` previously failed to compile. - -## 0.1.8 (2026-08-03) - -### Fixes - -- Add a unified `EgressClient::start_egress` that calls the v2 `Egress.StartEgress` RPC with a `StartEgressRequest`, alongside the existing per-type helpers. -- Fix the Android AAR build. Two cargo-make bugs kept `cargo make --profile release android-package` from ever producing a release artifact: the per-arch tasks' `env = { TARGET = ... }` replaced (rather than merged) the parent env map, dropping the `--release` flag, and the `TARGET` they set leaked into the Kotlin bindgen's host build, which then cross-compiled with the host linker. Also raise the Swift and Android size budgets to match the binaries as they stand since the data-track UniFFI surface landed, and check out the released tag rather than the dispatch ref when building the wrapper packages. - -## 0.1.7 (2026-07-29) - -### Fixes - -- Caching of tokio backend reqwest http client - #1285 (@MaxHeimbrock) -- Add data streams v2 - #1192 (@1egoman) - -## 0.1.6 (2026-07-27) - -### Fixes - -- Data tracks schema metadata support. -- Gate the uniffi `cli` feature (clap + bindgen backends) behind an opt-in feature so it is no longer compiled into shipped library builds, shrinking the static archive by ~43 MiB. The dynamic library is byte-unchanged. - #1275 (@jhugman) - -#### Route LiveKit signalling through a pluggable transport (new `livekit-net` crate). - -The signalling WebSocket and the two pre-connect HTTP GETs (validate, region discovery) now go through pluggable transport traits (`WsClient` for the WebSocket, `HttpClient` for request/response) resolved from a process-global registry with independent slots — a consumer can bring only HTTP, or only WebSocket. The new `livekit-net` crate owns the WebSocket/HTTP/TLS stack behind those traits and ships native (tokio / async-std) backends. Native builds are unchanged in behavior. - -**Breaking (`livekit-api`, and `livekit` via `EngineError::Signal`):** - -- `SignalError::WsError` is removed — `tungstenite` is no longer part of the public API. A failed WebSocket handshake now surfaces its HTTP status as `SignalError::Client`/`Server`; transport connection and close failures surface as the new `SignalError::Connection(String)` / `SignalError::Closed` variants (previously all collapsed into `Timeout`). -- `SignalError` is now `#[non_exhaustive]`, and gains a `SignalError::TransportNotConfigured` variant — returned when no transport is registered (host/foreign builds must call `livekit_net::set_ws_client` / `set_http_client` before connecting). This is a permanent configuration error; callers must not retry. -- The signalling WebSocket/HTTP/TLS crates are no longer transitive dependencies of `livekit-api`; TLS features delegate to `livekit-net`. Existing `signal-client-tokio` / `-async` / `-dispatcher` and TLS feature names are unchanged. - -## 0.1.5 (2026-07-14) - -### Features - -- Expose data tracks core functionality - -## 0.1.4 (2026-07-09) - -### Features - -- Add a Dart bindings target. Bumps the crate's UniFFI dependency from 0.30 to 0.31 to match the bindgen. - -## 0.1.3 (2026-06-24) - -### Fixes - -- harden reconnect behaviour - #1148 (@lukasIO) - -## 0.1.2 (2026-06-23) - -### Fixes - -- Upgrade protocol to v1.48.0 - -## 0.1.1 (2026-05-29) - -### Fixes - -- bump protocol to v1.46.4 - #1121 (@lukasIO) diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml deleted file mode 100644 index d8d946070..000000000 --- a/livekit-uniffi/Cargo.toml +++ /dev/null @@ -1,73 +0,0 @@ -[lib] -crate-type = ["cdylib", "staticlib", "lib"] - -[package] -name = "livekit-uniffi" -version = "0.3.0" -edition.workspace = true -license.workspace = true -description = "Experimental FFI interface using UniFFI" -repository.workspace = true -readme = "README.md" -publish = false - -[dependencies] -livekit-protocol = { workspace = true } -livekit-common = { workspace = true, features = ["uniffi"] } -livekit-token = { workspace = true } -livekit-datatrack = { workspace = true, features = ["uniffi"] } -livekit-net = { workspace = true, features = ["uniffi"] } -livekit-data-stream = { workspace = true } -uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] } -log = { workspace = true } -tokio = { workspace = true, features = ["sync", "rt-multi-thread"] } -tokio-util = "0.7.18" -prost = { workspace = true } -futures-util = { workspace = true, default-features = false, features = ["sink"] } -bytes = { workspace = true } -once_cell = "1.21.3" -thiserror = { workspace = true } -async-trait = "0.1" -# Dart binding generator. Not published to crates.io, so pinned by git rev. The -# rev must target the same uniffi-rs release (0.31) as the `uniffi` dependency -# above, or it cannot read this crate's compiled metadata. -# -# Pinned to upstream main rather than a release: the newest release -# (v0.2.1+v0.31.2) predates the three fixes this crate needs -- custom types -# leaking into FFI signatures (#154), object converter statics colliding with -# Rust methods named `write` (#156), and each crate re-declaring the runtime -# scaffolding, which kept livekit-datatrack's types from crossing into -# livekit-uniffi (#150). Move to a version requirement once a release carries -# them. -uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart", rev = "67547701d849c9ef6302f79bb2189bb6075f69fa", optional = true } -camino = { version = "1", optional = true } - -[features] -# Enables the `uniffi-bindgen` bin (uniffi's CLI: clap + the bindings backends). -# Tooling-only — kept off library builds so the shipped cdylib/staticlib don't -# carry the bindgen machinery. Enable it when running the bin: -# `cargo run --features cli --bin uniffi-bindgen generate ...`. -cli = ["uniffi/cli"] -# Enables the `uniffi-bindgen-dart` bin. Not enabled for library builds. -dart-bindgen = ["dep:uniffi-dart", "dep:camino"] - -[build-dependencies] -uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] } - -[[bin]] -name = "uniffi-bindgen" -path = "bindgen.rs" -required-features = ["cli"] - -[[bin]] -name = "uniffi-bindgen-dart" -path = "bindgen-dart.rs" -required-features = ["dart-bindgen"] - -[profile.release] -opt-level = "z" -lto = true -codegen-units = 1 -panic = "abort" -strip = "symbols" -debug = false diff --git a/livekit-uniffi/README.md b/livekit-uniffi/README.md deleted file mode 100644 index 361a0ab15..000000000 --- a/livekit-uniffi/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# LiveKit UniFFI - -Experimental FFI interface using [UniFFI](https://mozilla.github.io/uniffi-rs/latest/). - -At this stage in development, this interface will not attempt to replace the existing FFI interface defined in [_livekit-ffi_](../livekit-ffi/). Instead, it will focus on exposing core business logic that can be cleanly modularized and adopted by client SDKs incrementally. - -## Functionality exposed - -- [x] Logging -- [x] Access token generation and verification - -## Tasks - -Binding generation and multi-platform builds are handled by [_cargo-make_](https://github.com/sagiegurari/cargo-make)—please install on your system before proceeding. For a full list of available tasks, see [_Makefile.toml_](./Makefile.toml) or run `cargo make --list-all-steps`. The most important tasks are summarized below: - -### Swift - -Generate Swift bindings and build a multi-platform XCFramework: -``` -cargo make swift-package -``` - -For a fast, debuggable variant (macOS-only, unstripped), run `cargo make swift-package-debug` — see [support/swift/DEBUGGING.md](./support/swift/DEBUGGING.md). - -See [support/swift/README.md](./support/swift/README.md) for debugging vs. release modes, consumer integration, and prerequisites (Xcode, Rust Apple targets). - -### Node - -Generate Node bindings: -``` -cargo make node-package -``` - -To test them out, run `cd node_test && npx tsx index.ts` - -### Android - -Build native libraries, Kotlin bindings, and a release AAR: - -``` -cargo make android-package # debug .so in release AAR -cargo make --profile release android-package # release .so (CI / publishing) -cargo make android-package-local # + publish to Maven Local -``` - -See [support/android/README.md](./support/android/README.md) for prerequisites (Android SDK/NDK). From 9590c13d12447a5b4bc7e79ce193135e797ab955 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:12:19 -0400 Subject: [PATCH 12/42] feat(ci): declare which feature set each platform builds Add a table to livekit-ffi/Cargo.toml that gives the cargo flags for each downstream package build, and a script that reads it. There is now one place that shows what Python, Node, Unity, Swift, Android and Dart each get. The script is the single source of truth for CI and for the cargo-make tasks. A platform thus cannot move to the wrong surface without a change to the table. The script also refuses a core-modules entry that does not turn the default features off, because the default features select room-apis and the two are mutually exclusive. The option `--surface` answers for the build sites that build no artifact for a platform: the per-commit gates and the shared host build that the bindings generator reads. It answers only when every platform on that surface agrees. This commit is larger than the others. It is one new file and the table that it reads, which are of little use apart. --- .github/scripts/platform_features.py | 163 +++++++++++++++++++++++++++ livekit-ffi/Cargo.toml | 32 ++++++ 2 files changed, 195 insertions(+) create mode 100755 .github/scripts/platform_features.py diff --git a/.github/scripts/platform_features.py b/.github/scripts/platform_features.py new file mode 100755 index 000000000..de8b36fdb --- /dev/null +++ b/.github/scripts/platform_features.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# Copyright 2025 LiveKit, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Resolve cargo build flags for a downstream platform build of livekit-ffi. + +`livekit-ffi` exposes two mutually exclusive FFI surfaces, `room-apis` and +`core-modules`. Which one each downstream package build gets is declared once, +in `[package.metadata.platform-features]` in livekit-ffi/Cargo.toml, and read +back here so a platform cannot silently drift onto the wrong surface. + + $ platform_features.py swift + --no-default-features --features core-modules + + $ platform_features.py --surface core-modules + --no-default-features --features core-modules + +`--surface` is for build sites that are not platform builds and so have no +platform to name: the per-commit compile/test gates in builds.yml and +tests.yml, and the shared host cdylib in livekit-ffi/Makefile.toml that the +bindgens read UniFFI metadata from. Anything that builds an artifact for a +platform names that platform instead. + + $ platform_features.py --list + android --no-default-features --features core-modules + ... + +Used by the uniffi-* workflows and by livekit-ffi/Makefile.toml. +""" + +import argparse +import json +import subprocess +import sys + +PACKAGE = "livekit-ffi" +METADATA_KEY = "platform-features" + + +def load_table(): + out = subprocess.run( + ["cargo", "metadata", "--no-deps", "--format-version", "1"], + capture_output=True, + text=True, + check=True, + ).stdout + meta = json.loads(out) + for pkg in meta["packages"]: + if pkg["name"] == PACKAGE: + table = (pkg.get("metadata") or {}).get(METADATA_KEY) + if not table: + sys.exit( + f"{PACKAGE} declares no [package.metadata.{METADATA_KEY}] table" + ) + return table + sys.exit(f"{PACKAGE} not found in cargo metadata") + + +def flags_for(entry, platform): + features = entry.get("features") or [] + if not features: + sys.exit(f"platform '{platform}' declares no features") + + surfaces = {f for f in features if f in ("room-apis", "core-modules")} + if len(surfaces) != 1: + sys.exit( + f"platform '{platform}' must select exactly one of room-apis / " + f"core-modules (got: {sorted(surfaces) or 'none'})" + ) + # core-modules can only be reached by turning the default room-apis off. + if surfaces == {"core-modules"} and not entry.get("no-default-features"): + sys.exit( + f"platform '{platform}' selects core-modules but does not set " + f"no-default-features; the default feature set enables room-apis, " + f"and the two are mutually exclusive" + ) + + flags = [] + if entry.get("no-default-features"): + flags.append("--no-default-features") + flags += ["--features", ",".join(features)] + return " ".join(flags) + + +def flags_for_surface(table, surface): + """Flags shared by every platform on `surface`. + + For callers with no platform to name -- the per-commit gates in builds.yml + and tests.yml, and the shared host build in livekit-ffi/Makefile.toml that + serves the swift, android and dart flows at once. A caller that builds an + artifact for one platform should name that platform instead. + + Every platform on the surface must agree, otherwise there is no single + answer and the caller has to name a platform. That holds for core-modules + and deliberately does not for room-apis, where the TLS backend varies by + target. + """ + resolved = { + name: flags_for(entry, name) + for name, entry in table.items() + if surface in (entry.get("features") or []) + } + if not resolved: + sys.exit(f"no platform declares the '{surface}' surface") + distinct = set(resolved.values()) + if len(distinct) != 1: + detail = "\n".join(f" {n}: {f}" for n, f in sorted(resolved.items())) + sys.exit( + f"platforms on the '{surface}' surface do not share one feature " + f"set, so it cannot be resolved by surface:\n{detail}" + ) + return distinct.pop() + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("platform", nargs="?", help="platform key to resolve") + ap.add_argument( + "--surface", + choices=["room-apis", "core-modules"], + help="flags shared by every platform on this surface; for build " + "sites that are not platform builds (see the module docstring)", + ) + ap.add_argument( + "--list", action="store_true", help="print every platform and its flags" + ) + args = ap.parse_args() + + table = load_table() + + if args.surface: + print(flags_for_surface(table, args.surface)) + return + + if args.list: + width = max(len(k) for k in table) + for name in sorted(table): + print(f"{name:<{width}} {flags_for(table[name], name)}") + return + + if not args.platform: + ap.error("a platform key is required (or --surface / --list)") + + if args.platform not in table: + sys.exit( + f"unknown platform '{args.platform}'. Known: {', '.join(sorted(table))}" + ) + print(flags_for(table[args.platform], args.platform)) + + +if __name__ == "__main__": + main() diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 303b86d27..704dbd47b 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -138,5 +138,37 @@ webrtc-sys-build = { workspace = true, optional = true } livekit-token = { workspace = true } serial_test = "3.0" +# Which FFI mode each downstream package build is configured to use: room-apis or core-modules. +# +# This is the single source of truth: CI and Makefile.toml resolve their build +# flags from here via .github/scripts/platform_features.py, so a platform +# cannot silently drift onto the wrong surface. `room-apis` and `core-modules` +# are mutually exclusive, so every core-modules entry must turn the default +# feature set off. +# +# $ .github/scripts/platform_features.py --list +# +# A build site that produces an artifact for a platform names that platform. +# The script also answers `--surface ` for the few sites that are not +# platform builds at all -- the per-commit gates in builds.yml and tests.yml, +# and the shared host build in Makefile.toml that the bindgens read metadata +# from. +# +[package.metadata.platform-features] +# room-apis -- SDKs with no WebRTC stack of their own. These consume the C ABI +# (and, increasingly, UniFFI) out of the livekit-ffi release archives. +python = { features = ["room-apis", "rustls-tls-native-roots"] } +node = { features = ["room-apis", "rustls-tls-native-roots"] } +unity = { features = ["room-apis", "rustls-tls-native-roots"] } +ffi-ios = { no-default-features = true, features = ["room-apis", "rustls-tls-webpki-roots"] } +ffi-android = { no-default-features = true, features = ["room-apis", "rustls-tls-webpki-roots"] } +ffi-windows-arm64 = { no-default-features = true, features = ["room-apis", "native-tls"] } + +# core-modules -- SDKs that already include their own WebRTC stack and consume +# this crate purely over UniFFI. +swift = { no-default-features = true, features = ["core-modules"] } +android = { no-default-features = true, features = ["core-modules"] } +dart = { no-default-features = true, features = ["core-modules"] } + [lib] crate-type = ["lib", "staticlib", "cdylib"] From 38c71fed61a4f978dae2956fd6bdcfa60078df6f Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:12:39 -0400 Subject: [PATCH 13/42] IMPORTANT(livekit-ffi): build core-modules from the cargo-make packaging Every cargo command in Makefile.toml uses the working directory and gives no `-p` flag. After the merge, each one thus builds the default surface. IMPORTANT: without this change the Swift, Android and Dart packages all build livekit-ffi/room-apis and pull in libwebrtc. Take the flags from the platform table instead of writing them here, so that the packaging and the table cannot disagree. The duckscript `exec` command does not divide an expanded variable into separate arguments, thus those two tasks go through `sh -c`. Run the two bindings generators from the tools/bindgens crate. A `cargo run` in this crate turns on its default features. Point the download address for the prebuilt libraries at the livekit-ffi release tag. --- livekit-ffi/Makefile.toml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/livekit-ffi/Makefile.toml b/livekit-ffi/Makefile.toml index c922e851c..c4993f10a 100644 --- a/livekit-ffi/Makefile.toml +++ b/livekit-ffi/Makefile.toml @@ -28,7 +28,14 @@ PUBSPEC_PUBLISH_TO = "none" # Per-release directory holding the cdylib archives published by # .github/workflows/uniffi-cdylib.yml. Downloaders append # `/v/build-.zip` (and a `.sha256` sidecar). -CDYLIB_DOWNLOAD_BASE_URL = "https://github.com/livekit/rust-sdks/releases/download/livekit-uniffi" +CDYLIB_DOWNLOAD_BASE_URL = "https://github.com/livekit/rust-sdks/releases/download/livekit-ffi" + +# Cargo flags selecting livekit-ffi/core-modules, resolved from +# [package.metadata.platform-features] in Cargo.toml so the packaging here and +# the CI workflows cannot drift apart. Every cargo invocation below relies on +# the working directory rather than -p, so each one needs these: without them +# it would build the default livekit-ffi/room-apis and all of libwebrtc. +LK_CORE_FEATURE_ARGS = { script = ["python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py --surface core-modules"] } [env.release] PUBSPEC_PUBLISH_TO = "" @@ -52,11 +59,11 @@ echo "TARGET: ${TARGET}" if not ${TIER_3} # Ensure target is installed exec rustup target add ${TARGET} - exec cargo build --release --target ${TARGET} + exec sh -c "cargo build --release ${LK_CORE_FEATURE_ARGS} --target ${TARGET}" else # Ensure nightly toolchain is installed exec rustup component add rust-src --toolchain "nightly-${CARGO_MAKE_RUST_TARGET_TRIPLE}" - exec cargo +nightly build -Zbuild-std=std,panic_abort --release --target ${TARGET} + exec sh -c "cargo +nightly build -Zbuild-std=std,panic_abort --release ${LK_CORE_FEATURE_ARGS} --target ${TARGET}" end """ @@ -86,7 +93,7 @@ if [ "${CARGO_MAKE_PROFILE}" = "release" ]; then export RUSTFLAGS="-C force-unwind-tables=no" fi -cargo ndk --target "${TARGET}" build ${release_flag} +cargo ndk --target "${TARGET}" build ${LK_CORE_FEATURE_ARGS} ${release_flag} """ # MARK: - Build, specific platforms @@ -167,9 +174,7 @@ dependencies = ["build", "locate-lib"] script_runner = "@duckscript" script = """ out_dir = join_path ${PACKAGES_DIR} ${LANG} -# `--features cli` builds the bindgen bin (gated off library builds so the -# shipped cdylib/staticlib don't carry uniffi's CLI machinery). -exec cargo run --features cli --bin uniffi-bindgen generate \ +exec cargo run -p bindgens --bin uniffi-bindgen -- generate \ --language ${LANG} \ --out-dir ${out_dir} \ --library ${LIB_PATH} @@ -269,6 +274,7 @@ done cargo swift package \ --accept-all \ +${LK_CORE_FEATURE_ARGS} \ --name ${SPM_NAME} \ $platform_flags \ --lib-type dynamic \ @@ -619,7 +625,7 @@ dependencies = ["build", "locate-lib"] script_runner = "@duckscript" script = """ out_dir = join_path ${PACKAGES_DIR} ${LANG} -exec cargo run --features dart-bindgen --bin uniffi-bindgen-dart -- \ +exec cargo run -p bindgens --features dart-bindgen --bin uniffi-bindgen-dart -- \ --library ${LIB_PATH} \ --out-dir ${out_dir} """ From 867ce51cce8327554216fd148406063753e6cb3a Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:13:12 -0400 Subject: [PATCH 14/42] IMPORTANT(livekit-ffi): follow the cdylib rename in the bindings config The crate now builds `liblivekit_ffi` and not `liblivekit_uniffi`. The crate name sets this, so the merge cannot prevent it. Change the two places that name the library: the UniFFI configuration, which the generated Kotlin and Dart code uses to load it, and the Dart build hook, which names the native asset. IMPORTANT: this is a small change with a late failure. The package names do not change, so the Dart package still builds and still goes to pub.dev. It then fails on the machine of the user, when the asset does not resolve. A version on pub.dev cannot be removed. The Swift, Kotlin, Maven and pub.dev package names stay the same. No user of these packages must change an import. --- livekit-ffi/support/dart/hook/build.dart.tera | 4 ++-- livekit-ffi/uniffi.toml | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/livekit-ffi/support/dart/hook/build.dart.tera b/livekit-ffi/support/dart/hook/build.dart.tera index b8c04cedb..684a24b01 100644 --- a/livekit-ffi/support/dart/hook/build.dart.tera +++ b/livekit-ffi/support/dart/hook/build.dart.tera @@ -1,7 +1,7 @@ // Native Assets build hook for the LiveKit UniFFI Dart bindings. // // The generated bindings call the native library through `@Native` annotations -// bound to the asset id `package:/uniffi:livekit_uniffi`. This hook +// bound to the asset id `package:/uniffi:livekit_ffi`. This hook // resolves the dynamic library for the target and registers that code asset. // // Two modes, chosen automatically: @@ -21,7 +21,7 @@ import 'package:hooks/hooks.dart'; import 'package:http/http.dart' as http; // Matches `cdylib_name` in uniffi.toml and the asset name in the generated Dart. -const _cdylibName = 'livekit_uniffi'; +const _cdylibName = 'livekit_ffi'; const _version = '{{ CARGO_MAKE_CRATE_VERSION }}'; // Per-release directory holding the `build-.zip` assets. const _downloadBase = '{{ CDYLIB_DOWNLOAD_BASE_URL }}'; diff --git a/livekit-ffi/uniffi.toml b/livekit-ffi/uniffi.toml index 5e2941caf..e0e2d2081 100644 --- a/livekit-ffi/uniffi.toml +++ b/livekit-ffi/uniffi.toml @@ -4,7 +4,7 @@ ffi_module_name = "RustLiveKitUniFFI" [bindings.kotlin] android = true package_name = "io.livekit.uniffi" -cdylib_name = "livekit_uniffi" # the name of the so file to be loaded +cdylib_name = "livekit_ffi" # the name of the so file to be loaded # The Kotlin checksum test is off because it fails on ARM devices. # @@ -53,6 +53,8 @@ omit_checksums = true [bindings.dart] # Dart package name; must match the published pub package so the Native Assets -# asset id resolves to `package:livekit_uniffi/uniffi:livekit_uniffi`. +# asset id resolves to `package:livekit_uniffi/uniffi:livekit_ffi`. The package +# name is unchanged by the livekit-uniffi merge; only the library the crate +# builds is now liblivekit_ffi. package_name = "livekit_uniffi" -cdylib_name = "livekit_uniffi" \ No newline at end of file +cdylib_name = "livekit_ffi" \ No newline at end of file From 94a7ef9a2678485e6366d2bb2f0c6e1b3f33915b Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:13:36 -0400 Subject: [PATCH 15/42] ci: build livekit-ffi/core-modules in the uniffi workflows Point the four workflows that build the UniFFI packages at the merged crate. The cdylib workflow builds `-p livekit-ffi` with the flags that the platform table gives for Dart, and its artifacts now carry the livekit-ffi library name. The Swift, Android and Dart test workflows change their working directory to livekit-ffi. The linker workaround for 64-bit Linux stays. The file .cargo/config.toml still selects lld for that target to link libwebrtc, which this surface does not use. The package names do not change here. The Swift package keeps its current name until the rename commit. --- .github/workflows/uniffi-android.yml | 9 ++++--- .github/workflows/uniffi-cdylib.yml | 36 ++++++++++++++++---------- .github/workflows/uniffi-dart-test.yml | 14 +++++++--- .github/workflows/uniffi-swift.yml | 13 +++++----- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/.github/workflows/uniffi-android.yml b/.github/workflows/uniffi-android.yml index 29df8acb6..781d7b8f4 100644 --- a/.github/workflows/uniffi-android.yml +++ b/.github/workflows/uniffi-android.yml @@ -1,6 +1,7 @@ name: UniFFI Android package -# Builds livekit-uniffi as an Android AAR and publishes it to Maven Central. +# Builds livekit-ffi/core-modules as an Android AAR and publishes +# it to Maven Central. # # Reusable workflow — called from uniffi-packages.yml after the source draft # release has been resolved. Not triggered directly. @@ -13,7 +14,7 @@ on: required: true type: string tag_name: - description: Source release tag (e.g., livekit-uniffi/v0.0.7) — surfaced in publish logs for traceability + description: Source release tag (e.g., livekit-ffi/v0.13.0) — surfaced in publish logs for traceability required: true type: string dry_run: @@ -24,7 +25,7 @@ on: env: CARGO_TERM_COLOR: always - ANDROID_DIR: livekit-uniffi/support/android + ANDROID_DIR: livekit-ffi/support/android jobs: build-and-publish: @@ -54,7 +55,7 @@ jobs: java-version: 17 - name: Build Android package - working-directory: livekit-uniffi + working-directory: livekit-ffi run: cargo make --profile release android-package - name: Publish to Maven Central diff --git a/.github/workflows/uniffi-cdylib.yml b/.github/workflows/uniffi-cdylib.yml index b5002f136..90c66a3d2 100644 --- a/.github/workflows/uniffi-cdylib.yml +++ b/.github/workflows/uniffi-cdylib.yml @@ -1,7 +1,7 @@ name: UniFFI cdylib builds -# Builds the livekit-uniffi dynamic library for every Dart/Flutter target and -# attaches them to the livekit-uniffi release as `build-.zip` (+ a +# Builds the livekit-ffi core-modules dynamic library for every Dart/Flutter +# target and attaches them to the livekit-ffi release as `build-.zip` (+ a # `.sha256` sidecar). The Dart build hook (support/dart/hook/build.dart.tera) # downloads these at consumer build time. # @@ -13,11 +13,11 @@ on: workflow_call: inputs: version: - description: Release version (e.g. 0.1.1) + description: Release version (e.g. 0.13.0) required: true type: string tag_name: - description: Source release tag (e.g. livekit-uniffi/v0.1.1) + description: Source release tag (e.g. livekit-ffi/v0.13.0) required: true type: string dry_run: @@ -99,15 +99,18 @@ jobs: - name: Build (Apple / Windows) if: ${{ matrix.platform == 'apple' || matrix.platform == 'windows' }} shell: bash - run: cargo build --release --target ${{ matrix.target }} -p livekit-uniffi + run: | + set -euo pipefail + FLAGS=$(python3 .github/scripts/platform_features.py dart) + cargo build --release --target ${{ matrix.target }} -p livekit-ffi $FLAGS # Linux: build inside a manylinux_2_28 container so the shared library # links against glibc 2.28 and loads on the distributions the Dart package # is used on (Debian 12, Ubuntu 22.04, Amazon Linux 2023 and newer). A # native build on the runner would inherit its glibc 2.39 floor. Same # approach as ffi-builds.yml. The arm64 entry runs on a native arm64 - # runner, so no cross toolchain is needed, and livekit-uniffi has no C - # dependencies, so the image's stock gcc is enough. The toolchain version + # runner, so no cross toolchain is needed, and livekit-ffi/core-modules + # has no C dependencies, so the image's stock gcc is enough. The toolchain version # comes from rust-toolchain.toml. - name: Build (Linux) if: ${{ matrix.platform == 'linux' }} @@ -115,17 +118,19 @@ jobs: TARGET: ${{ matrix.target }} BUILD_IMAGE: ${{ matrix.build_image }} run: | - docker run --rm -e TARGET -v "$PWD:/workspace" -w /workspace "$BUILD_IMAGE" bash -euo pipefail -c ' + set -euo pipefail + export FLAGS=$(python3 .github/scripts/platform_features.py dart) + docker run --rm -e TARGET -e FLAGS -v "$PWD:/workspace" -w /workspace "$BUILD_IMAGE" bash -euo pipefail -c ' curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none export PATH="/root/.cargo/bin:$PATH" if [ "$TARGET" = "aarch64-unknown-linux-gnu" ]; then # .cargo/config.toml forces -fuse-ld=lld for this target to link - # libwebrtc, which livekit-uniffi does not use. A non-empty global + # libwebrtc, which livekit-ffi/core-modules does not use. A non-empty global # RUSTFLAGS overrides that per-target config, selecting the GNU # bfd linker the image provides. export RUSTFLAGS="-C link-arg=-fuse-ld=bfd" fi - cargo build --release --target "$TARGET" -p livekit-uniffi + cargo build --release --target "$TARGET" -p livekit-ffi $FLAGS ' # The container ran as root, so hand the outputs back to the runner user. sudo chown -R "$USER:$USER" target @@ -139,7 +144,10 @@ jobs: - name: Build (Android) if: ${{ matrix.platform == 'android' }} - run: cargo ndk --target ${{ matrix.target }} build --release -p livekit-uniffi + run: | + set -euo pipefail + FLAGS=$(python3 .github/scripts/platform_features.py dart) + cargo ndk --target ${{ matrix.target }} build --release -p livekit-ffi $FLAGS # macOS/Linux: zip + shasum are available. - name: Package artifact (Unix) @@ -149,8 +157,8 @@ jobs: triple="${{ matrix.target }}" dir="target/${triple}/release" case "$triple" in - *apple*) lib="liblivekit_uniffi.dylib" ;; - *) lib="liblivekit_uniffi.so" ;; + *apple*) lib="liblivekit_ffi.dylib" ;; + *) lib="liblivekit_ffi.so" ;; esac out="build-${triple}.zip" # Zip with the bare library name at the archive root. @@ -165,7 +173,7 @@ jobs: shell: pwsh run: | $triple = "${{ matrix.target }}" - $lib = "target/$triple/release/livekit_uniffi.dll" + $lib = "target/$triple/release/livekit_ffi.dll" $out = "build-$triple.zip" Compress-Archive -Path $lib -DestinationPath $out -Force $hash = (Get-FileHash -Algorithm SHA256 $out).Hash.ToLower() diff --git a/.github/workflows/uniffi-dart-test.yml b/.github/workflows/uniffi-dart-test.yml index 5a6c61182..533f55f08 100644 --- a/.github/workflows/uniffi-dart-test.yml +++ b/.github/workflows/uniffi-dart-test.yml @@ -2,7 +2,7 @@ name: UniFFI Dart tests # Builds the Dart package (dev mode: host cdylib + generated bindings) and runs # its test suite, exercising the FFI boundary end-to-end. Scoped to changes that -# can affect the livekit-uniffi Dart bindings. +# can affect the livekit-ffi Dart bindings. on: push: @@ -10,7 +10,13 @@ on: pull_request: branches: ["main"] paths: - - "livekit-uniffi/**" + - "livekit-ffi/src/core_modules/**" + - "livekit-ffi/src/lib.rs" + - "livekit-ffi/src/build_info.rs" + - "livekit-ffi/Cargo.toml" + - "livekit-ffi/uniffi.toml" + - "livekit-ffi/Makefile.toml" + - "livekit-ffi/support/dart/**" - "livekit-api/**" - "livekit-protocol/**" - "Cargo.lock" @@ -68,11 +74,11 @@ jobs: ${{ runner.os }}-cargo-target-dart- - name: Build Dart package - working-directory: livekit-uniffi + working-directory: livekit-ffi run: cargo make dart-package - name: Run Dart tests - working-directory: livekit-uniffi/packages/dart + working-directory: livekit-ffi/packages/dart run: | dart pub get dart test diff --git a/.github/workflows/uniffi-swift.yml b/.github/workflows/uniffi-swift.yml index e13fbc583..4a5fac6f1 100644 --- a/.github/workflows/uniffi-swift.yml +++ b/.github/workflows/uniffi-swift.yml @@ -1,7 +1,8 @@ name: UniFFI Swift package -# Builds livekit-uniffi as a Swift xcframework and publishes it to the -# livekit-uniffi-xcframework hosting repo (draft release + PR). +# Builds livekit-ffi/core-modules as a Swift xcframework and +# publishes it to the livekit-uniffi-xcframework hosting repo +# (draft release + PR). # # Reusable workflow — called from uniffi-packages.yml after the source draft # release has been resolved. Not triggered directly. @@ -14,7 +15,7 @@ on: required: true type: string tag_name: - description: Source release tag (e.g., livekit-uniffi/v0.0.7) — surfaced in PR/release bodies for traceability + description: Source release tag (e.g., livekit-ffi/v0.13.0) — surfaced in PR/release bodies for traceability required: true type: string dry_run: @@ -29,9 +30,9 @@ on: env: CARGO_TERM_COLOR: always HOSTING_REPO: livekit/livekit-uniffi-xcframework - # SPM_NAME must match the value in livekit-uniffi/Makefile.toml [env]. + # SPM_NAME must match the value in livekit-ffi/Makefile.toml [env]. SPM_NAME: LiveKitUniFFI - OUTPUT_DIR: livekit-uniffi/packages/swift/LiveKitUniFFI + OUTPUT_DIR: livekit-ffi/packages/swift/LiveKitUniFFI jobs: build-and-publish: @@ -77,7 +78,7 @@ jobs: aarch64-apple-visionos-sim - name: Build Swift package - working-directory: livekit-uniffi + working-directory: livekit-ffi env: SPM_VERSION: ${{ inputs.version }} SPM_HOSTING_REPO: ${{ env.HOSTING_REPO }} From cbf9b78882ba0961d40a3015a59dfbdf4786ddf2 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:13:53 -0400 Subject: [PATCH 16/42] IMPORTANT(ci): create the release tag with the repo-writer App The publishing on pub.dev needs a workflow that a tag push starts. This is part of its trusted publishing configuration. The knope configuration for livekit-ffi makes a draft release. A draft release has no git tag. The tag comes into existence only when the workflow publishes the release, and the default token does that. GitHub does not let an event from the default token start another workflow, because this would make a loop. No workflow downstream would therefore start. IMPORTANT: create the tag with a token from the livekit-repo-writer GitHub App, before the release becomes public. An event from an App does start other workflows. The step first makes sure that the tag agrees with the version in the manifest at that commit. This workflow runs for every push to the main branch and takes whatever draft release it finds, which can be older than the commit. If the tag is already there, the step gives a warning and stops with success. The release files are already uploaded at that point, and a failure would leave the release as a draft. --- .github/workflows/ffi-builds.yml | 61 +++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index b40db44dc..3ba82f3f4 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -266,7 +266,7 @@ jobs: # This has to run on a macOS entry. The release profile sets # `strip = "symbols"`, which removes the metadata on Linux while macOS # strips less aggressively -- see the note on `bindgen-dart` in - # livekit-uniffi/Makefile.toml. Do not move this to ffi-linux-x86_64 for + # livekit-ffi/Makefile.toml. Do not move this to ffi-linux-x86_64 for # symmetry; bindgen would silently emit an empty module. - name: Generate Python bindings (UniFFI) if: ${{ matrix.name == 'ffi-macos-arm64' }} @@ -368,7 +368,60 @@ jobs: merge-multiple: true path: ${{ github.workspace }}/ffi-builds - - name: Upload artifacts and publish release + - name: Upload artifacts + env: + TAG: ${{ needs.get-tag.outputs.tag_name }} + run: gh release upload "$TAG" ${{ github.workspace }}/ffi-builds/* + + # This is here because pub.dev's automated publishing *requires* a + # tag-push-triggered workflow as part of its trusted publishing + # configuration. If the default github actions worker were to publish the + # tag, then github prevents that from triggering other workflows to + # prevent workflow recursion. So this "breaks the cycle" and allows the + # downstream workflow to be triggered to satisfy pub.dev. + - name: Generate Repo Writer GitHub App token + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 + with: + app-id: ${{ vars.REPO_WRITER_GH_APP_ID }} + private-key: ${{ secrets.REPO_WRITER_GH_APP_PRIVATE_KEY }} + owner: livekit + repositories: | + rust-sdks + + - name: Create release tag + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + TAG: ${{ needs.get-tag.outputs.tag_name }} + SHA: ${{ github.sha }} run: | - gh release upload ${{ needs.get-tag.outputs.tag_name }} ${{ github.workspace }}/ffi-builds/* - gh release edit ${{ needs.get-tag.outputs.tag_name }} --draft=false + set -euo pipefail + + # This workflow runs on every push to main and adopts whatever draft + # release it finds, which may be several commits old. Tagging a commit + # whose Cargo.toml says a different version would publish mismatched + # packages, and uniffi-dart-publish.yml would reject it later anyway. + crate_version=$(grep -m1 '^version = ' livekit-ffi/Cargo.toml | cut -d'"' -f2) + if [ "${TAG#livekit-ffi/v}" != "$crate_version" ]; then + echo "::error::tag ${TAG} does not match livekit-ffi version ${crate_version} at ${SHA}" + exit 1 + fi + + existing=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq '.object.sha' 2>/dev/null || true) + if [ -n "$existing" ]; then + # Do not fail: the assets are already uploaded, and failing here + # would strand the release as a draft. No push event was emitted, + # so the downstream publishes need a manual dispatch. + echo "::warning::tag ${TAG} already exists at ${existing}; not re-creating it." + echo "::warning::uniffi-packages.yml and uniffi-dart-publish.yml did NOT run; dispatch them with this tag if the packages still need publishing." + exit 0 + fi + + gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f ref="refs/tags/${TAG}" \ + -f sha="${SHA}" + + - name: Publish release + env: + TAG: ${{ needs.get-tag.outputs.tag_name }} + run: gh release edit "$TAG" --draft=false From 20066b61ebb952cf1b1bd1a830b2397b7fb5ee0f Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:13:54 -0400 Subject: [PATCH 17/42] IMPORTANT(ci): trigger the UniFFI packages from the tag push This workflow started on a published release event. That worked only because knope-bot, which is a GitHub App, published each livekit-uniffi release directly, with its tag. IMPORTANT: livekit-ffi uses the draft release flow, so the same event now comes from the default token and starts nothing. Without this change, the Swift and Android packages would stop being published, and no error would show. Start on the push of the tag that the previous commit creates. The release job makes that tag only after the build matrix is good, so a tag push still means that all targets built. This is the same condition that the release event gave. The old condition on the tag prefix is no longer necessary. The tag filter does that work, and a check in the script rejects any other tag. --- .github/workflows/uniffi-packages.yml | 48 ++++++++++++++++----------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/uniffi-packages.yml b/.github/workflows/uniffi-packages.yml index 862dd772e..28cabec23 100644 --- a/.github/workflows/uniffi-packages.yml +++ b/.github/workflows/uniffi-packages.yml @@ -1,26 +1,30 @@ name: UniFFI packages -# Publishes the per-language livekit-uniffi wrapper packages (Swift xcframework → -# livekit/livekit-uniffi-xcframework; Android AAR → Maven Central) when a -# livekit-uniffi release is published. +# Publishes the per-language livekit-ffi core-modules wrapper packages (Swift +# xcframework → livekit/livekit-uniffi-xcframework; Android AAR → Maven +# Central) when a livekit-ffi release tag is pushed. # -# knope publishes the `livekit-uniffi/v*` GitHub release (and its git tag) -# directly on the release merge — no draft — so this reacts to the published -# release event, which fires only for genuine livekit-uniffi releases (the gate -# is free). workflow_dispatch allows a manual re-run with an explicit tag. +# Trigger note: this used to react to `release: published`, which worked only +# because knope-bot (a GitHub App) published the livekit-uniffi release and its +# tag directly. livekit-ffi uses knope's `assets = "marker"` draft flow, and a +# draft carries no tag until it is published — with GITHUB_TOKEN, which never +# starts a workflow run (see #1256). So the trigger is the git tag that +# ffi-builds.yml creates with the livekit-repo-writer App after its build +# matrix succeeds and before it publishes the draft. A tag push therefore still +# implies a green 11-target build, the same guarantee the release event gave. # # The Dart package, and the cdylib release assets its build hook downloads, are -# handled by uniffi-dart-publish.yml. That workflow is tag-push triggered, as -# pub.dev requires, and builds the assets itself so publishing can depend on -# them directly. +# handled by uniffi-dart-publish.yml, which reacts to the same tag push +# (pub.dev requires a tag-push-triggered workflow) and builds those assets +# itself so publishing can depend on them directly. on: - release: - types: [published] + push: + tags: ["livekit-ffi/v*"] workflow_dispatch: inputs: tag_name: - description: "Release tag (e.g. livekit-uniffi/v0.1.6)" + description: "Release tag (e.g. livekit-ffi/v0.13.0)" required: true type: string dry_run: @@ -34,23 +38,27 @@ permissions: jobs: resolve-tag: runs-on: ubuntu-latest - # The release event fires for every package's release, so proceed only for - # livekit-uniffi. workflow_dispatch always proceeds (explicit tag). - if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'livekit-uniffi/v') }} outputs: tag_name: ${{ steps.get-tag.outputs.tag_name }} version: ${{ steps.get-tag.outputs.version }} steps: - id: get-tag - # TAG is bound through env rather than template-expanded into the + # Tags are bound through env rather than template-expanded into the # script: tag names may contain shell metacharacters (Actions script # injection). env: - TAG: ${{ github.event.release.tag_name || inputs.tag_name }} + PUSHED_TAG: ${{ github.ref_name }} + INPUT_TAG: ${{ inputs.tag_name }} run: | + set -euo pipefail + TAG="${INPUT_TAG:-$PUSHED_TAG}" + case "$TAG" in + livekit-ffi/v*) ;; + *) echo "::error::unexpected tag: $TAG"; exit 1 ;; + esac echo "tag_name=${TAG}" >> "$GITHUB_OUTPUT" - # Strip the "livekit-uniffi/v" prefix to get the package version (e.g. 0.1.6). - echo "version=${TAG#livekit-uniffi/v}" >> "$GITHUB_OUTPUT" + # Strip the "livekit-ffi/v" prefix to get the package version (e.g. 0.13.0). + echo "version=${TAG#livekit-ffi/v}" >> "$GITHUB_OUTPUT" swift: needs: resolve-tag From 4f0337c09122396b79ab3bf0437a03aa1ad59e9e Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:14:13 -0400 Subject: [PATCH 18/42] ci: retarget the Dart publish at livekit-ffi/v* Follow the tag prefix of the merged crate. The workflow now starts on a `livekit-ffi/v*` tag, takes the version from that prefix, and compares it with the version in livekit-ffi/Cargo.toml. The staging directory and the guard against a native library in the package keep their paths correct for the new crate. The name of the package on pub.dev does not change in this commit. Users of the package therefore need no change. --- .github/workflows/uniffi-dart-publish.yml | 37 ++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/uniffi-dart-publish.yml b/.github/workflows/uniffi-dart-publish.yml index d2546dc6a..e2567e94a 100644 --- a/.github/workflows/uniffi-dart-publish.yml +++ b/.github/workflows/uniffi-dart-publish.yml @@ -1,13 +1,15 @@ name: UniFFI Dart package # Publishes the generated livekit_uniffi Dart package to pub.dev on a -# livekit-uniffi release, together with the native libraries it loads. +# livekit-ffi release, together with the native libraries it loads. # # This is a separate workflow from uniffi-packages.yml because pub.dev's # automated publishing only accepts workflows triggered by a tag push matching -# the tag pattern configured on the package (livekit-uniffi/v{{version}}); -# a release-event-triggered workflow is rejected. knope-bot creates the tag via -# the API, and App-created events do trigger workflows here (see the note in +# the tag pattern configured on the package (livekit-ffi/v{{version}}); +# a release-event-triggered workflow is rejected. livekit-ffi uses knope's +# draft-release flow, so the tag is created by ffi-builds.yml with the +# livekit-repo-writer App rather than by knope-bot; App-created events do +# trigger workflows here, GITHUB_TOKEN-created ones do not (see the note in # uniffi-packages.yml / #1256). # # The package's hook/build.dart downloads build-.zip assets from the @@ -20,11 +22,12 @@ name: UniFFI Dart package # Publishing is enabled. A tag push runs the whole pipeline and publishes to # pub.dev through OIDC (dart-lang/setup-dart exchanges the job token). pub.dev # accepts that token only because automated publishing is configured on the -# package for livekit/rust-sdks with tag pattern `livekit-uniffi/v{{version}}`; +# package for livekit/rust-sdks with tag pattern `livekit-ffi/v{{version}}`; # if that setting is removed the publish step fails and nothing else changes. # The package was created by a manual first publish of 0.1.10 (pub.dev only -# automates existing packages), and knope-bot's API-created tag was confirmed -# to fire this push trigger on that release. +# automates existing packages). NOTE: the configured tag pattern must be +# updated to `livekit-ffi/v{{version}}` on pub.dev before the first release +# after the livekit-uniffi merge, or the publish step will be rejected. # # Recovery: if a run fails, fix the cause and re-run the failed jobs, or # dispatch this workflow with the tag. A dispatch rebuilds and re-attaches the @@ -34,11 +37,11 @@ name: UniFFI Dart package on: push: - tags: ["livekit-uniffi/v*"] + tags: ["livekit-ffi/v*"] workflow_dispatch: inputs: tag_name: - description: "Release tag (e.g. livekit-uniffi/v0.1.9)" + description: "Release tag (e.g. livekit-ffi/v0.13.0)" required: true type: string dry_run: @@ -75,11 +78,11 @@ jobs: run: | TAG="${INPUT_TAG:-$PUSHED_TAG}" case "$TAG" in - livekit-uniffi/v*) ;; + livekit-ffi/v*) ;; *) echo "Unexpected tag: $TAG"; exit 1 ;; esac echo "tag_name=${TAG}" >> "$GITHUB_OUTPUT" - echo "version=${TAG#livekit-uniffi/v}" >> "$GITHUB_OUTPUT" + echo "version=${TAG#livekit-ffi/v}" >> "$GITHUB_OUTPUT" # Fast fail on a mis-pointed tag before spending runner time on the # cdylib matrix: the tag version must match the crate version the tag @@ -88,16 +91,16 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: ref: ${{ steps.tag.outputs.tag_name }} - sparse-checkout: livekit-uniffi/Cargo.toml + sparse-checkout: livekit-ffi/Cargo.toml sparse-checkout-cone-mode: false - name: Check tag matches crate version env: VERSION: ${{ steps.tag.outputs.version }} run: | - crate_version=$(grep -m1 '^version = ' livekit-uniffi/Cargo.toml | cut -d'"' -f2) + crate_version=$(grep -m1 '^version = ' livekit-ffi/Cargo.toml | cut -d'"' -f2) if [ "$crate_version" != "$VERSION" ]; then - echo "Tag version $VERSION does not match livekit-uniffi crate version $crate_version." >&2 + echo "Tag version $VERSION does not match livekit-ffi crate version $crate_version." >&2 exit 1 fi @@ -169,7 +172,7 @@ jobs: sdk: 3.13.1 - name: Build Dart package (release) - working-directory: livekit-uniffi + working-directory: livekit-ffi run: cargo make --profile release dart-package # pub builds the publish archive from git's file listing, and the @@ -182,8 +185,8 @@ jobs: - name: Stage package outside the work tree run: | rm -rf "$RUNNER_TEMP/livekit_uniffi" - cp -R livekit-uniffi/packages/dart "$RUNNER_TEMP/livekit_uniffi" - if find "$RUNNER_TEMP/livekit_uniffi" \( -name 'liblivekit_uniffi.*' -o -name 'livekit_uniffi.dll' \) | grep -q .; then + cp -R livekit-ffi/packages/dart "$RUNNER_TEMP/livekit_uniffi" + if find "$RUNNER_TEMP/livekit_uniffi" \( -name 'liblivekit_ffi.*' -o -name 'livekit_uniffi.dll' \) | grep -q .; then echo "Staged package contains a local native library; refusing to publish." >&2 exit 1 fi From bf05dffd58e9bebf3efefe435c0b0308a3d91f9b Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:14:27 -0400 Subject: [PATCH 19/42] test(ci): keep core-modules compiled and tested per commit Before the merge, livekit-uniffi was a separate crate. A build of the workspace thus compiled it, and a test run of the workspace ran its tests. That is no longer true. The `core-modules` feature is not a default feature, so a build of the workspace does not reach this code, and its thirteen tests would stop running without any message. Add one build step and one test step that select the feature. The two features are mutually exclusive, so these cannot join the workspace commands and need their own invocation. Both steps take their flags from the platform table. --- .github/workflows/builds.yml | 19 +++++++++++++++++++ .github/workflows/tests.yml | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index fd263ce78..3ea4de373 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -172,3 +172,22 @@ jobs: cargo install cargo-ndk # FIXME: wgpu_room does not link on aarch64 cargo ndk --target ${{ matrix.target }} build --release -p livekit --workspace -vv + + # `--workspace` above builds livekit-ffi with its default features, which + # select livekit-ffi/room-apis. core-modules is mutually exclusive with + # it, so it needs its own invocation or nothing would compile the + # core-modules code per-commit across this matrix. (Before the + # livekit-uniffi merge that coverage came free from the separate crate.) + - name: Build (Cargo, livekit-ffi core-modules) + if: ${{ !contains(matrix.target, 'android') }} + run: | + set -euo pipefail + FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) + cargo build --release -p livekit-ffi --target ${{ matrix.target }} $FLAGS + + - name: Build (Android, livekit-ffi core-modules) + if: ${{ contains(matrix.target, 'android') }} + run: | + set -euo pipefail + FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) + cargo ndk --target ${{ matrix.target }} build --release -p livekit-ffi $FLAGS diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d3eb0d98c..5936736f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -183,6 +183,16 @@ jobs: cargo test --verbose --target ${{ matrix.target }} -p livekit-net --features native cargo test --verbose --target ${{ matrix.target }} -p livekit-signaling --features native + # These live behind livekit-ffi's non-default `core-modules` feature, so a + # workspace-wide `cargo test` does not reach them. Before the + # livekit-uniffi merge they ran as part of that crate. + - name: Test core modules (data streams v2 / data tracks) + shell: bash + run: | + set -euo pipefail + FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) + cargo test --verbose --target ${{ matrix.target }} -p livekit-ffi $FLAGS + - name: Test RPC seam (livekit-rpc) shell: bash run: | From c4ae74f183070116871a94bc42e1a6c488012fd7 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:14:28 -0400 Subject: [PATCH 20/42] ci: narrow the gen-node-proto trigger This workflow generates the Node protobuf code from the files in livekit-ffi/protocol. Its path filter matched all of livekit-ffi, the two root manifests and all of .github. After the merge that filter also matches every change to the core modules, which have no protobuf files. The workflow writes a commit back to the branch, so it should run only when it has work to do. Match the protobuf directory, the Node bindings crate and this workflow file. This is less than the filter matched before the merge. --- .github/workflows/gen-node-proto.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gen-node-proto.yml b/.github/workflows/gen-node-proto.yml index 670746a8d..e91bfe0d0 100644 --- a/.github/workflows/gen-node-proto.yml +++ b/.github/workflows/gen-node-proto.yml @@ -5,11 +5,11 @@ on: branches: - main paths: - - livekit-ffi/** + # Only the protos this job regenerates from. livekit-ffi/** would now fire + # on every core-modules change too, and this job auto-commits. + - livekit-ffi/protocol/** - livekit-ffi-node-bindings/** - - Cargo.lock - - Cargo.toml - - .github/** + - .github/workflows/gen-node-proto.yml workflow_dispatch: workflow_call: From 441a1aa3286843032ed808382a1a3efe8071f46e Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:14:43 -0400 Subject: [PATCH 21/42] docs(livekit-ffi): rewrite the crate README for the two surfaces The readme described one FFI interface for Python, Node and Unity. The crate now has two surfaces, and the file must say which SDK uses which. Give each surface its section: what it contains, which SDKs consume it, where its code is and the command that builds it. Point at the platform table for the full list. Add the notes that save time later: the two features cannot be selected together, the cargo-make tasks need explicit feature flags, the build script is gated, and prost has two versions. The file AGENTS.md is a symbolic link to this file, so both are correct. --- livekit-ffi/README.md | 111 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/livekit-ffi/README.md b/livekit-ffi/README.md index fd4d98b58..f3bd8cccf 100644 --- a/livekit-ffi/README.md +++ b/livekit-ffi/README.md @@ -1,9 +1,116 @@ # LiveKit FFI -Foreign function interface (FFI) bindings for LiveKit, used to support the following client SDKs: +Foreign function interface (FFI) bindings for LiveKit. The crate exposes two +**mutually exclusive** surfaces, selected by cargo feature for each sdk build +based on that given sdk's implementation. + +## `room-apis` (default) + +The protobuf / C-ABI room surface, plus a growing UniFFI one. Used by the SDKs +that have no WebRTC stack of their own, so this build embeds the full +[_livekit_](https://crates.io/crates/livekit) + libwebrtc stack: - [Python](https://github.com/livekit/python-sdks) - [NodeJS](https://github.com/livekit/node-sdks) - [Unity](https://github.com/livekit/client-sdk-unity) -This crate is compiled as dynamic library, allowing client languages to invoke APIs from the core [_livekit_](https://crates.io/crates/livekit) crate. +```sh +cargo build -p livekit-ffi --no-default-features --features 'room-apis rustls-tls-native-roots' +# or: cargo build -p livekit-ffi +``` + +Mode specific source lives in `src/room_apis/`. + +## `core-modules` + +Core business logic exposed purely over +[UniFFI](https://mozilla.github.io/uniffi-rs/latest/): access tokens, log +forwarding, data tracks and data streams v2. Used by the SDKs that already ship +their own WebRTC stack, so this build depends on none of `livekit`, +`libwebrtc`, `soxr-sys` or `imgproc` — which is what lets it target visionOS, +tvOS and Mac Catalyst: + +- [Swift](https://github.com/livekit/client-sdk-swift) +- [Android](https://github.com/livekit/client-sdk-android) +- [Flutter](https://github.com/livekit/client-sdk-flutter) + +```sh +cargo build -p livekit-ffi --no-default-features --features core-modules +``` + +Mode specific source lives in `src/core_modules/`. + +## Which platform gets which + +`[package.metadata.platform-features]` in [Cargo.toml](./Cargo.toml) is the +single source of truth, and CI resolves its build flags from it: + +```sh +.github/scripts/platform_features.py --list +``` + +## Things that bite + +- **Never enable both features.** Each pins its own UniFFI namespace via + `setup_scaffolding!`, and a crate may declare only one. A `compile_error!` in + `src/lib.rs` enforces this; another rejects selecting neither. +- **The cargo-make tasks below take no `-p`** and rely on the working + directory, so they need explicit feature flags or they build the default + livekit-ffi/room-apis and all of libwebrtc. `Makefile.toml` resolves those from + the table above — do not write them out by hand. +- **`build.rs` is gated on `room-apis`.** It downloads libwebrtc, configures the + linker, and panics on any target it does not recognise — visionOS, tvOS and + Mac Catalyst among them. That gate is what makes the core-modules Apple + builds possible. +- **`prost` is split.** `prost` is 0.14, for this crate's own generated protos; + `prost_012` is 0.12, for the `livekit-protocol` types used in + `src/core_modules/`. The surfaces are mutually exclusive, so the two versions + never compile together. +- **Verify UniFFI API changes by compiling the Kotlin bindings** + (`cargo make android-package`). A green `cargo build` proves nothing there; + see the UniFFI section of the root [AGENTS.md](../AGENTS.md) for the specific + naming traps. + +## Tasks + +Binding generation and multi-platform builds are handled by +[_cargo-make_](https://github.com/sagiegurari/cargo-make) — please install it +before proceeding. For the full list see [_Makefile.toml_](./Makefile.toml) or +run `cargo make --list-all-steps`. The most important are summarized below. + +### Swift + +Generate Swift bindings and build a multi-platform XCFramework: + +``` +cargo make swift-package +``` + +For a fast, debuggable variant (macOS-only, unstripped), run +`cargo make swift-package-debug` — see +[support/swift/DEBUGGING.md](./support/swift/DEBUGGING.md). + +See [support/swift/README.md](./support/swift/README.md) for debugging vs. +release modes, consumer integration, and prerequisites (Xcode, Rust Apple +targets). + +### Android + +Build native libraries, Kotlin bindings, and a release AAR: + +``` +cargo make android-package # debug .so in release AAR +cargo make --profile release android-package # release .so (CI / publishing) +cargo make android-package-local # + publish to Maven Local +``` + +See [support/android/README.md](./support/android/README.md) for prerequisites +(Android SDK/NDK). + +### Dart + +``` +cargo make dart-package +``` + +See [support/dart/README.md](./support/dart/README.md). From c72aaf4734d4becd34120b41f7237a289f8a1881 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Thu, 17 Sep 2026 16:14:44 -0400 Subject: [PATCH 22/42] docs: update the repo docs and add the merge changeset Correct the places that describe two FFI crates: the agent notes, the root readme, the notes of three other crates, the comments that point at files in the deleted crate, and the note about which crates the feature job checks. Add the changeset for the merge. It records that the two surfaces are now one crate with two features, that everything which built livekit-ffi builds the same way, and that what built livekit-uniffi now selects the core-modules feature. The changeset also records the one visible difference for a downstream SDK: the native library is now liblivekit_ffi. No package name and no import changes. --- .changeset/merge_uniffi_into_ffi.md | 33 +++++++++++++++++++ .github/scripts/changeset_detect.py | 4 +-- .../feature-combinations-curated.yml | 4 +-- AGENTS.md | 16 ++++----- README.md | 2 +- livekit-common/Cargo.toml | 2 +- livekit-data-stream/AGENTS.md | 2 +- livekit-datatrack/AGENTS.md | 2 +- livekit-net/Cargo.toml | 2 +- livekit-net/uniffi.toml | 4 +-- livekit-rpc/AGENTS.md | 2 +- 11 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 .changeset/merge_uniffi_into_ffi.md diff --git a/.changeset/merge_uniffi_into_ffi.md b/.changeset/merge_uniffi_into_ffi.md new file mode 100644 index 000000000..6c04adb76 --- /dev/null +++ b/.changeset/merge_uniffi_into_ffi.md @@ -0,0 +1,33 @@ +--- +livekit-ffi: minor +--- + +Merge `livekit-uniffi` into `livekit-ffi`. + +There is now one crate, one version and one release tag. The `livekit-uniffi` +crate, its changelog and its `livekit-uniffi/v*` tags are gone; its history is +preserved in git. + +The crate carries two mutually exclusive surfaces, selected by feature: + +- `room-apis` (default) is the existing protobuf / C-ABI surface, with + `livekit` and libwebrtc. Everything that built `livekit-ffi` before builds it + unchanged. +- `core-modules` is the former `livekit-uniffi` surface — access tokens, log + forwarding, data tracks, data streams v2 — and depends on none of `livekit`, + `libwebrtc`, `soxr-sys` or `imgproc`. What previously built `livekit-uniffi` + now builds `-p livekit-ffi --no-default-features --features core-modules`. + +`[package.metadata.platform-features]` in `livekit-ffi/Cargo.toml` records +which surface each downstream package build gets, and CI resolves its build +flags from it. + +The merge itself requires no downstream SDK changes: the UniFFI namespace is +pinned per surface and every published package name is preserved. The one +difference is the native library the crate builds, `liblivekit_uniffi` → +`liblivekit_ffi`, which is loaded by name from the generated bindings and the +Dart build hook and needs no consumer change. The package renames are a +separate change, released alongside this one. + +The UniFFI node package (`@livekit/uniffi`) is not built for now and will be +reintroduced separately; node bindings ship from `livekit-ffi-node-bindings`. diff --git a/.github/scripts/changeset_detect.py b/.github/scripts/changeset_detect.py index 11f7b2581..e985e0973 100755 --- a/.github/scripts/changeset_detect.py +++ b/.github/scripts/changeset_detect.py @@ -228,8 +228,8 @@ def reconcile_knope_config(meta, knope_packages): (e.g. a renamed or removed crate leaving a dangling entry). Note the rule is one-directional: publishable implies knope-managed, but a - knope-managed crate may set `publish = false` (e.g. livekit-ffi / livekit- - uniffi, which CI publishes via wrapper packages rather than `cargo publish`). + knope-managed crate may set `publish = false` (e.g. livekit-ffi, which CI + publishes via wrapper packages rather than `cargo publish`). """ workspace_names = {pkg["name"] for pkg in meta["packages"]} publishable = {pkg["name"] for pkg in meta["packages"] if is_publishable(pkg)} diff --git a/.github/workflows/feature-combinations-curated.yml b/.github/workflows/feature-combinations-curated.yml index 37daf0bf8..0ad6851d3 100644 --- a/.github/workflows/feature-combinations-curated.yml +++ b/.github/workflows/feature-combinations-curated.yml @@ -31,7 +31,7 @@ # new feature is invisible to this job until it is added to that list. # # A crate with no such table is not feature-checked here at all. livekit-ffi -# and livekit-uniffi are the deliberate omissions; see the curated step below. +# is the deliberate omission; see the curated step below. # # Measured cold on a 14-core machine: the full 209-combination powerset takes # 359s; this split takes 242s, a 33% saving. That is what brings the job in @@ -269,7 +269,7 @@ jobs: # crate that declares that table is picked up, so adding a crate to this # half of the job means adding the table and nothing else. # - # livekit-ffi and livekit-uniffi deliberately do NOT declare one. + # livekit-ffi deliberately does NOT declare one. # Measured cold, their 8 combinations cost 103s of a 345s total — 30% of # the budget for two crates, because each TLS change rebuilds livekit # underneath livekit-ffi. diff --git a/AGENTS.md b/AGENTS.md index 97b61115d..6a69115af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,9 +71,9 @@ When reviewing changed code, search for `Arc::clone`, `.clone()` inside `move` c ## UniFFI integration -Several crates export items to Swift/Kotlin/Node/Python through UniFFI — `livekit-uniffi`, plus `livekit-common`, `livekit-datatrack`, and `livekit-net`, each carrying its own `uniffi.toml`. The Kotlin bindgen has sharp edges that `cargo build`, `cargo test`, and the Swift/Node/Python bindings do **not** catch: they surface only when the generated Kotlin is compiled, and one bad name fails the entire generated file. +Several crates export items to Swift/Kotlin/Python through UniFFI — `livekit-ffi` (its `core-modules` feature), plus `livekit-common`, `livekit-datatrack`, and `livekit-net`, each carrying its own `uniffi.toml`. The Kotlin bindgen has sharp edges that `cargo build`, `cargo test`, and the Swift/Node/Python bindings do **not** catch: they surface only when the generated Kotlin is compiled, and one bad name fails the entire generated file. -- Verify any change to UniFFI-exported API by actually generating and compiling the Kotlin bindings (`cargo make android-package` from `livekit-uniffi/`) — a green `cargo build` proves nothing here +- Verify any change to UniFFI-exported API by actually generating and compiling the Kotlin bindings (`cargo make android-package` from `livekit-ffi/`) — a green `cargo build` proves nothing here - **Never export a method named `close`** - UniFFI gives every object a non-`suspend` `close()` to satisfy `AutoCloseable`. An exported Rust method also called `close` differs from it only by `suspend`, which Kotlin rejects as conflicting overloads — see [mozilla/uniffi-rs#2955](https://github.com/mozilla/uniffi-rs/issues/2955) - Work around it with a Kotlin-only rename, so the Rust source and the Swift/Node/Python bindings keep the original name: @@ -81,17 +81,17 @@ Several crates export items to Swift/Kotlin/Node/Python through UniFFI — `live [bindings.kotlin.rename] "ByteStreamWriter.close" = "close_stream" ``` - - The rename must go in the `uniffi.toml` of the crate that **declares** the item, not in `livekit-uniffi/uniffi.toml`: a rename table only reaches items from the crate that owns it. `WsConnection.close` is renamed in `livekit-net/uniffi.toml` for exactly this reason + - The rename must go in the `uniffi.toml` of the crate that **declares** the item, not in `livekit-ffi/uniffi.toml`: a rename table only reaches items from the crate that owns it. `WsConnection.close` is renamed in `livekit-net/uniffi.toml` for exactly this reason - **Never name a field of an exported enum or record `message`** - For an error variant carrying a `message` field, UniFFI emits a constructor property `message` next to an `override val message` inherited from `Throwable` in one class body, which does not compile — and their types differ (`String` vs `String?`), so they cannot be merged into a single override. See [mozilla/uniffi-rs#2938](https://github.com/mozilla/uniffi-rs/issues/2938), closed without a fix - Unlike `close`, this **cannot** be renamed away: UniFFI keys the rename table by crate name but looks up enum and record members by the item's full module path, so a rename for anything declared in a submodule is silently ignored (method renames use the crate name and do work) - - Name the field `reason` in Rust instead — see `DataStreamError` in `livekit-uniffi/src/data_stream/common.rs` + - Name the field `reason` in Rust instead — see `DataStreamError` in `livekit-ffi/src/core_modules/data_stream/common.rs` - A new crate that exports UniFFI items needs its own `uniffi.toml`, including `omit_checksums = true` under `[bindings.kotlin]` - - The Kotlin checksum test is broken on ARM in every UniFFI release this workspace can use; the full explanation lives in `livekit-uniffi/uniffi.toml` and the root `Cargo.toml` + - The Kotlin checksum test is broken on ARM in every UniFFI release this workspace can use; the full explanation lives in `livekit-ffi/uniffi.toml` and the root `Cargo.toml` - **Never use `uniffi(flat_error)` on an error that foreign code can return** - Flat errors lower (Rust → foreign) but cannot be lifted: the derived `Lift` exists only to satisfy trait bounds, and its `try_read`/`try_lift` are `panic!("Can't lift flat errors")`. In an FFI callback that panic has nowhere to unwind to, so the host process aborts — and nothing catches it earlier, since the build, bindings generation, and Kotlin compilation all pass - Applies to the error type of any `#[uniffi::export(with_foreign)]` trait or callback interface, since foreign code implements the method. `flat_error` is correct only for an error that travels Rust → foreign exclusively, e.g. `PublishError` in `livekit-datatrack/src/local/mod.rs` - - Give a host-thrown error a single `Failed { reason: String }` variant plus `From`, so an undeclared exception surfaces as an error rather than aborting — see `PacketDeliveryError` in `livekit-uniffi/src/data_stream/common.rs` + - Give a host-thrown error a single `Failed { reason: String }` variant plus `From`, so an undeclared exception surfaces as an error rather than aborting — see `PacketDeliveryError` in `livekit-ffi/src/core_modules/data_stream/common.rs` ## Feature combinations @@ -115,7 +115,7 @@ check = [ ] ``` -A crate with no such table is not feature-checked at all. Today eleven crates are `"powerset"`, `livekit` and `livekit-api` are `"curated"`, and `livekit-ffi`/`livekit-uniffi` deliberately declare nothing. +A crate with no such table is not feature-checked at all. Today eleven crates are `"powerset"`, `livekit` and `livekit-api` are `"curated"`, and `livekit-ffi` deliberately declares nothing. - **When adding a workspace crate**, give it `mode = "powerset"` and stop there - Its features are then picked up automatically as they are added, and no CI file changes @@ -138,7 +138,7 @@ A crate with no such table is not feature-checked at all. Today eleven crates ar - The workflow validates the tables before running anything and fails on a typo'd `mode`, a `"curated"` crate with an empty `check`, a `"powerset"` crate carrying a `check` list that would be silently ignored, or either package set resolving to empty. A malformed table fails the job rather than quietly dropping a crate from CI - Keep `--depth 2`. `livekit` alone goes from 67 combinations to 232 at depth 3, and pairwise interactions are where these bugs actually live - A feature that pulls in `openssl-sys` cannot build for the three Android targets — there is no Android OpenSSL to link against. Add it to their `exclude_features` in the workflow matrix rather than trying to make it work; Android ships rustls (see `ffi-builds.yml`). It is honoured by both halves of the job -- `livekit-ffi` and `livekit-uniffi` declare no table on purpose: their combinations cost more than everything else in the job combined, because each TLS change rebuilds `livekit` underneath them. Their shipped configurations are covered by `builds.yml` and `ffi-builds.yml` instead +- `livekit-ffi` declares no table on purpose: its combinations cost more than everything else in the job combined, because each TLS change rebuilds `livekit` underneath it. Its two surfaces are mutually exclusive and cannot be combined anyway; the shipped configurations are declared in `[package.metadata.platform-features]` and covered by `builds.yml`, `ffi-builds.yml` and `uniffi-cdylib.yml` instead ## Documenting changes diff --git a/README.md b/README.md index 249030d56..48acc1d41 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Use this SDK to add realtime video, audio and data features to your Rust app. By - `livekit-api`: Server APIs and auth token generation - `livekit`: LiveKit real-time SDK -- `livekit-ffi`: Internal crate, used to generate bindings for other languages +- `livekit-ffi`: Internal crate, used to generate bindings for other languages. Its `room-apis` feature (default) serves the Python/Node/Unity SDKs; its `core-modules` feature serves the Swift/Android/Flutter SDKs over UniFFI - `livekit-protocol`: LiveKit protocol generated code When adding the SDK as a dependency to your project, make sure to add the diff --git a/livekit-common/Cargo.toml b/livekit-common/Cargo.toml index eb6f2a78f..9716b722d 100644 --- a/livekit-common/Cargo.toml +++ b/livekit-common/Cargo.toml @@ -14,7 +14,7 @@ uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"], optional [features] # Exposes this crate's shared FFI type registrations. Enabled transitively by -# livekit-uniffi so every component borrows one converter per type. +# livekit-ffi so every component borrows one converter per type. uniffi = ["dep:uniffi", "dep:bytes"] # How CI checks this crate's features, read by diff --git a/livekit-data-stream/AGENTS.md b/livekit-data-stream/AGENTS.md index 96685da44..7340e01ae 100644 --- a/livekit-data-stream/AGENTS.md +++ b/livekit-data-stream/AGENTS.md @@ -9,7 +9,7 @@ - Enables proper integration testing - Enables shared implementation amongst multiple _consumers_: - [`livekit`](../livekit/): Rust client SDK - - [`livekit-uniffi`](../livekit-uniffi/): will eventually power downstream client SDKs such as Swift and Kotlin + - [`livekit-ffi`](../livekit-ffi/) (`core-modules` feature): will eventually power downstream client SDKs such as Swift and Kotlin ## Incoming vs. outgoing split diff --git a/livekit-datatrack/AGENTS.md b/livekit-datatrack/AGENTS.md index e918f0b7c..6a60215c6 100644 --- a/livekit-datatrack/AGENTS.md +++ b/livekit-datatrack/AGENTS.md @@ -9,7 +9,7 @@ - Enables proper integration testing - Enables shared implementation amongst multiple _consumers_: - [`livekit`](../livekit/): Rust client SDK - - [`livekit-uniffi`](../livekit-uniffi/): will eventually power downstream client SDKs such as Swift and Kotlin + - [`livekit-ffi`](../livekit-ffi/) (`core-modules` feature): will eventually power downstream client SDKs such as Swift and Kotlin ## Local vs. remote split diff --git a/livekit-net/Cargo.toml b/livekit-net/Cargo.toml index 6852a18f0..1ceaae5fe 100644 --- a/livekit-net/Cargo.toml +++ b/livekit-net/Cargo.toml @@ -51,7 +51,7 @@ mode = "powerset" async-trait = "0.1" # Optional: scaffolding for the `uniffi` feature. `scaffolding-ffi-buffer-fns` -# matches livekit-datatrack/livekit-uniffi so the ABI is consistent when these +# matches livekit-datatrack/livekit-ffi so the ABI is consistent when these # crates are combined into one cdylib. No runtime feature: the exported traits # are `with_foreign` (host-driven futures) and the exported setters are sync, so # the crate stays runtime-agnostic. diff --git a/livekit-net/uniffi.toml b/livekit-net/uniffi.toml index 9031b7333..ca5a2801e 100644 --- a/livekit-net/uniffi.toml +++ b/livekit-net/uniffi.toml @@ -1,10 +1,10 @@ [bindings.kotlin] # The Kotlin checksum test is off because it fails on ARM devices. For more info, see the similar -# comment in livekit-uniffi/uniffi.toml. +# comment in livekit-ffi/uniffi.toml. omit_checksums = true # Kotlin only, so the Rust source and the Swift/Python/Node bindings keep the original name. Lives -# here rather than in livekit-uniffi/uniffi.toml because uniffi applies a rename table only to +# here rather than in livekit-ffi/uniffi.toml because uniffi applies a rename table only to # items from the crate that owns it. [bindings.kotlin.rename] "WsConnection.close" = "close_connection" diff --git a/livekit-rpc/AGENTS.md b/livekit-rpc/AGENTS.md index 6d2bb2d7a..a1d190b59 100644 --- a/livekit-rpc/AGENTS.md +++ b/livekit-rpc/AGENTS.md @@ -9,7 +9,7 @@ - Enables fast unit testing without linking `libwebrtc` - Enables shared implementation amongst multiple _consumers_: - [`livekit`](../livekit/): Rust client SDK - - [`livekit-uniffi`](../livekit-uniffi/): will eventually power downstream client SDKs such as Swift and Kotlin + - [`livekit-ffi`](../livekit-ffi/) (`core-modules` feature): will eventually power downstream client SDKs such as Swift and Kotlin ## Client vs. server split From 0b4aa9a118be8b6bd79580a1d8ad0d81c782c4b9 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 14:00:30 -0400 Subject: [PATCH 23/42] fix(livekit-ffi): make every platform-features entry self-contained Review question on the table: should every entry set `no-default-features = true`? Yes. The entries for python, node and unity did not set it, so they took the `default` feature list and added their own on top. They give the correct flags only while `default` stays as it is now. A later change to `default` would move these three platforms without any sign. Set the flag on all three. The resolved dependency tree does not change, because `default` lists the same two features that these entries list. Make the script ask for the flag on every entry and not only on a core-modules entry. An entry that omits it now stops with a message that says why. --- .github/scripts/platform_features.py | 14 +++++++++----- livekit-ffi/Cargo.toml | 12 ++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/scripts/platform_features.py b/.github/scripts/platform_features.py index de8b36fdb..9e50ac9b1 100755 --- a/.github/scripts/platform_features.py +++ b/.github/scripts/platform_features.py @@ -78,12 +78,16 @@ def flags_for(entry, platform): f"platform '{platform}' must select exactly one of room-apis / " f"core-modules (got: {sorted(surfaces) or 'none'})" ) - # core-modules can only be reached by turning the default room-apis off. - if surfaces == {"core-modules"} and not entry.get("no-default-features"): + # Every entry states its whole feature set. Without this, an entry inherits + # whatever `default` happens to list, so a change there moves platforms + # silently -- and for a core-modules entry it would select both surfaces, + # which do not compile together. + if not entry.get("no-default-features"): sys.exit( - f"platform '{platform}' selects core-modules but does not set " - f"no-default-features; the default feature set enables room-apis, " - f"and the two are mutually exclusive" + f"platform '{platform}' does not set no-default-features; every " + f"entry must list the whole feature set it wants, so that a change " + f"to the crate's `default` features cannot move it onto another " + f"surface" ) flags = [] diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 704dbd47b..1a4f91670 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -142,9 +142,9 @@ serial_test = "3.0" # # This is the single source of truth: CI and Makefile.toml resolve their build # flags from here via .github/scripts/platform_features.py, so a platform -# cannot silently drift onto the wrong surface. `room-apis` and `core-modules` -# are mutually exclusive, so every core-modules entry must turn the default -# feature set off. +# cannot silently drift onto the wrong surface. Every entry turns the default +# features off and lists the whole set it wants, so a change to `default` +# cannot move a platform onto another surface. # # $ .github/scripts/platform_features.py --list # @@ -157,9 +157,9 @@ serial_test = "3.0" [package.metadata.platform-features] # room-apis -- SDKs with no WebRTC stack of their own. These consume the C ABI # (and, increasingly, UniFFI) out of the livekit-ffi release archives. -python = { features = ["room-apis", "rustls-tls-native-roots"] } -node = { features = ["room-apis", "rustls-tls-native-roots"] } -unity = { features = ["room-apis", "rustls-tls-native-roots"] } +python = { no-default-features = true, features = ["room-apis", "rustls-tls-native-roots"] } +node = { no-default-features = true, features = ["room-apis", "rustls-tls-native-roots"] } +unity = { no-default-features = true, features = ["room-apis", "rustls-tls-native-roots"] } ffi-ios = { no-default-features = true, features = ["room-apis", "rustls-tls-webpki-roots"] } ffi-android = { no-default-features = true, features = ["room-apis", "rustls-tls-webpki-roots"] } ffi-windows-arm64 = { no-default-features = true, features = ["room-apis", "native-tls"] } From d83416df94b56e98f40d7424bae2a38fff9a3b81 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 14:11:36 -0400 Subject: [PATCH 24/42] fix(livekit-ffi): keep crate::conversion resolving after the move The capture source work on main adds livekit-ffi/src/server/capture.rs, which imports from `crate::conversion`. The rebase puts that file in the room_apis module, where the path no longer resolves, and a build with the `capture` feature stops with an error. The cause is visibility. The glob re-export at the crate root republishes only the public items of the surface, and `conversion` is private. The paths `crate::proto` and `crate::server` continue to work, because those modules are public, but `crate::conversion` does not. Make `conversion` visible to the crate and re-export it at the crate root. The module stays out of the public API, and the path is the same as before the move. Code that comes from main thus needs no change now or later. --- livekit-ffi/src/lib.rs | 6 ++++++ livekit-ffi/src/room_apis/mod.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/livekit-ffi/src/lib.rs b/livekit-ffi/src/lib.rs index 3f1e49735..8c9612e83 100644 --- a/livekit-ffi/src/lib.rs +++ b/livekit-ffi/src/lib.rs @@ -45,6 +45,12 @@ mod room_apis; #[cfg(feature = "room-apis")] pub use room_apis::*; +// `conversion` is not part of the public API, so the glob above does not reach +// it. Re-export it for this crate, which keeps the `crate::conversion` paths +// that the surface used before it moved into `room_apis`. +#[cfg(feature = "room-apis")] +pub(crate) use room_apis::conversion; + #[cfg(feature = "core-modules")] mod core_modules; #[cfg(feature = "core-modules")] diff --git a/livekit-ffi/src/room_apis/mod.rs b/livekit-ffi/src/room_apis/mod.rs index bcd294a53..380f635c2 100644 --- a/livekit-ffi/src/room_apis/mod.rs +++ b/livekit-ffi/src/room_apis/mod.rs @@ -28,7 +28,7 @@ use lazy_static::lazy_static; use livekit::prelude::*; use thiserror::Error; -mod conversion; +pub(crate) mod conversion; pub mod cabi; pub mod proto; From 99b9379484ab5eef107dd7f861b937da6d1a53ee Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 15:43:58 -0400 Subject: [PATCH 25/42] feat(livekit-ffi): resolve build features per platform in the packaging Every task in Makefile.toml built livekit-ffi/core-modules, because [tasks.build] asked the resolver for a surface and not for a platform. Nothing that a task said could change it. A bindgen only chose an output language, and the library it read was already built. Each task now names its platform in LK_PLATFORM, and the build resolves its flags from the platform table in Cargo.toml. To add a bindgen, add a row to that table. The kotlin, dart and swift tasks keep the features they had, because all three platforms are on core-modules. The python task changes: the Python SDK consumes the room-apis release archive, so its bindings now come from that surface. This makes the task the local equivalent of the Python bindings step in .github/workflows/ffi-builds.yml. LK_PLATFORM must be set on a task that drives the work through `run_task`. A task that uses `extend` has its own env out of scope while its dependencies run, and `build` is a dependency, so bindgen-kotlin and bindgen-python change to the wrapper shape that bindgen-dart already uses. The android and swift tasks name the platform in their scripts, for the same reason the android profile switch is in its script. The flags are resolved with `set -e` in front. If the resolver fails, the assignment fails and the shell stops. Without it the flags would be empty and cargo would quietly build the default features, which is the wrong surface for most of these tasks. --- livekit-ffi/Makefile.toml | 61 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/livekit-ffi/Makefile.toml b/livekit-ffi/Makefile.toml index c4993f10a..f898cf1ff 100644 --- a/livekit-ffi/Makefile.toml +++ b/livekit-ffi/Makefile.toml @@ -30,12 +30,15 @@ PUBSPEC_PUBLISH_TO = "none" # `/v/build-.zip` (and a `.sha256` sidecar). CDYLIB_DOWNLOAD_BASE_URL = "https://github.com/livekit/rust-sdks/releases/download/livekit-ffi" -# Cargo flags selecting livekit-ffi/core-modules, resolved from -# [package.metadata.platform-features] in Cargo.toml so the packaging here and -# the CI workflows cannot drift apart. Every cargo invocation below relies on -# the working directory rather than -p, so each one needs these: without them -# it would build the default livekit-ffi/room-apis and all of libwebrtc. -LK_CORE_FEATURE_ARGS = { script = ["python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py --surface core-modules"] } +# Every cargo invocation below relies on the working directory rather than -p, +# so each one must say which feature set to build. Each task names a platform +# in LK_PLATFORM, and the build resolves its flags from the +# [package.metadata.platform-features] table in Cargo.toml. A new bindgen thus +# needs a row in that table and nothing else. +# +# LK_PLATFORM has to be set on a task that drives the work through `run_task`, +# not on one that `extend`s another: a task's own env is not in scope while its +# dependencies run, and `build` is a dependency. [env.release] PUBSPEC_PUBLISH_TO = "" @@ -56,14 +59,19 @@ script_runner = "@duckscript" script = """ echo "TARGET: ${TARGET}" +echo "PLATFORM: ${LK_PLATFORM}" + +# `set -e` matters: if the resolver fails, the assignment fails and the shell +# stops. Without it the flags would be empty and cargo would quietly build the +# default feature set, which is the wrong surface for most callers. if not ${TIER_3} # Ensure target is installed exec rustup target add ${TARGET} - exec sh -c "cargo build --release ${LK_CORE_FEATURE_ARGS} --target ${TARGET}" + exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py ${LK_PLATFORM}); cargo build --release $FLAGS --target ${TARGET}" else # Ensure nightly toolchain is installed exec rustup component add rust-src --toolchain "nightly-${CARGO_MAKE_RUST_TARGET_TRIPLE}" - exec sh -c "cargo +nightly build -Zbuild-std=std,panic_abort --release ${LK_CORE_FEATURE_ARGS} --target ${TARGET}" + exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py ${LK_PLATFORM}); cargo +nightly build -Zbuild-std=std,panic_abort --release $FLAGS --target ${TARGET}" end """ @@ -93,26 +101,29 @@ if [ "${CARGO_MAKE_PROFILE}" = "release" ]; then export RUSTFLAGS="-C force-unwind-tables=no" fi -cargo ndk --target "${TARGET}" build ${LK_CORE_FEATURE_ARGS} ${release_flag} +# Named here and not in env for the same reason as the profile switch above. +FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py" android) + +cargo ndk --target "${TARGET}" build ${FLAGS} ${release_flag} """ # MARK: - Build, specific platforms [tasks.build-windows-x64] extend = "build" -env = { TARGET = "x86_64-pc-windows-msvc" } +env = { TARGET = "x86_64-pc-windows-msvc", LK_PLATFORM = "dart" } [tasks.build-windows-aarch64] extend = "build" -env = { TARGET = "aarch64-pc-windows-msvc" } +env = { TARGET = "aarch64-pc-windows-msvc", LK_PLATFORM = "dart" } [tasks.build-linux-aarch64] extend = "build" -env = { TARGET = "aarch64-unknown-linux-gnu" } +env = { TARGET = "aarch64-unknown-linux-gnu", LK_PLATFORM = "dart" } [tasks.build-linux-x64] extend = "build" -env = { TARGET = "x86_64-unknown-linux-gnu" } +env = { TARGET = "x86_64-unknown-linux-gnu", LK_PLATFORM = "dart" } [tasks.build-android-aarch64] private = false @@ -201,15 +212,18 @@ dependencies = [ [tasks.bindgen-kotlin] private = false -extend = "uniffi-bindgen" -env = { LANG = "kotlin" } +env = { LANG = "kotlin", LK_PLATFORM = "android" } +run_task = "uniffi-bindgen" # MARK: - Python [tasks.bindgen-python] private = false -extend = "uniffi-bindgen" -env = { LANG = "python" } +# The Python SDK consumes the room-apis release archive, so these bindings are +# generated from that surface. This is the local equivalent of the Python +# bindings step in .github/workflows/ffi-builds.yml. +env = { LANG = "python", LK_PLATFORM = "python" } +run_task = "uniffi-bindgen" # MARK: - Swift # @@ -272,9 +286,11 @@ for p in ${SPM_PLATFORMS}; do platform_flags="$platform_flags --platforms $p" done +FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py" "${LK_PLATFORM}") + cargo swift package \ --accept-all \ -${LK_CORE_FEATURE_ARGS} \ +${FLAGS} \ --name ${SPM_NAME} \ $platform_flags \ --lib-type dynamic \ @@ -441,7 +457,7 @@ dependencies = [ [tasks.swift-package] description = "Generate Swift package using cargo-swift" -env = { LANG = "swift" } +env = { LANG = "swift", LK_PLATFORM = "swift" } run_task = "swift-package-flow" # Same flow as `swift-package`, restricted to macos by default — a debug @@ -455,6 +471,7 @@ run_task = "swift-package-flow" [tasks.swift-package-debug.env] LANG = "swift" +LK_PLATFORM = "swift" SPM_PLATFORMS = { value = "macos", condition = { env_not_set = ["SPM_PLATFORMS"] } } [tasks.swift-package-debug] @@ -563,7 +580,7 @@ echo "AAR: ${out_dir}/${ANDROID_AAR_BASENAME}.aar" # only applies once its dependencies run, hence run_task. [tasks.android-bindgen-kotlin] private = true -env = { TARGET = "${CARGO_MAKE_RUST_TARGET_TRIPLE}", CARGO_PROFILE_RELEASE_STRIP = "false" } +env = { TARGET = "${CARGO_MAKE_RUST_TARGET_TRIPLE}", LK_PLATFORM = "android", CARGO_PROFILE_RELEASE_STRIP = "false" } run_task = "bindgen-kotlin" [tasks.android-package-flow] @@ -616,7 +633,7 @@ description = "Generate Dart bindings" # The env must be in scope while the `build` dependency compiles, but a task's # own env only applies after its dependencies have run; so set it on this # wrapper and drive the work through run_task (same pattern as dart-package). -env = { LANG = "dart", CARGO_PROFILE_RELEASE_STRIP = "false" } +env = { LANG = "dart", LK_PLATFORM = "dart", CARGO_PROFILE_RELEASE_STRIP = "false" } run_task = "bindgen-dart-flow" [tasks.bindgen-dart-flow] @@ -735,5 +752,5 @@ dependencies = [ [tasks.dart-package] description = "Build a consumable Dart package with bindings, hook, and (dev) native lib" # See bindgen-dart: keep symbols so library-mode bindgen can read metadata. -env = { LANG = "dart", CARGO_PROFILE_RELEASE_STRIP = "false" } +env = { LANG = "dart", LK_PLATFORM = "dart", CARGO_PROFILE_RELEASE_STRIP = "false" } run_task = "dart-package-flow" From e3d654eebf685926a8de53b66f92f81b0724e7ce Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 15:44:21 -0400 Subject: [PATCH 26/42] refactor(ci): state the core-modules flags in the per-commit gates These three steps build no artifact for any platform. They run over eleven target triples and ship nothing. Their only purpose is to show that the core-modules surface still compiles and that its tests still pass. Naming a platform here would mean choosing one that does not describe the step, which is the problem that the platform table exists to remove. State the two flags instead, with a comment that says where platform builds get theirs. This leaves the resolver with one job: answer for a platform. --- .github/workflows/builds.yml | 16 ++++++++++++---- .github/workflows/tests.yml | 8 ++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 3ea4de373..683c16e9d 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -182,12 +182,20 @@ jobs: if: ${{ !contains(matrix.target, 'android') }} run: | set -euo pipefail - FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) - cargo build --release -p livekit-ffi --target ${{ matrix.target }} $FLAGS + # Not a platform build: this gate only checks that the + # core-modules surface still compiles. Platform builds take their + # flags from [package.metadata.platform-features] in + # livekit-ffi/Cargo.toml. + cargo build --release -p livekit-ffi --target ${{ matrix.target }} \ + --no-default-features --features core-modules - name: Build (Android, livekit-ffi core-modules) if: ${{ contains(matrix.target, 'android') }} run: | set -euo pipefail - FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) - cargo ndk --target ${{ matrix.target }} build --release -p livekit-ffi $FLAGS + # Not a platform build: this gate only checks that the + # core-modules surface still compiles. Platform builds take their + # flags from [package.metadata.platform-features] in + # livekit-ffi/Cargo.toml. + cargo ndk --target ${{ matrix.target }} build --release -p livekit-ffi \ + --no-default-features --features core-modules diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5936736f3..8afa5cc34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -190,8 +190,12 @@ jobs: shell: bash run: | set -euo pipefail - FLAGS=$(python3 .github/scripts/platform_features.py --surface core-modules) - cargo test --verbose --target ${{ matrix.target }} -p livekit-ffi $FLAGS + # Not a platform build: this gate only checks that the + # core-modules surface still passes its tests. Platform builds take their + # flags from [package.metadata.platform-features] in + # livekit-ffi/Cargo.toml. + cargo test --verbose --target ${{ matrix.target }} -p livekit-ffi \ + --no-default-features --features core-modules - name: Test RPC seam (livekit-rpc) shell: bash From cded005915c4af75acc5cd119996414e1e46e674 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 15:45:01 -0400 Subject: [PATCH 27/42] refactor(ci): remove --surface from platform_features.py Nothing calls it now. The packaging asks for a platform, and the per-commit gates state their flags directly. The option existed because the packaging built one surface for several platforms at once and had no platform to name. That is no longer true: each task names the platform it builds for. Removing it also removes the question of what a caller should pass when no platform describes it. The answer now is that a build site which produces an artifact always has a platform, and one which does not is not in the table. --- .github/scripts/platform_features.py | 54 ++-------------------------- livekit-ffi/Cargo.toml | 9 +++-- 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/.github/scripts/platform_features.py b/.github/scripts/platform_features.py index 9e50ac9b1..3043fa81a 100755 --- a/.github/scripts/platform_features.py +++ b/.github/scripts/platform_features.py @@ -23,20 +23,12 @@ $ platform_features.py swift --no-default-features --features core-modules - $ platform_features.py --surface core-modules - --no-default-features --features core-modules - -`--surface` is for build sites that are not platform builds and so have no -platform to name: the per-commit compile/test gates in builds.yml and -tests.yml, and the shared host cdylib in livekit-ffi/Makefile.toml that the -bindgens read UniFFI metadata from. Anything that builds an artifact for a -platform names that platform instead. - $ platform_features.py --list android --no-default-features --features core-modules ... -Used by the uniffi-* workflows and by livekit-ffi/Makefile.toml. +Every build site that produces an artifact names its platform. Used by +livekit-ffi/Makefile.toml and by the uniffi-* workflows. """ import argparse @@ -97,45 +89,9 @@ def flags_for(entry, platform): return " ".join(flags) -def flags_for_surface(table, surface): - """Flags shared by every platform on `surface`. - - For callers with no platform to name -- the per-commit gates in builds.yml - and tests.yml, and the shared host build in livekit-ffi/Makefile.toml that - serves the swift, android and dart flows at once. A caller that builds an - artifact for one platform should name that platform instead. - - Every platform on the surface must agree, otherwise there is no single - answer and the caller has to name a platform. That holds for core-modules - and deliberately does not for room-apis, where the TLS backend varies by - target. - """ - resolved = { - name: flags_for(entry, name) - for name, entry in table.items() - if surface in (entry.get("features") or []) - } - if not resolved: - sys.exit(f"no platform declares the '{surface}' surface") - distinct = set(resolved.values()) - if len(distinct) != 1: - detail = "\n".join(f" {n}: {f}" for n, f in sorted(resolved.items())) - sys.exit( - f"platforms on the '{surface}' surface do not share one feature " - f"set, so it cannot be resolved by surface:\n{detail}" - ) - return distinct.pop() - - def main(): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("platform", nargs="?", help="platform key to resolve") - ap.add_argument( - "--surface", - choices=["room-apis", "core-modules"], - help="flags shared by every platform on this surface; for build " - "sites that are not platform builds (see the module docstring)", - ) ap.add_argument( "--list", action="store_true", help="print every platform and its flags" ) @@ -143,10 +99,6 @@ def main(): table = load_table() - if args.surface: - print(flags_for_surface(table, args.surface)) - return - if args.list: width = max(len(k) for k in table) for name in sorted(table): @@ -154,7 +106,7 @@ def main(): return if not args.platform: - ap.error("a platform key is required (or --surface / --list)") + ap.error("a platform key is required (or --list)") if args.platform not in table: sys.exit( diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 1a4f91670..21ac33fef 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -148,11 +148,10 @@ serial_test = "3.0" # # $ .github/scripts/platform_features.py --list # -# A build site that produces an artifact for a platform names that platform. -# The script also answers `--surface ` for the few sites that are not -# platform builds at all -- the per-commit gates in builds.yml and tests.yml, -# and the shared host build in Makefile.toml that the bindgens read metadata -# from. +# Every build site that produces an artifact names its platform and takes its +# flags from the row below, so adding a bindgen or a package means adding a +# row. The per-commit gates in builds.yml and tests.yml build no artifact for +# any platform and state their flags directly. # [package.metadata.platform-features] # room-apis -- SDKs with no WebRTC stack of their own. These consume the C ABI From c854feb566edd2dddcc21010adddc42c4c0675ef Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 15:45:10 -0400 Subject: [PATCH 28/42] chore(livekit-ffi): remove the orphaned python test harness The script imported the core-modules API from `../generated/python`. No task writes to that directory: bindgen-python writes to `packages/python`. The script was therefore already broken, and no CI job or cargo-make task ran it. It is now also unreachable in principle. The python bindings come from the room-apis surface, which has none of the functions that the script calls. The file is in the git history if the core-modules python bindings are wanted again. --- livekit-ffi/python_test/main.py | 55 --------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 livekit-ffi/python_test/main.py diff --git a/livekit-ffi/python_test/main.py b/livekit-ffi/python_test/main.py deleted file mode 100644 index 17ede299a..000000000 --- a/livekit-ffi/python_test/main.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 LiveKit, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import os - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'generated', 'python'))) - -import asyncio -from livekit_uniffi import * - -def main(): - # Receive log messages from Rust - log_forward_bootstrap(level=LogForwardFilter.DEBUG) - - # Print FFI version - print(f"FFI version: v{build_version()}") - - credentials = ApiCredentials(key="devkey", secret="secret") - - jwt = token_generate( - options=TokenOptions(room_name="test", identity="some_participant"), - credentials=credentials, - ) - print(f"Generated JWT: {jwt}") - - decoded_grants = token_verify( - token=jwt, - credentials=credentials, - ) - print(f"Verified generated JWT: {decoded_grants}") - - async def receive_log_messages(): - while True: - message = await log_forward_receive() - if message is None: - print("Log forwarding ended") - break - print(f"Log from Rust: {message}") - - asyncio.run(receive_log_messages()) - -if __name__ == "__main__": - main() From 99f5e06369b95255cc5c7fd37303a5d78f24a160 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 16:02:17 -0400 Subject: [PATCH 29/42] fix: adjust changeset to make ci check pass --- .changeset/merge_uniffi_into_ffi.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.changeset/merge_uniffi_into_ffi.md b/.changeset/merge_uniffi_into_ffi.md index 6c04adb76..c1b034d07 100644 --- a/.changeset/merge_uniffi_into_ffi.md +++ b/.changeset/merge_uniffi_into_ffi.md @@ -1,5 +1,11 @@ --- -livekit-ffi: minor +livekit-ffi: major +livekit-common: patch +livekit-data-stream: patch +livekit-datatrack: patch +livekit-net: patch +livekit-rpc: patch +livekit-token-source: patch --- Merge `livekit-uniffi` into `livekit-ffi`. From be33b2e13a99e537ca745a219c246d9a7b18fb08 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 16:02:39 -0400 Subject: [PATCH 30/42] refactor: drop useless comments --- livekit-ffi/Makefile.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/livekit-ffi/Makefile.toml b/livekit-ffi/Makefile.toml index f898cf1ff..045a72e56 100644 --- a/livekit-ffi/Makefile.toml +++ b/livekit-ffi/Makefile.toml @@ -219,9 +219,6 @@ run_task = "uniffi-bindgen" [tasks.bindgen-python] private = false -# The Python SDK consumes the room-apis release archive, so these bindings are -# generated from that surface. This is the local equivalent of the Python -# bindings step in .github/workflows/ffi-builds.yml. env = { LANG = "python", LK_PLATFORM = "python" } run_task = "uniffi-bindgen" @@ -616,9 +613,6 @@ description = "Build Android AAR with native libs and Kotlin bindings; publish t dependencies = ["android-package-flow"] run_task = "android-publish-maven-local" -# Add other bindgen tasks here, following the same pattern -# as above for the task name (e.g., "bindgen-"). - # MARK: - Dart # uniffi-dart has no standalone CLI, so the `uniffi-bindgen-dart` bin drives its @@ -754,3 +748,6 @@ description = "Build a consumable Dart package with bindings, hook, and (dev) na # See bindgen-dart: keep symbols so library-mode bindgen can read metadata. env = { LANG = "dart", LK_PLATFORM = "dart", CARGO_PROFILE_RELEASE_STRIP = "false" } run_task = "dart-package-flow" + +# Add other bindgen tasks here, following the same pattern +# as above for the task name (e.g., "bindgen-"). From 5da03fefdee76c4aa591c6e64990c8d516bac432 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 16:11:30 -0400 Subject: [PATCH 31/42] fix(changeset): cover the remaining dependent crates Dropping the commit that edited two older changesets took their bumps out of this pull request. The check counts a bump only from a changeset that the pull request changes, so livekit, livekit-api, livekit-capture and livekit-signaling lost their coverage, although this branch still changes crates that they depend on. Declare those four here, with the dependents that this changeset already names. --- .changeset/merge_uniffi_into_ffi.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.changeset/merge_uniffi_into_ffi.md b/.changeset/merge_uniffi_into_ffi.md index c1b034d07..5b60cbbf0 100644 --- a/.changeset/merge_uniffi_into_ffi.md +++ b/.changeset/merge_uniffi_into_ffi.md @@ -1,10 +1,14 @@ --- livekit-ffi: major +livekit: patch +livekit-api: patch +livekit-capture: patch livekit-common: patch livekit-data-stream: patch livekit-datatrack: patch livekit-net: patch livekit-rpc: patch +livekit-signaling: patch livekit-token-source: patch --- From 3aff285ce7c6f0ef00a62016cfaaeacfc57e4f35 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Fri, 18 Sep 2026 16:39:15 -0400 Subject: [PATCH 32/42] fix(ci): run the core-modules build steps under bash The two Windows build jobs failed. A Windows runner uses PowerShell, and both steps were written for bash: a backslash at the end of a line continues it in bash, but not in PowerShell. PowerShell read the second line as a new statement and stopped with "Missing expression after unary operator". The step failed before cargo started. Ask for bash, as the equivalent steps in tests.yml and uniffi-cdylib.yml do. The android step has the same shape and runs only on Linux, but say it there too: one of the two without it is how this comes back. Remove `set -euo pipefail`. GitHub already starts a `shell: bash` step with `-eo pipefail`. --- .github/workflows/builds.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 683c16e9d..abbf8435c 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -180,8 +180,8 @@ jobs: # livekit-uniffi merge that coverage came free from the separate crate.) - name: Build (Cargo, livekit-ffi core-modules) if: ${{ !contains(matrix.target, 'android') }} + shell: bash run: | - set -euo pipefail # Not a platform build: this gate only checks that the # core-modules surface still compiles. Platform builds take their # flags from [package.metadata.platform-features] in @@ -191,8 +191,8 @@ jobs: - name: Build (Android, livekit-ffi core-modules) if: ${{ contains(matrix.target, 'android') }} + shell: bash run: | - set -euo pipefail # Not a platform build: this gate only checks that the # core-modules surface still compiles. Platform builds take their # flags from [package.metadata.platform-features] in From 088b773a1b8ac6dc1a1ccf633846d95023db04a2 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Mon, 21 Sep 2026 13:27:49 -0400 Subject: [PATCH 33/42] feat: upgrade to prost 0.14 across the whole project Previously, prost 0.12 was being used in some places (livekit-protocol), and 0.14 in others (livekit, livekit-signaling, etc). This made it annoying in the livekit-ffi / livekit-uniffi merge, because a single crate was now re-exporting potentially many different subcrates, potentially with different prost versions. I can't figure out a good reason _why_ 0.12 is used in the places it is, so this pull request upgrades everything to 0.14 and pins all prost versions to the workspace version via `workspace = true`. Also, upgrades `pbjson-types` to `0.9` since that specifies a prost 0.14 peer dependency. --- Cargo.lock | 24 ------------------- livekit-ffi/Cargo.toml | 11 +-------- .../src/core_modules/data_stream/common.rs | 2 +- .../src/core_modules/data_stream/outgoing.rs | 2 +- .../src/core_modules/data_stream/tests.rs | 2 +- .../src/core_modules/data_track/common.rs | 4 ++-- .../src/core_modules/data_track/local.rs | 2 +- .../src/core_modules/data_track/remote.rs | 2 +- 8 files changed, 8 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72af7c10e..202b71d08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3923,7 +3923,6 @@ dependencies = [ "log", "once_cell", "parking_lot", - "prost 0.12.6", "prost 0.14.4", "prost-build", "serial_test", @@ -5687,16 +5686,6 @@ dependencies = [ "prost-derive 0.11.9", ] -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive 0.12.6", -] - [[package]] name = "prost" version = "0.14.4" @@ -5739,19 +5728,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "prost-derive" version = "0.14.4" diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 21ac33fef..9ed0dd28e 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -46,7 +46,7 @@ core-modules = [ "dep:livekit-datatrack", "dep:livekit-net", "dep:livekit-token", - "dep:prost_012", + "dep:prost", "dep:async-trait", "dep:once_cell", "dep:tokio-util", @@ -116,15 +116,6 @@ downcast-rs = { version = "1.2", optional = true } from_variants = { version = "1.0.2", optional = true } console-subscriber = { workspace = true, features = ["parking_lot"], optional = true } -# prost 0.12, alongside the 0.14 above. livekit-protocol and livekit-data-stream -# are on 0.12 and the core modules call `Message` on their generated types, -# while livekit-ffi's own protos are generated by prost-build 0.14 and emit -# `::prost::` paths, so that name has to stay bound to 0.14. The two surfaces -# are mutually exclusive, so the two versions never compile together. -# -# TODO: drop this once the workspace unifies on a single prost. -prost_012 = { package = "prost", version = "0.12", optional = true } - [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" link-cplusplus = { version = "1", features = ["nothing"] } diff --git a/livekit-ffi/src/core_modules/data_stream/common.rs b/livekit-ffi/src/core_modules/data_stream/common.rs index 12772af4f..4a92a8598 100644 --- a/livekit-ffi/src/core_modules/data_stream/common.rs +++ b/livekit-ffi/src/core_modules/data_stream/common.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use livekit_common as common; use livekit_data_stream::{api as ds_api, backend as ds}; use livekit_protocol as proto; -use prost_012::Message; +use prost::Message; // MARK: - Enums diff --git a/livekit-ffi/src/core_modules/data_stream/outgoing.rs b/livekit-ffi/src/core_modules/data_stream/outgoing.rs index b3660a545..ac6344b84 100644 --- a/livekit-ffi/src/core_modules/data_stream/outgoing.rs +++ b/livekit-ffi/src/core_modules/data_stream/outgoing.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use bytes::Bytes; use livekit_common as lk_common; use livekit_data_stream::{api as ds_api, backend as ds}; -use prost_012::Message as _; +use prost::Message as _; use tokio_util::sync::{CancellationToken, DropGuard}; use super::common::{ diff --git a/livekit-ffi/src/core_modules/data_stream/tests.rs b/livekit-ffi/src/core_modules/data_stream/tests.rs index 3270c00e8..2498c5e2c 100644 --- a/livekit-ffi/src/core_modules/data_stream/tests.rs +++ b/livekit-ffi/src/core_modules/data_stream/tests.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex}; use bytes::Bytes; use livekit_protocol as proto; -use prost_012::Message as _; +use prost::Message as _; use tokio::sync::oneshot; use super::common::{ diff --git a/livekit-ffi/src/core_modules/data_track/common.rs b/livekit-ffi/src/core_modules/data_track/common.rs index 52462b358..f7357418d 100644 --- a/livekit-ffi/src/core_modules/data_track/common.rs +++ b/livekit-ffi/src/core_modules/data_track/common.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use livekit_datatrack::api::{DataTrackFrameEncoding, DataTrackSchemaEncoding, DataTrackSid}; use livekit_protocol as proto; -use prost_012::Message; +use prost::Message; uniffi::custom_type!(DataTrackSid, String, { remote, @@ -98,7 +98,7 @@ impl From for DataTrackSchemaId { #[uniffi(flat_error)] pub enum HandleSignalResponseError { #[error("Response decoding failed: {0}")] - Decode(prost_012::DecodeError), + Decode(prost::DecodeError), #[error("Response container has no message")] EmptyMessage, #[error("Unsupported response type in this context")] diff --git a/livekit-ffi/src/core_modules/data_track/local.rs b/livekit-ffi/src/core_modules/data_track/local.rs index 95d92a89f..ebee263f7 100644 --- a/livekit-ffi/src/core_modules/data_track/local.rs +++ b/livekit-ffi/src/core_modules/data_track/local.rs @@ -23,7 +23,7 @@ use livekit_datatrack::{ backend::{local, EncryptionProvider}, }; use livekit_protocol as proto; -use prost_012::Message; +use prost::Message; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; diff --git a/livekit-ffi/src/core_modules/data_track/remote.rs b/livekit-ffi/src/core_modules/data_track/remote.rs index 13b1ae882..65a95202a 100644 --- a/livekit-ffi/src/core_modules/data_track/remote.rs +++ b/livekit-ffi/src/core_modules/data_track/remote.rs @@ -22,7 +22,7 @@ use livekit_datatrack::{ backend::{remote, DecryptionProvider}, }; use livekit_protocol as proto; -use prost_012::Message; +use prost::Message; use std::sync::Arc; use tokio::sync::Mutex; use tokio_util::sync::{CancellationToken, DropGuard}; From 84736d85ddef289b0e034ea0370faca1707993fb Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 22 Sep 2026 16:42:56 -0400 Subject: [PATCH 34/42] docs(livekit-ffi): drop the stale note about a split prost The crate no longer holds two versions of prost. The upgrade to prost 0.14 across the workspace removed the renamed `prost_012` dependency, and this note was the only place left that still described it. Verified: `prost_012` appears nowhere else in the tree. --- livekit-ffi/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/livekit-ffi/README.md b/livekit-ffi/README.md index f3bd8cccf..92b2e5830 100644 --- a/livekit-ffi/README.md +++ b/livekit-ffi/README.md @@ -62,10 +62,6 @@ single source of truth, and CI resolves its build flags from it: linker, and panics on any target it does not recognise — visionOS, tvOS and Mac Catalyst among them. That gate is what makes the core-modules Apple builds possible. -- **`prost` is split.** `prost` is 0.14, for this crate's own generated protos; - `prost_012` is 0.12, for the `livekit-protocol` types used in - `src/core_modules/`. The surfaces are mutually exclusive, so the two versions - never compile together. - **Verify UniFFI API changes by compiling the Kotlin bindings** (`cargo make android-package`). A green `cargo build` proves nothing there; see the UniFFI section of the root [AGENTS.md](../AGENTS.md) for the specific From 95faa05e6e965d35444c3e2f4f2318877d46f209 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 22 Sep 2026 16:43:14 -0400 Subject: [PATCH 35/42] docs(livekit-ffi): say why core-modules excludes the WebRTC stack The text explained the excluded dependencies by the Apple targets they make possible. That is a consequence, not the reason. An SDK that brings its own WebRTC stack must not get a second one from this crate. The Apple targets are still mentioned, under the build script note that actually depends on them. --- livekit-ffi/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/livekit-ffi/README.md b/livekit-ffi/README.md index 92b2e5830..e340ea600 100644 --- a/livekit-ffi/README.md +++ b/livekit-ffi/README.md @@ -26,9 +26,9 @@ Mode specific source lives in `src/room_apis/`. Core business logic exposed purely over [UniFFI](https://mozilla.github.io/uniffi-rs/latest/): access tokens, log forwarding, data tracks and data streams v2. Used by the SDKs that already ship -their own WebRTC stack, so this build depends on none of `livekit`, -`libwebrtc`, `soxr-sys` or `imgproc` — which is what lets it target visionOS, -tvOS and Mac Catalyst: +their own WebRTC stack. This build therefore depends on none of `livekit`, +`libwebrtc`, `soxr-sys` or `imgproc`, so an application does not ship two +WebRTC stacks: - [Swift](https://github.com/livekit/client-sdk-swift) - [Android](https://github.com/livekit/client-sdk-android) From d6cde30ca692a02c264d8f61f9831f43f1c8be73 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 22 Sep 2026 16:43:22 -0400 Subject: [PATCH 36/42] chore(ci): correct the copyright year on the feature resolver The file is new in this branch, written in 2026. Its header said 2025. --- .github/scripts/platform_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/platform_features.py b/.github/scripts/platform_features.py index 3043fa81a..989ab6433 100755 --- a/.github/scripts/platform_features.py +++ b/.github/scripts/platform_features.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2025 LiveKit, Inc. +# Copyright 2026 LiveKit, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 64c6dac71664969106f38b926fa4407a3de8d1ed Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 22 Sep 2026 16:43:46 -0400 Subject: [PATCH 37/42] refactor(ci): rename platform_features.py to ffi_features.py The script answers for one crate, livekit-ffi, and the name now says so. The word "platform" described only the key it takes. The table it reads keeps its name, because that name describes the keys and not the crate. --- .github/scripts/{platform_features.py => ffi_features.py} | 4 ++-- .github/workflows/uniffi-cdylib.yml | 6 +++--- livekit-ffi/Cargo.toml | 4 ++-- livekit-ffi/Makefile.toml | 8 ++++---- livekit-ffi/README.md | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename .github/scripts/{platform_features.py => ffi_features.py} (98%) diff --git a/.github/scripts/platform_features.py b/.github/scripts/ffi_features.py similarity index 98% rename from .github/scripts/platform_features.py rename to .github/scripts/ffi_features.py index 989ab6433..85329b11d 100755 --- a/.github/scripts/platform_features.py +++ b/.github/scripts/ffi_features.py @@ -20,10 +20,10 @@ in `[package.metadata.platform-features]` in livekit-ffi/Cargo.toml, and read back here so a platform cannot silently drift onto the wrong surface. - $ platform_features.py swift + $ ffi_features.py swift --no-default-features --features core-modules - $ platform_features.py --list + $ ffi_features.py --list android --no-default-features --features core-modules ... diff --git a/.github/workflows/uniffi-cdylib.yml b/.github/workflows/uniffi-cdylib.yml index 90c66a3d2..b8458acf8 100644 --- a/.github/workflows/uniffi-cdylib.yml +++ b/.github/workflows/uniffi-cdylib.yml @@ -101,7 +101,7 @@ jobs: shell: bash run: | set -euo pipefail - FLAGS=$(python3 .github/scripts/platform_features.py dart) + FLAGS=$(python3 .github/scripts/ffi_features.py dart) cargo build --release --target ${{ matrix.target }} -p livekit-ffi $FLAGS # Linux: build inside a manylinux_2_28 container so the shared library @@ -119,7 +119,7 @@ jobs: BUILD_IMAGE: ${{ matrix.build_image }} run: | set -euo pipefail - export FLAGS=$(python3 .github/scripts/platform_features.py dart) + export FLAGS=$(python3 .github/scripts/ffi_features.py dart) docker run --rm -e TARGET -e FLAGS -v "$PWD:/workspace" -w /workspace "$BUILD_IMAGE" bash -euo pipefail -c ' curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none export PATH="/root/.cargo/bin:$PATH" @@ -146,7 +146,7 @@ jobs: if: ${{ matrix.platform == 'android' }} run: | set -euo pipefail - FLAGS=$(python3 .github/scripts/platform_features.py dart) + FLAGS=$(python3 .github/scripts/ffi_features.py dart) cargo ndk --target ${{ matrix.target }} build --release -p livekit-ffi $FLAGS # macOS/Linux: zip + shasum are available. diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 9ed0dd28e..04dfe6457 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -132,12 +132,12 @@ serial_test = "3.0" # Which FFI mode each downstream package build is configured to use: room-apis or core-modules. # # This is the single source of truth: CI and Makefile.toml resolve their build -# flags from here via .github/scripts/platform_features.py, so a platform +# flags from here via .github/scripts/ffi_features.py, so a platform # cannot silently drift onto the wrong surface. Every entry turns the default # features off and lists the whole set it wants, so a change to `default` # cannot move a platform onto another surface. # -# $ .github/scripts/platform_features.py --list +# $ .github/scripts/ffi_features.py --list # # Every build site that produces an artifact names its platform and takes its # flags from the row below, so adding a bindgen or a package means adding a diff --git a/livekit-ffi/Makefile.toml b/livekit-ffi/Makefile.toml index 045a72e56..ef24baf1f 100644 --- a/livekit-ffi/Makefile.toml +++ b/livekit-ffi/Makefile.toml @@ -67,11 +67,11 @@ echo "PLATFORM: ${LK_PLATFORM}" if not ${TIER_3} # Ensure target is installed exec rustup target add ${TARGET} - exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py ${LK_PLATFORM}); cargo build --release $FLAGS --target ${TARGET}" + exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/ffi_features.py ${LK_PLATFORM}); cargo build --release $FLAGS --target ${TARGET}" else # Ensure nightly toolchain is installed exec rustup component add rust-src --toolchain "nightly-${CARGO_MAKE_RUST_TARGET_TRIPLE}" - exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py ${LK_PLATFORM}); cargo +nightly build -Zbuild-std=std,panic_abort --release $FLAGS --target ${TARGET}" + exec sh -c "set -e; FLAGS=$(python3 ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/ffi_features.py ${LK_PLATFORM}); cargo +nightly build -Zbuild-std=std,panic_abort --release $FLAGS --target ${TARGET}" end """ @@ -102,7 +102,7 @@ if [ "${CARGO_MAKE_PROFILE}" = "release" ]; then fi # Named here and not in env for the same reason as the profile switch above. -FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py" android) +FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/ffi_features.py" android) cargo ndk --target "${TARGET}" build ${FLAGS} ${release_flag} """ @@ -283,7 +283,7 @@ for p in ${SPM_PLATFORMS}; do platform_flags="$platform_flags --platforms $p" done -FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/platform_features.py" "${LK_PLATFORM}") +FLAGS=$(python3 "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/.github/scripts/ffi_features.py" "${LK_PLATFORM}") cargo swift package \ --accept-all \ diff --git a/livekit-ffi/README.md b/livekit-ffi/README.md index e340ea600..edd0bb18c 100644 --- a/livekit-ffi/README.md +++ b/livekit-ffi/README.md @@ -46,7 +46,7 @@ Mode specific source lives in `src/core_modules/`. single source of truth, and CI resolves its build flags from it: ```sh -.github/scripts/platform_features.py --list +.github/scripts/ffi_features.py --list ``` ## Things that bite From 912b8e8db57aba4eec564eec8894696d71ea5d15 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 22 Sep 2026 16:45:03 -0400 Subject: [PATCH 38/42] refactor(livekit-ffi): group the room-apis build steps into a module The build script carried nine `cfg` attributes, one on each item that only the room-apis surface needs. Put those items in a `room_apis` module with one entry point, and gate the module. Two attributes remain: one on the module and one on the call in `main`. The behaviour does not change. Verified: the core-modules build still runs no step, and the room-apis build still downloads libwebrtc, configures the linker and generates the protobuf code. --- livekit-ffi/build.rs | 160 +++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/livekit-ffi/build.rs b/livekit-ffi/build.rs index 5b08f0fc6..849f44447 100644 --- a/livekit-ffi/build.rs +++ b/livekit-ffi/build.rs @@ -13,11 +13,6 @@ // limitations under the License. use std::env; -#[cfg(feature = "room-apis")] -use std::path::Path; - -#[cfg(feature = "room-apis")] -const PROTO_SRC_DIR: &str = "protocol"; fn main() { if env::var("DOCS_RS").is_ok() { @@ -27,98 +22,103 @@ fn main() { // configuration (`configure_linker` panics on visionOS/tvOS/Mac Catalyst, // which the Swift package builds) and no protobuf generation. // - // This has to be a `cfg` rather than a `CARGO_FEATURE_ROOM_APIS` lookup: - // `prost-build` and `webrtc-sys-build` are optional build-dependencies - // gated on the same feature, so under core-modules those paths do not + // The whole module is gated rather than each item in it, and it has to be a + // `cfg` rather than a `CARGO_FEATURE_ROOM_APIS` lookup: `prost-build` and + // `webrtc-sys-build` are optional build-dependencies gated on the same + // feature, so under core-modules the paths that this code names do not // resolve at all. #[cfg(feature = "room-apis")] - { + room_apis::run_build_steps(); +} + +/// The build steps that only the room-apis surface needs. +#[cfg(feature = "room-apis")] +mod room_apis { + use std::{env, path::Path}; + + const PROTO_SRC_DIR: &str = "protocol"; + + pub fn run_build_steps() { download_webrtc(); copy_webrtc_license(); configure_linker(); generate_protobuf(); } -} -#[cfg(feature = "room-apis")] -fn download_webrtc() { - let webrtc_dir = webrtc_sys_build::webrtc_dir(); - if !webrtc_dir.exists() { - webrtc_sys_build::download_webrtc().unwrap(); + fn download_webrtc() { + let webrtc_dir = webrtc_sys_build::webrtc_dir(); + if !webrtc_dir.exists() { + webrtc_sys_build::download_webrtc().unwrap(); + } } -} -#[cfg(feature = "room-apis")] -/// Copy the webrtc license to `CARGO_MANIFEST_DIR`, used by the FFI release action. -fn copy_webrtc_license() { - let webrtc_dir = webrtc_sys_build::webrtc_dir(); - let license = webrtc_dir.join("LICENSE.md"); - let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); - std::fs::copy(license, out_file).unwrap(); -} + /// Copy the webrtc license to `CARGO_MANIFEST_DIR`, used by the FFI release action. + fn copy_webrtc_license() { + let webrtc_dir = webrtc_sys_build::webrtc_dir(); + let license = webrtc_dir.join("LICENSE.md"); + let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); + std::fs::copy(license, out_file).unwrap(); + } -#[cfg(feature = "room-apis")] -fn configure_linker() { - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - match target_os.as_str() { - "windows" => {} - "linux" => { - println!("cargo:rustc-link-lib=static=webrtc"); - } - "android" => { - webrtc_sys_build::configure_jni_symbols().unwrap(); - } - "macos" | "ios" => { - println!("cargo:rustc-link-arg=-ObjC"); - } - _ => { - panic!("Unsupported target, {}", target_os); + fn configure_linker() { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + match target_os.as_str() { + "windows" => {} + "linux" => { + println!("cargo:rustc-link-lib=static=webrtc"); + } + "android" => { + webrtc_sys_build::configure_jni_symbols().unwrap(); + } + "macos" | "ios" => { + println!("cargo:rustc-link-arg=-ObjC"); + } + _ => { + panic!("Unsupported target, {}", target_os); + } } } -} -#[cfg(feature = "room-apis")] -fn generate_protobuf() { - let paths: Vec<_> = std::fs::read_dir(PROTO_SRC_DIR) - .expect("Failed to read protobuf source directory") - .map(|entry| entry.unwrap().path()) - .filter(|path| path.extension().is_some_and(|ext| ext == "proto")) - .collect(); - for path in &paths { - println!("cargo:rerun-if-changed={}", path.display()); + fn generate_protobuf() { + let paths: Vec<_> = std::fs::read_dir(PROTO_SRC_DIR) + .expect("Failed to read protobuf source directory") + .map(|entry| entry.unwrap().path()) + .filter(|path| path.extension().is_some_and(|ext| ext == "proto")) + .collect(); + for path in &paths { + println!("cargo:rerun-if-changed={}", path.display()); + } + prost_build::Config::new() + .derive_from_variants("livekit.proto.FfiRequest.message") + .derive_from_variants("livekit.proto.FfiResponse.message") + .derive_from_variants("livekit.proto.FfiEvent.message") + .derive_from_variants("livekit.proto.RoomEvent.message") + .from_variants_skip_field("livekit.proto.RoomEvent.message.room_updated") + .from_variants_skip_field("livekit.proto.RoomEvent.message.moved") + .derive_from_variants("livekit.proto.AudioStreamEvent.message") + .derive_from_variants("livekit.proto.TextStreamReaderEvent.detail") + .derive_from_variants("livekit.proto.ByteStreamReaderEvent.detail") + .derive_from_variants("livekit.proto.DataTrackStreamEvent.detail") + .compile_protos(&paths, &[PROTO_SRC_DIR]) + .expect("Protobuf generation failed"); } - prost_build::Config::new() - .derive_from_variants("livekit.proto.FfiRequest.message") - .derive_from_variants("livekit.proto.FfiResponse.message") - .derive_from_variants("livekit.proto.FfiEvent.message") - .derive_from_variants("livekit.proto.RoomEvent.message") - .from_variants_skip_field("livekit.proto.RoomEvent.message.room_updated") - .from_variants_skip_field("livekit.proto.RoomEvent.message.moved") - .derive_from_variants("livekit.proto.AudioStreamEvent.message") - .derive_from_variants("livekit.proto.TextStreamReaderEvent.detail") - .derive_from_variants("livekit.proto.ByteStreamReaderEvent.detail") - .derive_from_variants("livekit.proto.DataTrackStreamEvent.detail") - .compile_protos(&paths, &[PROTO_SRC_DIR]) - .expect("Protobuf generation failed"); -} -#[cfg(feature = "room-apis")] -trait ProstConfigExt { - /// Derive [`from_variants::FromVariants`] on a oneof field's generated enum. - fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self; - - /// When using [`derive_from_variants`], skip a particular case. This is necessary - /// for oneofs that contain multiple cases with the same message type. - fn from_variants_skip_field(&mut self, path: impl AsRef) -> &mut Self; -} + trait ProstConfigExt { + /// Derive [`from_variants::FromVariants`] on a oneof field's generated enum. + fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self; -#[cfg(feature = "room-apis")] -impl ProstConfigExt for prost_build::Config { - fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self { - self.enum_attribute(path, "#[derive(from_variants::FromVariants)]") + /// When using [`derive_from_variants`], skip a particular case. This is necessary + /// for oneofs that contain multiple cases with the same message type. + fn from_variants_skip_field(&mut self, path: impl AsRef) -> &mut Self; } - fn from_variants_skip_field(&mut self, path: impl AsRef) -> &mut Self { - self.field_attribute(path, "#[from_variants(skip)]") + + impl ProstConfigExt for prost_build::Config { + fn derive_from_variants(&mut self, path: impl AsRef) -> &mut Self { + self.enum_attribute(path, "#[derive(from_variants::FromVariants)]") + } + fn from_variants_skip_field(&mut self, path: impl AsRef) -> &mut Self { + self.field_attribute(path, "#[from_variants(skip)]") + } } } From 206699f5429834a2d914d8c7f94ef6bcb878f9c2 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 20 Oct 2026 10:54:34 +1300 Subject: [PATCH 39/42] fix: port to use pub.dev.workflow_dispatch ci trigger --- .github/workflows/ffi-builds.yml | 62 ++++++++++++---------- .github/workflows/uniffi-dart-publish.yml | 63 ++++++++++++++--------- .github/workflows/uniffi-packages.yml | 11 ++-- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 3ba82f3f4..576fdfbc3 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -355,6 +355,10 @@ jobs: needs: [get-tag, build] permissions: contents: write + # Only for the `gh workflow run` dispatches in the last step. Scoped to + # this job rather than the workflow: it also grants cancelling and + # re-running workflows and deleting runs, artifacts and caches. + actions: write if: needs.get-tag.outputs.tag_name != '' && inputs.dry_run != true env: GH_TOKEN: ${{ github.token }} @@ -373,25 +377,8 @@ jobs: TAG: ${{ needs.get-tag.outputs.tag_name }} run: gh release upload "$TAG" ${{ github.workspace }}/ffi-builds/* - # This is here because pub.dev's automated publishing *requires* a - # tag-push-triggered workflow as part of its trusted publishing - # configuration. If the default github actions worker were to publish the - # tag, then github prevents that from triggering other workflows to - # prevent workflow recursion. So this "breaks the cycle" and allows the - # downstream workflow to be triggered to satisfy pub.dev. - - name: Generate Repo Writer GitHub App token - id: app-token - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 - with: - app-id: ${{ vars.REPO_WRITER_GH_APP_ID }} - private-key: ${{ secrets.REPO_WRITER_GH_APP_PRIVATE_KEY }} - owner: livekit - repositories: | - rust-sdks - - name: Create release tag env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} TAG: ${{ needs.get-tag.outputs.tag_name }} SHA: ${{ github.sha }} run: | @@ -409,19 +396,42 @@ jobs: existing=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq '.object.sha' 2>/dev/null || true) if [ -n "$existing" ]; then - # Do not fail: the assets are already uploaded, and failing here - # would strand the release as a draft. No push event was emitted, - # so the downstream publishes need a manual dispatch. + # A re-run. Carry on rather than failing: the tag is only needed as + # the ref to publish and dispatch against, and it already exists. echo "::warning::tag ${TAG} already exists at ${existing}; not re-creating it." - echo "::warning::uniffi-packages.yml and uniffi-dart-publish.yml did NOT run; dispatch them with this tag if the packages still need publishing." - exit 0 + else + gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f ref="refs/tags/${TAG}" \ + -f sha="${SHA}" fi - gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \ - -f ref="refs/tags/${TAG}" \ - -f sha="${SHA}" - - name: Publish release env: TAG: ${{ needs.get-tag.outputs.tag_name }} run: gh release edit "$TAG" --draft=false + + # A ref created with GITHUB_TOKEN emits no push event -- GitHub's guard + # against recursive workflow runs -- so the UniFFI package publishes + # cannot be tag-push triggered from here. `workflow_dispatch` is the + # documented exception to that guard: it "always creates workflow runs", + # GITHUB_TOKEN included. + # + # Dispatching *against the tag* is what keeps pub.dev happy. The run then + # carries `ref: refs/tags/`, so its OIDC token has refType "tag", + # which pub.dev's trusted publishing requires alongside the tag matching + # the pattern configured on the package (see the eventName / refType + # checks in dart-lang/pub-dev, app/lib/package/backend.dart). + # + # Last step on purpose: the release is already published by now, so if a + # dispatch fails the FFI artifacts are unaffected and only the UniFFI + # packages need the manual re-dispatch the failure tells you to do. + - name: Dispatch the UniFFI package publishes + env: + TAG: ${{ needs.get-tag.outputs.tag_name }} + run: | + set -euo pipefail + for workflow in uniffi-packages.yml uniffi-dart-publish.yml; do + echo "dispatching ${workflow} at ${TAG}" + gh workflow run "$workflow" --repo "$GITHUB_REPOSITORY" \ + --ref "$TAG" -f tag_name="$TAG" + done diff --git a/.github/workflows/uniffi-dart-publish.yml b/.github/workflows/uniffi-dart-publish.yml index e2567e94a..df25a9449 100644 --- a/.github/workflows/uniffi-dart-publish.yml +++ b/.github/workflows/uniffi-dart-publish.yml @@ -4,13 +4,16 @@ name: UniFFI Dart package # livekit-ffi release, together with the native libraries it loads. # # This is a separate workflow from uniffi-packages.yml because pub.dev's -# automated publishing only accepts workflows triggered by a tag push matching -# the tag pattern configured on the package (livekit-ffi/v{{version}}); -# a release-event-triggered workflow is rejected. livekit-ffi uses knope's -# draft-release flow, so the tag is created by ffi-builds.yml with the -# livekit-repo-writer App rather than by knope-bot; App-created events do -# trigger workflows here, GITHUB_TOKEN-created ones do not (see the note in -# uniffi-packages.yml / #1256). +# automated publishing accepts a run only when its OIDC token says the ref is +# a *tag* matching the pattern configured on the package +# (livekit-ffi/v{{version}}); a release-event-triggered workflow is rejected. +# +# livekit-ffi uses knope's draft-release flow, and the tag ffi-builds.yml +# creates with GITHUB_TOKEN emits no push event (#1256). So the normal trigger +# is ffi-builds.yml dispatching this workflow with `--ref ` once the +# release is published: workflow_dispatch is exempt from GitHub's recursion +# guard, and dispatching at the tag still gives the run refType "tag". The +# push trigger below stays for a tag pushed by hand. # # The package's hook/build.dart downloads build-.zip assets from the # GitHub release at consumer build time, so a pub.dev version whose assets are @@ -19,21 +22,30 @@ name: UniFFI Dart package # same run, and the publish job depends on it, so the package can only be # published once every asset is on the release. # -# Publishing is enabled. A tag push runs the whole pipeline and publishes to -# pub.dev through OIDC (dart-lang/setup-dart exchanges the job token). pub.dev -# accepts that token only because automated publishing is configured on the -# package for livekit/rust-sdks with tag pattern `livekit-ffi/v{{version}}`; -# if that setting is removed the publish step fails and nothing else changes. -# The package was created by a manual first publish of 0.1.10 (pub.dev only -# automates existing packages). NOTE: the configured tag pattern must be -# updated to `livekit-ffi/v{{version}}` on pub.dev before the first release -# after the livekit-uniffi merge, or the publish step will be rejected. +# Publishing is enabled. The run publishes to pub.dev through OIDC +# (dart-lang/setup-dart exchanges the job token), and pub.dev accepts that +# token only if all of the following hold on the package's automated-publishing +# settings. Every one of them has to be set up before the first release: +# +# - the package `livekit_ffi` exists. pub.dev only automates packages that +# already exist, so it needs one manual publish first -- this is a new +# package, not a rename of `livekit_uniffi`, whose settings do not carry +# over +# - repository `livekit/rust-sdks`, tag pattern `livekit-ffi/v{{version}}` +# - "Enable publishing from `workflow_dispatch` events" is checked, since +# that is how ffi-builds.yml starts this run +# - "Require GitHub Actions environment" is checked, with `pub.dev` as the +# name, matching the `environment:` on the publish job below +# +# The environment is the real guard: give it a deployment-tag rule for +# `livekit-ffi/v*` in the repository settings, and GitHub refuses to mint a +# token carrying that environment for a run on any other ref. That is enforced +# server-side, so editing this file cannot get around it. # # Recovery: if a run fails, fix the cause and re-run the failed jobs, or # dispatch this workflow with the tag. A dispatch rebuilds and re-attaches the -# assets (with --clobber) and re-validates the package, but never publishes, -# since the publish step only runs for push events. Set dry_run to skip the -# asset upload when only exercising the workflow. +# assets (with --clobber), re-validates, and publishes -- so set dry_run when +# only exercising the workflow. on: push: @@ -51,9 +63,9 @@ on: env: # Set to "false" to pause publishing while keeping asset uploads and - # validation. Real publishing also requires a tag-push trigger: publishing - # from workflow_dispatch is left disabled on pub.dev, and the publish step - # below only runs for push events, so dispatch runs stop at the dry run. + # validation. This is a convenience switch only -- what actually constrains + # publishing is the pub.dev configuration and the `pub.dev` environment + # described above, both of which GitHub and pub.dev enforce server-side. PUBLISH_ENABLED: "true" permissions: @@ -122,6 +134,11 @@ jobs: name: Build & publish Dart package needs: [resolve-tag, cdylib] runs-on: ubuntu-latest + # Names the environment in the OIDC token so pub.dev's "Require GitHub + # Actions environment" check passes, and subjects the job to the + # environment's deployment-tag rule. Configure that rule to allow only + # `livekit-ffi/v*`; see the note at the top of this file. + environment: pub.dev permissions: contents: read # Exchanged for a temporary pub.dev token by dart-lang/setup-dart. @@ -198,6 +215,6 @@ jobs: dart pub publish --dry-run - name: Publish to pub.dev - if: ${{ env.PUBLISH_ENABLED == 'true' && github.event_name == 'push' }} + if: ${{ env.PUBLISH_ENABLED == 'true' && !inputs.dry_run }} working-directory: ${{ runner.temp }}/livekit_uniffi run: dart pub publish --force diff --git a/.github/workflows/uniffi-packages.yml b/.github/workflows/uniffi-packages.yml index 28cabec23..0d90b8c42 100644 --- a/.github/workflows/uniffi-packages.yml +++ b/.github/workflows/uniffi-packages.yml @@ -7,11 +7,12 @@ name: UniFFI packages # Trigger note: this used to react to `release: published`, which worked only # because knope-bot (a GitHub App) published the livekit-uniffi release and its # tag directly. livekit-ffi uses knope's `assets = "marker"` draft flow, and a -# draft carries no tag until it is published — with GITHUB_TOKEN, which never -# starts a workflow run (see #1256). So the trigger is the git tag that -# ffi-builds.yml creates with the livekit-repo-writer App after its build -# matrix succeeds and before it publishes the draft. A tag push therefore still -# implies a green 11-target build, the same guarantee the release event gave. +# draft carries no tag until it is published — with GITHUB_TOKEN, whose refs +# and releases never start a workflow run (see #1256). So ffi-builds.yml +# dispatches this workflow explicitly once its build matrix is green and the +# release is published; workflow_dispatch is exempt from that recursion guard. +# A run therefore still implies a green 11-target build, the same guarantee the +# release event gave. The push trigger below stays for a tag pushed by hand. # # The Dart package, and the cdylib release assets its build hook downloads, are # handled by uniffi-dart-publish.yml, which reacts to the same tag push From 44426a21bd318adc3289a5291432e34cfa19c7fe Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 20 Oct 2026 11:34:18 +1300 Subject: [PATCH 40/42] fix(changeset): retarget the livekit-uniffi bumps at livekit-ffi The crate is gone, and knope ignores a changeset entry naming a package that is not in knope.toml -- without an error, and without the `changeset-check.yml` gate catching it (changeset_detect.py filters unknown packages out before the `invalid` check). `protocol_thread_schema.md` named it alongside live packages, so the stray line was merely inert. `uniffi_swift_dsyms.md` named nothing else, so it contributed no changelog entry *and was never deleted* -- it would have sat in .changeset/ being silently re-ignored on every future release, losing the dSYM entry permanently. Verified with knope 0.23.1: `knope --dry-run release` now consumes and deletes both files and emits the dSYM entry under livekit-ffi 0.13.0. Also corrects the framework name in that entry, since the changelog it lands in ships after the rename: RustLiveKitUniFFI -> RustLiveKitFFI. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/protocol_thread_schema.md | 1 - .changeset/uniffi_swift_dsyms.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.changeset/protocol_thread_schema.md b/.changeset/protocol_thread_schema.md index 6724a26b4..60fa1c080 100644 --- a/.changeset/protocol_thread_schema.md +++ b/.changeset/protocol_thread_schema.md @@ -10,7 +10,6 @@ livekit-ffi: patch livekit-rpc: patch livekit-signaling: patch livekit-token: patch -livekit-uniffi: patch --- `livekit-protocol` exposes the conversation schema of a thread's database, as protocol's Go package diff --git a/.changeset/uniffi_swift_dsyms.md b/.changeset/uniffi_swift_dsyms.md index d224650c3..d1d6afddd 100644 --- a/.changeset/uniffi_swift_dsyms.md +++ b/.changeset/uniffi_swift_dsyms.md @@ -1,5 +1,5 @@ --- -livekit-uniffi: patch +livekit-ffi: patch --- -Embed per-slice `.dSYM` bundles in the Swift `RustLiveKitUniFFI.xcframework` again so Xcode archives and crash symbolication can resolve Rust frames (livekit/client-sdk-swift#1149). +Embed per-slice `.dSYM` bundles in the Swift `RustLiveKitFFI.xcframework` again so Xcode archives and crash symbolication can resolve Rust frames (livekit/client-sdk-swift#1149). From 273783b1e0c91820fc6b14cc0c5d2988ddfec52a Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 20 Oct 2026 11:34:31 +1300 Subject: [PATCH 41/42] docs(livekit-common): name the renamed Swift framework in the comment The framework the cargo-swift fork produces is RustLiveKitFFI now, so the comment explaining why this crate sets ffi_module_name pointed at a name that no longer exists. Co-Authored-By: Claude Opus 5 (1M context) --- livekit-common/uniffi.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-common/uniffi.toml b/livekit-common/uniffi.toml index a516eec17..0b0ffc8b1 100644 --- a/livekit-common/uniffi.toml +++ b/livekit-common/uniffi.toml @@ -1,4 +1,4 @@ [bindings.swift] # Matches the Rust convention used by the other components; the cargo-swift -# fork folds every component's header into the single RustLiveKitUniFFI framework. +# fork folds every component's header into the single RustLiveKitFFI framework. ffi_module_name = "RustLiveKitCommon" From bb49119c9ff5acd2174018a356e430096ef4c315 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 20 Oct 2026 11:34:39 +1300 Subject: [PATCH 42/42] docs(livekit-ffi): title the Android README after the merged crate The module builds livekit-ffi/core-modules and publishes io.livekit:livekit-ffi-android; the heading still said UniFFI. Co-Authored-By: Claude Opus 5 (1M context) --- livekit-ffi/support/android/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/support/android/README.md b/livekit-ffi/support/android/README.md index a747938d3..40bc37067 100644 --- a/livekit-ffi/support/android/README.md +++ b/livekit-ffi/support/android/README.md @@ -1,4 +1,4 @@ -# LiveKit UniFFI — Android library +# LiveKit FFI — Android library Gradle Android library module that packages UniFFI Kotlin bindings and native `liblivekit_uniffi.so` binaries into an **AAR**.