Skip to content

(PART 2) Renames old livekit-uniffi builds to use ffi instead post livekit-ffi / livekit-uniffi merge - #1439

Draft
1egoman wants to merge 24 commits into
livekit-ffi-livekit-uniffi-mergefrom
livekit-ffi-livekit-uniffi-merge-rename
Draft

1egoman wants to merge 24 commits into
livekit-ffi-livekit-uniffi-mergefrom
livekit-ffi-livekit-uniffi-merge-rename

Conversation

@1egoman

@1egoman 1egoman commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

TODO: write more thorough description

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.
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.
…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 "<tls>"`. Without this rule,
those targets would build a crate with no surface in it.
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.
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.
…dules 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.
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.
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.
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.
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.
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.
Two changesets name livekit-uniffi. That package no longer exists. The script
.github/scripts/changeset_detect.py rejects a bump for an unknown package and
stops the build.

In the first changeset, the livekit-ffi patch bump already covers the change.
Delete the livekit-uniffi line only.

IMPORTANT: in the second changeset, the bump was major. Do not make the
livekit-ffi bump major. The version of livekit-ffi is below 1.0, so a major
bump gives 1.0.0 and not 0.13.0. Use a minor bump and write the breaking
change in the text.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
…FI identity

Every commit before this one keeps the names that the Swift, Android and
Flutter SDKs use today. This commit changes them, and it is the only commit
that does. Hold it back or move it to its own pull request if the rename is
not wanted now.

The Swift package and product become LiveKitFFI and RustLiveKitFFI. The Maven
artifact becomes io.livekit:livekit-ffi-android with the Kotlin package
io.livekit.ffi. The package on pub.dev becomes livekit_ffi. Both surfaces now
use the crate name as their UniFFI namespace.

Rename the podspec template with the package. The cargo-make task builds its
file name from the package name, so the old name would stop the Swift release
with a missing template.

The repository that holds the Swift framework keeps its current name. Only the
package and the product inside it change.

BREAKING CHANGE: imports, dependency coordinates and generated module names
all change together for the Swift, Android and Flutter SDKs. Before the first
release, publish livekit_ffi to pub.dev by hand and set its tag pattern,
because pub.dev automates only a package that exists. Also remove the old
source directory from the framework repository.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant