diff --git a/.github/actions/_generate_llvm_html_coverage/action.yaml b/.github/actions/_generate_llvm_html_coverage/action.yaml new file mode 100644 index 00000000..721ac596 --- /dev/null +++ b/.github/actions/_generate_llvm_html_coverage/action.yaml @@ -0,0 +1,36 @@ +name: Generate LLVM HTML Coverage +description: Builds the HTML coverage report for a coverage-enabled LLVM preset + +inputs: + build-preset: + description: CMake build preset used to generate HTML coverage artifacts + required: true + test-preset: + description: CTest preset whose recorded coverage data should be reported + required: true + +outputs: + html-cov-dir: + description: Directory containing the generated HTML coverage report + value: ${{ steps.htmlcov.outputs.html_cov_dir }} + +runs: + using: composite + steps: + - id: htmlcov + shell: bash + run: | + set +e + cmake --build --preset ${{ matrix.build_preset }} --target coverage-html 2>&1 | tee coverage_html_summary.txt + COV_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee + set -e + + HTML_COV_DIR=$(grep -Po "HTML coverage directory: \K.*" coverage_html_summary.txt) + # -P: Uses Perl-style regex. + # -o: Only outputs the matched part (not the whole line). + # \K: Tells the engine to match the string but ignore it in the final output. + # .*: Matches the rest of the line (the path) + + echo "html_cov_dir=$HTML_COV_DIR" >> "$GITHUB_OUTPUT" + + exit $COV_EXIT_CODE diff --git a/.github/actions/_log_to_gh_summary_bash/action.yaml b/.github/actions/_log_to_gh_summary_bash/action.yaml new file mode 100644 index 00000000..0ed6a42b --- /dev/null +++ b/.github/actions/_log_to_gh_summary_bash/action.yaml @@ -0,0 +1,46 @@ +name: CMake Bash Step With Summary +description: Runs a CMake command and logs output to GitHub Step Summary (Linux, MacOS) + +inputs: + step-name: + description: Display name (e.g. Build, Configure) + required: true + command: + description: The full cmake command to run + required: true + output-file: + description: Temporary text file to store command output + required: false + default: tmp_step_output.txt + +runs: + using: composite + steps: + - shell: bash + run: | + set +e + eval "${{ inputs.command }} 2>&1 | tee ${{ inputs.output-file }}" + EXIT_CODE=${PIPESTATUS[0]} + set -e + + LOG_SIZE=$(wc -c < ${{ inputs.output-file }}) + + if [ $EXIT_CODE -eq 0 ]; then + echo "
๐ŸŸข ${{ inputs.step-name }} Successful" >> $GITHUB_STEP_SUMMARY + else + echo "## ๐Ÿ”ด ${{ inputs.step-name }} Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo '' >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + + # Guard against 1MB GitHub Step Summary limit: only show the last 500 lines if it's huge + tail -n 500 ${{ inputs.output-file }} >> $GITHUB_STEP_SUMMARY + + echo '```' >> $GITHUB_STEP_SUMMARY + + if [ $EXIT_CODE -eq 0 ]; then + echo "
" >> $GITHUB_STEP_SUMMARY + fi + + exit $EXIT_CODE diff --git a/.github/actions/_log_to_gh_summary_pwsh/action.yaml b/.github/actions/_log_to_gh_summary_pwsh/action.yaml new file mode 100644 index 00000000..c35ef0fc --- /dev/null +++ b/.github/actions/_log_to_gh_summary_pwsh/action.yaml @@ -0,0 +1,43 @@ +name: CMake PowerShell Step With Summary +description: Runs a command and logs output to GitHub Step Summary on Windows runners + +inputs: + step-name: + description: Display name (e.g. Build, Configure) + required: true + command: + description: The full command to run + required: true + output-file: + description: Temporary text file to store command output + required: false + default: tmp_step_output.txt + +runs: + using: composite + steps: + - shell: pwsh + run: | + $command = "${{ inputs.command }}" + $outputFile = "${{ inputs.output-file }}" + + $global:LASTEXITCODE = 0 + Invoke-Expression "$command 2>&1" | Tee-Object -FilePath $outputFile + $exitCode = if ($null -ne $LASTEXITCODE) { $LASTEXITCODE } else { 0 } + + if ($exitCode -eq 0) { + Add-Content $env:GITHUB_STEP_SUMMARY "
๐ŸŸข ${{ inputs.step-name }} Successful" + } else { + Add-Content $env:GITHUB_STEP_SUMMARY "## ๐Ÿ”ด ${{ inputs.step-name }} Failed" + } + + Add-Content $env:GITHUB_STEP_SUMMARY "" + Add-Content $env:GITHUB_STEP_SUMMARY '```text' + Get-Content $outputFile -Tail 500 | Add-Content $env:GITHUB_STEP_SUMMARY + Add-Content $env:GITHUB_STEP_SUMMARY '```' + + if ($exitCode -eq 0) { + Add-Content $env:GITHUB_STEP_SUMMARY "
" + } + + exit $exitCode diff --git a/.github/actions/_publish_llvm_coverage/action.yaml b/.github/actions/_publish_llvm_coverage/action.yaml new file mode 100644 index 00000000..9d029cea --- /dev/null +++ b/.github/actions/_publish_llvm_coverage/action.yaml @@ -0,0 +1,70 @@ +name: Publish LLVM Coverage +description: Publishes the HTML coverage report and shields badges to GitHub Pages + +inputs: + build-preset: + description: CMake build preset used to generate coverage artifacts + required: true + test-preset: + description: CTest preset whose recorded coverage data should be reported + required: true + github-token: + description: GitHub token used to publish the generated site + required: true + publish-branch: + description: Git branch used by GitHub Pages + required: false + default: gh-pages + publish-dir: + description: Directory prepared locally before publication + required: false + default: out + +runs: + using: composite + steps: + - name: Generate html coverage report + id: htmlcov + uses: ./.github/actions/_generate_llvm_html_coverage + with: + build-preset: ${{ inputs.build-preset }} + test-preset: ${{ inputs.test-preset }} + + - name: Generate shields.io badge + id: shieldsio + shell: bash + run: | + set +e + cmake --build --preset ${{ matrix.build_preset }} --target coverage-shieldsio 2>&1 | tee coverage_shieldsio_summary.txt + COV_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee + set -e + + SHIELDSIO_REGION_COV_BADGE_FILE=$(grep -Po "Shields.io Region Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) + SHIELDSIO_FUNCTION_COV_BADGE_FILE=$(grep -Po "Shields.io Function Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) + SHIELDSIO_LINE_COV_BADGE_FILE=$(grep -Po "Shields.io Line Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) + SHIELDSIO_BRANCH_COV_BADGE_FILE=$(grep -Po "Shields.io Branch Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) + + echo "SHIELDSIO_REGION_COV_BADGE_FILE=$SHIELDSIO_REGION_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" + echo "SHIELDSIO_FUNCTION_COV_BADGE_FILE=$SHIELDSIO_FUNCTION_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" + echo "SHIELDSIO_LINE_COV_BADGE_FILE=$SHIELDSIO_LINE_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" + echo "SHIELDSIO_BRANCH_COV_BADGE_FILE=$SHIELDSIO_BRANCH_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" + + exit $COV_EXIT_CODE + + - name: Prepare files + shell: bash + run: | + mkdir -p "${{ inputs.publish-dir }}/coverage/badges" + cp -r "${{ steps.htmlcov.outputs.html-cov-dir }}" "${{ inputs.publish-dir }}/coverage/" + cp "${{ steps.shieldsio.outputs.SHIELDSIO_REGION_COV_BADGE_FILE }}" "${{ inputs.publish-dir }}/coverage/badges" + cp "${{ steps.shieldsio.outputs.SHIELDSIO_FUNCTION_COV_BADGE_FILE }}" "${{ inputs.publish-dir }}/coverage/badges" + cp "${{ steps.shieldsio.outputs.SHIELDSIO_LINE_COV_BADGE_FILE }}" "${{ inputs.publish-dir }}/coverage/badges" + cp "${{ steps.shieldsio.outputs.SHIELDSIO_BRANCH_COV_BADGE_FILE }}" "${{ inputs.publish-dir }}/coverage/badges" + + - name: Publish coverage badge and report to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ inputs.github-token }} + publish_branch: ${{ inputs.publish-branch }} + publish_dir: ${{ inputs.publish-dir }} + keep_files: true diff --git a/.github/actions/_setup_configure_build_linux/action.yaml b/.github/actions/_setup_configure_build_linux/action.yaml new file mode 100644 index 00000000..d5fe3ee9 --- /dev/null +++ b/.github/actions/_setup_configure_build_linux/action.yaml @@ -0,0 +1,115 @@ +name: Setup, Configure, and Build Linux CI +description: Configures the Linux CI environment, restores caches, and runs CMake configure/build + +inputs: + arch: + description: Architecture label used in cache keys + required: true + configure-preset: + description: CMake configure preset to use + required: true + build-preset: + description: CMake build preset to use + required: true + build-target: + description: Optional CMake build target + required: false + default: "" + build-step-name: + description: Display label used in the GitHub summary for the build step + required: false + default: Build + +runs: + using: composite + steps: + - name: Setup VCPKG environment + shell: bash + run: | + set -euo pipefail + + if [[ -z "${VCPKG_INSTALLATION_ROOT:-}" ]]; then + echo "::error::No pre-installed vcpkg root was found on this runner." + exit 1 + fi + + mkdir -p \ + "$GITHUB_WORKSPACE/.cache/vcpkg/downloads" \ + "$GITHUB_WORKSPACE/.cache/vcpkg/archives" + + { + echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" + echo "VCPKG_DOWNLOADS=$GITHUB_WORKSPACE/.cache/vcpkg/downloads" + echo "VCPKG_DEFAULT_BINARY_CACHE=$GITHUB_WORKSPACE/.cache/vcpkg/archives" + } >> "$GITHUB_ENV" + + - name: Cache VCPKG assets + uses: actions/cache@v4 + with: + path: | + .cache/vcpkg/downloads + .cache/vcpkg/archives + key: ${{ runner.os }}-${{ inputs.arch }}-vcpkg-${{ inputs.build-preset }}-${{ hashFiles('vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ inputs.arch }}-vcpkg- + + - name: Cache build + uses: actions/cache@v4 + with: + path: build/${{ inputs.build-preset }} + key: ${{ runner.os }}-${{ inputs.arch }}-cmake-${{ inputs.build-preset }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'vcpkg.json', '**/*.cmake', '.github/actions/_setup_configure_build_linux/action.yaml') }} + + - name: Setup LLVM tool aliases + shell: bash + run: | + set -euo pipefail + + # GitHub's Ubuntu runners sometimes expose versioned LLVM binaries + # (for example `llvm-cov-18`) without the unversioned names that our + # existing CMake lookup expects. Adding a lightweight workspace-local + # aliases here instead of reinstalling LLVM packages on every run. + + TOOL_BIN_DIR="$GITHUB_WORKSPACE/.local/bin" + mkdir -p "$TOOL_BIN_DIR" + + ensure_llvm_tool() { + local tool="$1" + + if command -v "$tool" >/dev/null 2>&1; then + return 0 + fi + + for suffix in 20 19 18 17 16; do + if candidate=$(command -v "${tool}-${suffix}" 2>/dev/null); then + ln -sf "$candidate" "$TOOL_BIN_DIR/$tool" + return 0 + fi + done + + echo "::warning::Could not find $tool or a versioned variant in PATH." + return 0 + } + + ensure_llvm_tool llvm-cov + ensure_llvm_tool llvm-profdata + + echo "$TOOL_BIN_DIR" >> "$GITHUB_PATH" + + - name: Configure (${{ inputs.configure-preset }}) + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: Configure (${{ inputs.configure-preset }}) + command: "cmake --preset ${{ inputs.configure-preset }}" + + - name: Build (${{ inputs.build-preset }}) + if: ${{ inputs.build-target == '' }} + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }}" + + - name: Build target (${{ inputs.build-target }}) + if: ${{ inputs.build-target != '' }} + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }} --target ${{ inputs.build-target }}" diff --git a/.github/actions/_setup_configure_build_macos/action.yaml b/.github/actions/_setup_configure_build_macos/action.yaml new file mode 100644 index 00000000..45a5067f --- /dev/null +++ b/.github/actions/_setup_configure_build_macos/action.yaml @@ -0,0 +1,79 @@ +name: Setup, Configure, and Build macOS CI +description: Configures the macOS CI environment, restores caches, and runs CMake configure/build + +inputs: + arch: + description: Architecture label used in cache keys + required: true + configure-preset: + description: CMake configure preset to use + required: true + build-preset: + description: CMake build preset to use + required: true + build-target: + description: Optional CMake build target + required: false + default: "" + build-step-name: + description: Display label used in the GitHub summary for the build step + required: false + default: Build + +runs: + using: composite + steps: + - name: Setup VCPKG environment + shell: bash + run: | + set -euo pipefail + + if [[ -z "${VCPKG_INSTALLATION_ROOT:-}" ]]; then + echo "::error::No pre-installed vcpkg root was found on this runner." + exit 1 + fi + + mkdir -p \ + "$GITHUB_WORKSPACE/.cache/vcpkg/downloads" \ + "$GITHUB_WORKSPACE/.cache/vcpkg/archives" + + { + echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" + echo "VCPKG_DOWNLOADS=$GITHUB_WORKSPACE/.cache/vcpkg/downloads" + echo "VCPKG_DEFAULT_BINARY_CACHE=$GITHUB_WORKSPACE/.cache/vcpkg/archives" + } >> "$GITHUB_ENV" + + - name: Cache VCPKG assets + uses: actions/cache@v4 + with: + path: | + .cache/vcpkg/downloads + .cache/vcpkg/archives + key: ${{ runner.os }}-${{ inputs.arch }}-vcpkg-${{ inputs.build-preset }}-${{ hashFiles('vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ inputs.arch }}-vcpkg- + + - name: Cache build + uses: actions/cache@v4 + with: + path: build/${{ inputs.build-preset }} + key: ${{ runner.os }}-${{ inputs.arch }}-cmake-${{ inputs.build-preset }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'vcpkg.json', '**/*.cmake', '.github/actions/_setup_configure_build_macos/action.yaml') }} + + - name: Configure (${{ inputs.configure-preset }}) + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: Configure (${{ inputs.configure-preset }}) + command: "cmake --preset ${{ inputs.configure-preset }}" + + - name: Build (${{ inputs.build-preset }}) + if: ${{ inputs.build-target == '' }} + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }}" + + - name: Build target (${{ inputs.build-target }}) + if: ${{ inputs.build-target != '' }} + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }} --target ${{ inputs.build-target }}" diff --git a/.github/actions/_setup_configure_build_windows/action.yaml b/.github/actions/_setup_configure_build_windows/action.yaml new file mode 100644 index 00000000..58dc540b --- /dev/null +++ b/.github/actions/_setup_configure_build_windows/action.yaml @@ -0,0 +1,78 @@ +name: Setup, Configure, and Build Windows CI +description: Configures the Windows CI environment, restores caches, and runs CMake configure/build + +inputs: + arch: + description: Architecture label used in cache keys + required: true + configure-preset: + description: CMake configure preset to use + required: true + build-preset: + description: CMake build preset to use + required: true + build-target: + description: Optional CMake build target + required: false + default: "" + build-step-name: + description: Display label used in the GitHub summary for the build step + required: false + default: Build + +runs: + using: composite + steps: + - name: Setup VCPKG environment + shell: pwsh + run: | + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkgRoot -or -not (Test-Path $vcpkgRoot)) { + throw 'No pre-installed vcpkg root was found on this runner.' + } + + $downloadsDir = Join-Path $env:GITHUB_WORKSPACE '.cache\vcpkg\downloads' + $binaryCacheDir = Join-Path $env:GITHUB_WORKSPACE '.cache\vcpkg\archives' + + New-Item -ItemType Directory -Force -Path $downloadsDir, $binaryCacheDir | Out-Null + + @( + "VCPKG_ROOT=$vcpkgRoot" + "VCPKG_DOWNLOADS=$downloadsDir" + "VCPKG_DEFAULT_BINARY_CACHE=$binaryCacheDir" + ) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Cache VCPKG assets + uses: actions/cache@v4 + with: + path: | + .cache/vcpkg/downloads + .cache/vcpkg/archives + key: ${{ runner.os }}-${{ inputs.arch }}-vcpkg-${{ inputs.build-preset }}-${{ hashFiles('vcpkg.json') }} + restore-keys: ${{ runner.os }}-${{ inputs.arch }}-vcpkg- + + - name: Cache build + uses: actions/cache@v4 + with: + path: build/${{ inputs.build-preset }} + key: ${{ runner.os }}-${{ inputs.arch }}-cmake-${{ inputs.build-preset }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'vcpkg.json', '**/*.cmake', '.github/actions/_setup_configure_build_windows/action.yaml') }} + + - name: Configure (${{ inputs.configure-preset }}) + uses: ./.github/actions/_log_to_gh_summary_pwsh + with: + step-name: Configure (${{ inputs.configure-preset }}) + command: "cmake --preset ${{ inputs.configure-preset }}" + + - name: Build (${{ inputs.build-preset }}) + if: ${{ inputs.build-target == '' }} + uses: ./.github/actions/_log_to_gh_summary_pwsh + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }}" + + - name: Build target (${{ inputs.build-target }}) + if: ${{ inputs.build-target != '' }} + uses: ./.github/actions/_log_to_gh_summary_pwsh + with: + step-name: ${{ inputs.build-step-name }} (${{ inputs.build-preset }}) + command: "cmake --build --preset ${{ inputs.build-preset }} --target ${{ inputs.build-target }}" diff --git a/.github/workflows/clang_tidy.yaml b/.github/workflows/clang_tidy.yaml index 0a54f98b..52ebe053 100644 --- a/.github/workflows/clang_tidy.yaml +++ b/.github/workflows/clang_tidy.yaml @@ -4,10 +4,10 @@ on: workflow_dispatch: pull_request: branches: [ main ] - merge_group: + types: [ synchronize ] concurrency: - group: pr-${{ github.event.pull_request.number }}-clang-tidy-linting + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true permissions: @@ -16,82 +16,26 @@ permissions: jobs: clang-tidy-linting: - runs-on: ubuntu-latest - name: ${{ matrix.name }} + runs-on: ${{ matrix.runner }} + name: ${{ matrix.runner }} - ${{ matrix.build_preset }} strategy: + fail-fast: false matrix: include: - - name: linting-clang-release - configure_preset: clang_release - build_preset: clang_release + - runner: ubuntu-24.04-arm + arch: arm64 + configure_preset: clang_debug + build_preset: clang_debug steps: - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - clang \ - clang-tidy \ - gcc \ - g++ \ - git \ - curl \ - zip \ - unzip \ - pkg-config \ - ninja-build \ - cmake - - - name: Setup vcpkg - run: | - git clone https://github.com/microsoft/vcpkg.git /tmp/vcpkg - /tmp/vcpkg/bootstrap-vcpkg.sh - echo "VCPKG_ROOT=/tmp/vcpkg" >> $GITHUB_ENV - echo "/tmp/vcpkg" >> $GITHUB_PATH - - - name: Cache VCPKG - uses: actions/cache@v4 - with: - path: | - /tmp/vcpkg/downloads - /tmp/vcpkg/installed - ~/.cache/vcpkg - key: ${{ runner.os }}-vcpkg-${{ matrix.name }}-${{ hashFiles('vcpkg.json') }} - restore-keys: ${{ runner.os }}-vcpkg- - - - name: Cache build - uses: actions/cache@v4 + - name: Setup, configure, and run clang-tidy + uses: ./.github/actions/_setup_configure_build_linux with: - path: build - key: ${{ runner.os }}-cmake-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} - restore-keys: ${{ runner.os }}-cmake- - - - name: Configure (${{ matrix.configure_preset }}) - run: cmake --preset ${{ matrix.configure_preset }} - - - name: Run clang-tidy - shell: bash - run: | - set +e - cmake --build --preset ${{ matrix.build_preset }} --target clang-tidy 2>&1 | tee clang_tidy_output.txt - CLANG_TIDY_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - if [ $CLANG_TIDY_EXIT_CODE -eq 0 ]; then - echo "
๐ŸŸข Clang Tidy Results (click to expand)" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐Ÿ”ด Clang Tidy Results" >> $GITHUB_STEP_SUMMARY - fi - echo '' >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat clang_tidy_output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ $CLANG_TIDY_EXIT_CODE -eq 0 ]; then - echo "
" >> $GITHUB_STEP_SUMMARY - fi - - exit $CLANG_TIDY_EXIT_CODE + arch: ${{ matrix.arch }} + configure-preset: ${{ matrix.configure_preset }} + build-preset: ${{ matrix.build_preset }} + build-target: clang-tidy + build-step-name: Run clang-tidy diff --git a/.github/workflows/linux_build_test.yaml b/.github/workflows/linux_build_test.yaml index e6578fdc..36f361e1 100644 --- a/.github/workflows/linux_build_test.yaml +++ b/.github/workflows/linux_build_test.yaml @@ -1,236 +1,101 @@ name: Linux Build Test on: - workflow_dispatch: pull_request: branches: [ main ] - merge_group: + types: [ opened, reopened, synchronize, labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true permissions: contents: read packages: read -concurrency: - group: pr-${{ github.event.pull_request.number }}-linux-build - cancel-in-progress: true - jobs: - linux-build: - runs-on: ubuntu-latest - name: ${{ matrix.test_preset }} + plan-linux-build: + runs-on: ubuntu-slim + name: Plan Linux Build - permissions: - # coverage artefacts export requires write priviledges - # (html report and shields.io badge) - contents: write - - strategy: - matrix: - include: - - name: clang_debug - configure_preset: clang_debug - build_preset: clang_debug - test_preset: quick-validation-clang-debug - - name: clang_release - configure_preset: clang_release - build_preset: clang_release - test_preset: quick-validation-clang-release + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + should_run: ${{ steps.set-matrix.outputs.should_run }} steps: - - uses: actions/checkout@v4 - - - name: Install dependencies + - id: set-matrix + env: + EVENT_ACTION: ${{ github.event.action }} + EVENT_LABEL: ${{ github.event.label.name || '' }} + HAS_PREMERGE_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'run-pre-merge-checks') }} run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - clang \ - clang-tidy \ - llvm \ - gcc \ - g++ \ - git \ - curl \ - zip \ - unzip \ - pkg-config \ - ninja-build \ - cmake - - - name: Setup vcpkg - run: | - git clone https://github.com/microsoft/vcpkg.git /tmp/vcpkg - /tmp/vcpkg/bootstrap-vcpkg.sh - echo "VCPKG_ROOT=/tmp/vcpkg" >> $GITHUB_ENV - echo "/tmp/vcpkg" >> $GITHUB_PATH - - - name: Cache VCPKG - uses: actions/cache@v4 - with: - path: | - /tmp/vcpkg/downloads - /tmp/vcpkg/installed - ~/.cache/vcpkg - key: ${{ runner.os }}-vcpkg-${{ matrix.name }}-${{ hashFiles('vcpkg.json') }} - restore-keys: ${{ runner.os }}-vcpkg- - - - name: Cache build - uses: actions/cache@v4 - with: - path: build - key: ${{ runner.os }}-cmake-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} - restore-keys: ${{ runner.os }}-cmake- - - - name: Configure (${{ matrix.configure_preset }}) - shell: bash - run: | - set +e - cmake --preset ${{ matrix.configure_preset }} 2>&1 | tee configure_output.txt - CONFIGURE_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - if [ $CONFIGURE_EXIT_CODE -eq 0 ]; then - echo "
๐ŸŸข Configure Results (click to expand)" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐Ÿ”ด Configure Results" >> $GITHUB_STEP_SUMMARY - fi - echo '' >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat configure_output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ $CONFIGURE_EXIT_CODE -eq 0 ]; then - echo "
" >> $GITHUB_STEP_SUMMARY - fi - - exit $CONFIGURE_EXIT_CODE - - - name: Build (${{ matrix.build_preset }}) - shell: bash - run: | - set +e - cmake --build --preset ${{ matrix.build_preset }} 2>&1 | tee build_output.txt - BUILD_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - if [ $BUILD_EXIT_CODE -eq 0 ]; then - echo "
๐ŸŸข Build Results (click to expand)" >> $GITHUB_STEP_SUMMARY + SHOULD_RUN=true + if [[ "$EVENT_ACTION" == "labeled" && "$EVENT_LABEL" == "run-pre-merge-checks" ]]; then + MATRIX='{"include":[ + {"runner":"ubuntu-24.04","arch":"x64","configure_preset":"clang_release","build_preset":"clang_release","test_preset":"quick-validation-clang-release"}, + {"runner":"ubuntu-24.04-arm","arch":"arm64","configure_preset":"clang_release","build_preset":"clang_release","test_preset":"quick-validation-clang-release"} + ]}' + elif [[ "$EVENT_ACTION" == "synchronize" && "$HAS_PREMERGE_LABEL" == "true" ]]; then + MATRIX='{"include":[ + {"runner":"ubuntu-24.04-arm","arch":"arm64","configure_preset":"clang_debug","build_preset":"clang_debug","test_preset":"quick-validation-clang-debug"}, + {"runner":"ubuntu-24.04","arch":"x64","configure_preset":"clang_release","build_preset":"clang_release","test_preset":"quick-validation-clang-release"}, + {"runner":"ubuntu-24.04-arm","arch":"arm64","configure_preset":"clang_release","build_preset":"clang_release","test_preset":"quick-validation-clang-release"} + ]}' + elif [[ "$EVENT_ACTION" == "opened" || "$EVENT_ACTION" == "reopened" || "$EVENT_ACTION" == "synchronize" ]]; then + MATRIX='{"include":[ + {"runner":"ubuntu-24.04-arm","arch":"arm64","configure_preset":"clang_debug","build_preset":"clang_debug","test_preset":"quick-validation-clang-debug"} + ]}' else - echo "## ๐Ÿ”ด Build Results" >> $GITHUB_STEP_SUMMARY - fi - echo '' >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat build_output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ $BUILD_EXIT_CODE -eq 0 ]; then - echo "
" >> $GITHUB_STEP_SUMMARY + MATRIX='{"include":[]}' + SHOULD_RUN=false fi - exit $BUILD_EXIT_CODE + EOF_MARKER=$(openssl rand -hex 8) + { + echo "matrix<<$EOF_MARKER" + echo "$MATRIX" + echo "$EOF_MARKER" + echo "should_run=$SHOULD_RUN" + } >> "$GITHUB_OUTPUT" - - name: Test (${{ matrix.test_preset }}) - shell: bash - run: | - set +e - ctest --preset ${{ matrix.test_preset }} --output-on-failure 2>&1 | tee test_output.txt - TEST_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - if [ $TEST_EXIT_CODE -eq 0 ]; then - echo "
๐ŸŸข Test Results (click to expand)" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐Ÿ”ด Test Results" >> $GITHUB_STEP_SUMMARY - fi - echo '' >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat test_output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ $TEST_EXIT_CODE -eq 0 ]; then - echo "
" >> $GITHUB_STEP_SUMMARY - fi - - exit $TEST_EXIT_CODE + linux-build: + needs: plan-linux-build + if: needs.plan-linux-build.outputs.should_run == 'true' + runs-on: ${{ matrix.runner }} + name: ${{ matrix.runner }} - ${{ matrix.test_preset }} - - name: Generate markdown coverage report (${{ matrix.test_preset }}) - if: matrix.name == 'clang_debug' - shell: bash - run: | - set +e - cmake --build --preset ${{ matrix.build_preset }} --target coverage-markdown 2>&1 | tee coverage_gh_summary.txt - COV_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan-linux-build.outputs.matrix) }} - COVERAGE_FILE=$(grep -Po "GitHub Markdown summary written to: \K.*" coverage_gh_summary.txt) - # -P: Uses Perl-style regex. - # -o: Only outputs the matched part (not the whole line). - # \K: Tells the engine to match the string but ignore it in the final output. - # .*: Matches the rest of the line (the path) + steps: + - uses: actions/checkout@v4 - if [ $COV_EXIT_CODE -eq 0 ]; then - echo "
๐ŸŸข Coverage Results (click to expand)" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐Ÿ”ด Coverage Results" >> $GITHUB_STEP_SUMMARY - fi - echo '' >> $GITHUB_STEP_SUMMARY - cat $COVERAGE_FILE >> $GITHUB_STEP_SUMMARY - if [ $COV_EXIT_CODE -eq 0 ]; then - echo "
" >> $GITHUB_STEP_SUMMARY - fi + - name: Setup, configure, and build Linux CI + uses: ./.github/actions/_setup_configure_build_linux + with: + arch: ${{ matrix.arch }} + configure-preset: ${{ matrix.configure_preset }} + build-preset: ${{ matrix.build_preset }} - exit $COV_EXIT_CODE + - name: Test (${{ matrix.test_preset }}) + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: Test (${{ matrix.test_preset }}) + command: "ctest --preset ${{ matrix.test_preset }} --output-on-failure" - - name: Generate html coverage report (${{ matrix.test_preset }}) - if: github.ref_name == 'main' && matrix.name == 'clang_debug' + - name: Generate coverage HTML (${{ matrix.build_preset }}) + if: matrix.build_preset == 'clang_debug' id: htmlcov - shell: bash - run: | - set +e - cmake --build --preset ${{ matrix.build_preset }} --target coverage-html 2>&1 | tee coverage_html_summary.txt - COV_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - HTML_COV_DIR=$(grep -Po "HTML coverage directory: \K.*" coverage_html_summary.txt) - echo "HTML_COV_DIR=$HTML_COV_DIR" >> "$GITHUB_OUTPUT" - - exit $COV_EXIT_CODE - - - name: Generate shields.io badge (${{ matrix.test_preset }}) - if: github.ref_name == 'main' && matrix.name == 'clang_debug' - id: shieldsio - shell: bash - run: | - set +e - cmake --build --preset ${{ matrix.build_preset }} --target coverage-shieldsio 2>&1 | tee coverage_shieldsio_summary.txt - COV_EXIT_CODE=${PIPESTATUS[0]} # Capture CMake exit code, not tee - set -e - - SHIELDSIO_REGION_COV_BADGE_FILE=$(grep -Po "Shields.io Region Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) - SHIELDSIO_FUNCTION_COV_BADGE_FILE=$(grep -Po "Shields.io Function Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) - SHIELDSIO_LINE_COV_BADGE_FILE=$(grep -Po "Shields.io Line Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) - SHIELDSIO_BRANCH_COV_BADGE_FILE=$(grep -Po "Shields.io Branch Coverage badge written to: \K.*" coverage_shieldsio_summary.txt) - - echo "SHIELDSIO_REGION_COV_BADGE_FILE=$SHIELDSIO_REGION_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" - echo "SHIELDSIO_FUNCTION_COV_BADGE_FILE=$SHIELDSIO_FUNCTION_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" - echo "SHIELDSIO_LINE_COV_BADGE_FILE=$SHIELDSIO_LINE_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" - echo "SHIELDSIO_BRANCH_COV_BADGE_FILE=$SHIELDSIO_BRANCH_COV_BADGE_FILE" >> "$GITHUB_OUTPUT" - - exit $COV_EXIT_CODE - - - name: Prepare files - if: github.ref_name == 'main' && matrix.name == 'clang_debug' - shell: bash - run: | - mkdir -p out/coverage/badges - cp -r "${{ steps.htmlcov.outputs.HTML_COV_DIR }}" out/coverage/ - cp "${{ steps.shieldsio.outputs.SHIELDSIO_REGION_COV_BADGE_FILE }}" out/coverage/badges - cp "${{ steps.shieldsio.outputs.SHIELDSIO_FUNCTION_COV_BADGE_FILE }}" out/coverage/badges - cp "${{ steps.shieldsio.outputs.SHIELDSIO_LINE_COV_BADGE_FILE }}" out/coverage/badges - cp "${{ steps.shieldsio.outputs.SHIELDSIO_BRANCH_COV_BADGE_FILE }}" out/coverage/badges + uses: ./.github/actions/_generate_llvm_html_coverage + with: + build-preset: ${{ matrix.build_preset }} + test-preset: ${{ matrix.test_preset }} - - name: Publish coverage badge and report to gh-pages - if: github.ref_name == 'main' && matrix.name == 'clang_debug' - uses: peaceiris/actions-gh-pages@v4 + - name: Upload coverage artifact (${{ matrix.build_preset }}) + if: matrix.build_preset == 'clang_debug' + uses: actions/upload-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: out - keep_files: true + name: llvm-html-coverage-${{ matrix.runner }}-${{ matrix.build_preset }} + path: ${{ steps.htmlcov.outputs.html-cov-dir }} + if-no-files-found: error diff --git a/.github/workflows/macos_build_test.yaml b/.github/workflows/macos_build_test.yaml new file mode 100644 index 00000000..c66edc39 --- /dev/null +++ b/.github/workflows/macos_build_test.yaml @@ -0,0 +1,52 @@ +name: MacOS Build Test + +on: + pull_request: + branches: [ main ] + types: [ labeled, synchronize ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + macos-build: + if: | + (github.event.action == 'labeled' && github.event.label.name == 'run-pre-merge-checks') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-pre-merge-checks')) + runs-on: ${{ matrix.runner }} + name: ${{ matrix.runner }} - ${{ matrix.test_preset }} + + strategy: + fail-fast: false + matrix: + include: + - runner: macos-26-intel + arch: intel + configure_preset: clang_release + build_preset: clang_release + test_preset: quick-validation-clang-release + - runner: macos-26 + arch: arm64 + configure_preset: clang_release + build_preset: clang_release + test_preset: quick-validation-clang-release + + steps: + - uses: actions/checkout@v4 + + - name: Setup, configure, and build macOS CI + uses: ./.github/actions/_setup_configure_build_macos + with: + arch: ${{ matrix.arch }} + configure-preset: ${{ matrix.configure_preset }} + build-preset: ${{ matrix.build_preset }} + + - name: Test (${{ matrix.test_preset }}) + uses: ./.github/actions/_log_to_gh_summary_bash + with: + step-name: Test (${{ matrix.test_preset }}) + command: "ctest --preset ${{ matrix.test_preset }} --output-on-failure" diff --git a/.github/workflows/publish_llvm_coverage.yaml b/.github/workflows/publish_llvm_coverage.yaml new file mode 100644 index 00000000..a2bf23ba --- /dev/null +++ b/.github/workflows/publish_llvm_coverage.yaml @@ -0,0 +1,35 @@ +name: Publish LLVM Coverage + +on: + push: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + packages: read + +jobs: + linux-coverage-publish: + runs-on: ubuntu-24.04 + name: ubuntu-24.04 - quick-validation-clang-debug + + steps: + - uses: actions/checkout@v4 + + - name: Setup, configure, and build Linux CI + uses: ./.github/actions/_setup_configure_build_linux + with: + arch: x64 + configure-preset: clang_debug + build-preset: clang_debug + + - name: Publish coverage (clang_debug) + uses: ./.github/actions/_publish_llvm_coverage + with: + build-preset: clang_debug + test-preset: quick-validation-clang-debug + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/windows_build_test.yaml b/.github/workflows/windows_build_test.yaml index 5b02aa4f..ed1e4ce7 100644 --- a/.github/workflows/windows_build_test.yaml +++ b/.github/workflows/windows_build_test.yaml @@ -1,122 +1,57 @@ name: Windows Build Test on: - workflow_dispatch: pull_request: - types: [labeled, synchronize] + branches: [ main ] + types: [ labeled, synchronize ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read jobs: windows-build: - if: contains(github.event.pull_request.labels.*.name, 'run-pre-merge-checks') - runs-on: windows-2022 - name: ${{ matrix.test_preset }} - - concurrency: - group: pr-${{ github.event.pull_request.number }}-windows-build - cancel-in-progress: true + if: | + (github.event.action == 'labeled' && github.event.label.name == 'run-pre-merge-checks') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-pre-merge-checks')) + runs-on: ${{ matrix.runner }} + name: ${{ matrix.runner }} - ${{ matrix.test_preset }} strategy: + fail-fast: false matrix: include: - - name: msvc_release + - runner: windows-2022 + arch: x64 configure_preset: msvc_release build_preset: msvc_release test_preset: quick-validation-msvc-release + - runner: windows-2025 + arch: x64 + configure_preset: msvc_release + build_preset: msvc_release + test_preset: quick-validation-msvc-release + - runner: windows-11-arm + arch: arm64 + configure_preset: msvc_release_arm64 + build_preset: msvc_release_arm64 + test_preset: quick-validation-msvc-release-arm64 steps: - uses: actions/checkout@v4 - - name: Cache VCPKG - uses: actions/cache@v4 + - name: Setup, configure, and build Windows CI + uses: ./.github/actions/_setup_configure_build_windows with: - path: | - C:\vcpkg\installed - C:\vcpkg\packages - C:\vcpkg\buildtrees - ~\AppData\Local\vcpkg\archives - key: ${{ runner.os }}-vcpkg-${{ matrix.name }}-${{ hashFiles('vcpkg.json') }} - restore-keys: ${{ runner.os }}-vcpkg- - - - name: Cache build - uses: actions/cache@v4 - with: - path: build - key: ${{ runner.os }}-cmake-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', '**/*.cmake') }} - restore-keys: ${{ runner.os }}-cmake- - - - name: Configure (${{ matrix.configure_preset }}) - env: - VCPKG_ROOT: C:\vcpkg - shell: pwsh - run: | - $outputFile = "configure_output.txt" - - cmake --preset ${{ matrix.configure_preset }} 2>&1 | Tee-Object -FilePath $outputFile - $exitCode = $LASTEXITCODE - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
๐ŸŸข Configure Results (click to expand)" - } else { - Add-Content $env:GITHUB_STEP_SUMMARY "## ๐Ÿ”ด Configure Results" - } - - Add-Content $env:GITHUB_STEP_SUMMARY "" - Add-Content $env:GITHUB_STEP_SUMMARY '```' - Get-Content $outputFile | Add-Content $env:GITHUB_STEP_SUMMARY - Add-Content $env:GITHUB_STEP_SUMMARY '```' - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
" - } - - exit $exitCode - - - name: Build (${{ matrix.build_preset }}) - shell: pwsh - run: | - $outputFile = "build_output.txt" - - cmake --build --preset ${{ matrix.build_preset }} 2>&1 | Tee-Object -FilePath $outputFile - $exitCode = $LASTEXITCODE - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
๐ŸŸข Build Results (click to expand)" - } else { - Add-Content $env:GITHUB_STEP_SUMMARY "## ๐Ÿ”ด Build Results" - } - - Add-Content $env:GITHUB_STEP_SUMMARY "" - Add-Content $env:GITHUB_STEP_SUMMARY '```' - Get-Content $outputFile | Add-Content $env:GITHUB_STEP_SUMMARY - Add-Content $env:GITHUB_STEP_SUMMARY '```' - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
" - } - - exit $exitCode + arch: ${{ matrix.arch }} + configure-preset: ${{ matrix.configure_preset }} + build-preset: ${{ matrix.build_preset }} - name: Test (${{ matrix.test_preset }}) - shell: pwsh - run: | - $outputFile = "test_output.txt" - - ctest --preset ${{ matrix.test_preset }} --output-on-failure 2>&1 | Tee-Object -FilePath $outputFile - $exitCode = $LASTEXITCODE - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
๐ŸŸข Test Results (click to expand)" - } else { - Add-Content $env:GITHUB_STEP_SUMMARY "## ๐Ÿ”ด Test Results" - } - - Add-Content $env:GITHUB_STEP_SUMMARY "" - Add-Content $env:GITHUB_STEP_SUMMARY '```' - Get-Content $outputFile | Add-Content $env:GITHUB_STEP_SUMMARY - Add-Content $env:GITHUB_STEP_SUMMARY '```' - - if ($exitCode -eq 0) { - Add-Content $env:GITHUB_STEP_SUMMARY "
" - } - - exit $exitCode + uses: ./.github/actions/_log_to_gh_summary_pwsh + with: + step-name: Test (${{ matrix.test_preset }}) + command: "ctest --preset ${{ matrix.test_preset }} --output-on-failure" diff --git a/CMakePresets.json b/CMakePresets.json index 772f9858..2cb24984 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -54,6 +54,7 @@ "generator": "Visual Studio 17 2022", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_GENERATOR_PLATFORM": "x64", "VCPKG_TARGET_TRIPLET": "x64-windows" }, "condition": { @@ -69,6 +70,7 @@ "generator": "Visual Studio 17 2022", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", + "CMAKE_GENERATOR_PLATFORM": "x64", "VCPKG_TARGET_TRIPLET": "x64-windows" }, "condition": { @@ -76,6 +78,22 @@ "lhs": "${hostSystemName}", "rhs": "Windows" } + }, + { + "name": "msvc_release_arm64", + "inherits": "default", + "description": "Release build using MSVC for Windows ARM64", + "generator": "Visual Studio 17 2022", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_GENERATOR_PLATFORM": "ARM64", + "VCPKG_TARGET_TRIPLET": "arm64-windows" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } } ], "buildPresets": [ @@ -116,6 +134,16 @@ "lhs": "${hostSystemName}", "rhs": "Windows" } + }, + { + "name": "msvc_release_arm64", + "configurePreset": "msvc_release_arm64", + "configuration": "Release", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } } ], "testPresets": [ @@ -248,12 +276,30 @@ "configurePreset": "msvc_release", "configuration": "Release" }, + { + "name": "quick-validation-msvc-release-arm64", + "inherits": ["tier-quick", "windows"], + "configurePreset": "msvc_release_arm64", + "configuration": "Release" + }, + { + "name": "intermediate-validation-msvc-release-arm64", + "inherits": ["tier-intermediate", "windows"], + "configurePreset": "msvc_release_arm64", + "configuration": "Release" + }, { "name": "intermediate-validation-msvc-release", "inherits": ["tier-intermediate", "windows"], "configurePreset": "msvc_release", "configuration": "Release" }, + { + "name": "deep-validation-msvc-release-arm64", + "inherits": ["tier-deep", "windows"], + "configurePreset": "msvc_release_arm64", + "configuration": "Release" + }, { "name": "deep-validation-msvc-release", "inherits": ["tier-deep", "windows"], @@ -265,6 +311,12 @@ "inherits": ["tier-all", "windows"], "configurePreset": "msvc_release", "configuration": "Release" + }, + { + "name": "full-suite-msvc-release-arm64", + "inherits": ["tier-all", "windows"], + "configurePreset": "msvc_release_arm64", + "configuration": "Release" } ], "packagePresets": [ @@ -275,6 +327,10 @@ { "name": "msvc_release", "configurePreset": "msvc_release" + }, + { + "name": "msvc_release_arm64", + "configurePreset": "msvc_release_arm64" } ] -} \ No newline at end of file +} diff --git a/cmake/coverage/define_coverage_targets.cmake b/cmake/coverage/define_coverage_targets.cmake index fef95881..9c97904b 100644 --- a/cmake/coverage/define_coverage_targets.cmake +++ b/cmake/coverage/define_coverage_targets.cmake @@ -11,141 +11,227 @@ # - https://clang.llvm.org/docs/SourceBasedCodeCoverage.html # - https://llvm.org/docs/CommandGuide/llvm-cov.html -if (ENABLE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") - message(STATUS "Enabling LLVM coverage tools") +function(_find_preferred_llvm_tool OUT_PATH_VAR OUT_NAME_VAR TOOL_BASENAME) + # Reads the active compiler's major version from CMAKE_CXX_COMPILER_VERSION. + # Look in the compiler's own directory first to locate a matching llvm tool. + # Fallback to the unversioned base tool name in the PATH if the binary was not found in compiler's directory. - message(STATUS "Coverage Build: Downgrading CX macros to runtime for tests instrumentation.") - add_compile_definitions(COVERAGE_BUILD) # see include/bitbishop/config.hpp + set(_search_hints "") - find_program(LLVM_PROFDATA llvm-profdata REQUIRED) - find_program(LLVM_COV llvm-cov REQUIRED) + if (CMAKE_CXX_COMPILER) + get_filename_component(_compiler_bin_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + list(APPEND _search_hints "${_compiler_bin_dir}") + endif () - add_compile_options( - -fprofile-instr-generate - -fcoverage-mapping - ) - add_link_options( - -fprofile-instr-generate - ) + set(_candidate_names "${TOOL_BASENAME}") - # Coverage tier selection - # Must match the tier used by the CTest preset - set(CTEST_PRESET "quick-validation-clang-debug" - CACHE STRING "Coverage tier (quick-validation-clang-debug, intermediate-validation-clang-debug, deep-validation-clang-debug, full-suite-clang-debug)") - - set(COVERAGE_BASE_DIR "${CMAKE_BINARY_DIR}/coverage") - set(COVERAGE_DIR "${COVERAGE_BASE_DIR}/${CTEST_PRESET}") - set(PROFDATA_FILE "${COVERAGE_DIR}/coverage.profdata") - set(TESTS_BIN_DIR "${CMAKE_BINARY_DIR}/tests") - - # Internal target to run tests for the specified ctest preset - # This target is then used as a dependency for subsequent custom targets - add_custom_target(_coverage-run-tests - COMMAND ${CMAKE_CTEST_COMMAND} --preset ${CTEST_PRESET} --output-on-failure - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMENT "Running tests for preset ${CTEST_PRESET}" - USES_TERMINAL - ) + if (CMAKE_CXX_COMPILER_VERSION) + string(REGEX MATCH "^[0-9]+" _compiler_major_version "${CMAKE_CXX_COMPILER_VERSION}") + if (_compiler_major_version) + list(INSERT _candidate_names 0 "${TOOL_BASENAME}-${_compiler_major_version}") + endif () + endif () - # Internal target to merge raw coverage profiles - # Triggers automatically the tests before running - # Creates the coverage directory if non-existing (indempotent) - add_custom_target(_coverage-merge - COMMAND ${CMAKE_COMMAND} -E make_directory "${COVERAGE_DIR}" - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DLLVM_PROFDATA=${LLVM_PROFDATA}" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_merge.cmake" - BYPRODUCTS "${PROFDATA_FILE}" - COMMENT "Merging LLVM coverage profiles for ${CTEST_PRESET}" - VERBATIM - ) - add_dependencies(_coverage-merge _coverage-run-tests) - - # Public target allowing to generate html (css, js) coverage report - # Triggers automatically the tests and coverage merge before running - add_custom_target(coverage-html - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" - "-DCTEST_PRESET=${CTEST_PRESET}" - "-DLLVM_COV=${LLVM_COV}" - "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DREPORT_MODE=html" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" - COMMENT "Generating LLVM HTML coverage report (${CTEST_PRESET})" - VERBATIM - ) - add_dependencies(coverage-html _coverage-merge) - - # Public target allowing to generate stdout console coverage report - # Triggers automatically the tests and coverage merge before running - add_custom_target(coverage-summary - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" - "-DCTEST_PRESET=${CTEST_PRESET}" - "-DLLVM_COV=${LLVM_COV}" - "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DREPORT_MODE=console" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" - COMMENT "Generating LLVM coverage summary (${CTEST_PRESET})" - VERBATIM - ) - add_dependencies(coverage-summary _coverage-merge) - - # Public target allowing to generate json coverage report - # Triggers automatically the tests and coverage merge before running - add_custom_target(coverage-json - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" - "-DCTEST_PRESET=${CTEST_PRESET}" - "-DLLVM_COV=${LLVM_COV}" - "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DREPORT_MODE=json" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" - COMMENT "Exporting LLVM coverage as JSON (${CTEST_PRESET})" - VERBATIM - ) - add_dependencies(coverage-json _coverage-merge) - - # Public target allowing to generate markdown coverage report - # Triggers automatically the tests and coverage merge before running - add_custom_target(coverage-markdown - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" - "-DCTEST_PRESET=${CTEST_PRESET}" - "-DLLVM_COV=${LLVM_COV}" - "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DREPORT_MODE=markdown" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" - COMMENT "Generating GitHub Actions coverage summary (${CTEST_PRESET})" - VERBATIM - ) - add_dependencies(coverage-markdown _coverage-merge) - - # Public target allowing to generate a shields.io coverage badge - # Triggers automatically the tests and coverage merge before running - add_custom_target(coverage-shieldsio - COMMAND ${CMAKE_COMMAND} - "-DCOVERAGE_DIR=${COVERAGE_DIR}" - "-DPROFDATA_FILE=${PROFDATA_FILE}" - "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" - "-DCTEST_PRESET=${CTEST_PRESET}" - "-DLLVM_COV=${LLVM_COV}" - "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DREPORT_MODE=shieldsio" - -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" - COMMENT "Generating Shields.io coverage badge (${CTEST_PRESET})" - VERBATIM + # Build a "safe" dynamic cache filename for storing the tool's location in a CMake cache variable. + string(REGEX REPLACE "[^A-Za-z0-9_]" "_" _tool_cache_suffix "${TOOL_BASENAME}") + set(_tool_cache_var "_llvm_tool_${_tool_cache_suffix}") + unset(${_tool_cache_var} CACHE) + unset(${_tool_cache_var}) + + find_program(${_tool_cache_var} + NAMES ${_candidate_names} + HINTS ${_search_hints} ) - add_dependencies(coverage-shieldsio _coverage-merge) -endif() + set(_selected_tool "${${_tool_cache_var}}") + if (_selected_tool) + get_filename_component(_selected_tool_name "${_selected_tool}" NAME) + set(${OUT_PATH_VAR} "${_selected_tool}" PARENT_SCOPE) + set(${OUT_NAME_VAR} "${_selected_tool_name}" PARENT_SCOPE) + else () + set(${OUT_PATH_VAR} "" PARENT_SCOPE) + set(${OUT_NAME_VAR} "" PARENT_SCOPE) + endif () +endfunction() + + +if (ENABLE_COVERAGE) + set(_CAN_ENABLE_COVERAGE TRUE) + + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(WARNING "Coverage requested, but compiler is ${CMAKE_CXX_COMPILER_ID}. LLVM coverage requires Clang.") + set(_CAN_ENABLE_COVERAGE FALSE) + endif() + + if (_CAN_ENABLE_COVERAGE) + if (CMAKE_CXX_COMPILER_VERSION) + string(REGEX MATCH "^[0-9]+" LLVM_TOOLCHAIN_MAJOR_VERSION "${CMAKE_CXX_COMPILER_VERSION}") + else () + set(LLVM_TOOLCHAIN_MAJOR_VERSION "") + endif () + + _find_preferred_llvm_tool(LLVM_PROFDATA LLVM_PROFDATA_NAME llvm-profdata) + _find_preferred_llvm_tool(LLVM_COV LLVM_COV_NAME llvm-cov) + + if (NOT LLVM_PROFDATA) + message(WARNING "Required tool 'llvm-profdata' not found in PATH.") + set(_CAN_ENABLE_COVERAGE FALSE) + else () + if (LLVM_TOOLCHAIN_MAJOR_VERSION AND LLVM_PROFDATA_NAME STREQUAL "llvm-profdata-${LLVM_TOOLCHAIN_MAJOR_VERSION}") + message(STATUS "Using compiler-matched llvm-profdata tool: ${LLVM_PROFDATA_NAME} (${LLVM_PROFDATA})") + else () + message(STATUS "Using fallback llvm-profdata tool: ${LLVM_PROFDATA_NAME} (${LLVM_PROFDATA})") + endif () + endif() + + if (NOT LLVM_COV) + message(WARNING "Required tool 'llvm-cov' not found in PATH.") + set(_CAN_ENABLE_COVERAGE FALSE) + else () + if (LLVM_TOOLCHAIN_MAJOR_VERSION AND LLVM_COV_NAME STREQUAL "llvm-cov-${LLVM_TOOLCHAIN_MAJOR_VERSION}") + message(STATUS "Using compiler-matched llvm-cov tool: ${LLVM_COV_NAME} (${LLVM_COV})") + else () + message(STATUS "Using fallback llvm-cov tool: ${LLVM_COV_NAME} (${LLVM_COV})") + endif () + endif() + endif() + + if (NOT _CAN_ENABLE_COVERAGE) + # Force the option to OFF in the cache so subsequent runs/scripts know it failed + set(ENABLE_COVERAGE OFF CACHE BOOL "Enable LLVM coverage tools" FORCE) + message(STATUS "LLVM coverage setup failed. Coverage targets will not be created.") + else() + message(STATUS "Enabling LLVM coverage tools") + message(STATUS "Coverage Build: Downgrading CX macros to runtime for tests instrumentation.") + + add_compile_definitions(COVERAGE_BUILD) # see include/bitbishop/config.hpp + + add_compile_options( + -fprofile-instr-generate + -fcoverage-mapping + ) + add_link_options( + -fprofile-instr-generate + ) + + # Coverage tier selection + # Must match the tier used by the CTest preset + set(CTEST_PRESET "quick-validation-clang-debug" + CACHE STRING "Coverage tier (quick-validation-clang-debug, intermediate-validation-clang-debug, deep-validation-clang-debug, full-suite-clang-debug)") + + set(COVERAGE_BASE_DIR "${CMAKE_BINARY_DIR}/coverage") + set(COVERAGE_DIR "${COVERAGE_BASE_DIR}/${CTEST_PRESET}") + set(PROFDATA_FILE "${COVERAGE_DIR}/coverage.profdata") + set(TESTS_BIN_DIR "${CMAKE_BINARY_DIR}/tests") + + # Internal target to run tests for the specified ctest preset + # This target is then used as a dependency for subsequent custom targets + add_custom_target(_coverage-run-tests + COMMAND ${CMAKE_CTEST_COMMAND} --preset ${CTEST_PRESET} --output-on-failure + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Running tests for preset ${CTEST_PRESET}" + USES_TERMINAL + ) + + # Internal target to merge raw coverage profiles + # Triggers automatically the tests before running + # Creates the coverage directory if non-existing (indempotent) + add_custom_target(_coverage-merge + COMMAND ${CMAKE_COMMAND} -E make_directory "${COVERAGE_DIR}" + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DLLVM_PROFDATA=${LLVM_PROFDATA}" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_merge.cmake" + BYPRODUCTS "${PROFDATA_FILE}" + COMMENT "Merging LLVM coverage profiles for ${CTEST_PRESET}" + VERBATIM + ) + add_dependencies(_coverage-merge _coverage-run-tests) + + # Public target allowing to generate html (css, js) coverage report + # Triggers automatically the tests and coverage merge before running + add_custom_target(coverage-html + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" + "-DCTEST_PRESET=${CTEST_PRESET}" + "-DLLVM_COV=${LLVM_COV}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DREPORT_MODE=html" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" + COMMENT "Generating LLVM HTML coverage report (${CTEST_PRESET})" + VERBATIM + ) + add_dependencies(coverage-html _coverage-merge) + + # Public target allowing to generate stdout console coverage report + # Triggers automatically the tests and coverage merge before running + add_custom_target(coverage-summary + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" + "-DCTEST_PRESET=${CTEST_PRESET}" + "-DLLVM_COV=${LLVM_COV}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DREPORT_MODE=console" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" + COMMENT "Generating LLVM coverage summary (${CTEST_PRESET})" + VERBATIM + ) + add_dependencies(coverage-summary _coverage-merge) + + # Public target allowing to generate json coverage report + # Triggers automatically the tests and coverage merge before running + add_custom_target(coverage-json + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" + "-DCTEST_PRESET=${CTEST_PRESET}" + "-DLLVM_COV=${LLVM_COV}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DREPORT_MODE=json" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" + COMMENT "Exporting LLVM coverage as JSON (${CTEST_PRESET})" + VERBATIM + ) + add_dependencies(coverage-json _coverage-merge) + + # Public target allowing to generate markdown coverage report + # Triggers automatically the tests and coverage merge before running + add_custom_target(coverage-markdown + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" + "-DCTEST_PRESET=${CTEST_PRESET}" + "-DLLVM_COV=${LLVM_COV}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DREPORT_MODE=markdown" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" + COMMENT "Generating GitHub Actions coverage summary (${CTEST_PRESET})" + VERBATIM + ) + add_dependencies(coverage-markdown _coverage-merge) + + # Public target allowing to generate a shields.io coverage badge + # Triggers automatically the tests and coverage merge before running + add_custom_target(coverage-shieldsio + COMMAND ${CMAKE_COMMAND} + "-DCOVERAGE_DIR=${COVERAGE_DIR}" + "-DPROFDATA_FILE=${PROFDATA_FILE}" + "-DTESTS_BIN_DIR=${TESTS_BIN_DIR}" + "-DCTEST_PRESET=${CTEST_PRESET}" + "-DLLVM_COV=${LLVM_COV}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DREPORT_MODE=shieldsio" + -P "${CMAKE_SOURCE_DIR}/cmake/coverage/target_coverage_report.cmake" + COMMENT "Generating Shields.io coverage badge (${CTEST_PRESET})" + VERBATIM + ) + add_dependencies(coverage-shieldsio _coverage-merge) + endif () +endif () diff --git a/docs/cmake.md b/docs/cmake.md index 1d06f597..caf258f3 100644 --- a/docs/cmake.md +++ b/docs/cmake.md @@ -49,6 +49,7 @@ build/ โ”œโ”€โ”€ clang_release/ # Artifacts for Clang Release โ”œโ”€โ”€ msvc_debug/ # Artifacts for MSVC Debug โ”œโ”€โ”€ msvc_release/ # Artifacts for MSVC Release + โ”œโ”€โ”€ msvc_release_arm64/ # Artifacts for MSVC Release on Windows ARM64 โ””โ”€โ”€ install/ # Staged install artifacts ``` @@ -114,6 +115,7 @@ These presets handle compiler selection, generator choice and toolchain injectio | **`clang_release`** | Unix | Ninja | **Production** Optimized build. | | **`msvc_debug`** | Windows | VS 2022 | **Dev Mode** Uses `x64-windows` triplet. | | **`msvc_release`** | Windows | VS 2022 | **Production** Uses `x64-windows` triplet. | +| **`msvc_release_arm64`** | Windows | VS 2022 | **Production** Uses `arm64-windows` triplet. | ### ๐Ÿ‘ท Build Presets @@ -123,6 +125,7 @@ These presets handle compiler selection, generator choice and toolchain injectio | **`clang_release`** | Unix | Ninja | **Production** | | **`msvc_debug`** | Windows | VS 2022 | **Dev Mode** | | **`msvc_release`** | Windows | VS 2022 | **Production** | +| **`msvc_release_arm64`** | Windows | VS 2022 | **Production** | ### ๐Ÿงช Test Presets Reference @@ -167,8 +170,12 @@ Test presets follow this pattern: | `intermediate-validation-msvc-debug` | Deeper checks on debug build. | | `full-suite-msvc-debug` | Run everything on debug. | | `quick-validation-msvc-release` | Fast checks on optimized build. | +| `quick-validation-msvc-release-arm64` | Fast checks on optimized Windows ARM64 build. | | `intermediate-validation-msvc-release` | **Pre-Push.** Standard checks on release build. | +| `intermediate-validation-msvc-release-arm64` | Standard checks on optimized Windows ARM64 build. | | `deep-validation-msvc-release` | **CI.** Exhaustive checks on release build. | +| `deep-validation-msvc-release-arm64` | Exhaustive checks on optimized Windows ARM64 build. | +| `full-suite-msvc-release-arm64` | Run everything on optimized Windows ARM64 build. | ## ๐Ÿ— Developer Guide: How Tests are Discovered