Skip to content

build: add lint, CI, and build tooling - #1

Merged
jadecubes merged 6 commits into
mainfrom
tooling/lint-ci-tests
Aug 16, 2026
Merged

build: add lint, CI, and build tooling#1
jadecubes merged 6 commits into
mainfrom
tooling/lint-ci-tests

Conversation

@jadecubes

@jadecubes jadecubes commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Tooling only: clang-format/clang-tidy configs, GitHub Actions CI (TSan test matrix, benchmark compile check, lint job), and CMake scaffolding. The build no-ops gracefully until source directories land.

The queue implementation, tests, and benchmarks follow in #2, stacked on this branch.

🤖 Generated with Claude Code

- .clang-format (Google style, 100 cols) and .clang-tidy (bugprone,
  concurrency, google, modernize, performance, readability checks with
  Google-style naming rules)
- GitHub Actions CI: TSan test matrix (ubuntu/macos), Release benchmark
  smoke run, clang-format + clang-tidy lint job
- CMake build: header-only cq INTERFACE library, warnings target
  (-Wall -Wextra -Wpedantic -Wconversion, clang -Wdocumentation for
  Doxygen tag validation), GoogleTest and Google Benchmark via
  FetchContent
- GoogleTest unit + stress tests and Google Benchmark suite for
  MutexQueue
- VS Code format-on-save settings, STYLE.md comment/layout standards,
  .gitignore

Note: tests and benchmarks reference include/cq/mutex_queue.hpp, which
lands in a separate code PR — CI stays red until that merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jadecubes jadecubes self-assigned this Aug 16, 2026
Debra and others added 2 commits August 16, 2026 16:03
Lint configs:
- Move bench-only exceptions (^_$ identifier, BM_ naming) from root
  .clang-tidy into bench/.clang-tidy so the root config only describes
  library style
- Replace tests' blanket cognitive-complexity disable with the check's
  IgnoreMacros option at root
- Drop PointerAlignment: Left (restates the Google-style default)

CMake:
- Export compile_commands.json from every build tree
- Put TSan flags on a cq_sanitizers INTERFACE target (guarded per
  compiler) instead of global add_compile_options, so googletest is no
  longer instrumented
- Flatten the benchmark condition; register the benchmark smoke run as
  a CTest test; gtest discovery moved to PRE_TEST

CI:
- Discover lint targets via git ls-files instead of hardcoded dirs
- Pin clang-format/clang-tidy to version 18; run tidy in parallel
- Bound build parallelism; cancel superseded runs; smoke-run benchmarks
  via ctest instead of a hardcoded binary path

Tests/bench:
- run_blocked() helper replaces four copies of the spawn/settle/unblock
  protocol; spawn_threads() collapses the stress test's thread teams
- Benchmark queue held in std::optional (no heap alloc), queue ref
  hoisted out of the measured loop, missing-Setup guarded with
  SkipWithError, single registration with chained thread counts

Docs: STYLE.md points at configs instead of restating their values;
drop the default-restating C_Cpp.formatting setting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move test and benchmark sources out to a follow-up PR alongside the
queue implementation they compile against. Build and lint scaffolding
now no-ops until those directories land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jadecubes jadecubes changed the title build: add lint, CI, test, and benchmark tooling build: add lint, CI, and build tooling Aug 16, 2026
Debra and others added 2 commits August 16, 2026 16:20
- one parallelism knob (CMAKE_BUILD_PARALLEL_LEVEL / CTEST_PARALLEL_LEVEL)
  instead of four getconf incantations
- lint all tracked *.cpp instead of hardcoded tests/bench globs, so tidy
  coverage tracks the build automatically
- hoist the 'auto _' placeholder carve-out to the root .clang-tidy
- state the pinned clang-format major version in STYLE.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jadecubes
jadecubes force-pushed the tooling/lint-ci-tests branch from 1059c3c to 70a85b1 Compare August 16, 2026 08:31
Comment thread .github/workflows/ci.yml Outdated
- name: Build
run: cmake --build build-tsan
- name: Test
run: ctest --test-dir build-tsan --output-on-failure

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI passes vacuously when zero tests are registered. ctest's default no-tests action is warn + exit 0 (reproduced locally: "No tests were found!!!", exit 0). On this PR the "Tests (ThreadSanitizer)" job configures zero tests (tests/ doesn't exist yet) and still goes green; after tests land, any silent registration regression — a cached -DCQ_BUILD_TESTS=OFF, a typo'd EXISTS path, a tests/ rename, gtest discovery failure — keeps CI green while running nothing. Same for the "Smoke-run benchmarks" ctest at line 45. Add --no-tests=error (in PR #2 if this check must stay green until then — but as written the safety net never arms).

Comment thread .github/workflows/ci.yml
- name: clang-tidy
run: |
cmake -B build-lint
git ls-files '*.cpp' |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy never covers the header-only product directly. git ls-files '*.cpp' selects only translation units, so the include/cq/ headers that HeaderFilterRegex names are tidied only transitively, when some .cpp happens to include them. In this PR there are zero .cpp files, so this step is a green no-op; later, a header added before (or without) a test that includes it merges with no tidy coverage at all, despite WarningsAsErrors: '*'. Consider tidying the public headers directly (e.g. a lint TU that includes every header under include/cq/).

Comment thread .github/workflows/ci.yml
env:
# Pinned so a runner-image rollover can't change the formatting
# contract under us; bump deliberately.
CLANG_VERSION: 18

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version pin isn't backed by an install step. CLANG_VERSION: 18 only selects among binaries the runner image happens to preinstall, and runs-on: ubuntu-latest is unpinned. When ubuntu-latest rolls to an image that drops clang-18 (past rollovers dropped older majors), every lint run hard-fails with clang-format-18: command not found — the exact rollover event this comment says the pin defends against. Back the pin with runs-on: ubuntu-24.04 or an explicit LLVM 18 install step.

Comment thread .github/workflows/ci.yml

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In-flight CI on main gets cancelled by the next merge. Push events on main all share group CI-refs/heads/main, so two quick merges cancel the first commit's run — its TSan result and the "last green main" bisect signal are lost.

Suggested change
cancel-in-progress: true
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

Comment thread .gitignore
@@ -0,0 +1,4 @@
build*/

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unanchored pattern ignores nested source directories. build*/ matches at any depth and any name starting with "build" (verified with git check-ignore: tests/build_matrix/, bench/builders/, even docs/building/ are all ignored). Files added under such a directory silently never appear in git status and are omitted from commits.

Suggested change
build*/
/build*/

Comment thread .github/workflows/ci.yml
steps:
- uses: actions/checkout@v4
- name: clang-format
run: git ls-files '*.hpp' '*.ipp' '*.cpp' | xargs -r "clang-format-$CLANG_VERSION" --dry-run --Werror

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git ls-files | xargs breaks on unusual filenames. Whitespace in a path word-splits into nonexistent files (spurious lint failure), git C-quotes non-ASCII paths so xargs aborts on the quotes, and a --prefixed filename is parsed as an option (no --). NUL-delimiting fixes all three; the clang-tidy pipeline at lines 61-62 has the same issue.

Suggested change
run: git ls-files '*.hpp' '*.ipp' '*.cpp' | xargs -r "clang-format-$CLANG_VERSION" --dry-run --Werror
run: git ls-files -z '*.hpp' '*.ipp' '*.cpp' | xargs -0 -r "clang-format-$CLANG_VERSION" --dry-run --Werror

Comment thread CMakeLists.txt
target_include_directories(cq INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_features(cq INTERFACE cxx_std_20)

add_library(cq_warnings INTERFACE)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cq_warnings is free-floating, so STYLE.md's enforcement claim doesn't hold. Nothing links cq_warnings here, and nothing makes future targets link it — STYLE.md's "tag hygiene is compiler-enforced" via -Wdocumentation relies on each consumer voluntarily opting in, and even then only the macOS job compiles with clang (both ubuntu jobs default to GCC, which has no -Wdocumentation). A target linking only cq::cq builds stale @param tags green on every CI job. Consider attaching the warnings to cq itself behind $<BUILD_INTERFACE:...>, or at least noting the contract's actual scope in STYLE.md.

Comment thread .vscode/settings.json
// Relayout on save via the repo's .clang-format (Prettier-style).
// Works with either the clangd extension or Microsoft's C/C++ extension.
"[cpp]": {
"editor.formatOnSave": true

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatOnSave needs editor.defaultFormatter to be reliable. With both suggested extensions installed (clangd and Microsoft C/C++ — a common combination), VS Code hits the "multiple formatters" conflict and format-on-save silently does nothing, so the checked-in setting fails at its one job and CI's --dry-run --Werror rejects the push. Pick one, e.g. "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" inside the [cpp] block.

Comment thread CMakeLists.txt
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
URL_HASH SHA256=7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOWNLOAD_EXTRACT_TIMESTAMP TRUE re-opts into the behavior CMP0135 fixed. With cmake_minimum_required(3.24) the policy is NEW, defaulting to extraction-time timestamps; TRUE restores archive timestamps, so bumping the URL to a new gtest/benchmark version can leave extracted files older than existing build stamps and skip rebuilds in incremental trees (the exact bug the policy was created for). Same at line 61. Cleanest is to drop the option from both blocks and let the policy default apply.

Suggested change
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
DOWNLOAD_EXTRACT_TIMESTAMP FALSE)

Comment thread .github/workflows/ci.yml
Comment on lines +61 to +62
git ls-files '*.cpp' |
xargs -r -P "$CMAKE_BUILD_PARALLEL_LEVEL" -n1 "clang-tidy-$CLANG_VERSION" -p build-lint

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hand-rolled pipeline re-implements run-clang-tidy-18, which ships in the same ubuntu package as clang-tidy-18 and handles per-file fan-out, parallelism, quoting, and exit aggregation itself. The xargs form also couples lint concurrency to the unrelated CMAKE_BUILD_PARALLEL_LEVEL build knob — if that env block is ever dropped in favor of cmake --build --parallel, xargs -P "" aborts with "invalid number" and breaks this job from a distance. A path regex keeps it off the FetchContent _deps TUs:

Suggested change
git ls-files '*.cpp' |
xargs -r -P "$CMAKE_BUILD_PARALLEL_LEVEL" -n1 "clang-tidy-$CLANG_VERSION" -p build-lint
"run-clang-tidy-$CLANG_VERSION" -p build-lint 'tests/|bench/'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jadecubes

Copy link
Copy Markdown
Owner Author

Review

Verdict: solid. Reviewed all 7 files. One real defect found and fixed in 6420b49; everything else checks out.

Fixed

  • Vacuous ctest passes (.github/workflows/ci.yml): both the TSan test step and the benchmark smoke-run pass with zero tests registered, since ctest exits 0 when it finds nothing. Harmless today (scaffolding is designed to no-op until sources land), but when tests//bench/ arrive in feat: add MutexQueue v1 with tests and benchmarks #2, a registration mistake — bad glob, missing gtest_discover_tests — would keep CI green while verifying nothing. Now --no-tests flips from ignore to error automatically once the directory exists.

Verified, no action

  • CMake: FetchContent pins with SHA256 hashes; TSan flags scoped to own targets only (deps stay uninstrumented, correct per pthread interception); benchmarks correctly excluded under TSan; EXISTS guards make the scaffolding-first merge safe.
  • CI: concurrency cancellation, single parallelism knob reused across cmake/ctest/xargs, clang tools pinned to 18 (breaks loudly on runner-image rollover — deliberate and documented), gcc+AppleClang matrix gives two independent compilers under TSan.
  • clang-tidy: naming config matches the documented Google-style-with-STL-methods choice; HeaderFilterRegex correctly limits diagnostics to include/cq/.
  • STYLE.md ↔ configs ↔ workflow are mutually consistent (clang 18 pin, format-on-save, -Wdocumentation enforcement on clang builds).

For #2 (not this PR)

  • Tests must land in the same PR as include/cq/mutex_queue.hpp — the --no-tests=error arming assumes that.
  • Consider -Wconversion fallout in GTest-heavy test code; keep cq_warnings linked to test targets anyway and NOLINT sparingly.

🤖 Posted by Claude Code on request

@jadecubes
jadecubes merged commit 45f3ab4 into main Aug 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants