From d6ce7bbc2588b2c479396f7883787a33008d1c7c Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 12:45:36 -0700 Subject: [PATCH 1/8] move Low/HighFpsMultiPacket to stress tests, enable integration tests on mac runners --- .github/workflows/tests.yml | 6 +- client-sdk-rust | 2 +- src/tests/integration/test_data_track.cpp | 140 ------------ src/tests/stress/test_data_track_stress.cpp | 222 ++++++++++++++++++++ 4 files changed, 225 insertions(+), 145 deletions(-) create mode 100644 src/tests/stress/test_data_track_stress.cpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 733017d1..3ed6440f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,13 +63,11 @@ jobs: - os: macos-26-xlarge name: macos-arm64 build_cmd: ./build.sh release-tests - # E2E not possible on GHA Mac runner currently - e2e-testing: false + e2e-testing: true - os: macos-26-large name: macos-x64 build_cmd: ./build.sh release-tests --macos-arch x86_64 - # E2E not possible on GHA Mac runner currently - e2e-testing: false + e2e-testing: true # Pinned to Windows 2022 for current VS 17 implementation - os: windows-2022 name: windows-x64 diff --git a/client-sdk-rust b/client-sdk-rust index d74892de..b4e737be 160000 --- a/client-sdk-rust +++ b/client-sdk-rust @@ -1 +1 @@ -Subproject commit d74892de57724bd08dc2307b68e4251b8eae2f33 +Subproject commit b4e737bec36da58776c0a9c2e1fd9e67037b823c diff --git a/src/tests/integration/test_data_track.cpp b/src/tests/integration/test_data_track.cpp index f0d3dc63..2df4d175 100644 --- a/src/tests/integration/test_data_track.cpp +++ b/src/tests/integration/test_data_track.cpp @@ -25,15 +25,12 @@ #include #include -#include #include #include #include -#include #include "../common/test_common.h" #include "ffi_client.h" -#include "lk_log.h" namespace livekit::test { @@ -287,135 +284,9 @@ void runEncryptedDataTrackRoundTrip(KeyDerivationFunction key_derivation_functio class DataTrackE2ETest : public LiveKitTestBase {}; -class DataTrackTransportTest : public DataTrackE2ETest, - public ::testing::WithParamInterface> {}; - class DataTrackKeyDerivationTest : public DataTrackE2ETest, public ::testing::WithParamInterface {}; -TEST_P(DataTrackTransportTest, PublishesAndReceivesFramesEndToEnd) { - const auto publish_fps = std::get<0>(GetParam()); - const auto payload_len = std::get<1>(GetParam()); - const auto track_name = makeTrackName("transport"); - - // How long to publish frames for. - constexpr auto PUBLISH_DURATION = 10s; - - // Percentage of total frames that must be received on the subscriber end in - // order for the test to pass. - constexpr float MIN_PERCENTAGE = 0.90f; - - std::vector room_configs(2); - room_configs[0].room_options.single_peer_connection = false; - room_configs[1].room_options.single_peer_connection = false; - - DataTrackPublishedDelegate subscriber_delegate; - room_configs[1].delegate = &subscriber_delegate; - - auto rooms = testRooms(room_configs); - auto& publisher_room = rooms[0]; - const auto publisher_identity = publisher_room->localParticipant()->identity(); - - auto track = requirePublishedTrack(publisher_room->localParticipant(), track_name); - std::cerr << "Track published\n"; - - auto remote_track = subscriber_delegate.waitForTrack(kTrackWaitTimeout); - std::cerr << "Got remote track: " << remote_track->info().sid << "\n"; - - ASSERT_NE(remote_track, nullptr) << "Timed out waiting for remote data track"; - EXPECT_TRUE(remote_track->isPublished()); - EXPECT_FALSE(remote_track->info().uses_e2ee); - EXPECT_EQ(remote_track->info().name, track_name); - EXPECT_EQ(remote_track->publisherIdentity(), publisher_identity); - - const auto frame_count = - static_cast(std::llround(std::chrono::duration(PUBLISH_DURATION).count() * publish_fps)); - - auto publish = [&]() { - if (!track->isPublished()) { - throw std::runtime_error("Publisher failed to publish data track"); - } - if (track->info().uses_e2ee) { - throw std::runtime_error("Unexpected E2EE on test data track"); - } - if (track->info().name != track_name) { - throw std::runtime_error("Published track name mismatch"); - } - - const auto frame_interval = std::chrono::duration_cast( - std::chrono::duration(1.0 / publish_fps)); - auto next_send = std::chrono::steady_clock::now(); - - std::cout << "Publishing " << frame_count << " frames with payload length " << payload_len << '\n'; - for (size_t index = 0; index < frame_count; ++index) { - std::vector payload(payload_len, static_cast(index)); - requirePushSuccess(track->tryPush(std::move(payload)), "Failed to push data frame"); - - next_send += frame_interval; - std::this_thread::sleep_until(next_send); - } - - track->unpublishDataTrack(); - }; - - auto subscribe_result = remote_track->subscribe(); - if (!subscribe_result) { - FAIL() << describeDataTrackError(subscribe_result.error()); - } - auto subscription = subscribe_result.value(); - - std::promise receive_count_promise; - auto receive_count_future = receive_count_promise.get_future(); - - auto subscribe = [&]() { - size_t received_count = 0; - DataTrackFrame frame; - while (subscription->read(frame) && received_count < frame_count) { - if (frame.payload.empty()) { - throw std::runtime_error("Received empty data frame"); - } - - const auto first_byte = frame.payload.front(); - if (!std::all_of(frame.payload.begin(), frame.payload.end(), - [first_byte](std::uint8_t byte) { return byte == first_byte; })) { - throw std::runtime_error("Received frame with inconsistent payload"); - } - if (frame.user_timestamp.has_value()) { - throw std::runtime_error("Received unexpected user timestamp in transport test"); - } - - ++received_count; - } - - receive_count_promise.set_value(received_count); - }; - - // Launch both publisher and subscriber - auto pub_fut = std::async(std::launch::async, publish); - auto sub_fut = std::async(std::launch::async, subscribe); - - // Wait for both, with a combined deadline (the timeout(...) wrapper). - const auto deadline = std::chrono::steady_clock::now() + PUBLISH_DURATION + 25s; - - const bool pub_ok = pub_fut.wait_until(deadline) == std::future_status::ready; - const bool sub_ok = sub_fut.wait_until(deadline) == std::future_status::ready; - - if (!pub_ok || !sub_ok) { - ADD_FAILURE() << "Timed out waiting for data frames"; - } - - // Equivalent of `try_join!`'s ? — re-throws any exception from either task - pub_fut.get(); - sub_fut.get(); - - const auto received_count = receive_count_future.get(); - const auto received_percent = static_cast(received_count) / static_cast(frame_count); - std::cout << "Received " << received_count << "/" << frame_count << " frames (" << received_percent * 100.0f << "%)" - << '\n'; - - EXPECT_GE(received_percent, MIN_PERCENTAGE) << "Received " << received_count << "/" << frame_count << " frames"; -} - TEST_F(DataTrackE2ETest, UnpublishUpdatesPublishedStateEndToEnd) { const auto track_name = makeTrackName("published_state"); @@ -853,21 +724,10 @@ TEST_F(DataTrackE2ETest, PreservesUserTimestampOnEncryptedDataTrack) { local_track->unpublishDataTrack(); } -std::string dataTrackParamName(const ::testing::TestParamInfo>& info) { - if (std::get<0>(info.param) > 100.0) { - return "HighFpsSinglePacket"; - } - return "LowFpsMultiPacket"; -} - std::string keyDerivationParamName(const ::testing::TestParamInfo& info) { return keyDerivationFunctionName(info.param); } -INSTANTIATE_TEST_SUITE_P(DataTrackScenarios, DataTrackTransportTest, - ::testing::Values(std::make_tuple(120.0, size_t{8192}), std::make_tuple(10.0, size_t{196608})), - dataTrackParamName); - INSTANTIATE_TEST_SUITE_P(KeyDerivationFunctions, DataTrackKeyDerivationTest, ::testing::Values(KeyDerivationFunction::PBKDF2, KeyDerivationFunction::HKDF), keyDerivationParamName); diff --git a/src/tests/stress/test_data_track_stress.cpp b/src/tests/stress/test_data_track_stress.cpp new file mode 100644 index 00000000..d64cef35 --- /dev/null +++ b/src/tests/stress/test_data_track_stress.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2026 LiveKit + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/test_common.h" + +namespace livekit::test { + +using namespace std::chrono_literals; + +namespace { + +constexpr char kTrackNamePrefix[] = "data_track_stress"; +constexpr auto kTrackWaitTimeout = 10s; + +std::string makeTrackName(const std::string& suffix) { + return std::string(kTrackNamePrefix) + "_" + suffix + "_" + std::to_string(getTimestampUs()); +} + +template +std::string describeDataTrackError(const Error& error) { + return "code=" + std::to_string(static_cast(error.code)) + " message=" + error.message; +} + +std::shared_ptr requirePublishedTrack(LocalParticipant* participant, const std::string& name) { + auto result = participant->publishDataTrack(name); + if (!result) { + throw std::runtime_error("Failed to publish data track: " + describeDataTrackError(result.error())); + } + return result.value(); +} + +void requirePushSuccess(const Result& result, const std::string& context) { + if (!result) { + throw std::runtime_error(context + ": " + describeDataTrackError(result.error())); + } +} + +class DataTrackPublishedDelegate : public RoomDelegate { +public: + void onDataTrackPublished(Room&, const DataTrackPublishedEvent& event) override { + if (!event.track) { + return; + } + + std::lock_guard lock(mutex_); + tracks_.push_back(event.track); + cv_.notify_all(); + } + + std::shared_ptr waitForTrack(std::chrono::milliseconds timeout) { + std::unique_lock lock(mutex_); + if (!cv_.wait_for(lock, timeout, [this] { return !tracks_.empty(); })) { + return nullptr; + } + return tracks_.front(); + } + +private: + std::mutex mutex_; + std::condition_variable cv_; + std::vector> tracks_; +}; + +void runDataTrackTransportStress(double publish_fps, std::size_t payload_len) { + const auto track_name = makeTrackName("transport"); + + constexpr auto PUBLISH_DURATION = 10s; + constexpr float MIN_PERCENTAGE = 0.90f; + + std::vector room_configs(2); + room_configs[0].room_options.single_peer_connection = false; + room_configs[1].room_options.single_peer_connection = false; + + DataTrackPublishedDelegate subscriber_delegate; + room_configs[1].delegate = &subscriber_delegate; + + auto rooms = testRooms(room_configs); + auto& publisher_room = rooms[0]; + const auto publisher_identity = publisher_room->localParticipant()->identity(); + + auto track = requirePublishedTrack(publisher_room->localParticipant(), track_name); + std::cerr << "Track published\n"; + + auto remote_track = subscriber_delegate.waitForTrack(kTrackWaitTimeout); + ASSERT_NE(remote_track, nullptr) << "Timed out waiting for remote data track"; + std::cerr << "Got remote track: " << remote_track->info().sid << "\n"; + + EXPECT_TRUE(remote_track->isPublished()); + EXPECT_FALSE(remote_track->info().uses_e2ee); + EXPECT_EQ(remote_track->info().name, track_name); + EXPECT_EQ(remote_track->publisherIdentity(), publisher_identity); + + const auto frame_count = + static_cast(std::llround(std::chrono::duration(PUBLISH_DURATION).count() * publish_fps)); + + auto publish = [&]() { + if (!track->isPublished()) { + throw std::runtime_error("Publisher failed to publish data track"); + } + if (track->info().uses_e2ee) { + throw std::runtime_error("Unexpected E2EE on test data track"); + } + if (track->info().name != track_name) { + throw std::runtime_error("Published track name mismatch"); + } + + const auto frame_interval = std::chrono::duration_cast( + std::chrono::duration(1.0 / publish_fps)); + auto next_send = std::chrono::steady_clock::now(); + + std::cout << "Publishing " << frame_count << " frames with payload length " << payload_len << '\n'; + for (std::size_t index = 0; index < frame_count; ++index) { + std::vector payload(payload_len, static_cast(index)); + requirePushSuccess(track->tryPush(std::move(payload)), "Failed to push data frame"); + + next_send += frame_interval; + std::this_thread::sleep_until(next_send); + } + + track->unpublishDataTrack(); + }; + + auto subscribe_result = remote_track->subscribe(); + if (!subscribe_result) { + FAIL() << describeDataTrackError(subscribe_result.error()); + } + auto subscription = subscribe_result.value(); + + std::promise receive_count_promise; + auto receive_count_future = receive_count_promise.get_future(); + + auto subscribe = [&]() { + std::size_t received_count = 0; + DataTrackFrame frame; + while (subscription->read(frame) && received_count < frame_count) { + if (frame.payload.empty()) { + throw std::runtime_error("Received empty data frame"); + } + + const auto first_byte = frame.payload.front(); + if (!std::all_of(frame.payload.begin(), frame.payload.end(), + [first_byte](std::uint8_t byte) { return byte == first_byte; })) { + throw std::runtime_error("Received frame with inconsistent payload"); + } + if (frame.user_timestamp.has_value()) { + throw std::runtime_error("Received unexpected user timestamp in transport test"); + } + + ++received_count; + } + + receive_count_promise.set_value(received_count); + }; + + auto pub_fut = std::async(std::launch::async, publish); + auto sub_fut = std::async(std::launch::async, subscribe); + + const auto deadline = std::chrono::steady_clock::now() + PUBLISH_DURATION + 25s; + + const bool pub_ok = pub_fut.wait_until(deadline) == std::future_status::ready; + const bool sub_ok = sub_fut.wait_until(deadline) == std::future_status::ready; + + if (!pub_ok || !sub_ok) { + ADD_FAILURE() << "Timed out waiting for data frames"; + } + + pub_fut.get(); + sub_fut.get(); + + const auto received_count = receive_count_future.get(); + const auto received_percent = static_cast(received_count) / static_cast(frame_count); + std::cout << "Received " << received_count << "/" << frame_count << " frames (" << received_percent * 100.0f << "%)" + << '\n'; + + EXPECT_GE(received_percent, MIN_PERCENTAGE) << "Received " << received_count << "/" << frame_count << " frames"; +} + +} // namespace + +class DataTrackStressTest : public LiveKitTestBase {}; + +TEST_F(DataTrackStressTest, LowFpsMultiPacket) { + runDataTrackTransportStress(10.0, std::size_t{196608}); +} + +TEST_F(DataTrackStressTest, HighFpsMultiPacket) { + runDataTrackTransportStress(120.0, std::size_t{8192}); +} + +} // namespace livekit::test From 3ff7883055be1f7969b87d99d521292ee9957c0c Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 12:49:34 -0700 Subject: [PATCH 2/8] rm test which is more benchmark --- src/tests/stress/test_data_track_stress.cpp | 222 -------------------- 1 file changed, 222 deletions(-) delete mode 100644 src/tests/stress/test_data_track_stress.cpp diff --git a/src/tests/stress/test_data_track_stress.cpp b/src/tests/stress/test_data_track_stress.cpp deleted file mode 100644 index d64cef35..00000000 --- a/src/tests/stress/test_data_track_stress.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2026 LiveKit - * - * 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. - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../common/test_common.h" - -namespace livekit::test { - -using namespace std::chrono_literals; - -namespace { - -constexpr char kTrackNamePrefix[] = "data_track_stress"; -constexpr auto kTrackWaitTimeout = 10s; - -std::string makeTrackName(const std::string& suffix) { - return std::string(kTrackNamePrefix) + "_" + suffix + "_" + std::to_string(getTimestampUs()); -} - -template -std::string describeDataTrackError(const Error& error) { - return "code=" + std::to_string(static_cast(error.code)) + " message=" + error.message; -} - -std::shared_ptr requirePublishedTrack(LocalParticipant* participant, const std::string& name) { - auto result = participant->publishDataTrack(name); - if (!result) { - throw std::runtime_error("Failed to publish data track: " + describeDataTrackError(result.error())); - } - return result.value(); -} - -void requirePushSuccess(const Result& result, const std::string& context) { - if (!result) { - throw std::runtime_error(context + ": " + describeDataTrackError(result.error())); - } -} - -class DataTrackPublishedDelegate : public RoomDelegate { -public: - void onDataTrackPublished(Room&, const DataTrackPublishedEvent& event) override { - if (!event.track) { - return; - } - - std::lock_guard lock(mutex_); - tracks_.push_back(event.track); - cv_.notify_all(); - } - - std::shared_ptr waitForTrack(std::chrono::milliseconds timeout) { - std::unique_lock lock(mutex_); - if (!cv_.wait_for(lock, timeout, [this] { return !tracks_.empty(); })) { - return nullptr; - } - return tracks_.front(); - } - -private: - std::mutex mutex_; - std::condition_variable cv_; - std::vector> tracks_; -}; - -void runDataTrackTransportStress(double publish_fps, std::size_t payload_len) { - const auto track_name = makeTrackName("transport"); - - constexpr auto PUBLISH_DURATION = 10s; - constexpr float MIN_PERCENTAGE = 0.90f; - - std::vector room_configs(2); - room_configs[0].room_options.single_peer_connection = false; - room_configs[1].room_options.single_peer_connection = false; - - DataTrackPublishedDelegate subscriber_delegate; - room_configs[1].delegate = &subscriber_delegate; - - auto rooms = testRooms(room_configs); - auto& publisher_room = rooms[0]; - const auto publisher_identity = publisher_room->localParticipant()->identity(); - - auto track = requirePublishedTrack(publisher_room->localParticipant(), track_name); - std::cerr << "Track published\n"; - - auto remote_track = subscriber_delegate.waitForTrack(kTrackWaitTimeout); - ASSERT_NE(remote_track, nullptr) << "Timed out waiting for remote data track"; - std::cerr << "Got remote track: " << remote_track->info().sid << "\n"; - - EXPECT_TRUE(remote_track->isPublished()); - EXPECT_FALSE(remote_track->info().uses_e2ee); - EXPECT_EQ(remote_track->info().name, track_name); - EXPECT_EQ(remote_track->publisherIdentity(), publisher_identity); - - const auto frame_count = - static_cast(std::llround(std::chrono::duration(PUBLISH_DURATION).count() * publish_fps)); - - auto publish = [&]() { - if (!track->isPublished()) { - throw std::runtime_error("Publisher failed to publish data track"); - } - if (track->info().uses_e2ee) { - throw std::runtime_error("Unexpected E2EE on test data track"); - } - if (track->info().name != track_name) { - throw std::runtime_error("Published track name mismatch"); - } - - const auto frame_interval = std::chrono::duration_cast( - std::chrono::duration(1.0 / publish_fps)); - auto next_send = std::chrono::steady_clock::now(); - - std::cout << "Publishing " << frame_count << " frames with payload length " << payload_len << '\n'; - for (std::size_t index = 0; index < frame_count; ++index) { - std::vector payload(payload_len, static_cast(index)); - requirePushSuccess(track->tryPush(std::move(payload)), "Failed to push data frame"); - - next_send += frame_interval; - std::this_thread::sleep_until(next_send); - } - - track->unpublishDataTrack(); - }; - - auto subscribe_result = remote_track->subscribe(); - if (!subscribe_result) { - FAIL() << describeDataTrackError(subscribe_result.error()); - } - auto subscription = subscribe_result.value(); - - std::promise receive_count_promise; - auto receive_count_future = receive_count_promise.get_future(); - - auto subscribe = [&]() { - std::size_t received_count = 0; - DataTrackFrame frame; - while (subscription->read(frame) && received_count < frame_count) { - if (frame.payload.empty()) { - throw std::runtime_error("Received empty data frame"); - } - - const auto first_byte = frame.payload.front(); - if (!std::all_of(frame.payload.begin(), frame.payload.end(), - [first_byte](std::uint8_t byte) { return byte == first_byte; })) { - throw std::runtime_error("Received frame with inconsistent payload"); - } - if (frame.user_timestamp.has_value()) { - throw std::runtime_error("Received unexpected user timestamp in transport test"); - } - - ++received_count; - } - - receive_count_promise.set_value(received_count); - }; - - auto pub_fut = std::async(std::launch::async, publish); - auto sub_fut = std::async(std::launch::async, subscribe); - - const auto deadline = std::chrono::steady_clock::now() + PUBLISH_DURATION + 25s; - - const bool pub_ok = pub_fut.wait_until(deadline) == std::future_status::ready; - const bool sub_ok = sub_fut.wait_until(deadline) == std::future_status::ready; - - if (!pub_ok || !sub_ok) { - ADD_FAILURE() << "Timed out waiting for data frames"; - } - - pub_fut.get(); - sub_fut.get(); - - const auto received_count = receive_count_future.get(); - const auto received_percent = static_cast(received_count) / static_cast(frame_count); - std::cout << "Received " << received_count << "/" << frame_count << " frames (" << received_percent * 100.0f << "%)" - << '\n'; - - EXPECT_GE(received_percent, MIN_PERCENTAGE) << "Received " << received_count << "/" << frame_count << " frames"; -} - -} // namespace - -class DataTrackStressTest : public LiveKitTestBase {}; - -TEST_F(DataTrackStressTest, LowFpsMultiPacket) { - runDataTrackTransportStress(10.0, std::size_t{196608}); -} - -TEST_F(DataTrackStressTest, HighFpsMultiPacket) { - runDataTrackTransportStress(120.0, std::size_t{8192}); -} - -} // namespace livekit::test From cbe60a5930f3d8c130d5c1f80bc48adf07f15754 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 15:38:49 -0700 Subject: [PATCH 3/8] update CMake to work with newest rust hash --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 895284b1..2332661d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,10 @@ execute_process( if(rv) message(FATAL_ERROR \"cargo build failed with code: \${rv}\") endif() + +if(DEFINED EXPECTED_OUTPUT AND NOT EXPECTED_OUTPUT STREQUAL \"\" AND NOT EXISTS \"\${EXPECTED_OUTPUT}\") + message(FATAL_ERROR \"cargo build completed but did not produce expected Rust FFI artifact: \${EXPECTED_OUTPUT}\") +endif() ") # Use SHARED library (DLL) on Windows to isolate /MT CRT from consuming /MD apps @@ -335,7 +339,7 @@ file(GLOB_RECURSE RUST_SOURCES ) add_custom_command( - OUTPUT "${RUST_LIB_DEBUG}" "${RUST_LIB_RELEASE}" + OUTPUT "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" COMMAND "${CMAKE_COMMAND}" -DCFG=$ -DRUST_ROOT=${RUST_ROOT} @@ -343,7 +347,9 @@ add_custom_command( -DPROTOC_PATH=${Protobuf_PROTOC_EXECUTABLE} -DRUST_TARGET=${RUST_TARGET_TRIPLE} -DGCC_LIB_DIR=${GCC_LIB_DIR} + -DEXPECTED_OUTPUT=$,${RUST_LIB_DEBUG},${RUST_LIB_RELEASE}> -P "${RUN_CARGO_SCRIPT}" + COMMAND "${CMAKE_COMMAND}" -E touch "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" WORKING_DIRECTORY "${RUST_ROOT}" DEPENDS ${RUST_SOURCES} COMMENT "Building Rust FFI via cargo" @@ -351,7 +357,7 @@ add_custom_command( ) add_custom_target(build_rust_ffi - DEPENDS "${RUST_LIB_DEBUG}" "${RUST_LIB_RELEASE}" + DEPENDS "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" ) # Note: protozero_plugin.o removal is no longer needed since we use dynamic libraries on Unix From 6598261929620bd417f4e196483e984bc92bdf7c Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 16:12:22 -0700 Subject: [PATCH 4/8] update rust hash --- client-sdk-rust | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-sdk-rust b/client-sdk-rust index b4e737be..516e43c2 160000 --- a/client-sdk-rust +++ b/client-sdk-rust @@ -1 +1 @@ -Subproject commit b4e737bec36da58776c0a9c2e1fd9e67037b823c +Subproject commit 516e43c23577cb9a3ab57dae7fc805984acb9c11 From 624849f37676d23180755b2788da96aa7655ec2f Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 16:14:38 -0700 Subject: [PATCH 5/8] Revert "update CMake to work with newest rust hash" This reverts commit ae4b2de95ed6028cdb752b4c257cff9653a2f989. --- CMakeLists.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2332661d..895284b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,10 +268,6 @@ execute_process( if(rv) message(FATAL_ERROR \"cargo build failed with code: \${rv}\") endif() - -if(DEFINED EXPECTED_OUTPUT AND NOT EXPECTED_OUTPUT STREQUAL \"\" AND NOT EXISTS \"\${EXPECTED_OUTPUT}\") - message(FATAL_ERROR \"cargo build completed but did not produce expected Rust FFI artifact: \${EXPECTED_OUTPUT}\") -endif() ") # Use SHARED library (DLL) on Windows to isolate /MT CRT from consuming /MD apps @@ -339,7 +335,7 @@ file(GLOB_RECURSE RUST_SOURCES ) add_custom_command( - OUTPUT "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" + OUTPUT "${RUST_LIB_DEBUG}" "${RUST_LIB_RELEASE}" COMMAND "${CMAKE_COMMAND}" -DCFG=$ -DRUST_ROOT=${RUST_ROOT} @@ -347,9 +343,7 @@ add_custom_command( -DPROTOC_PATH=${Protobuf_PROTOC_EXECUTABLE} -DRUST_TARGET=${RUST_TARGET_TRIPLE} -DGCC_LIB_DIR=${GCC_LIB_DIR} - -DEXPECTED_OUTPUT=$,${RUST_LIB_DEBUG},${RUST_LIB_RELEASE}> -P "${RUN_CARGO_SCRIPT}" - COMMAND "${CMAKE_COMMAND}" -E touch "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" WORKING_DIRECTORY "${RUST_ROOT}" DEPENDS ${RUST_SOURCES} COMMENT "Building Rust FFI via cargo" @@ -357,7 +351,7 @@ add_custom_command( ) add_custom_target(build_rust_ffi - DEPENDS "${LIVEKIT_BINARY_DIR}/rust_ffi_$.stamp" + DEPENDS "${RUST_LIB_DEBUG}" "${RUST_LIB_RELEASE}" ) # Note: protozero_plugin.o removal is no longer needed since we use dynamic libraries on Unix From 0e53b3af4e8da624f2514777a632244cede48cc1 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Wed, 20 May 2026 16:46:52 -0700 Subject: [PATCH 6/8] restore rust hash --- client-sdk-rust | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-sdk-rust b/client-sdk-rust index 516e43c2..d74892de 160000 --- a/client-sdk-rust +++ b/client-sdk-rust @@ -1 +1 @@ -Subproject commit 516e43c23577cb9a3ab57dae7fc805984acb9c11 +Subproject commit d74892de57724bd08dc2307b68e4251b8eae2f33 From 70e81a28970bbd4e721a94285a4e83303c433604 Mon Sep 17 00:00:00 2001 From: Stephen DeRosa Date: Fri, 22 May 2026 09:47:27 -0700 Subject: [PATCH 7/8] update cppm example commit --- cpp-example-collection | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp-example-collection b/cpp-example-collection index 46083ea4..839e38fa 160000 --- a/cpp-example-collection +++ b/cpp-example-collection @@ -1 +1 @@ -Subproject commit 46083ea4e5d3def8e44a53148c2c7800131efca0 +Subproject commit 839e38fa3079c1c63efeeb73ca476bd1c42afc51 From f7fc47f519a7fbcf9c80923726055f265fc412cc Mon Sep 17 00:00:00 2001 From: Alan George Date: Wed, 20 May 2026 15:08:12 -0700 Subject: [PATCH 8/8] Fix missed cpp example collection hash --- .github/workflows/docker-validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-validate.yml b/.github/workflows/docker-validate.yml index 68ce475d..7a89d352 100644 --- a/.github/workflows/docker-validate.yml +++ b/.github/workflows/docker-validate.yml @@ -11,7 +11,7 @@ permissions: env: # Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection) - CPP_EXAMPLE_COLLECTION_REF: f231c0c75028d1dcf13edcecd369d030d2c7c8d4 + CPP_EXAMPLE_COLLECTION_REF: 46083ea4e5d3def8e44a53148c2c7800131efca0 jobs: validate-x64: