diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..b7f7371 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,143 @@ +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +name: Unit tests + +on: + push: + branches: + - master + - develop + - bugfix/** + - feature/** + - fix/** + - github/** + - pr/** + pull_request: + +concurrency: + group: ${{format('tests-{0}:{1}', github.repository, github.ref)}} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + StyleAndFormatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - run: curl -sSL --retry 5 --connect-timeout 15 https://github.com/Return-To-The-Roots/s25client/raw/refs/heads/master/.clang-format --output .clang-format + - name: Validation + run: tools/ci/staticValidation.sh "$GITHUB_WORKSPACE" + - name: Formatting + uses: DoozyX/clang-format-lint-action@bcb4eb2cb0d707ee4f3e5cc3b456eb075f12cf73 # v0.20 + with: + source: "libs tests" + clangFormatVersion: 10 + - name: Lint markdown files + uses: avto-dev/markdown-lint@04d43ee9191307b50935a753da3b775ab695eceb # v1.5.0 + with: + args: . + - name: Check licensing + run: + pip install --user reuse + reuse lint + UnitTests: + strategy: + matrix: + include: + - { compiler: gcc-9, os: ubuntu-22.04, buildType: Debug, boostVersion: 1.73.0 } # Older ones fail due to PTHREAD_STACK_MIN use in Boost.Thread + - { compiler: clang, os: macos-26, buildType: Debug, boostVersion: 1.81.0 } # New libc++ only supported since 1.81 + # Latest GCC + - { compiler: gcc-14, os: ubuntu-24.04, buildType: Debug, boostVersion: 1.87.0 } + # Latest Clang + - { compiler: clang-19, os: ubuntu-24.04, buildType: Debug, boostVersion: 1.81.0 } + # Windows + - { compiler: msvc-14.3, os: windows-2022, buildType: Debug, boostVersion: 1.87.0, generator: 'Visual Studio 17 2022' } + - { compiler: msvc-14.5, os: windows-2025-vs2026, buildType: Debug, boostVersion: 1.91.0, generator: 'Visual Studio 18 2026' } + - { compiler: gcc, sys: MINGW64, os: windows-latest, buildType: Debug, boostVersion: 1.91.0, generator: 'Ninja' } + + runs-on: ${{matrix.os}} + defaults: + run: + shell: ${{matrix.sys && 'msys2 {0}' || 'bash'}} + steps: + - name: Setup MSYS2 environment for MinGW + if: matrix.sys + uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 + with: + msystem: ${{matrix.sys}} + update: true + install: git python + pacboy: gcc:p cmake:p ninja:p miniupnpc:p + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install system packages (Linux) + if: runner.os == 'Linux' + run: | + pkgs=${{matrix.compiler}} + pkgs=${pkgs/gcc-/g++-} + sudo apt install ccache libminiupnpc-dev $pkgs + - name: Install system packages (OSX) + if: runner.os == 'macOS' + run: brew install ccache cmake miniupnpc + - name: Install system packages (Windows) + if: runner.os == 'Windows' && ! matrix.sys + run: choco install wget + + - name: Setup compiler + if: "!startsWith(matrix.compiler, 'msvc')" + run: tools/ci/setupCompiler.sh + env: + COMPILER: ${{matrix.compiler}} + + - name: Setup cmake + if: runner.os != 'Windows' + uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2.2.0 + with: + cmake-version: '3.20.x' + + - name: Setup CCache + if: runner.os != 'Windows' + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + key: ${{matrix.os}}-${{matrix.compiler}}-${{matrix.buildType}}-${{matrix.boostVersion}} + max-size: 200M + + - run: echo "BOOST_ROOT=${{runner.temp}}/boost${{matrix.boostVersion}}" >> $GITHUB_ENV + - name: Cache Boost installation + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{env.BOOST_ROOT}} + key: ${{matrix.os}}-${{matrix.compiler}}-${{matrix.boostVersion}}-${{matrix.buildType}} + + - name: Install Boost + env: + BOOST_VERSION: ${{matrix.boostVersion}} + TOOLSET: ${{matrix.compiler}} + VARIANT: ${{matrix.buildType}} + run: | + echo "ADDITIONAL_CMAKE_FLAGS=-DBoost_NO_SYSTEM_PATHS=ON" >> $GITHUB_ENV + LINK="" + if [[ "$RUNNER_OS" == "Windows" ]]; then + LINK=static + else + LINK=shared + fi + tools/ci/installBoost.sh "${BOOST_VERSION}" "${BOOST_ROOT}" "filesystem,locale,test" $LINK + + - name: Checkout libendian + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Return-To-The-Roots/libendian + ref: master + path: ${{github.workspace}}/libendian + - name: Build + run: tools/ci/build.sh "${{matrix.buildType}}" "${ADDITIONAL_CMAKE_FLAGS}" "tests" + env: + GENERATOR: ${{matrix.generator}} + LIBENDIAN_DIR: ${{github.workspace}}/libendian diff --git a/CMakeLists.txt b/CMakeLists.txt index 8512d04..12c621a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,21 @@ project(s25util) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(isTopLevel TRUE) + find_path(RTTR_LIBENDIAN_DIR NAMES "include/libendian" HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../libendian PATHS ENV LIBENDIAN_DIR PATH_SUFFIXES ".." libendian) + if(NOT RTTR_LIBENDIAN_DIR) + message(FATAL_ERROR "Path to libendian not found! You can set the full path in the env var LIBENDIAN_DIR ($ENV{LIBENDIAN_DIR})") + endif() + add_subdirectory(${RTTR_LIBENDIAN_DIR} libendian) + include(EnableCCache) + include(RttrBoostCfg) + # Boost 1.73 contains Boost.Nowide, 1.74 a required fix + find_package(Boost 1.74 COMPONENTS nowide) +else() + set(isTopLevel FALSE) +endif() + # The super project might e.g. call find_package(Boost ... nowide), if the target is not defined, download directly if(NOT TARGET Boost::nowide) message(STATUS "Fetching Boost.Nowide from repository...") diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..c2760d2 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,38 @@ +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +version: 1.0.{build} + +shallow_clone: true + +branches: + only: [master] + +image: Visual Studio 2019 + +configuration: + - Debug + - Release + +platform: x64 + +environment: + global: + BOOST_DIR: 'C:\Libraries\boost_1_85_0' + RTTR_DISABLE_ASSERT_BREAKPOINT: 1 + +install: + - git clone https://github.com/Return-To-The-Roots/libendian.git libendian + - set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%cd%\libendian + +before_build: + - mkdir build + - cd build + - dir /a:d %BOOST_DIR% + - cmake -G "Visual Studio 16 2019" -A %platform% -DRTTR_ENABLE_WERROR=ON -DBOOST_ROOT="%BOOST_DIR%\lib64-msvc-14.2\cmake" -DBoost_VERBOSE=ON .. + +build_script: cmake --build . --config %configuration% --parallel 4 + +test_script: ctest --output-on-failure -C %configuration% --parallel 4 + diff --git a/cmake/AddTestcase.cmake b/cmake/AddTestcase.cmake index 352b471..63306a1 100644 --- a/cmake/AddTestcase.cmake +++ b/cmake/AddTestcase.cmake @@ -1,4 +1,4 @@ -# Copyright (C) 2005 - 2021 Settlers Freaks +# Copyright (C) 2005 - 2026 Settlers Freaks # # SPDX-License-Identifier: GPL-2.0-or-later @@ -56,4 +56,8 @@ function(add_testcase) ) set_tests_properties(${name} PROPERTIES ENVIRONMENT "${options}") endif() + if(NOT TARGET tests) + add_custom_target(tests) + endif() + add_dependencies(tests ${name}) endfunction() diff --git a/cmake/RttrBoostCfg.cmake b/cmake/RttrBoostCfg.cmake index 3b2fed5..f427acb 100644 --- a/cmake/RttrBoostCfg.cmake +++ b/cmake/RttrBoostCfg.cmake @@ -7,7 +7,7 @@ # Show message about boost config # Default boost libs are static on self-build windows versions and dynamic in the linux package repos -if(WIN32 AND NOT MINGW) +if(WIN32) set(RTTR_USE_STATIC_BOOST_DEFAULT ON) else() set(RTTR_USE_STATIC_BOOST_DEFAULT OFF) diff --git a/libs/common/include/s25util/span.hpp b/libs/common/include/s25util/span.hpp index be99ed2..891191c 100644 --- a/libs/common/include/s25util/span.hpp +++ b/libs/common/include/s25util/span.hpp @@ -214,19 +214,19 @@ class span constexpr span() noexcept : s_(0, 0) {} - template, int> = 0> + template && E == dynamic_extent, int> = 0> constexpr span(I* f, size_type c) : s_(f, c) {} - template, int> = 0> + template && E != dynamic_extent, int> = 0> explicit constexpr span(I* f, size_type c) : s_(f, c) {} - template, int> = 0> + template && E == dynamic_extent, int> = 0> constexpr span(I* f, L* l) : s_(f, l - f) {} - template, int> = 0> + template && E != dynamic_extent, int> = 0> explicit constexpr span(I* f, L* l) : s_(f, l - f) {} @@ -242,11 +242,11 @@ class span constexpr span(const std::array& a) noexcept : s_(a.data(), N) {} - template, int> = 0> + template && E == dynamic_extent, int> = 0> constexpr span(R&& r) noexcept(noexcept(std::data(r)) && noexcept(r.size())) : s_(std::data(r), r.size()) {} - template, int> = 0> + template && E != dynamic_extent, int> = 0> explicit constexpr span(R&& r) noexcept(noexcept(std::data(r)) && noexcept(r.size())) : s_(std::data(r), r.size()) {} diff --git a/tools/ci/build.sh b/tools/ci/build.sh new file mode 100755 index 0000000..1bbfa2d --- /dev/null +++ b/tools/ci/build.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +function print_on_gha { + [[ "${GITHUB_ACTIONS:-false}" != "true" ]] || echo "$@" +} +function startGroup { + print_on_gha "::group::$1" +} +function endGroup { + print_on_gha "::endgroup::" +} + +# Required by the tests +export USER="${USER:-TestUser}" + +BUILD_TYPE="${1}" +CMAKE_FLAGS="${2}" +TARGET="${3:-install}" + +[[ -d build ]] || mkdir build +cd build + +if [[ -z ${GENERATOR:-} ]]; then + if [[ "${RUNNER_OS:-}" == "Linux" ]] || [[ "${RUNNER_OS:-}" == "macOS" ]]; then + GENERATOR="Unix Makefiles" + else + echo 'Missing $GENERATOR!' + exit 1 + fi +fi +if [[ $GENERATOR == "Visual Studio"* ]]; then + BUILD_FLAGS="" +else + BUILD_FLAGS="-j3" +fi + +cmake --version + +set -x +# shellcheck disable=SC2086 +cmake \ + -G "$GENERATOR" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DRTTR_ENABLE_WERROR=ON \ + -DCMAKE_INSTALL_PREFIX="$PWD/installed" \ + ${CMAKE_FLAGS} \ + .. + +cmake --build . --target "$TARGET" --config "${BUILD_TYPE}" -- $BUILD_FLAGS + +# Execute tests +export RTTR_DISABLE_ASSERT_BREAKPOINT=1 +export BOOST_TEST_LOG_LEVEL=message +export BOOST_TEST_REPORT_LEVEL=detailed +if ! ctest --output-on-failure -C "${BUILD_TYPE}" -j2; then + startGroup "CMake cache" + cat CMakeCache.txt + endGroup + echo "LD:${LD_LIBRARY_PATH:-}" + echo "DYLD:${DYLD_LIBRARY_PATH:-}" + echo "PATH:$PATH" + startGroup "Rerun failed tests only" + ctest --output-on-failure -C "${BUILD_TYPE}" --rerun-failed --extra-verbose + endGroup +fi diff --git a/tools/ci/checkUTF8.sh b/tools/ci/checkUTF8.sh new file mode 100755 index 0000000..4c9b02d --- /dev/null +++ b/tools/ci/checkUTF8.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +# Check that all source files in the current folder are valid UTF-8. +# Prints the filenames of files that are not valid UTF-8 to stderr. +# Exits 0 if all files are valid UTF-8, 1 otherwise. + +set -euo pipefail + +failed=0 +while IFS= read -r -d '' file; do + # Try to convert all source files from UTF-8 to detect encoding errors + if ! iconv -f UTF-8 "$file" > /dev/null; then + printf '%s\n' "$file" >&2 + failed=1 + fi +done < <(find . --exclude-dir=.git -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.tpp' -o -name '*.h' \) -print0) + +exit "$failed" diff --git a/tools/ci/installBoost.sh b/tools/ci/installBoost.sh index f9dc52b..15f7dea 100755 --- a/tools/ci/installBoost.sh +++ b/tools/ci/installBoost.sh @@ -11,6 +11,7 @@ INSTALL_DIR="${2:?Missing install dir}" required_libs="${3:?Missing libs}" link="${4:-static}" +# shellcheck disable=SC2206 required_libs=( ${required_libs//,/ } ) function print_on_gha { @@ -46,15 +47,14 @@ if all_libs_exist; then fi BUILD_DIR=/tmp/boost -BUILD_LOG=/tmp/boost.log mkdir -p "${BUILD_DIR}" && cd "${BUILD_DIR}" startGroup "Download Boost" FILE_NAME="boost_${VERSION//./_}" -URL="https://archives.boost.io/release/${VERSION}/source/${FILE_NAME}.tar.bz2" +URL="https://archives.boost.io/release/${VERSION}/source/${FILE_NAME}.tar.gz" echo "Downloading and extracting file from $URL..." -wget "${URL}" -qO- | tar jx +wget "${URL}" -qO- | tar zx if [ ! -f "${FILE_NAME}/bootstrap.sh" ]; then echo "Download failed or files invalid" >&2 endGroup @@ -67,13 +67,14 @@ endGroup if [[ "${TRAVIS_OS_NAME:-}" == "windows" ]] || [[ "${RUNNER_OS:-}" == "Windows" ]]; then NPROC=3 - : "${TOOLSET:=toolset=msvc}" + : "${TOOLSET:=msvc}" : "${VARIANT:=debug,release}" else # Linux and OSX version NPROC=$(nproc 2>/dev/null || sysctl -n hw.ncpu) : "${VARIANT:=release}" fi +VARIANT=$(printf '%s' "$VARIANT" | tr '[:upper:]' '[:lower:]') libraries=$(join , "${required_libs[@]}") @@ -84,7 +85,9 @@ endGroup startGroup "Building $link Boost in $VARIANT mode" echo "Installing ${libraries}, variants ${VARIANT} to ${INSTALL_DIR} using ${TOOLSET:-default toolset}" -./b2 link="${link}" ${TOOLSET:-} variant="${VARIANT}" --prefix="${INSTALL_DIR}" -j${NPROC} install +set -x +# shellcheck disable=SC2086 +./b2 address-model=64 link="${link}" ${TOOLSET+toolset=$TOOLSET} variant="${VARIANT}" --prefix="${INSTALL_DIR}" -j${NPROC} install endGroup echo "Boost build finished successfully" diff --git a/tools/ci/setupCompiler.sh b/tools/ci/setupCompiler.sh new file mode 100755 index 0000000..54b3040 --- /dev/null +++ b/tools/ci/setupCompiler.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +CC="$COMPILER" + +if [[ "$CC" =~ clang ]]; then + CXX=${CC/clang/clang++} +elif [[ "$CC" =~ gcc ]]; then + CXX=${CC/gcc/g++} +else + echo "Unknown compiler: $CC" + exit 1 +fi +ver=7 # default +if [[ "$CC" =~ gcc- ]]; then + ver="${CC##*gcc-}" +fi +GCOV=gcov-${ver} +echo "CC=$CC" >> $GITHUB_ENV +echo "CXX=$CXX" >> $GITHUB_ENV +echo "GCOV=$GCOV" >> $GITHUB_ENV diff --git a/tools/ci/staticValidation.sh b/tools/ci/staticValidation.sh new file mode 100755 index 0000000..0bc07c8 --- /dev/null +++ b/tools/ci/staticValidation.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +SRC_DIR="${1:?SRC_DIR missing}" +scriptPath=$(dirname "$(readlink -f "$0")") + +cd "${SRC_DIR}" + +if ! "$scriptPath/checkUTF8.sh"; then + echo "Some files are not in UTF8 encoding (see filenames above)" + exit 1 +fi + +canonicalBugs="$(grep -r -l --include '*.h' --include '*.cpp' --include '*.hpp' --include '*.tpp' "::canonical(" || true)" +if [[ "${canonicalBugs}" != "" ]]; then + echo "Following files use buggy boost::filesystem::canonical: ${canonicalBugs}" + echo "If this is only used on non-windows use canonical unqualified: using bfs::canonical; canonical(...)" + exit 1 +fi