diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 64fe55d7f..09b0643a1 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -84,6 +84,7 @@ jobs: timeout-minutes: 5 run: | brew install --force \ + boost \ cmake \ doxygen \ graphviz \ @@ -124,6 +125,11 @@ jobs: env: APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} run: | + icu4c_root="$(brew --prefix icu4c@78 2>/dev/null)" + export CPPFLAGS="${CPPFLAGS:+${CPPFLAGS} }-I${icu4c_root}/include" + export LDFLAGS="${LDFLAGS:+${LDFLAGS} }-L${icu4c_root}/lib" + export LIBRARY_PATH="${icu4c_root}/lib${LIBRARY_PATH:+:${LIBRARY_PATH}}" + mkdir -p build cmake \ -B build \ @@ -131,7 +137,7 @@ jobs: -DBUILD_DOCS="${{ matrix.build_docs }}" \ -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ - -DICU_ROOT="$(brew --prefix icu4c@78 2>/dev/null)" \ + -DICU_ROOT="${icu4c_root}" \ -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3 2>/dev/null)" \ -DOpus_ROOT_DIR="$(brew --prefix opus 2>/dev/null)" \ -DQt6_DIR="$(brew --prefix qtbase 2>/dev/null)/lib/cmake/Qt6" \ diff --git a/cmake/compile_definitions/macos.cmake b/cmake/compile_definitions/macos.cmake index acf336c10..5c1608ce8 100644 --- a/cmake/compile_definitions/macos.cmake +++ b/cmake/compile_definitions/macos.cmake @@ -21,7 +21,7 @@ foreach(dir ${MACOS_LINK_DIRECTORIES}) endif() endforeach() -if(NOT BOOST_USE_STATIC AND NOT FETCH_CONTENT_BOOST_USED) +if(NOT BOOST_USE_STATIC AND NOT CPM_BOOST_USED) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) endif() diff --git a/cmake/dependencies/Boost_Sunshine.cmake b/cmake/dependencies/Boost_Sunshine.cmake index f0ff9a78f..c820be103 100644 --- a/cmake/dependencies/Boost_Sunshine.cmake +++ b/cmake/dependencies/Boost_Sunshine.cmake @@ -1,16 +1,14 @@ # -# Loads the boost library giving the priority to the system package first, with a fallback to FetchContent. +# Loads the boost library giving priority to a compatible system package, with a fallback to CPM. # include_guard(GLOBAL) -set(BOOST_VERSION "1.89.0") +set(BOOST_MINIMUM_VERSION "1.89.0") set(BOOST_COMPONENTS filesystem log program_options - system ) -# system is not used by Sunshine, but by Simple-Web-Server, added here for convenience if(NOT WIN32) list(APPEND BOOST_COMPONENTS locale) @@ -30,20 +28,9 @@ if(BOOST_USE_STATIC) set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103 endif() -if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") - cmake_policy(SET CMP0167 NEW) # Get BoostConfig.cmake from upstream -endif() -find_package(Boost CONFIG ${BOOST_VERSION} EXACT COMPONENTS ${BOOST_COMPONENTS}) +find_package(Boost ${BOOST_MINIMUM_VERSION} CONFIG COMPONENTS ${BOOST_COMPONENTS}) if(NOT Boost_FOUND) - message(STATUS "Boost v${BOOST_VERSION} package not found in the system. Falling back to FetchContent.") - include(FetchContent) - - if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") - cmake_policy(SET CMP0135 NEW) # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24 - endif() - if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.31.0") - cmake_policy(SET CMP0174 NEW) # Handle empty variables - endif() + message(STATUS "Boost v${BOOST_MINIMUM_VERSION}+ package not found in the system. Falling back to CPM.") # more components required for compiling boost targets list(APPEND BOOST_COMPONENTS @@ -53,41 +40,21 @@ if(NOT Boost_FOUND) process property_tree) - set(BOOST_ENABLE_CMAKE ON) + set(BOOST_ENABLE_CMAKE ON) # Use the Boost superproject to resolve library dependencies recursively + set(BOOST_INCLUDE_LIBRARIES ${BOOST_COMPONENTS}) # Limit Boost to the required libraries + set(BOOST_SKIP_INSTALL_RULES ON) # Do not install Boost libraries or headers - # Limit boost to the required libraries only - set(BOOST_INCLUDE_LIBRARIES ${BOOST_COMPONENTS}) - set(BOOST_URL "https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}-cmake.tar.xz") # cmake-lint: disable=C0301 - set(BOOST_HASH "SHA256=67acec02d0d118b5de9eb441f5fb707b3a1cdd884be00ca24b9a73c995511f74") + CPMGetPackage(Boost) + set(CPM_BOOST_USED TRUE) - if(CMAKE_VERSION VERSION_LESS "3.24.0") - FetchContent_Declare( - Boost - URL ${BOOST_URL} - URL_HASH ${BOOST_HASH} - ) - elseif(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.25.0") - # add SYSTEM to FetchContent_Declare, this fails on debian bookworm - FetchContent_Declare( - Boost - URL ${BOOST_URL} - URL_HASH ${BOOST_HASH} - SYSTEM # requires CMake 3.25+ - OVERRIDE_FIND_PACKAGE # requires CMake 3.24+, but we have a macro to handle it for other versions - ) - elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") - FetchContent_Declare( - Boost - URL ${BOOST_URL} - URL_HASH ${BOOST_HASH} - OVERRIDE_FIND_PACKAGE # requires CMake 3.24+, but we have a macro to handle it for other versions - ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.25.0") + add_subdirectory(${Boost_SOURCE_DIR} ${Boost_BINARY_DIR} SYSTEM) + else() + add_subdirectory(${Boost_SOURCE_DIR} ${Boost_BINARY_DIR}) endif() - FetchContent_MakeAvailable(Boost) - set(FETCH_CONTENT_BOOST_USED TRUE) - set(Boost_FOUND TRUE) # cmake-lint: disable=C0103 + set(Boost_VERSION "${BOOST_VERSION}") # cmake-lint: disable=C0103 set(Boost_INCLUDE_DIRS # cmake-lint: disable=C0103 "$") diff --git a/cmake/macros/common.cmake b/cmake/macros/common.cmake index 53a3c694a..be50e8d54 100644 --- a/cmake/macros/common.cmake +++ b/cmake/macros/common.cmake @@ -18,10 +18,16 @@ endif() macro(find_package) # cmake-lint: disable=C0103 string(TOLOWER "${ARGV0}" ARGV0_LOWER) if( - (("${ARGV0_LOWER}" STREQUAL "boost") AND DEFINED FETCH_CONTENT_BOOST_USED) OR + (("${ARGV0_LOWER}" STREQUAL "boost") AND DEFINED CPM_BOOST_USED) OR (("${ARGV0_LOWER}" STREQUAL "libevdev") AND DEFINED EXTERNAL_PROJECT_LIBEVDEV_USED) ) # Do nothing, as the package has already been fetched + elseif(("${ARGV0_LOWER}" STREQUAL "boost") AND POLICY CMP0167) + # Prefer upstream BoostConfig.cmake for compatible system packages on CMake 3.30+. + cmake_policy(PUSH) + cmake_policy(SET CMP0167 NEW) + _find_package(${ARGV}) + cmake_policy(POP) else() # Call the original find_package function _find_package(${ARGV}) diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index a7f2e901c..f6d4e5b4c 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -26,11 +26,7 @@ endif() option(SUNSHINE_SYSTEM_VULKAN_HEADERS "Use system installation of vulkan-headers rather than the submodule." OFF) option(SUNSHINE_SYSTEM_WAYLAND_PROTOCOLS "Use system installation of wayland-protocols rather than the submodule." OFF) -if(APPLE) - option(BOOST_USE_STATIC "Use static boost libraries." OFF) -else() - option(BOOST_USE_STATIC "Use static boost libraries." ON) -endif() +option(BOOST_USE_STATIC "Use static boost libraries." ON) option(CUDA_FAIL_ON_MISSING "Fail the build if CUDA is not found." ON) option(CUDA_INHERIT_COMPILE_OPTIONS diff --git a/docs/building.md b/docs/building.md index 0fe5414dd..fd3742200 100644 --- a/docs/building.md +++ b/docs/building.md @@ -14,6 +14,14 @@ It is recommended to use one of the following compilers: ### Dependencies +Boost `1.89.0` or newer can be supplied as a system package. When a compatible system package is unavailable, CMake +downloads the project-locked Boost release through CPM. Static Boost libraries are preferred on every platform by +default; pass `-DBOOST_USE_STATIC=OFF` to use shared system libraries instead. + +Sunshine's build and packaging automation uses packaged static Boost libraries on Arch Linux, Fedora 44 or newer, +Ubuntu 26.04 or newer, macOS with Homebrew, Windows with MSYS2, and FreeBSD. Older supported distributions continue +to use the CPM fallback because their repositories do not meet the minimum Boost version. + #### FreeBSD > [!CAUTION] > Sunshine support for FreeBSD is experimental and may be incomplete or not work as expected diff --git a/package-lock.cmake b/package-lock.cmake index b1bc09022..5231caa5a 100644 --- a/package-lock.cmake +++ b/package-lock.cmake @@ -32,3 +32,17 @@ # - `CPMDeclarePackage(...)` set(PATCH_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/patches") + +# Boost +# renovate: datasource=github-release-attachments depName=boostorg/boost +# versioning=regex:^boost-(?\d+)\.(?\d+)\.(?\d+)(?\.[A-Za-z0-9.-]+)?$ +set(BOOST_TAG boost-1.92.0) +set(BOOST_SHA256 9bed76128d4e46755dbe818487788c6fceb6f72b378f4daa49b7e1e600d9088d) +string(REGEX REPLACE "^boost-" "" BOOST_VERSION "${BOOST_TAG}") +CPMDeclarePackage(Boost + NAME Boost + VERSION ${BOOST_VERSION} + URL https://github.com/boostorg/boost/releases/download/${BOOST_TAG}/${BOOST_TAG}-cmake.tar.xz + URL_HASH SHA256=${BOOST_SHA256} + DOWNLOAD_ONLY YES +) diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index d28b7430c..019b5249f 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -61,6 +61,8 @@ depends=( makedepends=( 'appstream' 'appstream-glib' + 'boost' + 'boost-libs' 'cmake' 'desktop-file-utils' "gcc${_gcc_dep_suffix}" diff --git a/packaging/linux/copr/Sunshine.spec b/packaging/linux/copr/Sunshine.spec index 486fc25b6..73def6b84 100644 --- a/packaging/linux/copr/Sunshine.spec +++ b/packaging/linux/copr/Sunshine.spec @@ -50,11 +50,12 @@ BuildRequires: which %if 0%{?fedora} # Fedora-specific BuildRequires BuildRequires: appstream -# BuildRequires: boost-devel >= 1.86.0 BuildRequires: glslc BuildRequires: libappstream-glib BuildRequires: vulkan-loader-devel %if 0%{fedora} > 43 +BuildRequires: boost-devel >= 1.89.0 +BuildRequires: boost-static >= 1.89.0 # needed for npm from nvm BuildRequires: libatomic BuildRequires: xz diff --git a/packaging/linux/flatpak/modules/boost.json b/packaging/linux/flatpak/modules/boost.json index c78661dae..1901fc2b3 100644 --- a/packaging/linux/flatpak/modules/boost.json +++ b/packaging/linux/flatpak/modules/boost.json @@ -9,8 +9,8 @@ "sources": [ { "type": "archive", - "url": "https://github.com/boostorg/boost/releases/download/boost-1.89.0/boost-1.89.0-cmake.tar.xz", - "sha256": "67acec02d0d118b5de9eb441f5fb707b3a1cdd884be00ca24b9a73c995511f74" + "url": "https://github.com/boostorg/boost/releases/download/boost-1.92.0/boost-1.92.0-cmake.tar.xz", + "sha256": "9bed76128d4e46755dbe818487788c6fceb6f72b378f4daa49b7e1e600d9088d" } ] } diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index bda7813ae..75606523b 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -451,6 +451,26 @@ class Sunshine < Formula %w[docs src src_assets test_assets].each do |directory| test_runtime.install "build/tests/#{directory}" end + boost_test_fixtures = %w[ + cmake/compile_definitions/macos.cmake + cmake/dependencies/Boost_Sunshine.cmake + cmake/macros/common.cmake + cmake/prep/options.cmake + package-lock.cmake + packaging/linux/Arch/PKGBUILD + packaging/linux/copr/Sunshine.spec + packaging/linux/flatpak/modules/boost.json + scripts/linux_build.sh + ] + if OS.mac? + boost_test_fixtures += %w[ + .github/workflows/ci-macos.yml + scripts/macos_build.sh + ] + end + boost_test_fixtures.each do |fixture| + (test_runtime/Pathname.new(fixture).dirname).install "build/tests/#{fixture}" + end test_runtime.install "sunshine.png" (test_runtime/"tests/unit").install "tests/unit/test_video.cpp" @@ -527,6 +547,8 @@ class Sunshine < Formula assert_path_exists coverage_buildpath assert_path_exists bin/TEST_BINARY assert_path_exists test_runtime/"docs/getting_started.md" + assert_path_exists test_runtime/"cmake/dependencies/Boost_Sunshine.cmake" + assert_path_exists test_runtime/"scripts/macos_build.sh" if OS.mac? assert_path_exists test_runtime/"src/config.cpp" assert_path_exists test_runtime/"src_assets/common/assets/web/public/assets/locale/en.json" assert_path_exists test_runtime/"test_assets/web/images/logo-sunshine.svg" diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index a32fefd1e..d0d5ca303 100755 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -280,6 +280,8 @@ function add_arch_deps() { 'appstream-glib' 'avahi' 'base-devel' + 'boost' + 'boost-libs' 'cmake' 'curl' 'doxygen' @@ -419,6 +421,17 @@ function add_ubuntu_deps() { add_test_ppa add_debian_based_deps + # Ubuntu 26.04+ provides compatible Boost static libraries in the component development packages. + if [[ "$(printf '%s\n' "$version" "26.04" | sort -V | head -n1)" == "26.04" ]]; then + dependencies+=( + "libboost-filesystem-dev" + "libboost-locale-dev" + "libboost-log-dev" + "libboost-program-options-dev" + "libicu-dev" + ) + fi + if [[ "$skip_cuda" == 0 ]] && [[ "$cuda_system_package" == 1 ]]; then if [[ -z "$cuda_system_package_name" ]]; then echo "CUDA system package was requested, but no package name was configured." @@ -484,6 +497,14 @@ function add_fedora_deps() { "xorg-x11-server-Xvfb" # necessary for headless unit testing ) + # Fedora 44+ provides Boost 1.90 or newer and packages its static libraries separately. + if [[ "$version" =~ ^[0-9]+$ ]] && (( version >= 44 )); then + dependencies+=( + "boost-devel" + "boost-static" + ) + fi + if [[ "$skip_libva" == 0 ]]; then dependencies+=( "libva-devel" # VA-API diff --git a/scripts/macos_build.sh b/scripts/macos_build.sh index c900687c0..b675269b5 100755 --- a/scripts/macos_build.sh +++ b/scripts/macos_build.sh @@ -25,8 +25,8 @@ export BUILD_VERSION export BRANCH export COMMIT -# boost could be included here but cmake will build the right version we need required_formulas=( + "boost" "cmake" "doxygen" "graphviz" @@ -92,6 +92,12 @@ function run_step_deps() { function run_step_cmake() { echo "Running step: CMake configure" + # Static Boost.Locale exposes ICU as transitive linker arguments, while Homebrew keeps ICU keg-only. + icu4c_root=$(brew --prefix icu4c@78 2>/dev/null) + export CPPFLAGS="${CPPFLAGS:+${CPPFLAGS} }-I${icu4c_root}/include" + export LDFLAGS="${LDFLAGS:+${LDFLAGS} }-L${icu4c_root}/lib" + export LIBRARY_PATH="${icu4c_root}/lib${LIBRARY_PATH:+:${LIBRARY_PATH}}" + # prepare CMAKE args cmake_args=( "-B=build" @@ -100,7 +106,7 @@ function run_step_cmake() { "-DBUILD_TESTS=${build_tests}" "-DBUILD_WERROR=ON" "-DCMAKE_BUILD_TYPE=${build_type}" - "-DICU_ROOT=$(brew --prefix icu4c@78 2>/dev/null)" + "-DICU_ROOT=${icu4c_root}" "-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3 2>/dev/null)" "-DOpus_ROOT_DIR=$(brew --prefix opus 2>/dev/null)" "-DQt6_DIR=$(brew --prefix qtbase 2>/dev/null)/lib/cmake/Qt6" diff --git a/src/boost_process_compat.h b/src/boost_process_compat.h new file mode 100644 index 000000000..d5441db3a --- /dev/null +++ b/src/boost_process_compat.h @@ -0,0 +1,17 @@ +/** + * @file src/boost_process_compat.h + * @brief Includes Boost.Process while isolating third-party deprecation diagnostics. + */ +#pragma once + +#if defined(__APPLE__) && defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + + // Boost.Process v1 still uses std::codecvt_utf8 on macOS, including in Boost 1.92. + #include + + #pragma clang diagnostic pop +#endif + +#include diff --git a/src/platform/common.h b/src/platform/common.h index 325f913b3..e8aff70ec 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -16,10 +16,12 @@ #include #ifndef _WIN32 #include - #include #endif // local includes +#ifndef _WIN32 + #include "src/boost_process_compat.h" +#endif #include "src/config.h" #include "src/logging.h" #include "src/thread_safe.h" diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index ca32309da..78cf71e26 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -45,7 +45,6 @@ // lib includes #include #include -#include #include #include #include @@ -59,6 +58,7 @@ // local includes #include "graphics.h" #include "misc.h" +#include "src/boost_process_compat.h" #include "src/config.h" #include "src/entry_handler.h" #include "src/logging.h" diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 7c3127dcd..d35f44eee 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -28,10 +28,10 @@ // lib includes #include #include -#include // local includes #include "misc.h" +#include "src/boost_process_compat.h" #include "src/entry_handler.h" #include "src/logging.h" #include "src/platform/common.h" diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 151b8a680..1872b905e 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -9,16 +9,18 @@ // platform includes #include +// local includes required before library includes +// Boost.Process must be included before MinHook pulls in Windows.h so Boost.Asio sees WinSock2.h first. +#include "src/boost_process_compat.h" + // lib includes #include -#include #include // local includes #include "utf_utils.h" -// We have to include boost/process/v1.hpp before display.h due to WinSock.h, -// but that prevents the definition of NTSTATUS so we must define it ourself. +// Including Boost.Process before Windows.h prevents the definition of NTSTATUS, so we must define it ourselves. /** * @brief Windows NT status code returned by native APIs. */ diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index a03f56d3c..ad3e660cf 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -14,9 +14,11 @@ // lib includes #include #include -#include #include +// local includes required before platform includes +#include "src/boost_process_compat.h" + // prevent clang format from "optimizing" the header include order // clang-format off #include diff --git a/src/process.h b/src/process.h index 233137b15..9b89060e5 100644 --- a/src/process.h +++ b/src/process.h @@ -16,13 +16,11 @@ #include #include -// lib includes -#include - // local includes #include "config.h" #include "platform/common.h" #include "rtsp.h" +#include "src/boost_process_compat.h" #include "utility.h" /** diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 397194d18..0a2ae33aa 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -64,7 +64,6 @@ // lib includes #include - #include #include #ifdef _WIN32 #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 85b5aca1a..d045872f6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -188,8 +188,17 @@ add_executable(${PROJECT_NAME} # This ensures both CLI and CLion can access the same files relative to the test executable # Using configure_file ensures files are copied when they change between builds set(INTEGRATION_TEST_FILES + "cmake/compile_definitions/macos.cmake" + "cmake/dependencies/Boost_Sunshine.cmake" + "cmake/macros/common.cmake" + "cmake/prep/options.cmake" + "package-lock.cmake" + "packaging/linux/Arch/PKGBUILD" + "packaging/linux/flatpak/modules/boost.json" + "scripts/linux_build.sh" "src/config.cpp" "src_assets/common/assets/web/config.html" + "docs/building.md" "docs/configuration.md" "docs/getting_started.md" "docs/troubleshooting.md" @@ -198,6 +207,18 @@ set(INTEGRATION_TEST_FILES "src_assets/linux/misc/60-sunshine.rules" ) +if(EXISTS "${CMAKE_SOURCE_DIR}/packaging/linux/copr/Sunshine.spec") + list(APPEND INTEGRATION_TEST_FILES "packaging/linux/copr/Sunshine.spec") + list(APPEND TEST_DEFINITIONS SUNSHINE_COPR_SPEC_FIXTURE_AVAILABLE) +endif() + +if(APPLE) + list(APPEND INTEGRATION_TEST_FILES + ".github/workflows/ci-macos.yml" + "scripts/macos_build.sh" + ) +endif() + foreach(file ${INTEGRATION_TEST_FILES}) configure_file( "${CMAKE_SOURCE_DIR}/${file}" diff --git a/tests/integration/test_boost_version_consistency.cpp b/tests/integration/test_boost_version_consistency.cpp new file mode 100644 index 000000000..c2f78974d --- /dev/null +++ b/tests/integration/test_boost_version_consistency.cpp @@ -0,0 +1,141 @@ +/** + * @file tests/integration/test_boost_version_consistency.cpp + * @brief Tests for the Boost system-version and fallback-version contracts. + */ + +// test includes +#include "../tests_common.h" + +// standard includes +#include +#include +#include +#include + +// local includes +#include "src/file_handler.h" + +namespace { + /** + * @brief Read a project file copied into the test runtime directory. + * + * @param path Project-relative file path. + * @return File contents. + */ + std::string read_project_file(const std::string_view path) { + const auto path_string = std::format("{}/{}", SUNSHINE_TEST_BIN_DIR, path); + auto content = file_handler::read_file(path_string.c_str()); + EXPECT_FALSE(content.empty()) << "Unable to read " << path; + return content; + } + + /** + * @brief Extract the first capture group from text. + * + * @param content Text to search. + * @param pattern Regular expression containing a capture group. + * @return The first captured value, or an empty string when no match exists. + */ + std::string extract_value(const std::string &content, const std::regex &pattern) { + std::smatch match; + EXPECT_TRUE(std::regex_search(content, match, pattern)); + return match.size() > 1 ? match[1].str() : std::string {}; + } +} // namespace + +TEST(BoostVersionConsistencyTest, SystemPackageAllowsCompatibleVersions) { + const auto dependency = read_project_file("cmake/dependencies/Boost_Sunshine.cmake"); + const auto documentation = read_project_file("docs/building.md"); + const auto macos_definitions = read_project_file("cmake/compile_definitions/macos.cmake"); + const auto common_macros = read_project_file("cmake/macros/common.cmake"); + const auto options = read_project_file("cmake/prep/options.cmake"); + const auto minimum_version = extract_value( + dependency, + std::regex {R"regex(set\(BOOST_MINIMUM_VERSION "([0-9]+\.[0-9]+\.[0-9]+)"\))regex"} + ); + + EXPECT_NE( + dependency.find("find_package(Boost ${BOOST_MINIMUM_VERSION} CONFIG COMPONENTS ${BOOST_COMPONENTS})"), + std::string::npos + ); + EXPECT_EQ(dependency.find("EXACT"), std::string::npos); + EXPECT_EQ(dependency.find("FetchContent"), std::string::npos); + EXPECT_EQ(dependency.find("\n system"), std::string::npos); + EXPECT_NE(dependency.find("CPMGetPackage(Boost)"), std::string::npos); + EXPECT_NE(documentation.find(std::format("Boost `{}` or newer", minimum_version)), std::string::npos); + EXPECT_EQ(macos_definitions.find("FETCH_CONTENT_BOOST_USED"), std::string::npos); + EXPECT_NE(macos_definitions.find("CPM_BOOST_USED"), std::string::npos); + EXPECT_EQ(common_macros.find("FETCH_CONTENT_BOOST_USED"), std::string::npos); + EXPECT_NE(common_macros.find("CPM_BOOST_USED"), std::string::npos); + EXPECT_NE(options.find("option(BOOST_USE_STATIC \"Use static boost libraries.\" ON)"), std::string::npos); + EXPECT_EQ(options.find("option(BOOST_USE_STATIC \"Use static boost libraries.\" OFF)"), std::string::npos); + EXPECT_NE(documentation.find("Static Boost libraries are preferred on every platform"), std::string::npos); + EXPECT_NE(documentation.find("-DBOOST_USE_STATIC=OFF"), std::string::npos); +} + +TEST(BoostVersionConsistencyTest, CpmFallbackMatchesFlatpakSource) { + const auto package_lock = read_project_file("package-lock.cmake"); + const auto flatpak_module = read_project_file("packaging/linux/flatpak/modules/boost.json"); + const auto fallback_version = extract_value( + package_lock, + std::regex {R"regex(set\(BOOST_TAG boost-([0-9]+\.[0-9]+\.[0-9]+)\))regex"} + ); + const auto fallback_hash = extract_value( + package_lock, + std::regex {R"regex(set\(BOOST_SHA256 ([0-9a-f]{64})\))regex"} + ); + + EXPECT_NE( + flatpak_module.find(std::format("boost-{0}/boost-{0}-cmake.tar.xz", fallback_version)), + std::string::npos + ); + EXPECT_NE(flatpak_module.find(std::format("\"sha256\": \"{}\"", fallback_hash)), std::string::npos); +} + +TEST(BoostVersionConsistencyTest, SupportedBuildsInstallStaticSystemPackages) { + const auto documentation = read_project_file("docs/building.md"); + const auto linux_script = read_project_file("scripts/linux_build.sh"); + const auto arch_package = read_project_file("packaging/linux/Arch/PKGBUILD"); +#ifdef SUNSHINE_COPR_SPEC_FIXTURE_AVAILABLE + const auto dependency = read_project_file("cmake/dependencies/Boost_Sunshine.cmake"); + const auto copr_spec = read_project_file("packaging/linux/copr/Sunshine.spec"); + const auto minimum_version = extract_value( + dependency, + std::regex {R"regex(set\(BOOST_MINIMUM_VERSION "([0-9]+\.[0-9]+\.[0-9]+)"\))regex"} + ); +#endif + + EXPECT_NE(linux_script.find("'boost'"), std::string::npos); + EXPECT_NE(linux_script.find("'boost-libs'"), std::string::npos); + EXPECT_NE(linux_script.find("\"boost-devel\""), std::string::npos); + EXPECT_NE(linux_script.find("\"boost-static\""), std::string::npos); + EXPECT_NE(linux_script.find("\"libboost-filesystem-dev\""), std::string::npos); + EXPECT_NE(linux_script.find("\"libboost-locale-dev\""), std::string::npos); + EXPECT_NE(linux_script.find("\"libboost-log-dev\""), std::string::npos); + EXPECT_NE(linux_script.find("\"libboost-program-options-dev\""), std::string::npos); + EXPECT_NE(linux_script.find("\"libicu-dev\""), std::string::npos); + EXPECT_NE(linux_script.find("\"26.04\" | sort -V"), std::string::npos); + EXPECT_NE(linux_script.find("version >= 44"), std::string::npos); + EXPECT_NE(arch_package.find("'boost'"), std::string::npos); + EXPECT_NE(arch_package.find("'boost-libs'"), std::string::npos); +#ifdef SUNSHINE_COPR_SPEC_FIXTURE_AVAILABLE + EXPECT_NE(copr_spec.find("%if 0%{fedora} > 43"), std::string::npos); + EXPECT_NE( + copr_spec.find(std::format("BuildRequires: boost-devel >= {}", minimum_version)), + std::string::npos + ); + EXPECT_NE( + copr_spec.find(std::format("BuildRequires: boost-static >= {}", minimum_version)), + std::string::npos + ); +#endif +#ifdef __APPLE__ + const auto macos_script = read_project_file("scripts/macos_build.sh"); + const auto macos_workflow = read_project_file(".github/workflows/ci-macos.yml"); + EXPECT_NE(macos_script.find("\"boost\""), std::string::npos); + EXPECT_NE(macos_script.find("LIBRARY_PATH"), std::string::npos); + EXPECT_NE(macos_workflow.find("boost \\"), std::string::npos); + EXPECT_NE(macos_workflow.find("LIBRARY_PATH"), std::string::npos); +#endif + EXPECT_NE(documentation.find("packaged static Boost libraries"), std::string::npos); +} diff --git a/tests/integration/test_external_commands.cpp b/tests/integration/test_external_commands.cpp index acff45c6c..691cfb94c 100644 --- a/tests/integration/test_external_commands.cpp +++ b/tests/integration/test_external_commands.cpp @@ -12,10 +12,8 @@ #include #include -// lib includes -#include - // local includes +#include "src/boost_process_compat.h" #include "src/platform/common.h" // Test data structure for parameterized testing