diff --git a/.github/workflows/ci-homebrew.yml b/.github/workflows/ci-homebrew.yml index 472952576..908e1354c 100644 --- a/.github/workflows/ci-homebrew.yml +++ b/.github/workflows/ci-homebrew.yml @@ -61,6 +61,10 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Select Xcode 16.2 + if: matrix.os_name == 'macos' && matrix.os_version == '14' + run: sudo xcode-select --switch /Applications/Xcode_16.2.app + - name: Seed additional macOS TCC permissions if: runner.os == 'macOS' run: | diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index b108986ef..5ec3457a5 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -74,6 +74,10 @@ jobs: with: submodules: recursive + - name: Select Xcode 16.2 + if: matrix.os == 'macos-14' + run: sudo xcode-select --switch /Applications/Xcode_16.2.app + - name: Install dependencies timeout-minutes: 5 run: | diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index 924f2f5fa..a95f9627b 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -25,11 +25,17 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-uninitialized) endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$") # Clang specific compile options # Clang doesn't actually complain about this this, so disabling for now # list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-uninitialized) + + # Some libc++ versions on Apple and FreeBSD guard std::jthread behind this flag. + if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + list(APPEND SUNSHINE_COMPILE_OPTIONS -fexperimental-library) + list(APPEND SUNSHINE_LINK_OPTIONS -fexperimental-library) + endif() endif() if(BUILD_WERROR) list(APPEND SUNSHINE_COMPILE_OPTIONS -Werror) diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index 31028fb3d..e89f3d819 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -34,6 +34,7 @@ if(CUDA_INHERIT_COMPILE_OPTIONS) endif() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 +target_link_options(sunshine PRIVATE ${SUNSHINE_LINK_OPTIONS}) # Homebrew build fails the vite build if we set these environment variables if(${SUNSHINE_BUILD_HOMEBREW}) diff --git a/cmake/targets/linux.cmake b/cmake/targets/linux.cmake index b7cce8a7d..6f9fb362c 100644 --- a/cmake/targets/linux.cmake +++ b/cmake/targets/linux.cmake @@ -2,8 +2,11 @@ if(NOT FREEBSD) # Using newer c++ compilers / features on older distros causes runtime dyn link errors - list(APPEND SUNSHINE_EXTERNAL_LIBRARIES - -static-libgcc - -static-libstdc++ - ) + list(APPEND SUNSHINE_EXTERNAL_LIBRARIES -static-libgcc) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) + list(APPEND SUNSHINE_EXTERNAL_LIBRARIES stdc++) + else() + list(APPEND SUNSHINE_EXTERNAL_LIBRARIES -static-libstdc++) + endif() endif() diff --git a/docs/building.md b/docs/building.md index 8b80ce2ef..2cb0b6d3d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -28,6 +28,7 @@ pkg install -y \ devel/git \ devel/libevdev \ devel/libnotify \ + devel/llvm19 \ devel/ninja \ devel/pkgconf \ devel/qt6-base \ @@ -48,6 +49,13 @@ pkg install -y \ x11/libXtst ``` +Use LLVM 19 when configuring a local FreeBSD build: + +```sh +export CC=clang19 +export CXX=clang++19 +``` + #### Linux Dependencies vary depending on the distribution. You can reference our [linux_build.sh](https://github.com/LizardByte/Sunshine/blob/master/scripts/linux_build.sh) script for a list of diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index ef6244070..6cd89f9d3 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -31,6 +31,7 @@ fi depends=( 'avahi' 'curl' + 'gcc-libs' 'gtk3' 'hicolor-icon-theme' 'libayatana-appindicator' diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index d0e400b75..30d07095d 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -59,6 +59,10 @@ class Sunshine < Formula depends_on "openssl@3" depends_on "opus" + on_sonoma do + depends_on xcode: ["16.2", :build] # required for jthreads on macos-14 + end + on_linux do depends_on GCC_FORMULA => [:build, :test] depends_on "gcovr" => [:build, :test] diff --git a/src/audio.cpp b/src/audio.cpp index 55d50c475..5f4c9e43a 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -234,7 +234,7 @@ namespace audio { platf::adjust_thread_priority(platf::thread_priority_e::critical); auto samples = std::make_shared(30); - std::thread thread {encodeThread, samples, config, channel_data}; + std::jthread thread {encodeThread, samples, config, channel_data}; auto fg = util::fail_guard([&]() { samples->stop(); diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 8b9468e34..b04b1b6e5 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -1845,7 +1845,7 @@ namespace confighttp { return; } }; - std::thread tcp {accept_and_run, &server}; + std::jthread tcp {accept_and_run, &server}; // Wait for any event shutdown_event->view(); diff --git a/src/main.cpp b/src/main.cpp index 03ee8a860..78ec6565c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -270,7 +270,7 @@ int main(int argc, char *argv[]) { std::promise session_monitor_join_thread_promise; auto session_monitor_join_thread_future = session_monitor_join_thread_promise.get_future(); - std::thread session_monitor_thread([&]() { + std::jthread session_monitor_thread([&]() { platf::set_thread_name("session_monitor"); session_monitor_join_thread_promise.set_value_at_thread_exit(); @@ -437,9 +437,9 @@ int main(int argc, char *argv[]) { return lifetime::desired_exit_code; } - std::thread httpThread {nvhttp::start}; - std::thread configThread {confighttp::start}; - std::thread rtspThread {rtsp_stream::start}; + std::jthread httpThread {nvhttp::start}; + std::jthread configThread {confighttp::start}; + std::jthread rtspThread {rtsp_stream::start}; #ifdef _WIN32 // If we're using the default port and GameStream is enabled, warn the user diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 0ef9df418..585cc68e1 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -1401,8 +1401,8 @@ namespace nvhttp { return; } }; - std::thread ssl {accept_and_run, &https_server}; - std::thread tcp {accept_and_run, &http_server}; + std::jthread ssl {accept_and_run, &https_server}; + std::jthread tcp {accept_and_run, &http_server}; // Wait for any event shutdown_event->view(); diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index ac3aa7b36..11fe05551 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -302,7 +302,7 @@ namespace platf { std::unique_ptr> events; ///< Event queue receiving PulseAudio context state changes. std::unique_ptr> events_cb; ///< Callback that translates PulseAudio context updates into events. - std::thread worker; ///< Thread running the PulseAudio mainloop. + std::jthread worker; ///< Thread running the PulseAudio mainloop. /** * @brief Initialize PulseAudio mainloop, context, and Sunshine null sinks. @@ -344,7 +344,7 @@ namespace platf { return -1; } - worker = std::thread { + worker = std::jthread { [](loop_t::pointer loop) { int retval; platf::set_thread_name("audio::pulseaudio"); diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index 099c112cc..6ad3ce8db 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -529,14 +529,14 @@ namespace platf::publish { */ class deinit_t: public ::platf::deinit_t { public: - std::thread poll_thread; ///< Poll thread. + std::jthread poll_thread; ///< Poll thread. /** * @brief Store the Avahi polling thread for shutdown on destruction. * * @param poll_thread Poll thread. */ - deinit_t(std::thread poll_thread): + deinit_t(std::jthread poll_thread): poll_thread {std::move(poll_thread)} { } @@ -581,6 +581,6 @@ namespace platf::publish { return nullptr; } - return std::make_unique(std::thread {avahi::simple_poll_loop, poll.get()}); + return std::make_unique(std::jthread {avahi::simple_poll_loop, poll.get()}); } } // namespace platf::publish diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index 982515ede..edd830053 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -42,7 +42,7 @@ namespace platf::publish { */ deinit_t(DNSServiceRef serviceRef): unique_ptr(serviceRef) { - _thread = std::thread {[serviceRef, &_stopRequested = std::as_const(_stopRequested)]() { + _thread = std::jthread {[serviceRef, &_stopRequested = std::as_const(_stopRequested)]() { platf::set_thread_name("publish::mdns"); const auto socket = DNSServiceRefSockFD(serviceRef); while (!_stopRequested) { @@ -74,7 +74,7 @@ namespace platf::publish { deinit_t &operator=(const deinit_t &) = delete; private: - std::thread _thread; ///< Thread for polling the mDNS service for a response. + std::jthread _thread; ///< Thread for polling the mDNS service for a response. std::atomic _stopRequested = false; ///< Whether to stop polling the mDNS service. }; diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 9ef67a7e6..3b339db1d 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -1349,7 +1349,7 @@ namespace rtsp_stream { return; } - std::thread rtsp_thread {[&shutdown_event] { + std::jthread rtsp_thread {[&shutdown_event] { platf::set_thread_name("rtsp::handler"); auto broadcast_shutdown_event = mail::man->event(mail::broadcast_shutdown); diff --git a/src/stream.cpp b/src/stream.cpp index 97e042e23..a366ede50 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -454,10 +454,10 @@ namespace stream { struct broadcast_ctx_t { message_queue_queue_t message_queue_queue; ///< Queues carrying encoded video and audio packets to sender threads. - std::thread recv_thread; ///< Thread that receives incoming control-channel messages. - std::thread video_thread; ///< Thread that sends encoded video packets. - std::thread audio_thread; ///< Thread that sends encoded audio packets. - std::thread control_thread; ///< Thread that runs the ENet control server. + std::jthread recv_thread; ///< Thread that receives incoming control-channel messages. + std::jthread video_thread; ///< Thread that sends encoded video packets. + std::jthread audio_thread; ///< Thread that sends encoded audio packets. + std::jthread control_thread; ///< Thread that runs the ENet control server. asio::io_context io_context; ///< Asio context used by the UDP broadcast sockets. @@ -477,8 +477,8 @@ namespace stream { std::shared_ptr input; ///< Platform input device state for this stream. - std::thread audioThread; ///< Audio thread. - std::thread videoThread; ///< Video thread. + std::jthread audioThread; ///< Audio thread. + std::jthread videoThread; ///< Video thread. std::chrono::steady_clock::time_point pingTimeout; ///< Deadline for receiving the next client ping. @@ -1960,11 +1960,11 @@ namespace stream { ctx.message_queue_queue = std::make_shared(30); - ctx.video_thread = std::thread {videoBroadcastThread, std::ref(ctx.video_sock)}; - ctx.audio_thread = std::thread {audioBroadcastThread, std::ref(ctx.audio_sock)}; - ctx.control_thread = std::thread {controlBroadcastThread, &ctx.control_server}; + ctx.video_thread = std::jthread {videoBroadcastThread, std::ref(ctx.video_sock)}; + ctx.audio_thread = std::jthread {audioBroadcastThread, std::ref(ctx.audio_sock)}; + ctx.control_thread = std::jthread {controlBroadcastThread, &ctx.control_server}; - ctx.recv_thread = std::thread {recvThread, std::ref(ctx)}; + ctx.recv_thread = std::jthread {recvThread, std::ref(ctx)}; return 0; } @@ -2234,8 +2234,8 @@ namespace stream { session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout; - session.audioThread = std::thread {audioThread, &session}; - session.videoThread = std::thread {videoThread, &session}; + session.audioThread = std::jthread {audioThread, &session}; + session.videoThread = std::jthread {videoThread, &session}; session.state.store(state_e::RUNNING, std::memory_order_relaxed); diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 9fd7931fa..d1d0f11e6 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -445,7 +445,7 @@ namespace system_tray { int init_tray_threaded() { try { - auto tray_thread = std::thread(tray_thread_worker); + auto tray_thread = std::jthread(tray_thread_worker); // The tray thread doesn't require strong lifetime management. // It will exit asynchronously when tray_exit() is called. diff --git a/src/thread_pool.h b/src/thread_pool.h index bb8c234b0..4153eae1d 100644 --- a/src/thread_pool.h +++ b/src/thread_pool.h @@ -23,7 +23,7 @@ namespace thread_pool_util { typedef TaskPool::__task __task; private: - std::vector _thread; + std::vector _thread; std::condition_variable _cv; std::mutex _lock; @@ -44,7 +44,7 @@ namespace thread_pool_util { _thread(threads), _continue {true} { for (auto &t : _thread) { - t = std::thread(&ThreadPool::_main, this); + t = std::jthread(&ThreadPool::_main, this); } } @@ -113,7 +113,7 @@ namespace thread_pool_util { _thread.resize(threads); for (auto &t : _thread) { - t = std::thread(&ThreadPool::_main, this); + t = std::jthread(&ThreadPool::_main, this); } } diff --git a/src/upnp.cpp b/src/upnp.cpp index 96e51e397..29bd9e3ad 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -94,7 +94,7 @@ namespace upnp { } // Start the mapping thread - upnp_thread = std::thread {&deinit_t::upnp_thread_proc, this}; + upnp_thread = std::jthread {&deinit_t::upnp_thread_proc, this}; } /** @@ -373,7 +373,7 @@ namespace upnp { } std::vector mappings; ///< Port mappings Sunshine should keep registered with the gateway. - std::thread upnp_thread; ///< Worker thread that refreshes mappings until shutdown. + std::jthread upnp_thread; ///< Worker thread that refreshes mappings until shutdown. }; /** diff --git a/src/video.cpp b/src/video.cpp index c34751aa4..60c8d361e 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -630,7 +630,7 @@ namespace video { */ struct capture_thread_async_ctx_t { std::shared_ptr> capture_ctx_queue; ///< Capture ctx queue. - std::thread capture_thread; ///< Capture thread. + std::jthread capture_thread; ///< Capture thread. safe::signal_t reinit_event; ///< Reinit event. const encoder_t *encoder_p; ///< Encoder p. @@ -2354,7 +2354,7 @@ namespace video { // streaming to continue without requiring a full restart of Sunshine. auto fail_guard = util::fail_guard([&encoder, &session] { if (encoder.flags & ASYNC_TEARDOWN) { - std::thread encoder_teardown_thread {[session = std::move(session)]() mutable { + std::jthread encoder_teardown_thread {[session = std::move(session)]() mutable { BOOST_LOG(info) << "Starting async encoder teardown"; session.reset(); BOOST_LOG(info) << "Async encoder teardown complete"; @@ -3593,7 +3593,7 @@ namespace video { capture_thread_ctx.capture_ctx_queue = std::make_shared>(30); - capture_thread_ctx.capture_thread = std::thread { + capture_thread_ctx.capture_thread = std::jthread { captureThread, capture_thread_ctx.capture_ctx_queue, std::ref(capture_thread_ctx.display_wp), @@ -3617,7 +3617,7 @@ namespace video { * @brief Start capture sync. */ int start_capture_sync(capture_thread_sync_ctx_t &ctx) { - std::thread {&captureThreadSync}.detach(); + std::jthread {&captureThreadSync}.detach(); return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ebeea959..56ebd4c5a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -181,7 +181,7 @@ endif() target_link_libraries(${PROJECT_NAME} ${TEST_LINK_LIBRARIES}) target_compile_definitions(${PROJECT_NAME} PUBLIC ${SUNSHINE_DEFINITIONS} ${TEST_DEFINITIONS}) target_compile_options(${PROJECT_NAME} PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 -target_link_options(${PROJECT_NAME} PRIVATE) +target_link_options(${PROJECT_NAME} PRIVATE ${SUNSHINE_LINK_OPTIONS}) if (WIN32) # prefer static libraries since we're linking statically diff --git a/tests/unit/test_audio.cpp b/tests/unit/test_audio.cpp index 9b894112b..b21087a8a 100644 --- a/tests/unit/test_audio.cpp +++ b/tests/unit/test_audio.cpp @@ -42,7 +42,7 @@ INSTANTIATE_TEST_SUITE_P( ); TEST_P(AudioTest, TestEncode) { - std::thread timer([&] { + std::jthread timer([&] { // Terminate the audio capture after 100 ms std::this_thread::sleep_for(100ms); const auto shutdown_event = m_mail->event(mail::shutdown); @@ -50,7 +50,7 @@ TEST_P(AudioTest, TestEncode) { shutdown_event->raise(true); audio_packets->stop(); }); - std::thread capture([&] { + std::jthread capture([&] { const auto packets = m_mail->queue(mail::audio_packets); const auto shutdown_event = m_mail->event(mail::shutdown); while (const auto packet = packets->pop()) { diff --git a/tests/unit/test_confighttp.cpp b/tests/unit/test_confighttp.cpp index a633ae3b5..8516448ec 100644 --- a/tests/unit/test_confighttp.cpp +++ b/tests/unit/test_confighttp.cpp @@ -93,7 +93,7 @@ class ConfigHttpTest: public BaseTest { // NOSONAR(cpp:S3656) - protected membe protected: std::unique_ptr> server; std::unique_ptr> client; - std::thread server_thread; // NOSONAR(cpp:S6168) - jthread not available on FreeBSD 14.3 libc++ + std::jthread server_thread; unsigned short port = 0; std::string saved_username; @@ -310,7 +310,7 @@ protected: }; // Start server - server_thread = std::thread([this]() { // NOSONAR(cpp:S6168) - jthread not available on FreeBSD 14.3 libc++ + server_thread = std::jthread([this]() { server->start([this](const unsigned short assigned_port) { port = assigned_port; });