Fix shell quoting in coverage-comment workflow #20
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Coverage | |
| # Runs clang source-based coverage on every PR (advisory only — | |
| # never gates) and on a nightly schedule. The output is a per-line | |
| # union of all platforms in the matrix, exported as the | |
| # `coverage-merged` artifact (containing both .json and .md), and | |
| # consumed by `coverage-comment.yml` (a separate workflow with the | |
| # write token) to post the report on PRs / update the tracking | |
| # issue. | |
| # | |
| # This workflow mirrors the regular ctest invocation across the CI | |
| # matrix, so the coverage it reports is exactly what every-PR CI | |
| # exercises. | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| schedule: | |
| # Nightly at 04:00 UTC; cheapest free-runner slot. | |
| - cron: '0 4 * * *' | |
| workflow_dispatch: | |
| # Default token; the build does not push, comment, or modify any | |
| # resource. The follow-on `coverage-comment.yml` is the only writer. | |
| permissions: read-all | |
| concurrency: | |
| group: coverage-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| # ============================================================================ | |
| # Linux + macOS host builds via reusable-cmake-build.yml. | |
| # ============================================================================ | |
| build: | |
| name: coverage / ${{ matrix.os }}${{ matrix.label-suffix }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| label-suffix: '' | |
| label: macos-14 | |
| # macOS runners have AppleClang preinstalled but `llvm-cov` | |
| # / `llvm-profdata` are NOT on PATH (they live behind | |
| # `xcrun`). The coverage target's find_program(LLVM_COV ...) | |
| # only looks for unversioned/-19/-15 names, so we install | |
| # llvm@19 via brew and pass the explicit binary paths. | |
| # | |
| # SDKROOT is exported in the dependencies step so brew | |
| # clang treats the Apple SDK as a system header path, | |
| # which suppresses -Wundef on Apple's _STDC_VERSION_ | |
| # check inside <sys/cdefs.h>. | |
| dependencies: | | |
| brew install --quiet ninja llvm@19 | |
| echo "SDKROOT=$(xcrun --show-sdk-path)" >> "$GITHUB_ENV" | |
| extra-cmake-flags: >- | |
| -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm@19/bin/clang++ | |
| -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm@19/bin/clang | |
| -DLLVM_COV=/opt/homebrew/opt/llvm@19/bin/llvm-cov | |
| -DLLVM_PROFDATA=/opt/homebrew/opt/llvm@19/bin/llvm-profdata | |
| self-host: false | |
| # ------------------------------------------------------------------ | |
| # Linux representative leg. Mirrors main.yml's self-host job | |
| # (SNMALLOC_MEMCPY_BOUNDS=ON + SNMALLOC_CHECK_LOADS=ON), so | |
| # the bounds-checked memcpy and load-check paths are | |
| # exercised; these are not reached by any other coverage | |
| # leg. The self-host step iterates over every shim variant | |
| # built (libsnmallocshim.so, libsnmallocshim-checks.so, | |
| # libsnmallocshim-checks-memcpy-only.so) and the export | |
| # step combines profraws from all of them. | |
| # ------------------------------------------------------------------ | |
| - os: ubuntu-24.04 | |
| label-suffix: ' / self-host shim-checks' | |
| label: linux-self-host-shim-checks | |
| dependencies: 'sudo apt install -y ninja-build clang-19 llvm-19' | |
| extra-cmake-flags: '-DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_C_COMPILER=clang-19 -DSNMALLOC_MEMCPY_BOUNDS=ON -DSNMALLOC_CHECK_LOADS=ON' | |
| self-host: true | |
| uses: ./.github/workflows/reusable-cmake-build.yml | |
| with: | |
| os: ${{ matrix.os }} | |
| # build-type is overridden to Debug by the reusable workflow | |
| # whenever coverage is true, but the input is required. | |
| build-type: Debug | |
| cmake-config: '-G Ninja' | |
| extra-cmake-flags: ${{ matrix.extra-cmake-flags }} | |
| dependencies: ${{ matrix.dependencies }} | |
| self-host: ${{ matrix.self-host }} | |
| coverage: true | |
| coverage-artifact-name: ${{ matrix.label }} | |
| # ============================================================================ | |
| # FreeBSD / NetBSD via reusable-vm-build.yml. | |
| # | |
| # llvm-profdata / llvm-cov must be installed in the VM by the | |
| # `dependencies:` script. The reusable workflow forces | |
| # copyback: true when coverage is true so coverage.json | |
| # makes it back to the host runner. | |
| # ============================================================================ | |
| build-vm: | |
| name: coverage / ${{ matrix.label }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: freebsd-14 | |
| vm-type: freebsd | |
| vm-version: '14.1' | |
| # FreeBSD's llvm19 port installs versioned binaries | |
| # (clang19, clang++19, llvm-cov19, llvm-profdata19) | |
| # directly under /usr/local/bin/ — not under a | |
| # /usr/local/llvm19/bin/ subtree. Pass absolute paths so | |
| # find_program is preset and doesn't depend on PATH. | |
| dependencies: 'pkg install -y cmake ninja llvm19' | |
| cmake-flags: >- | |
| -DCMAKE_CXX_COMPILER=/usr/local/bin/clang++19 | |
| -DCMAKE_C_COMPILER=/usr/local/bin/clang19 | |
| -DLLVM_COV=/usr/local/bin/llvm-cov19 | |
| -DLLVM_PROFDATA=/usr/local/bin/llvm-profdata19 | |
| # NetBSD intentionally omitted. pkgsrc's compiler-rt-19 | |
| # package ships a libclang_rt.profile-x86_64.a in which | |
| # __llvm_profile_raw_version is declared hidden but not | |
| # defined, so any -fprofile-instr-generate shared library | |
| # (e.g. libsnmallocshim.so) fails to link with: | |
| # R_X86_64_PC32 against undefined hidden symbol | |
| # `__llvm_profile_raw_version` can not be used when | |
| # making a shared object | |
| # Revisit when pkgsrc ships a fixed compiler-rt, or wire | |
| # up an in-VM compiler-rt build from llvm-project source. | |
| uses: ./.github/workflows/reusable-vm-build.yml | |
| with: | |
| vm-type: ${{ matrix.vm-type }} | |
| vm-version: ${{ matrix.vm-version }} | |
| build-type: Debug | |
| dependencies: ${{ matrix.dependencies }} | |
| cmake-flags: ${{ matrix.cmake-flags }} | |
| coverage: true | |
| coverage-artifact-name: ${{ matrix.label }} | |
| # ============================================================================ | |
| # Windows clang-cl coverage. Exercises the Windows PAL surface, | |
| # which no other leg in the matrix touches. | |
| # | |
| # GitHub windows-2022 runners ship LLVM (clang-cl, llvm-profdata, | |
| # llvm-cov) under C:\Program Files\LLVM\bin, with that directory | |
| # already on PATH and ninja preinstalled. We rely on PATH lookup | |
| # rather than passing -DLLVM_COV / -DLLVM_PROFDATA absolute paths, | |
| # because the install dir contains a space ("Program Files") which | |
| # YAML folded scalars + the reusable workflow's shell-expansion of | |
| # ${{ inputs.extra-cmake-flags }} cannot preserve. Quoting at any | |
| # single layer is undone by the next, leaving CMake to receive | |
| # -DLLVM_PROFDATA=C:/Program and a phantom positional argument. | |
| # ============================================================================ | |
| windows: | |
| name: coverage / windows-2022 clang-cl | |
| uses: ./.github/workflows/reusable-cmake-build.yml | |
| with: | |
| os: windows-2022 | |
| build-type: Debug | |
| cmake-config: '-G Ninja' | |
| extra-cmake-flags: '-DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl' | |
| dependencies: '' | |
| coverage: true | |
| coverage-artifact-name: windows-2022 | |
| # ============================================================================ | |
| # Merge per-line union across every leg that produced | |
| # coverage.json. | |
| # ============================================================================ | |
| merge: | |
| name: merge coverage | |
| needs: [ build, build-vm, windows ] | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download coverage artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: coverage-artifacts | |
| pattern: coverage-* | |
| merge-multiple: false | |
| - name: Inventory artifacts | |
| run: | | |
| set -euo pipefail | |
| echo "Downloaded artifacts:" | |
| find coverage-artifacts -mindepth 1 -maxdepth 1 -type d -printf ' %f\n' | |
| echo | |
| echo "JSON files:" | |
| find coverage-artifacts -name '*.json' -printf ' %p (%s bytes)\n' | |
| - name: Build merge inputs | |
| id: inputs | |
| run: | | |
| set -euo pipefail | |
| # Each artifact directory is named coverage-<label>; the | |
| # merger takes "label=path" pairs. Self-host runs that | |
| # produced selfhost.json get a separate label suffixed | |
| # "-selfhost" so the merger treats them as distinct | |
| # platforms in the per-platform breakdown. | |
| # The directory list is sorted for deterministic merge | |
| # input order (otherwise the per-platform table order in | |
| # the rendered markdown depends on filesystem readdir). | |
| inputs=() | |
| while IFS= read -r d; do | |
| label="${d##*/coverage-}" | |
| label="${label%/}" | |
| if [ -f "$d/coverage.json" ]; then | |
| inputs+=("$label=$d/coverage.json") | |
| fi | |
| if [ -f "$d/selfhost.json" ]; then | |
| inputs+=("$label-selfhost=$d/selfhost.json") | |
| fi | |
| done < <(find coverage-artifacts -mindepth 1 -maxdepth 1 -type d -name 'coverage-*' | sort) | |
| if [ ${#inputs[@]} -eq 0 ]; then | |
| echo "::error::no coverage JSON artifacts found" | |
| exit 1 | |
| fi | |
| printf 'merge inputs:\n' | |
| printf ' %s\n' "${inputs[@]}" | |
| # Hand off via env (newline-delimited). | |
| { | |
| echo 'INPUTS<<EOF' | |
| printf '%s\n' "${inputs[@]}" | |
| echo 'EOF' | |
| } >> "$GITHUB_ENV" | |
| - name: Merge per-line union | |
| run: | | |
| set -euo pipefail | |
| mapfile -t args < <(printf '%s\n' "$INPUTS") | |
| python3 .github/scripts/merge_coverage.py \ | |
| --output-json coverage-merged.json \ | |
| --output-md coverage-merged.md \ | |
| "${args[@]}" | |
| echo | |
| echo "=== merged coverage (markdown preview) ===" | |
| head -40 coverage-merged.md | |
| echo | |
| echo "=== merged coverage (JSON totals) ===" | |
| python3 -c "import json; m=json.load(open('coverage-merged.json')); print('files:', len(m['files'])); print('totals:', m['totals']); print('platforms:', list(m['platforms'].keys()))" | |
| - name: Upload merged coverage | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # Artifact name is consumed by coverage-comment.yml. If | |
| # this name changes, update coverage-comment.yml's | |
| # download-artifact step in lockstep. | |
| name: coverage-merged | |
| path: | | |
| coverage-merged.json | |
| coverage-merged.md | |
| if-no-files-found: error | |
| retention-days: 30 |