build: add lint, CI, and build tooling - #1
Conversation
- .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>
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>
- 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>
1059c3c to
70a85b1
Compare
| - name: Build | ||
| run: cmake --build build-tsan | ||
| - name: Test | ||
| run: ctest --test-dir build-tsan --output-on-failure |
There was a problem hiding this comment.
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).
| - name: clang-tidy | ||
| run: | | ||
| cmake -B build-lint | ||
| git ls-files '*.cpp' | |
There was a problem hiding this comment.
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/).
| env: | ||
| # Pinned so a runner-image rollover can't change the formatting | ||
| # contract under us; bump deliberately. | ||
| CLANG_VERSION: 18 |
There was a problem hiding this comment.
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.
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
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.
| cancel-in-progress: true | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} |
| @@ -0,0 +1,4 @@ | |||
| build*/ | |||
There was a problem hiding this comment.
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.
| build*/ | |
| /build*/ |
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: clang-format | ||
| run: git ls-files '*.hpp' '*.ipp' '*.cpp' | xargs -r "clang-format-$CLANG_VERSION" --dry-run --Werror |
There was a problem hiding this comment.
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.
| 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 |
| target_include_directories(cq INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) | ||
| target_compile_features(cq INTERFACE cxx_std_20) | ||
|
|
||
| add_library(cq_warnings INTERFACE) |
There was a problem hiding this comment.
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.
| // 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 |
There was a problem hiding this comment.
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.
| googletest | ||
| URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz | ||
| URL_HASH SHA256=7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926 | ||
| DOWNLOAD_EXTRACT_TIMESTAMP TRUE) |
There was a problem hiding this comment.
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.
| DOWNLOAD_EXTRACT_TIMESTAMP TRUE) | |
| DOWNLOAD_EXTRACT_TIMESTAMP FALSE) |
| git ls-files '*.cpp' | | ||
| xargs -r -P "$CMAKE_BUILD_PARALLEL_LEVEL" -n1 "clang-tidy-$CLANG_VERSION" -p build-lint |
There was a problem hiding this comment.
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:
| 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>
ReviewVerdict: solid. Reviewed all 7 files. One real defect found and fixed in 6420b49; everything else checks out. Fixed
Verified, no action
For #2 (not this PR)
🤖 Posted by Claude Code on request |
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