Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
- cpp-example-collection/**
- bridge/**
- client-sdk-rust/**
- cmake/**
- scripts/**
- CMakeLists.txt
- build.sh
- build.cmd
Expand All @@ -25,6 +27,8 @@ on:
- cpp-example-collection/**
- bridge/**
- client-sdk-rust/**
- cmake/**
- scripts/**
- CMakeLists.txt
- build.sh
- build.cmd
Expand Down Expand Up @@ -177,6 +181,49 @@ jobs:
shell: pwsh
run: ${{ matrix.build_cmd }}

# ---------- Verify exported ABI: no spdlog/fmt/protobuf/absl leaks ----------
- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v6.0.0
with:
python-version: '3.x'

- name: Verify no private symbols leak (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
set -eux
libdir="${{ matrix.build_dir }}/lib"
if [[ "$RUNNER_OS" == "macOS" ]]; then
lib="${libdir}/liblivekit.dylib"
if [[ ! -f "${lib}" ]]; then
lib="${{ matrix.build_dir }}/bin/liblivekit.dylib"
fi
else
lib="${libdir}/liblivekit.so"
if [[ ! -f "${lib}" ]]; then
lib="${{ matrix.build_dir }}/bin/liblivekit.so"
fi
fi
python3 scripts/check_no_private_symbols.py "${lib}"

- name: Verify no private symbols leak (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$candidates = @(
"${{ matrix.build_dir }}/bin/livekit.dll",
"${{ matrix.build_dir }}/lib/livekit.dll"
)
$lib = $null
foreach ($p in $candidates) {
if (Test-Path -LiteralPath $p) { $lib = $p; break }
}
if ($null -eq $lib) {
Write-Error "livekit.dll not found in any of: $($candidates -join ', ')"
exit 1
}
python scripts/check_no_private_symbols.py "$lib"

# ---------- Smoke test cpp-example-collection binaries ----------
# Built under cpp-example-collection-build/ (not build-dir/bin). Visual Studio
# multi-config places executables in per-target Release/ (or Debug/) subdirs.
Expand Down
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,37 @@ All source files must have the LiveKit Apache 2.0 copyright header. Use the curr
- protobuf is vendored via FetchContent on non-Windows platforms; Windows uses vcpkg.
- The CMake install produces a `find_package(LiveKit CONFIG)`-compatible package with `LiveKitConfig.cmake`, `LiveKitTargets.cmake`, and `LiveKitConfigVersion.cmake`.

### Symbol Visibility / Exported ABI

The `livekit` shared library is built with hidden symbol visibility on all
platforms. Only symbols explicitly tagged with `LIVEKIT_API` (declared in
`include/livekit/export.h`) are part of the public ABI. Vendored static
dependencies (spdlog, fmt, protobuf, absl) are also compiled with hidden
visibility so their symbols cannot leak into `liblivekit.{so,dylib,dll}`.

Rules for new code:

- Mark any new public class, free function, or out-of-line static method with
`LIVEKIT_API`. POD/aggregate structs and pure-inline classes do not need
annotation because they emit no out-of-line symbols.
- For internal symbols that must remain visible to in-tree tests (the tests
link the same shared library), use `LIVEKIT_INTERNAL_API`. External
consumers must not rely on these.
- Do not add `__declspec(dllexport)` or `__attribute__((visibility("default")))`
by hand; always go through `LIVEKIT_API` / `LIVEKIT_INTERNAL_API`.
- On Windows, `WINDOWS_EXPORT_ALL_SYMBOLS` is **deliberately disabled** for the
`livekit` target. Use `LIVEKIT_API` to export.

CI enforces the policy via `scripts/check_no_private_symbols.py`, which is
also wired in as a CTest test (label `abi`):

```
ctest -L abi --output-on-failure
```

The script fails if `nm`/`dumpbin` reports any exported symbol containing
`spdlog`, `fmt::v`, `google::protobuf`, or `absl::`.

### Readability and Performance
Code should be easy to read and understand. If a sacrifice is made for performance or readability, it should be documented.

Expand Down
29 changes: 27 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ endif()
message(STATUS "Using protoc: ${Protobuf_PROTOC_EXECUTABLE}")

add_library(livekit_proto OBJECT ${FFI_PROTO_FILES})
# livekit_proto is compiled into liblivekit; apply the same hidden visibility
# policy so generated protobuf code does not leak into the SDK's exported ABI.
set_target_properties(livekit_proto PROPERTIES
CXX_VISIBILITY_PRESET hidden
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
if(TARGET protobuf::libprotobuf)
set(LIVEKIT_PROTOBUF_TARGET protobuf::libprotobuf)
else()
Expand Down Expand Up @@ -366,8 +373,26 @@ add_library(livekit SHARED
src/trace/trace_event.h
src/trace/tracing.cpp
)
if(WIN32)
set_target_properties(livekit PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
# Symbol visibility: keep liblivekit's exported ABI restricted to the public
# LiveKit API. Every TU is compiled with -fvisibility=hidden, and only
# declarations annotated with LIVEKIT_API (see include/livekit/export.h) are
# re-exposed. This prevents private dependencies (spdlog, protobuf, absl) from
# leaking into the dynamic symbol table and clashing with consumer apps that
# also link those libraries (e.g. ROS 2's rcl_logging_spdlog).
set_target_properties(livekit PROPERTIES
CXX_VISIBILITY_PRESET hidden
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
target_compile_definitions(livekit PRIVATE LIVEKIT_BUILDING_SDK=1)

# Linker-level belt-and-suspenders for Linux: even if a static-archive object
# slipped through with default visibility, --exclude-libs,ALL strips it from
# the dynamic symbol table. ld64 (macOS) has no equivalent flag; hidden
# visibility per TU is sufficient there. On Windows, explicit dllexport via
# LIVEKIT_API replaces WINDOWS_EXPORT_ALL_SYMBOLS.
if(UNIX AND NOT APPLE)
target_link_options(livekit PRIVATE "LINKER:--exclude-libs,ALL")
endif()


Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,24 @@ livekit::setLogLevel(livekit::LogLevel::Debug); // show more detail
livekit::setLogLevel(livekit::LogLevel::Warn); // suppress info chatter
```

### Embedding the SDK in processes that already use spdlog (`LIVEKIT_USE_SYSTEM_SPDLOG`)

By default on Linux/macOS the SDK vendors spdlog and statically links it into
`liblivekit`. If you embed the SDK in a process that **already loads spdlog**
itself. Two independent spdlog/fmt copies in the same process can crash at runtime.
Build the SDK with the system spdlog instead so both components share one implementation:

```bash
cmake -B build-release -S . \
-DCMAKE_BUILD_TYPE=Release \
-DLIVEKIT_USE_SYSTEM_SPDLOG=ON
cmake --build build-release
```

Pass this only to the SDK build, not to the consumer's build. On Windows the
vcpkg path already supplies a single shared spdlog, so the option is a no-op
there.

### Custom log callback

Replace the default stderr sink with your own handler. This is the integration
Expand Down
46 changes: 46 additions & 0 deletions cmake/protobuf.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,28 @@ set(protobuf_INSTALL OFF CACHE BOOL "" FORCE)
set(ABSL_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(utf8_range_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)

# Force hidden visibility on every target created by the FetchContent
# subprojects below. Setting these as CACHE BOOL FORCE before
# FetchContent_MakeAvailable propagates to every add_library() call inside
# absl / protobuf / utf8_range, so their .o files are compiled with
# -fvisibility=hidden -fvisibility-inlines-hidden. Without this, even though
# liblivekit itself is hidden, absl's static archives would carry default-
# visibility symbols that the linker happily re-exports.
#
# We snapshot the previous values so we can restore them after
# FetchContent_MakeAvailable; otherwise sibling components like the bridge
# library would also inherit hidden visibility, which would break the bridge
# unit tests that need access to internal bridge symbols.
set(_livekit_prev_cxx_visibility "${CMAKE_CXX_VISIBILITY_PRESET}")
set(_livekit_prev_c_visibility "${CMAKE_C_VISIBILITY_PRESET}")
set(_livekit_prev_inlines_hidden "${CMAKE_VISIBILITY_INLINES_HIDDEN}")
set(_livekit_prev_pic "${CMAKE_POSITION_INDEPENDENT_CODE}")

set(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING "" FORCE)
set(CMAKE_C_VISIBILITY_PRESET hidden CACHE STRING "" FORCE)
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL "" FORCE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "" FORCE)

set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(protobuf_BUILD_CONFORMANCE OFF CACHE BOOL "" FORCE)
Expand Down Expand Up @@ -162,6 +184,30 @@ endif()
# Now make protobuf available.
FetchContent_MakeAvailable(livekit_protobuf)

# Restore the prior visibility cache values so the rest of the build (e.g.
# the deprecated bridge target and its tests) can use the project default
# visibility. The livekit target itself sets CXX_VISIBILITY_PRESET hidden
# explicitly via target properties, so it does not depend on these globals.
if(_livekit_prev_cxx_visibility STREQUAL "")
unset(CMAKE_CXX_VISIBILITY_PRESET CACHE)
else()
set(CMAKE_CXX_VISIBILITY_PRESET "${_livekit_prev_cxx_visibility}"
CACHE STRING "" FORCE)
endif()
if(_livekit_prev_c_visibility STREQUAL "")
unset(CMAKE_C_VISIBILITY_PRESET CACHE)
else()
set(CMAKE_C_VISIBILITY_PRESET "${_livekit_prev_c_visibility}"
CACHE STRING "" FORCE)
endif()
if(_livekit_prev_inlines_hidden STREQUAL "")
unset(CMAKE_VISIBILITY_INLINES_HIDDEN CACHE)
else()
set(CMAKE_VISIBILITY_INLINES_HIDDEN "${_livekit_prev_inlines_hidden}"
CACHE BOOL "" FORCE)
endif()
# Position-independent code remains ON globally (it was forced ON anyway).

# Protobuf targets: modern protobuf exports protobuf::protoc etc.
if(TARGET protobuf::protoc)
set(Protobuf_PROTOC_EXECUTABLE "$<TARGET_FILE:protobuf::protoc>" CACHE STRING "protoc (vendored)" FORCE)
Expand Down
29 changes: 28 additions & 1 deletion cmake/spdlog.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# cmake/spdlog.cmake
#
# Windows: use vcpkg spdlog
# macOS/Linux: vendored spdlog via FetchContent (static library)
# macOS/Linux: vendored spdlog via FetchContent (static library), unless
# LIVEKIT_USE_SYSTEM_SPDLOG=ON, in which case the system-provided
# spdlog (find_package) is used.
#
# Exposes:
# - Target spdlog::spdlog
Expand Down Expand Up @@ -50,6 +52,10 @@ include(FetchContent)

set(LIVEKIT_SPDLOG_VERSION "1.15.1" CACHE STRING "Vendored spdlog version")

option(LIVEKIT_USE_SYSTEM_SPDLOG
"Use system spdlog (find_package) instead of vendored"
OFF)

# ---------------------------------------------------------------------------
# Windows: use vcpkg
# ---------------------------------------------------------------------------
Expand All @@ -59,6 +65,15 @@ if(WIN32 AND LIVEKIT_USE_VCPKG)
return()
endif()

# ---------------------------------------------------------------------------
# macOS/Linux: system spdlog (opt-in via LIVEKIT_USE_SYSTEM_SPDLOG=ON)
# ---------------------------------------------------------------------------
if(LIVEKIT_USE_SYSTEM_SPDLOG)
find_package(spdlog CONFIG REQUIRED)
message(STATUS "Using system spdlog (LIVEKIT_USE_SYSTEM_SPDLOG=ON): version=${spdlog_VERSION}")
return()
endif()

# ---------------------------------------------------------------------------
# macOS/Linux: vendored spdlog via FetchContent
# ---------------------------------------------------------------------------
Expand All @@ -75,4 +90,16 @@ set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(livekit_spdlog)

# spdlog is linked PRIVATE into liblivekit and must not leak its symbols into
# the SDK's exported ABI. Force hidden visibility on the spdlog target so its
# object files don't carry default-visibility symbols into liblivekit.
if(TARGET spdlog)
set_target_properties(spdlog PROPERTIES
CXX_VISIBILITY_PRESET hidden
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
POSITION_INDEPENDENT_CODE ON
)
endif()

message(STATUS "macOS/Linux: using vendored spdlog v${LIVEKIT_SPDLOG_VERSION}")
4 changes: 3 additions & 1 deletion include/livekit/audio_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#pragma once

#include "livekit/export.h"

#include <cstdint>
#include <string>
#include <vector>
Expand All @@ -34,7 +36,7 @@ class OwnedAudioFrameBuffer;
* number of channels, and samples per channel. It is used for capturing and
* processing audio in the LiveKit SDK.
*/
class AudioFrame {
class LIVEKIT_API AudioFrame {
public:
/**
* Construct an AudioFrame from raw PCM samples.
Expand Down
3 changes: 2 additions & 1 deletion include/livekit/audio_processing_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cstdint>

#include "livekit/audio_frame.h"
#include "livekit/export.h"
#include "livekit/ffi_handle.h"

namespace livekit {
Expand All @@ -41,7 +42,7 @@ namespace livekit {
*
* Note: Audio frames must be exactly 10ms in duration.
*/
class AudioProcessingModule {
class LIVEKIT_API AudioProcessingModule {
public:
/**
* @brief Configuration options for the Audio Processing Module.
Expand Down
3 changes: 2 additions & 1 deletion include/livekit/audio_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cstdint>

#include "livekit/audio_frame.h"
#include "livekit/export.h"
#include "livekit/ffi_handle.h"

namespace livekit {
Expand All @@ -33,7 +34,7 @@ class FfiClient;
/**
* Represents a real-time audio source with an internal audio queue.
*/
class AudioSource {
class LIVEKIT_API AudioSource {
public:
/**
* Create a new native audio source.
Expand Down
3 changes: 2 additions & 1 deletion include/livekit/audio_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <string>

#include "audio_frame.h"
#include "export.h"
#include "ffi_handle.h"
#include "participant.h"
#include "track.h"
Expand Down Expand Up @@ -65,7 +66,7 @@ struct AudioFrameEvent {
*
* stream->close(); // optional, called automatically in destructor
*/
class AudioStream {
class LIVEKIT_API AudioStream {
public:
/// Configuration options for AudioStream creation.
struct Options {
Expand Down
Loading
Loading