Skip to content

Commit 4b58c0a

Browse files
authored
feat: Use dynamic library linking for Linux/macOS to fix debug build heap corruption (#26)
* fix: simplify build output structure and fix CI issues - Simplify lib output: remove platform/config subdirectories (build-debug/release already provides separation) - Prevent SDL3 from polluting lib directory (build to _deps/) - Fix protobuf debug DLL naming (libprotobufd.dll) - Sync make-release.yml Linux deps with builds.yml - Remove macos-x64 cross-compile from release (keep arm64 only) * fix: update CI paths and use manual copy for release bundle * feat: use dynamic library linking for Linux/macOS to fix debug build heap corruption - Add RPATH settings ($ORIGIN for Linux, @executable_path for macOS) - Change livekit_ffi from static (.a) to shared library (.so/.dylib) - Add IMPORTED_NO_SONAME to ensure relative path linking - Copy shared library to examples bin directory - Update make-release.yml to include bin directory with examples - Fix double-compression issue in release workflow This resolves the 'corrupted size vs. prev_size' error on Linux debug builds by isolating the Rust/libwebrtc code behind a shared library boundary, matching the Windows DLL architecture. * fix: resolve release archive double-compression and update docs Release workflow: - Remove pre-compression in build jobs to avoid artifact double-zip - Move archive creation to release job (tar.gz for Unix, zip for Windows) - Include bin directory with example executables Documentation: - Update DEPENDENCIES.md for dynamic library usage - Update README_BUILD.md with new build output structure - Update README.md with .so/.dylib deployment requirements * fix: create SDL3 SONAME symlink and improve release workflow - Set RPATH to \$ORIGIN for examples to load libs from bin directory - Create libSDL3.so.0 -> libSDL3.so.0.x.x symlink on Unix - Copy SDL3 shared library on all platforms - Use versioned artifact names in release workflow for cleaner structure * fix: only create SDL3 SONAME symlink on Linux, not macOS - macOS dylib versioning doesn't use SONAME symlinks like Linux .so files. - Creating a symlink with the same source and target causes ELOOP error.
1 parent f048052 commit 4b58c0a

File tree

8 files changed

+268
-116
lines changed

8 files changed

+268
-116
lines changed

.github/workflows/builds.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ jobs:
181181
$examples = @('SimpleRoom', 'SimpleRpc', 'SimpleDataStream')
182182
$failed = $false
183183
foreach ($exe in $examples) {
184-
$exePath = "${{ matrix.build_dir }}/bin/Release/${exe}.exe"
184+
$exePath = "${{ matrix.build_dir }}/bin/${exe}.exe"
185185
if (Test-Path $exePath) {
186186
Write-Host "Testing ${exe}..."
187187
$pinfo = New-Object System.Diagnostics.ProcessStartInfo

.github/workflows/make-release.yml

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ jobs:
2727
- os: macos-latest
2828
name: macos-arm64
2929
generator: Ninja
30-
macos_arch: arm64
31-
- os: macos-latest
32-
name: macos-x64
33-
generator: Ninja
34-
macos_arch: x86_64
3530
- os: windows-latest
3631
name: windows-x64
3732
generator: "Visual Studio 17 2022"
@@ -85,9 +80,14 @@ jobs:
8580
sudo apt-get install -y \
8681
build-essential cmake ninja-build pkg-config \
8782
llvm-dev libclang-dev clang \
83+
libva-dev libdrm-dev libgbm-dev libx11-dev libgl1-mesa-dev \
84+
libxext-dev libxcomposite-dev libxdamage-dev libxfixes-dev \
85+
libxrandr-dev libxi-dev libxkbcommon-dev \
86+
libasound2-dev libpulse-dev \
8887
libssl-dev \
8988
libprotobuf-dev protobuf-compiler \
90-
libabsl-dev
89+
libabsl-dev \
90+
libwayland-dev libdecor-0-dev
9191
9292
- name: Install deps (macOS)
9393
if: runner.os == 'macOS'
@@ -125,55 +125,49 @@ jobs:
125125
shell: bash
126126
run: |
127127
chmod +x build.sh
128-
args=(release -G "${{ matrix.generator }}" \
129-
--version "${{ steps.version.outputs.version }}" \
130-
--bundle --prefix "sdk-out/livekit-sdk-${{ matrix.name }}")
131-
if [[ "${{ runner.os }}" == "macOS" && -n "${{ matrix.macos_arch }}" ]]; then
132-
args+=(--macos-arch "${{ matrix.macos_arch }}")
133-
fi
134-
./build.sh "${args[@]}"
128+
./build.sh release-examples
129+
130+
# Create bundle directory with version in name
131+
bundleDir="sdk-out/livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}"
132+
mkdir -p "$bundleDir/lib"
133+
mkdir -p "$bundleDir/include"
134+
mkdir -p "$bundleDir/bin"
135+
136+
# Copy files
137+
cp -r build-release/lib/* "$bundleDir/lib/" 2>/dev/null || true
138+
cp -r build-release/include/* "$bundleDir/include/" 2>/dev/null || true
139+
cp -r build-release/bin/* "$bundleDir/bin/" 2>/dev/null || true
140+
141+
# List bundle contents
142+
echo "Bundle contents:"
143+
find "$bundleDir" -type f | head -50
135144
136145
# ---------- Build + Bundle (Windows) ----------
137146
- name: Build and Bundle (Windows)
138147
if: runner.os == 'Windows'
139148
shell: pwsh
140149
run: |
141-
# Build release
142-
.\build.cmd release
150+
# Build release with examples
151+
.\build.cmd release-examples
143152
144-
# Create bundle directory
145-
$bundleDir = "sdk-out/livekit-sdk-${{ matrix.name }}"
153+
# Create bundle directory with version in name
154+
$bundleDir = "sdk-out/livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}"
146155
New-Item -ItemType Directory -Force -Path $bundleDir
147156
New-Item -ItemType Directory -Force -Path "$bundleDir/lib"
148157
New-Item -ItemType Directory -Force -Path "$bundleDir/include"
158+
New-Item -ItemType Directory -Force -Path "$bundleDir/bin"
149159
150160
# Copy files
151161
Copy-Item -Recurse -Force "build-release/lib/*" "$bundleDir/lib/"
152162
Copy-Item -Recurse -Force "build-release/include/*" "$bundleDir/include/"
163+
Copy-Item -Recurse -Force "build-release/bin/*" "$bundleDir/bin/"
153164
154-
# ---------- Create archive ----------
155-
- name: Archive (Unix)
156-
if: runner.os != 'Windows'
157-
shell: bash
158-
run: |
159-
cd sdk-out
160-
tar -czf "../livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}.tar.gz" "livekit-sdk-${{ matrix.name }}"
161-
162-
- name: Archive (Windows)
163-
if: runner.os == 'Windows'
164-
shell: pwsh
165-
run: |
166-
Compress-Archive -Path "sdk-out/livekit-sdk-${{ matrix.name }}/*" `
167-
-DestinationPath "livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}.zip"
168-
169-
# ---------- Upload artifact ----------
165+
# ---------- Upload artifact (raw directory, no pre-compression) ----------
170166
- name: Upload build artifact
171167
uses: actions/upload-artifact@v4
172168
with:
173-
name: sdk-${{ matrix.name }}
174-
path: |
175-
livekit-sdk-${{ matrix.name }}-*.tar.gz
176-
livekit-sdk-${{ matrix.name }}-*.zip
169+
name: livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}
170+
path: sdk-out/livekit-sdk-${{ matrix.name }}-${{ steps.version.outputs.version }}
177171

178172
# ---------- Release Job ----------
179173
release:
@@ -204,9 +198,42 @@ jobs:
204198
- name: Download all artifacts
205199
uses: actions/download-artifact@v4
206200
with:
207-
pattern: sdk-*
208-
merge-multiple: true
209-
path: ${{ github.workspace }}/release-assets
201+
path: ${{ github.workspace }}/artifacts
202+
203+
- name: List downloaded artifacts
204+
run: |
205+
echo "Downloaded artifacts structure:"
206+
find ${{ github.workspace }}/artifacts -type f | head -100
207+
208+
# ---------- Create archives in release job ----------
209+
- name: Create release archives
210+
shell: bash
211+
run: |
212+
mkdir -p release-assets
213+
VERSION="${{ steps.version.outputs.version }}"
214+
cd ${{ github.workspace }}/artifacts
215+
216+
# List what we have
217+
echo "Artifacts downloaded:"
218+
ls -la
219+
220+
# Create tar.gz for Linux and macOS
221+
for platform in linux-x64 macos-arm64; do
222+
dirName="livekit-sdk-${platform}-${VERSION}"
223+
if [[ -d "${dirName}" ]]; then
224+
echo "Creating archive for ${platform}..."
225+
tar -czf "${{ github.workspace }}/release-assets/${dirName}.tar.gz" "${dirName}"
226+
echo "Created: ${dirName}.tar.gz"
227+
fi
228+
done
229+
230+
# Create zip for Windows
231+
dirName="livekit-sdk-windows-x64-${VERSION}"
232+
if [[ -d "${dirName}" ]]; then
233+
echo "Creating archive for windows-x64..."
234+
zip -r "${{ github.workspace }}/release-assets/${dirName}.zip" "${dirName}"
235+
echo "Created: ${dirName}.zip"
236+
fi
210237
211238
- name: List release assets
212239
run: ls -la ${{ github.workspace }}/release-assets/

CMakeLists.txt

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,43 @@ set(CMAKE_CXX_STANDARD 17)
2222
set(CMAKE_CXX_STANDARD_REQUIRED ON)
2323
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
2424

25+
# Set RPATH for Unix systems to find shared libraries in executable directory
26+
if(UNIX)
27+
# Use $ORIGIN on Linux, @executable_path on macOS
28+
if(APPLE)
29+
set(CMAKE_BUILD_RPATH "@executable_path")
30+
set(CMAKE_INSTALL_RPATH "@executable_path")
31+
else()
32+
set(CMAKE_BUILD_RPATH "$ORIGIN")
33+
set(CMAKE_INSTALL_RPATH "$ORIGIN")
34+
endif()
35+
set(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE)
36+
endif()
37+
2538
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
2639
set(LIVEKIT_IS_TOPLEVEL TRUE)
2740
else()
2841
set(LIVEKIT_IS_TOPLEVEL FALSE)
2942
endif()
3043

3144
if(LIVEKIT_IS_TOPLEVEL)
32-
if(WIN32)
33-
set(PLATFORM_DIR "windows-x64")
34-
elseif(APPLE)
35-
set(PLATFORM_DIR "macos-universal")
36-
elseif(UNIX)
37-
set(PLATFORM_DIR "linux-x64")
38-
else()
39-
set(PLATFORM_DIR "unknown")
40-
endif()
41-
42-
if(WIN32)
43-
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${PLATFORM_DIR}/$<LOWER_CASE:$<CONFIG>>)
44-
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${PLATFORM_DIR}/$<LOWER_CASE:$<CONFIG>>)
45-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<LOWER_CASE:$<CONFIG>>)
46-
else()
47-
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${PLATFORM_DIR})
48-
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${PLATFORM_DIR})
49-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
50-
endif()
45+
# Since we use separate build directories (build-debug/build-release),
46+
# we don't need additional platform/config subdirectories
47+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
48+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
49+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
50+
51+
# For multi-config generators (Visual Studio), override per-config directories
52+
# to prevent adding Debug/Release subdirectories
53+
foreach(CONFIG_TYPE Debug Release RelWithDebInfo MinSizeRel)
54+
string(TOUPPER ${CONFIG_TYPE} CONFIG_TYPE_UPPER)
55+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG_TYPE_UPPER} ${CMAKE_BINARY_DIR}/lib)
56+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG_TYPE_UPPER} ${CMAKE_BINARY_DIR}/lib)
57+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_TYPE_UPPER} ${CMAKE_BINARY_DIR}/bin)
58+
endforeach()
5159
endif()
5260

61+
5362
if(MSVC)
5463
# Use dynamic CRT (/MD) for compatibility with Qt and other /MD libraries.
5564
# The livekit_ffi.dll isolates the /MT libwebrtc dependency, so livekit.lib
@@ -202,18 +211,34 @@ if(WIN32)
202211
IMPORTED_IMPLIB_MINSIZEREL "${RUST_IMPLIB_RELEASE}"
203212
INTERFACE_INCLUDE_DIRECTORIES "${RUST_ROOT}/livekit-ffi/include"
204213
)
214+
elseif(APPLE)
215+
# macOS: use dynamic library (.dylib) to isolate Debug/Release ABI differences
216+
add_library(livekit_ffi SHARED IMPORTED GLOBAL)
217+
set(RUST_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.dylib")
218+
set(RUST_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.dylib")
219+
220+
set_target_properties(livekit_ffi PROPERTIES
221+
IMPORTED_CONFIGURATIONS "Debug;Release;RelWithDebInfo;MinSizeRel"
222+
IMPORTED_LOCATION_DEBUG "${RUST_LIB_DEBUG}"
223+
IMPORTED_LOCATION_RELEASE "${RUST_LIB_RELEASE}"
224+
IMPORTED_LOCATION_RELWITHDEBINFO "${RUST_LIB_RELEASE}"
225+
IMPORTED_LOCATION_MINSIZEREL "${RUST_LIB_RELEASE}"
226+
IMPORTED_NO_SONAME TRUE
227+
INTERFACE_INCLUDE_DIRECTORIES "${RUST_ROOT}/livekit-ffi/include"
228+
)
205229
else()
206-
# Non-Windows: use static library as before
207-
add_library(livekit_ffi STATIC IMPORTED GLOBAL)
208-
set(RUST_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.a")
209-
set(RUST_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.a")
230+
# Linux: use dynamic library (.so) to isolate Debug/Release ABI differences
231+
add_library(livekit_ffi SHARED IMPORTED GLOBAL)
232+
set(RUST_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.so")
233+
set(RUST_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.so")
210234

211235
set_target_properties(livekit_ffi PROPERTIES
212236
IMPORTED_CONFIGURATIONS "Debug;Release;RelWithDebInfo;MinSizeRel"
213237
IMPORTED_LOCATION_DEBUG "${RUST_LIB_DEBUG}"
214238
IMPORTED_LOCATION_RELEASE "${RUST_LIB_RELEASE}"
215239
IMPORTED_LOCATION_RELWITHDEBINFO "${RUST_LIB_RELEASE}"
216240
IMPORTED_LOCATION_MINSIZEREL "${RUST_LIB_RELEASE}"
241+
IMPORTED_NO_SONAME TRUE
217242
INTERFACE_INCLUDE_DIRECTORIES "${RUST_ROOT}/livekit-ffi/include"
218243
)
219244
endif()
@@ -241,20 +266,7 @@ add_custom_target(build_rust_ffi
241266
DEPENDS "${RUST_LIB_DEBUG}" "${RUST_LIB_RELEASE}"
242267
)
243268

244-
if(UNIX AND NOT APPLE)
245-
if(NOT CMAKE_AR)
246-
find_program(CMAKE_AR ar REQUIRED)
247-
endif()
248-
# Remove protozero_plugin.o from static library to avoid duplicate symbols
249-
# The file may not exist in all builds, so we use '|| true' to ignore errors
250-
add_custom_command(
251-
TARGET build_rust_ffi
252-
POST_BUILD
253-
COMMAND ${CMAKE_AR} -dv "${RUST_LIB_RELEASE}" protozero_plugin.o || true
254-
COMMAND ${CMAKE_AR} -dv "${RUST_LIB_DEBUG}" protozero_plugin.o || true
255-
COMMENT "Removing protozero_plugin.o from liblivekit_ffi.a (if present)"
256-
)
257-
endif()
269+
# Note: protozero_plugin.o removal is no longer needed since we use dynamic libraries on Unix
258270

259271
add_library(livekit STATIC
260272
src/audio_frame.cpp
@@ -350,15 +362,27 @@ if(LIVEKIT_IS_TOPLEVEL)
350362
"$<TARGET_FILE_DIR:livekit>/livekit_ffi.dll.lib"
351363
COMMENT "Copying livekit_ffi.dll.lib to output directory"
352364
)
365+
elseif(APPLE)
366+
# macOS: copy .dylib
367+
set(FFI_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.dylib")
368+
set(FFI_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.dylib")
369+
add_custom_command(
370+
TARGET livekit POST_BUILD
371+
COMMAND ${CMAKE_COMMAND} -E copy
372+
"$<IF:$<CONFIG:Debug>,${FFI_LIB_DEBUG},${FFI_LIB_RELEASE}>"
373+
"$<TARGET_FILE_DIR:livekit>/liblivekit_ffi.dylib"
374+
COMMENT "Copying liblivekit_ffi.dylib to output directory"
375+
)
353376
else()
354-
set(FFI_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.a")
355-
set(FFI_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.a")
377+
# Linux: copy .so
378+
set(FFI_LIB_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.so")
379+
set(FFI_LIB_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.so")
356380
add_custom_command(
357381
TARGET livekit POST_BUILD
358382
COMMAND ${CMAKE_COMMAND} -E copy
359383
"$<IF:$<CONFIG:Debug>,${FFI_LIB_DEBUG},${FFI_LIB_RELEASE}>"
360-
"$<TARGET_FILE_DIR:livekit>/liblivekit_ffi.a"
361-
COMMENT "Copying liblivekit_ffi.a to output directory"
384+
"$<TARGET_FILE_DIR:livekit>/liblivekit_ffi.so"
385+
COMMENT "Copying liblivekit_ffi.so to output directory"
362386
)
363387
endif()
364388

DEPENDENCIES.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# LiveKit C++ SDK - System Dependencies
22

3-
This file lists all system-level dependencies required to link against the LiveKit C++ SDK static libraries.
3+
This file lists all system-level dependencies required to link against the LiveKit C++ SDK libraries.
44

55
## Overview
66

7-
The LiveKit SDK consists of two static libraries:
8-
- `livekit.lib` (or `liblivekit.a` on Unix)
9-
- `livekit_ffi.lib` (or `liblivekit_ffi.a` on Unix)
7+
The LiveKit SDK consists of two libraries:
8+
- `livekit.lib` / `liblivekit.a` - Main SDK static library
9+
- `livekit_ffi.dll` / `liblivekit_ffi.so` / `liblivekit_ffi.dylib` - Rust FFI dynamic library
1010

1111
## Distribution Model
1212

@@ -15,7 +15,7 @@ The SDK uses different distribution strategies per platform:
1515
### Windows (Complete Package)
1616
**Ready to use** - All dependencies included:
1717
- `livekit.lib` - Main SDK static library
18-
- `livekit_ffi.dll` + `livekit_ffi.dll.lib` - Rust FFI layer
18+
- `livekit_ffi.dll` + `livekit_ffi.dll.lib` - Rust FFI dynamic library
1919
- `libprotobuf.dll` + `libprotobuf.lib` - Protocol Buffers runtime
2020
- `abseil_dll.dll` + `abseil_dll.lib` - Abseil C++ library
2121

@@ -24,21 +24,21 @@ The SDK uses different distribution strategies per platform:
2424
### Linux (Minimal Package)
2525
⚠️ **Requires system dependencies**:
2626
- `liblivekit.a` - Main SDK static library (included)
27-
- `liblivekit_ffi.a` - Rust FFI layer (included)
27+
- `liblivekit_ffi.so` - Rust FFI dynamic library (included, **must be placed alongside your executable**)
2828
- `libprotobuf` - Must install via `apt install libprotobuf-dev`
2929
- `libssl` - Must install via `apt install libssl-dev`
3030
- `libabsl` - Only if built with Protobuf 6.0+: `apt install libabsl-dev`
3131

32-
**User action**: Install required packages on target system before linking.
32+
**User action**: Install required packages and copy `liblivekit_ffi.so` to your executable directory.
3333

3434
### macOS (Minimal Package)
3535
⚠️ **Requires system dependencies**:
3636
- `liblivekit.a` - Main SDK static library (included)
37-
- `liblivekit_ffi.a` - Rust FFI layer (included)
37+
- `liblivekit_ffi.dylib` - Rust FFI dynamic library (included, **must be placed alongside your executable**)
3838
- `protobuf` - Must install via `brew install protobuf`
3939
- `abseil` - Only if built with Protobuf 6.0+: `brew install abseil`
4040

41-
**User action**: Install required packages on target system before linking.
41+
**User action**: Install required packages and copy `liblivekit_ffi.dylib` to your executable directory.
4242

4343
---
4444

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ This SDK enables native C++ applications to connect to LiveKit servers for real-
2020

2121
### For Using the Pre-built SDK:
2222
- **Windows:** ✅ All dependencies included (DLLs bundled) - ready to use
23-
- **Linux:** ⚠️ Requires `libprotobuf` and `libssl-dev` installed on target system
24-
- **macOS:** ⚠️ Requires `protobuf` installed via Homebrew on target system
23+
- **Linux:** ⚠️ Requires `libprotobuf` and `libssl-dev`; deploy `liblivekit_ffi.so` with your executable
24+
- **macOS:** ⚠️ Requires `protobuf`; deploy `liblivekit_ffi.dylib` with your executable
2525

2626
> **Note**: If the SDK was built with Protobuf 6.0+, you also need `libabsl-dev` (Linux) or `abseil` (macOS).
2727

0 commit comments

Comments
 (0)