Raise the cognitive-complexity threshold to 30 and clear the rest #1078
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: Test | |
| # Branches only — release tags are handled by release.yml. Running this on tag | |
| # pushes fired the full matrix needlessly and broke `trunk check`, which diffs | |
| # against `github.event.before` (the prior tag object, gone after a tag move). | |
| on: | |
| push: | |
| branches: ["**"] | |
| permissions: read-all | |
| jobs: | |
| trunk: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Trunk Check | |
| uses: trunk-io/trunk-action@v1 | |
| - name: Trunk must not own git hooks | |
| run: | | |
| # Git hooks belong to pre-commit (see .pre-commit-config.yaml); trunk's | |
| # git-hook actions are disabled in .trunk/trunk.yaml so trunk never | |
| # clobbers pre-commit's clang-format run. This can only be checked here: | |
| # a pre-commit hook can't (if trunk owned core.hooksPath, pre-commit | |
| # would never run), and a trunk linter can't (it runs in an isolated | |
| # sandbox blind to the real git config). | |
| # 1) Config invariant: no trunk git-hook action may be enabled. | |
| enabled="$(yq -r '.actions.enabled // [] | .[]' .trunk/trunk.yaml)" | |
| for action in trunk-announce trunk-check-pre-commit trunk-check-pre-push \ | |
| trunk-check-pre-push-always trunk-fmt-pre-commit trufflehog-pre-commit; do | |
| if grep -qxF "${action}" <<<"${enabled}"; then | |
| echo "::error::trunk git-hook action '${action}' is enabled in .trunk/trunk.yaml; disable it so pre-commit keeps ownership of git hooks" | |
| exit 1 | |
| fi | |
| done | |
| # 2) Runtime truth (when trunk is available): sync hooks, then require | |
| # core.hooksPath not point back into trunk's cache. | |
| if command -v trunk >/dev/null 2>&1; then | |
| trunk git-hooks sync 2>&1 || true | |
| hooks_path="$(git config --get core.hooksPath || true)" | |
| if [[ "${hooks_path}" == *trunk* ]]; then | |
| echo "::error::core.hooksPath is trunk-managed (${hooks_path}); trunk grabbed the git hooks" | |
| exit 1 | |
| fi | |
| fi | |
| echo "OK: pre-commit owns git hooks; trunk is not managing them" | |
| pre-commit: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - uses: pre-commit/action@v3.0.1 | |
| env: | |
| # The `trunk` job runs the full `trunk check`; do not also run trunk | |
| # from pre-commit in CI (see the trunk-fmt hook). Likewise the | |
| # `clang-tidy` job owns clang-tidy: it needs a compile_commands.json | |
| # this job deliberately does not build. `clang-tidy` is `stages: | |
| # [manual]` today, so it is already absent here; naming it keeps that | |
| # true when the hook is promoted to an automatic gate. | |
| SKIP: trunk-fmt,clang-tidy | |
| - uses: pre-commit-ci/lite-action@v1.0.2 | |
| if: always() | |
| clang-tidy: | |
| needs: [trunk, pre-commit] | |
| runs-on: ubuntu-latest | |
| # Note there is deliberately no job-level `continue-on-error`: building the | |
| # compile DB must gate, so a broken extractor/toolchain fails this job. Only | |
| # the findings themselves are tolerated, at the step below. | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # Full history so the branch can be diffed against `main` to pick the | |
| # changed sources below. | |
| fetch-depth: 0 | |
| - uses: bazelbuild/setup-bazelisk@v3 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Select the scope to lint | |
| # clang-tidy costs ~50s per translation unit here, so linting all 81 | |
| # sources takes ~24 minutes even though pre-commit already spreads them | |
| # over every core. On a branch, lint only what the branch changed; `main` | |
| # still gets the whole tree after merge. | |
| # Known gap: editing only a header changes findings in the sources that | |
| # include it, and none of those are in the branch's changed-file set. The | |
| # post-merge run on `main` is what catches that. | |
| run: | | |
| set -euo pipefail | |
| if [ "${GITHUB_REF}" = "refs/heads/main" ]; then | |
| echo "CLANG_TIDY_SCOPE=--all-files" >>"${GITHUB_ENV}" | |
| echo "Linting the whole tree (on main)." | |
| else | |
| git fetch --no-tags --quiet origin main | |
| echo "CLANG_TIDY_SCOPE=--from-ref origin/main --to-ref HEAD" >>"${GITHUB_ENV}" | |
| echo "Linting sources changed against origin/main:" | |
| git diff --name-only origin/main...HEAD -- '*.cc' '*.cpp' '*.cxx' | sed 's/^/ /' | |
| fi | |
| - uses: actions/cache/restore@v5 | |
| id: cache_restore | |
| with: | |
| path: "~/.cache/bazel" | |
| key: clang-tidy-${{github.ref}}-${{github.sha}} | |
| restore-keys: | | |
| clang-tidy-${{github.ref}} | |
| clang-tidy-refs/heads/main | |
| clang-tidy | |
| - name: Build generated sources and headers | |
| # Generated headers must exist on disk before the compile DB is | |
| # extracted, or the extractor cannot resolve the sources that include | |
| # them (`hash_mangle_seed_gen.h is missing`) and those translation units | |
| # land in the DB degraded - which would later read as clang-tidy | |
| # findings rather than as the build artifact gap it really is. | |
| # Generated headers are the only build output a translation unit needs to | |
| # parse, so this builds just those rather than all of `//...`; the query | |
| # keeps it correct as generated files are added. | |
| run: | | |
| set -euo pipefail | |
| # Read into an array without `mapfile`, so this snippet can also be run | |
| # as-is on stock macOS bash 3.2 when reproducing a CI result locally. | |
| GENERATED=() | |
| while IFS= read -r target; do | |
| GENERATED+=("${target}") | |
| done < <(bazel query 'kind("generated file", //...:*)' | | |
| grep -E '\.(h|hh|hpp|inc|ipp|cc|cpp|cxx)$') | |
| if [ "${#GENERATED[@]}" -eq 0 ]; then | |
| echo "::error::No generated sources/headers found. Either the query or the extension filter needs updating; without this the compile DB silently degrades." | |
| exit 1 | |
| fi | |
| printf 'Building %s generated file(s):\n' "${#GENERATED[@]}" | |
| printf ' %s\n' "${GENERATED[@]}" | |
| bazel build --config=clang "${GENERATED[@]}" | |
| - name: Generate compile_commands.json | |
| # The compile DB is a local artifact (gitignored), so it has to be built | |
| # here. The script fetches the hermetic LLVM toolchain if needed and | |
| # records ITS clang as the compiler, which is what makes the resulting | |
| # commands parseable by the matching hermetic clang-tidy. | |
| run: ./compile_commands-update.sh | |
| - uses: actions/upload-artifact@v4 | |
| # Only from `main`, which is the run that lints the whole tree: that DB | |
| # is the one worth keeping, to reproduce a finding locally against the | |
| # exact same commands and to inspect flag/toolchain drift. A branch run | |
| # lints only what the branch changed, so its DB describes a near-empty | |
| # lint and is not worth storing. | |
| id: upload_cdb | |
| if: github.ref == 'refs/heads/main' | |
| with: | |
| name: compile-commands-json | |
| path: compile_commands.json | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Summarize the compile DB | |
| # Renders at the top of the run page, so the DB's shape and (on main) a | |
| # direct link to it are visible without digging through step logs or | |
| # scrolling to the artifacts panel. | |
| run: | | |
| set -euo pipefail | |
| STATS="$(python3 -c ' | |
| import collections, json | |
| entries = json.load(open("compile_commands.json")) | |
| files = collections.Counter(e["file"] for e in entries) | |
| print(len(entries), len(files), sum(n - 1 for n in files.values() if n > 1)) | |
| ')" | |
| read -r ENTRIES UNIQUE DUPES <<<"${STATS}" | |
| { | |
| echo "### clang-tidy compile database" | |
| echo | |
| echo "| | |" | |
| echo "|---|---|" | |
| echo "| Entries | ${ENTRIES} |" | |
| echo "| Unique files | ${UNIQUE} |" | |
| echo "| Duplicate entries | ${DUPES} |" | |
| echo "| Lint scope | \`${CLANG_TIDY_SCOPE}\` |" | |
| if [ -n "${CDB_ARTIFACT_URL}" ]; then | |
| echo "| Artifact | [compile-commands-json](${CDB_ARTIFACT_URL}) |" | |
| else | |
| echo "| Artifact | not uploaded (branch run; only \`main\` publishes one) |" | |
| fi | |
| } >>"${GITHUB_STEP_SUMMARY}" | |
| env: | |
| CDB_ARTIFACT_URL: ${{steps.upload_cdb.outputs.artifact-url}} | |
| - uses: pre-commit/action@v3.0.1 | |
| # TODO(helly25): drop this once the finding sweep lands, which is what | |
| # turns this job into a real gate. `.clang-tidy` sets | |
| # `WarningsAsErrors: '*'` and the tree is not clean yet, so findings are | |
| # reported here rather than enforced. Scoped to this step so that a | |
| # failure to produce the compile DB above still fails the job. | |
| continue-on-error: true | |
| with: | |
| # Only the clang-tidy hook: every other hook already ran in the | |
| # `pre-commit` job. `--hook-stage manual` is required while the hook | |
| # is opt-in; it stays harmless once it becomes an automatic gate. | |
| # Scope comes from the step above: changed sources on a branch, the | |
| # whole tree on main. | |
| extra_args: clang-tidy --hook-stage manual ${{env.CLANG_TIDY_SCOPE}} | |
| - uses: actions/cache/save@v5 | |
| if: always() && steps.cache_restore.outputs.cache-hit != 'true' | |
| with: | |
| path: "~/.cache/bazel" | |
| key: clang-tidy-${{github.ref}}-${{github.sha}} | |
| test-gcc: | |
| needs: [trunk, pre-commit] | |
| secrets: inherit | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| compiler: [gcc] | |
| # gcc 14 gets the full config set (asan/cpp23/opt) here, early. gcc 13 is | |
| # still built (opt) later by test-bcr's ubuntu+gcc rungs, so it need not | |
| # repeat in this job. | |
| gcc_version: [14] | |
| bazel_config: [asan, cpp23, opt] | |
| uses: ./.github/workflows/test.yml | |
| with: | |
| continue-on-error: false | |
| os: ${{ matrix.os }} | |
| compiler: ${{ matrix.compiler }} | |
| gcc_version: ${{ matrix.gcc_version }} | |
| bazel_config: ${{ matrix.bazel_config }} | |
| test-bcr: | |
| needs: [trunk, pre-commit, test-gcc] | |
| secrets: inherit | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-26] | |
| compiler: [gcc, native, clang] | |
| gcc_version: [13] | |
| # The pinned default toolchain (see bazelmod/llvm.MODULE.bazel). | |
| llvm_version: [22.1.8] | |
| bazel_config: [opt] | |
| # Bazel-version compatibility rungs: one per supported major (we test | |
| # the last three, 7/8/9), crossed with the os/compiler combos below. | |
| # 7.2.1 is the earliest 7.x that works: MODULE.bazel uses `include()` | |
| # (added in 7.2.0, so 7.1.x fails with "name 'include' is not defined") | |
| # and the dep `depend_on_what_you_use@0.16.0` declares | |
| # `bazel_compatibility: [>=7.2.1]` (so 7.2.0 is rejected too). 8.7.0 is | |
| # the latest 8.x; 9.1.1 is the latest 9.x and the checked-in default. | |
| bazel_version: [7.2.1, 8.7.0, 9.1.1] | |
| exclude: | |
| - os: ubuntu-latest | |
| compiler: native | |
| - os: macos-26 | |
| compiler: gcc | |
| # The 7.x/8.x rungs are a build-system-compat check (MODULE.bazel | |
| # loading, rules resolution) - platform-agnostic, so run them on | |
| # ubuntu only. macOS keeps the default 9.1.1 for platform coverage. | |
| - os: macos-26 | |
| bazel_version: 7.2.1 | |
| - os: macos-26 | |
| bazel_version: 8.7.0 | |
| uses: ./.github/workflows/test.yml | |
| with: | |
| continue-on-error: false | |
| os: ${{ matrix.os }} | |
| compiler: ${{ matrix.compiler }} | |
| gcc_version: ${{ matrix.gcc_version }} | |
| llvm_version: ${{ matrix.llvm_version }} | |
| bazel_config: ${{ matrix.bazel_config }} | |
| bazel_version: ${{ matrix.bazel_version }} | |
| test-clang: | |
| needs: [trunk, pre-commit, test-gcc, test-bcr] | |
| secrets: inherit | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-26] | |
| compiler: [clang] | |
| # Hermetic toolchains_llvm clang (independent of the runner's Apple clang). | |
| # Ladder: oldest supported (20.1.8) and the pinned default, also newest (22.1.8). | |
| # TODO(llvm-23): add 23.x here once released and listed in toolchains_llvm. | |
| llvm_version: [20.1.8, 22.1.8] | |
| bazel_config: [asan, cpp23, opt] | |
| exclude: | |
| # asan runs only on the newest toolchain (both platforms); test-gcc | |
| # already exercises asan early (gcc 14). macOS asan must be 22.1.8 | |
| # regardless - 20.1.8 hangs in compiler-rt FindDynamicShadowStart on | |
| # macOS 26, while 22.1.8's sanitizer dylib uses toolchains_llvm's | |
| # @loader_path rpath fix (helly25 fork; upstream PR #767) - so | |
| # consolidating asan there also drops the redundant clang-20 asan. | |
| - llvm_version: 20.1.8 | |
| bazel_config: asan | |
| # C++23 is used on recent compilers, so exercise cpp23 on the newest | |
| # clang only; the oldest rung just proves the minimum supported LLVM | |
| # still builds (opt). | |
| - llvm_version: 20.1.8 | |
| bazel_config: cpp23 | |
| uses: ./.github/workflows/test.yml | |
| with: | |
| continue-on-error: false | |
| os: ${{ matrix.os }} | |
| compiler: ${{ matrix.compiler }} | |
| llvm_version: ${{ matrix.llvm_version }} | |
| bazel_config: ${{ matrix.bazel_config }} | |
| done: | |
| needs: [trunk, pre-commit, clang-tidy, test-gcc, test-clang, test-bcr] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Ensure every job is wired into this gate | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| # Parse the workflow with a real YAML reader (yq) and fail if any | |
| # declared job (other than this gate) is absent from `needs` above, so | |
| # a newly added job cannot silently escape the required gate. | |
| declared="$(yq '.jobs | keys | .[]' .github/workflows/main.yml | | |
| grep -vx done | sort)" | |
| wired="$(jq -r 'keys[]' <<<"${NEEDS_JSON}" | sort)" | |
| missing="$(comm -23 <(printf '%s\n' "${declared}") <(printf '%s\n' "${wired}"))" | |
| if [[ -n "${missing}" ]]; then | |
| echo "Jobs declared in the workflow but missing from done.needs:" | |
| while read -r job; do | |
| echo " - ${job}" | |
| echo "::error title=Gate is missing a job::${job} not in done.needs" | |
| done <<<"${missing}" | |
| exit 1 | |
| fi | |
| echo "done covers all $(grep -c . <<<"${declared}") workflow jobs." | |
| - name: Fail if any dependency did not succeed | |
| if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') | |
| run: exit 1 |