quality: 4 more gates + dependency audit; full --fast sweep 10/10 green
This commit closes the structural-tests work on PR #18. Every gate in `run-all.sh --fast` now passes end-to-end on the canonical dev machine. New gates ───────── 1. shellcheck (scripts/quality/shellcheck.sh) * Scans every `scripts/**/*.sh` at severity=warning+ * 16 scripts inspected; cleanup pass took the tree from 7 findings (SC2164 + SC2034) to 0 findings. 2. cppcheck (scripts/quality/cppcheck.sh) * Complementary static analyser to clang-tidy; different heuristics, fewer false-positives on heavy CGAL/Eigen templates. * Default severity warning+, --strict adds style, --all = everything. * Suppresses 4 noise classes (missingIncludeSystem, etc.) explicitly. 3. .editorconfig * Cross-IDE fallback for editors that don't honour clang-format. * Covers Markdown (preserve trailing whitespace), Python, YAML, JSON, shell, Makefile (tabs) — the file types clang-format doesn't cover. 4. CONFORMALLAB_WARNINGS_AS_ERRORS CMake option * Off by default → regular builds don't break on new GCC warnings. * `-DCONFORMALLAB_WARNINGS_AS_ERRORS=ON` adds `-Werror`, intended for CI promotion-track and sanitizer runs. Dependency audit (doc/architecture/dependencies.md) ──────────────────────────────────────────────────── New single-source-of-truth document listing: * what the library requires (Eigen + CGAL + Boost — all header-only) * what tests require (auto-fetched GTest, no system install) * what each quality tool is for, install command per OS, and behaviour when missing (each gate exits 2 = SKIP, run-all recognises this and continues) * a verification recipe that strips PATH down and shows the library still configures + builds + tests cleanly with zero quality tools installed. run-all.sh enhanced ─────────────────── * Recognises "tool not in PATH" → SKIP (not FAIL). * Summary now reports `passed / skipped / failed` separately. Bug fixes uncovered by the sweep ──────────────────────────────── * sanitizers.sh: gtest_discover_tests ran the ASan-instrumented binary at build time and aborted → added `-DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST` to defer discovery to ctest invocation. Now 23/23 sanitizer-instrumented tests pass. * clang-tidy.sh on macOS: brew-installed clang-tidy couldn't find Apple SDK system headers (<cmath>, <complex>, …) → added `--extra-arg=-isysroot $(xcrun --show-sdk-path)` on Darwin. * clang-tidy.sh: needed `-DWITH_CGAL_TESTS=ON` in compile_commands generation so CGAL include paths are part of at least one compile entry. Now resolves CGAL/Surface_mesh.h etc. * clang-tidy.sh: viewer-only headers (`viewer_utils.h`, `mesh_utils.hpp`) excluded — they need `WITH_VIEWER=ON` + system GLFW/libigl that the lint build doesn't drag in. * `.codespellrc`: extended ignore list (recognise, signalled, modelled, travelled, …) for British-English consistency across own writing. Final state — local quality block on this commit, this branch: ✅ License headers (66/66 carry MIT SPDX) ✅ CGAL conventions (0/6 violations on 6 CGAL headers) ✅ clang-format drift (0 drift) ✅ cmake-format/-lint (0 drift, 0 lint findings) ✅ codespell (0 typos in scope) ✅ shellcheck (0 findings across 16 .sh files) ✅ cppcheck (warning+ severity clean) ✅ Markdown links (122/122 resolve) ✅ Sanitizers (ASan+UBSan) (23/23 fast tests pass) ✅ clang-tidy (35 headers inspected, 0 findings) Library standalone-ness verified: env -i PATH=... cmake -S code -B /tmp/build-standalone cmake --build /tmp/build-standalone --target conformallab_tests ctest -E '^cgal\.' → all green Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -73,7 +73,12 @@ ignore-words-list = bessel,ist,sinces,nd,te,inout,nin,numer,neet,anc,sinks,doubl
|
|||||||
labelled,labelling,labels,labelled,
|
labelled,labelling,labels,labelled,
|
||||||
fulfil,fulfils,fulfilled,fulfilling,
|
fulfil,fulfils,fulfilled,fulfilling,
|
||||||
endcode,
|
endcode,
|
||||||
deklaration,deklarationen
|
deklaration,deklarationen,
|
||||||
|
recognise,recognised,recognises,recognising,recognisation,
|
||||||
|
signalled,signalling,
|
||||||
|
travelled,travelling,
|
||||||
|
cancelled,cancelling,
|
||||||
|
modelled,modelling
|
||||||
|
|
||||||
# Words we explicitly DO want flagged (override the default skip list).
|
# Words we explicitly DO want flagged (override the default skip list).
|
||||||
# Keep empty for now; add as we hit real-but-not-flagged typos.
|
# Keep empty for now; add as we hit real-but-not-flagged typos.
|
||||||
|
|||||||
56
.editorconfig
Normal file
56
.editorconfig
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# conformallab++ EditorConfig
|
||||||
|
#
|
||||||
|
# Honoured natively by VSCode (with the EditorConfig extension), CLion,
|
||||||
|
# Vim, Emacs, Sublime, … Covers the basics that .clang-format /
|
||||||
|
# .cmake-format don't catch (Markdown, Python, YAML, shell, JSON, …)
|
||||||
|
# and acts as a cross-IDE fallback when clang-format isn't installed.
|
||||||
|
#
|
||||||
|
# Authoritative formatting for C++ source still comes from .clang-format;
|
||||||
|
# this file just keeps the editor's defaults from fighting it.
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# C++ — match .clang-format
|
||||||
|
[*.{h,hpp,cpp,c,cc}]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
|
# CMake — match .cmake-format.yaml
|
||||||
|
[{CMakeLists.txt,*.cmake}]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
|
# Python — PEP-8 default
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
|
# Shell — Google shell style
|
||||||
|
[*.sh]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
|
# YAML — community convention
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# JSON
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Markdown — preserve trailing spaces (used for line breaks); don't strip
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
max_line_length = off
|
||||||
|
|
||||||
|
# Makefiles must use tabs
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
@@ -55,8 +55,26 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||||
|
# ─── Compiler-warning policy ─────────────────────────────────────────────
|
||||||
|
# `-Wall -Wextra -Wpedantic` is the project default for first-party code.
|
||||||
|
# Vendored deps under code/deps/ get a separate, looser policy (handled
|
||||||
|
# via per-target SYSTEM include marking when they are pulled in).
|
||||||
|
#
|
||||||
|
# `CONFORMALLAB_WARNINGS_AS_ERRORS=ON` flips on `-Werror` — used in CI's
|
||||||
|
# promotion-track and by `scripts/quality/sanitizers.sh` to make sure no
|
||||||
|
# new warning class slips in unannounced. Off by default so regular
|
||||||
|
# builds on slightly older toolchains aren't broken by a new GCC's
|
||||||
|
# added warning.
|
||||||
|
option(CONFORMALLAB_WARNINGS_AS_ERRORS
|
||||||
|
"Treat compiler warnings as errors (-Werror)." OFF)
|
||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||||
|
|
||||||
|
if(CONFORMALLAB_WARNINGS_AS_ERRORS)
|
||||||
|
add_compile_options(-Werror)
|
||||||
|
message(STATUS "Warnings-as-errors mode active (-Werror).")
|
||||||
|
endif()
|
||||||
|
|
||||||
# AddressSanitizer only in Debug (gtest_discover_tests runs the binary at
|
# AddressSanitizer only in Debug (gtest_discover_tests runs the binary at
|
||||||
# configure time and hangs with ASan enabled).
|
# configure time and hangs with ASan enabled).
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT BUILD_TESTING)
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT BUILD_TESTING)
|
||||||
|
|||||||
151
doc/architecture/dependencies.md
Normal file
151
doc/architecture/dependencies.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Dependencies & standalone-ness
|
||||||
|
|
||||||
|
This document is the single source of truth for "what does conformallab++
|
||||||
|
**require** vs. what does it **optionally** use". Anyone evaluating the
|
||||||
|
project for inclusion (CGAL submission, downstream consumer, Linux
|
||||||
|
distribution package, reviewer audit) should be able to read this page
|
||||||
|
and know exactly which dev tools are mandatory, which are nice-to-have,
|
||||||
|
and which can be skipped or replaced.
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
| Layer | What is required | What is optional |
|
||||||
|
|---|---|---|
|
||||||
|
| **Library use** (header-only, end-user code includes our headers) | C++17 compiler, CMake ≥ 3.20, Eigen ≥ 3.4 (headers), CGAL ≥ 5.6 (headers), Boost ≥ 1.74 (headers — needed by CGAL's BGL adapters) | — |
|
||||||
|
| **Test build + run** | the above + GTest (auto-fetched by CMake `FetchContent`, no system install needed) | — |
|
||||||
|
| **Documentation build** | Doxygen ≥ 1.10 | Graphviz (call graphs), MathJax (renders inline) |
|
||||||
|
| **Local quality gates** (`scripts/quality/`) | nothing the library doesn't already need | every gate is **independent**; each one not installed is **skipped**, not failed |
|
||||||
|
| **Optional viewer** (`-DWITH_VIEWER=ON`) | GLFW (vendored under `code/deps/glfw-3.4`), libigl, OpenGL system headers | — |
|
||||||
|
|
||||||
|
The library itself is **header-only**. There is no compiled `.so` /
|
||||||
|
`.a` / `.lib` we ship; consumers just `#include` and let their build
|
||||||
|
system do the rest.
|
||||||
|
|
||||||
|
## Library deps (required to build/use the C++ headers)
|
||||||
|
|
||||||
|
| Dep | Version | Header-only? | Purchase | Required by |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **C++17 compiler** | g++ ≥ 11, clang++ ≥ 14, AppleClang ≥ 14 | n/a | system / brew / apt | everything |
|
||||||
|
| **CMake** | ≥ 3.20 | n/a | system / brew / apt | build orchestration |
|
||||||
|
| **Eigen** | ≥ 3.4 (header-only) | yes | system (`apt install libeigen3-dev`) or vendored under `code/deps/eigen-*/` | every functional & solver |
|
||||||
|
| **CGAL** | ≥ 5.6 (header-only) | yes | system (`apt install libcgal-dev`) or downloaded tarball | `code/include/CGAL/*` wrappers + Surface_mesh |
|
||||||
|
| **Boost** | ≥ 1.74 (header-only) | yes | system (`apt install libboost-dev`) | only when `WITH_CGAL_TESTS=ON` or `WITH_CGAL=ON`, because CGAL's BGL adapters pull in `boost::graph_traits` |
|
||||||
|
| **GTest** | 1.14 | yes (auto-fetched) | `FetchContent_Declare` in `code/CMakeLists.txt` — never installed system-wide | tests only |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The library headers in `code/include/*.hpp` use only Eigen + STL.
|
||||||
|
- The CGAL wrapper headers in `code/include/CGAL/*.h` add CGAL + Boost
|
||||||
|
(transitively).
|
||||||
|
- `code/deps/single_includes/json.hpp` is the vendored
|
||||||
|
[nlohmann/json](https://github.com/nlohmann/json) header — used by
|
||||||
|
`serialization.hpp` only. No system install needed.
|
||||||
|
|
||||||
|
## Build modes — what each requires
|
||||||
|
|
||||||
|
| Mode | CMake invocation | Extra system deps |
|
||||||
|
|---|---|---|
|
||||||
|
| **Fast / pure-math tests** (default) | `cmake -S code -B build` | none beyond C++17 + CMake |
|
||||||
|
| **CGAL headless tests** | `cmake -S code -B build -DWITH_CGAL_TESTS=ON` | Boost headers |
|
||||||
|
| **Full build** (CLI + viewer) | `cmake -S code -B build -DWITH_CGAL=ON` | Boost + Wayland/X11 dev headers |
|
||||||
|
| **Coverage / sanitizers / etc.** | see `scripts/quality/` | per-script (each documents its prereqs and skips if missing) |
|
||||||
|
|
||||||
|
The `-DWITH_*` flags **all default to OFF**. A fresh checkout +
|
||||||
|
`cmake -S code -B build` works with nothing but a C++17 compiler and
|
||||||
|
CMake — useful for evaluating the math without taking on the full CGAL
|
||||||
|
toolchain.
|
||||||
|
|
||||||
|
## Local quality gates — all optional, each independently skippable
|
||||||
|
|
||||||
|
`scripts/quality/` contains 12 gate scripts. None of them is wired
|
||||||
|
into the regular CMake build; each is a standalone shell or Python
|
||||||
|
invocation. When the underlying tool is not installed, the script
|
||||||
|
exits with **code 2** and a clear message; `scripts/quality/run-all.sh`
|
||||||
|
recognises this as **SKIP**, not FAIL.
|
||||||
|
|
||||||
|
| Tool | Used by | Install (macOS) | Install (Debian/Ubuntu) | Behaviour if missing |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `clang-format` ≥ 15 | `clang-format.sh` | `brew install clang-format` | `apt install clang-format` | gate prints install hint, exits 2 → SKIP |
|
||||||
|
| `clang-tidy` ≥ 14 | `clang-tidy.sh` | `brew install llvm` (then PATH-prepend `$(brew --prefix llvm)/bin`) | `apt install clang-tidy` | SKIP |
|
||||||
|
| `cmake-format` / `cmake-lint` | `cmake-format.sh` | `pip3 install --user cmakelang` + PATH-prepend `~/.local/bin` | `pip3 install --user cmakelang` | SKIP |
|
||||||
|
| `codespell` | `codespell.sh` | `brew install codespell` | `apt install codespell` (or `pip3 install codespell`) | SKIP |
|
||||||
|
| `shellcheck` | `shellcheck.sh` | `brew install shellcheck` | `apt install shellcheck` | SKIP |
|
||||||
|
| `cppcheck` | `cppcheck.sh` | `brew install cppcheck` | `apt install cppcheck` | SKIP |
|
||||||
|
| `lcov` (+ `gcov` from the compiler) | `coverage.sh` | `brew install lcov` | `apt install lcov` | SKIP |
|
||||||
|
| second `g++` or `clang++` | `multi-compiler.sh` | `brew install gcc` or `brew install llvm` | `apt install g++` / `clang++-N` | runs against whatever compilers it finds; WARNING if < 2 |
|
||||||
|
| extra CGAL source trees | `cgal-version-matrix.sh` | manually `git clone` under `~/cgal/<ver>/` (or pass `CGAL_ROOTS=...`) | same | exits 2 → SKIP with explicit recovery hint |
|
||||||
|
|
||||||
|
### How to disable a gate temporarily
|
||||||
|
|
||||||
|
Two options:
|
||||||
|
|
||||||
|
1. **Don't install the tool** — `run-all.sh` skips it.
|
||||||
|
2. **Remove the line from `GATES_FAST` / `GATES_SLOW` in `run-all.sh`** —
|
||||||
|
the script is a 5-line edit; no separate "disabled" flag system.
|
||||||
|
|
||||||
|
There is no global "disable all quality gates" switch by design. If
|
||||||
|
the gates feel heavy, run only the fast subset (`run-all.sh --fast`,
|
||||||
|
~5 seconds wall-time when all tools are present); if even that is too
|
||||||
|
much, invoke the one gate you care about directly.
|
||||||
|
|
||||||
|
### `CONFORMALLAB_WARNINGS_AS_ERRORS` — the only CMake-level quality flag
|
||||||
|
|
||||||
|
By default the build adds `-Wall -Wextra -Wpedantic` but does **not**
|
||||||
|
fail on warnings. Set `-DCONFORMALLAB_WARNINGS_AS_ERRORS=ON` for a
|
||||||
|
strict build (intended for CI promotion-track and for sanitizer runs).
|
||||||
|
Defaulting to off keeps the build green on slightly-newer toolchains
|
||||||
|
that may flag new warning classes we haven't yet annotated.
|
||||||
|
|
||||||
|
## CI gates (active on every PR via `.gitea/workflows/`)
|
||||||
|
|
||||||
|
These run inside the `git.eulernest.eu/conformallab/ci-cpp:latest`
|
||||||
|
container, so the tools are baked into the image — contributors do not
|
||||||
|
need any of them locally:
|
||||||
|
|
||||||
|
| Gate | Workflow file |
|
||||||
|
|---|---|
|
||||||
|
| ctest (fast + CGAL suites) | `cpp-tests.yml` |
|
||||||
|
| test-count consistency | same |
|
||||||
|
| End-to-end `try_it.sh` | same |
|
||||||
|
| Markdown link check | `markdown-links.yml` |
|
||||||
|
| Doxygen build + Codeberg Pages publish | `doxygen-pages.yml` |
|
||||||
|
| Mirror to Codeberg | `mirror-to-codeberg.yml` |
|
||||||
|
|
||||||
|
The local quality gates under `scripts/quality/` are **not** in CI
|
||||||
|
today. Each one's promotion path is documented in
|
||||||
|
`scripts/quality/README.md`.
|
||||||
|
|
||||||
|
## Verification of the standalone claim
|
||||||
|
|
||||||
|
Test recipe (any UNIX, ~30 s):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Strip PATH down to system + brew core (no quality tools).
|
||||||
|
env -i PATH="/usr/bin:/bin:/opt/homebrew/bin" HOME="$HOME" \
|
||||||
|
cmake -S code -B /tmp/build-standalone
|
||||||
|
|
||||||
|
# Build the fast test suite.
|
||||||
|
cmake --build /tmp/build-standalone --target conformallab_tests
|
||||||
|
|
||||||
|
# Run them.
|
||||||
|
ctest --test-dir /tmp/build-standalone -E "^cgal\."
|
||||||
|
```
|
||||||
|
|
||||||
|
If this passes, the library is genuinely independent of every quality
|
||||||
|
tool listed above. Tested locally on macOS-arm64 — green.
|
||||||
|
|
||||||
|
For the CGAL-mode equivalent (adds Boost headers as a system dep):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env -i PATH="/usr/bin:/bin:/opt/homebrew/bin" HOME="$HOME" \
|
||||||
|
cmake -S code -B /tmp/build-cgal -DWITH_CGAL_TESTS=ON
|
||||||
|
cmake --build /tmp/build-cgal --target conformallab_cgal_tests
|
||||||
|
ctest --test-dir /tmp/build-cgal -R "^cgal\."
|
||||||
|
```
|
||||||
|
|
||||||
|
## What is **not** in this repo (out of scope)
|
||||||
|
|
||||||
|
- No package-manager metadata (Debian `.deb`, RPM, Conan, vcpkg, …) —
|
||||||
|
yet. Adding them is downstream work; the header-only nature makes
|
||||||
|
each trivial.
|
||||||
|
- No language bindings (Python, …) — out of scope; the library is C++.
|
||||||
|
- No GPU compute path — out of scope; numerical work is CPU-only.
|
||||||
@@ -23,6 +23,8 @@ consistency + markdown links + end-to-end smoke via `try_it.sh`).
|
|||||||
| `clang-format.sh` | every C++ source matches `.clang-format` (dry-run by default; `--fix` to apply) | ~2 s | `clang-format` ≥ 15 |
|
| `clang-format.sh` | every C++ source matches `.clang-format` (dry-run by default; `--fix` to apply) | ~2 s | `clang-format` ≥ 15 |
|
||||||
| `cmake-format.sh` | every `CMakeLists.txt` matches `.cmake-format.yaml` + passes `cmake-lint` | ~2 s | `cmake-format` (pip: cmakelang) |
|
| `cmake-format.sh` | every `CMakeLists.txt` matches `.cmake-format.yaml` + passes `cmake-lint` | ~2 s | `cmake-format` (pip: cmakelang) |
|
||||||
| `codespell.sh` | typo check across docs + source comments + script messages | ~1 s | `codespell` |
|
| `codespell.sh` | typo check across docs + source comments + script messages | ~1 s | `codespell` |
|
||||||
|
| `shellcheck.sh` | static analysis of every `scripts/**/*.sh` | ~1 s | `shellcheck` |
|
||||||
|
| `cppcheck.sh` | second-opinion static analyser over `code/include/` | ~5 s | `cppcheck` |
|
||||||
| `../check-markdown-links.py` | every internal markdown link resolves | ~2 s | `python3` |
|
| `../check-markdown-links.py` | every internal markdown link resolves | ~2 s | `python3` |
|
||||||
|
|
||||||
### Correctness / quality gates (run before tagging or reviewer demos)
|
### Correctness / quality gates (run before tagging or reviewer demos)
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ echo " versions tested:"
|
|||||||
echo "$AVAILABLE" | sed 's/^/ /'
|
echo "$AVAILABLE" | sed 's/^/ /'
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
overall=0
|
# Failures are recorded in $ROOT/.cgal-matrix-failures because the
|
||||||
|
# while-loop runs in a subshell (consequence of the pipe from echo), so
|
||||||
|
# a plain `overall=0; overall=1` would not survive back to the parent.
|
||||||
|
rm -f "$ROOT/.cgal-matrix-failures"
|
||||||
|
|
||||||
echo "$AVAILABLE" | while IFS= read -r cgal_root; do
|
echo "$AVAILABLE" | while IFS= read -r cgal_root; do
|
||||||
[ -z "$cgal_root" ] && continue
|
[ -z "$cgal_root" ] && continue
|
||||||
ver="$(basename "$cgal_root")"
|
ver="$(basename "$cgal_root")"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
command -v clang-format >/dev/null 2>&1 || {
|
command -v clang-format >/dev/null 2>&1 || {
|
||||||
echo "FAIL: clang-format not in PATH." >&2
|
echo "FAIL: clang-format not in PATH." >&2
|
||||||
|
|||||||
@@ -40,20 +40,39 @@ TARGET_DIR="${1:-code/include}"
|
|||||||
[ -d "$TARGET_DIR" ] || { echo "FAIL: $TARGET_DIR is not a directory" >&2; exit 2; }
|
[ -d "$TARGET_DIR" ] || { echo "FAIL: $TARGET_DIR is not a directory" >&2; exit 2; }
|
||||||
|
|
||||||
# Generate compile_commands.json (clang-tidy needs it for include paths).
|
# Generate compile_commands.json (clang-tidy needs it for include paths).
|
||||||
|
# Enable WITH_CGAL_TESTS so the CGAL include directories are part of at
|
||||||
|
# least one compile entry — clang-tidy walks those when linting headers
|
||||||
|
# that don't appear in compile_commands.json directly.
|
||||||
cmake -S code -B "$BUILD_DIR" \
|
cmake -S code -B "$BUILD_DIR" \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
|
-DWITH_CGAL_TESTS=ON \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-Wno-dev >/dev/null
|
-Wno-dev >/dev/null
|
||||||
|
|
||||||
|
# ── macOS workaround: brew-installed clang-tidy doesn't know where the
|
||||||
|
# Apple Command-Line-Tools SDK lives, so it can't find <cmath>, <complex>,
|
||||||
|
# <CGAL/...>, etc. Pass `--extra-arg=-isysroot ...` to teach it.
|
||||||
|
EXTRA_ARGS=()
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
SDK="$(xcrun --show-sdk-path 2>/dev/null || true)"
|
||||||
|
if [ -n "$SDK" ]; then
|
||||||
|
EXTRA_ARGS+=(--extra-arg=-isysroot --extra-arg="$SDK")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
||||||
: > "$LOG"
|
: > "$LOG"
|
||||||
|
|
||||||
# Find every .h / .hpp under TARGET_DIR (skip deps + macOS dup files).
|
# Find every .h / .hpp under TARGET_DIR (skip deps + macOS dup files +
|
||||||
|
# viewer-only headers — those need `-DWITH_VIEWER=ON` plus a system
|
||||||
|
# GLFW/libigl that we don't drag into the lint build).
|
||||||
HEADERS=$(find "$TARGET_DIR" \
|
HEADERS=$(find "$TARGET_DIR" \
|
||||||
\( -name "*.h" -o -name "*.hpp" \) \
|
\( -name "*.h" -o -name "*.hpp" \) \
|
||||||
-type f \
|
-type f \
|
||||||
| grep -v "code/deps/" \
|
| grep -v "code/deps/" \
|
||||||
| grep -v " 2\." \
|
| grep -v " 2\." \
|
||||||
|
| grep -v "viewer_utils\.h$" \
|
||||||
|
| grep -v "mesh_utils\.hpp$" \
|
||||||
| sort)
|
| sort)
|
||||||
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
@@ -72,6 +91,7 @@ for h in $HEADERS; do
|
|||||||
# generated" boilerplate. Pipe through tee for the log file.
|
# generated" boilerplate. Pipe through tee for the log file.
|
||||||
clang-tidy --quiet \
|
clang-tidy --quiet \
|
||||||
-p "$BUILD_DIR" \
|
-p "$BUILD_DIR" \
|
||||||
|
"${EXTRA_ARGS[@]}" \
|
||||||
"$h" 2>&1 | tee -a "$LOG" || true
|
"$h" 2>&1 | tee -a "$LOG" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
# Allow ~/.local/bin (pip-installed tools) in PATH.
|
# Allow ~/.local/bin (pip-installed tools) in PATH.
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
command -v codespell >/dev/null 2>&1 || {
|
command -v codespell >/dev/null 2>&1 || {
|
||||||
echo "FAIL: codespell not in PATH." >&2
|
echo "FAIL: codespell not in PATH." >&2
|
||||||
|
|||||||
97
scripts/quality/cppcheck.sh
Executable file
97
scripts/quality/cppcheck.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/quality/cppcheck.sh
|
||||||
|
#
|
||||||
|
# Run cppcheck over the public headers. Complementary to clang-tidy:
|
||||||
|
# cppcheck has different heuristics, fewer false-positives on heavy
|
||||||
|
# template code (CGAL/Eigen), and catches some bugs (unused includes,
|
||||||
|
# memory leaks in detail/) that clang-tidy is bad at.
|
||||||
|
#
|
||||||
|
# Local-only. Promotion to CI when the existing tree is finding-free
|
||||||
|
# at the chosen severity level.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash scripts/quality/cppcheck.sh # error+warning only
|
||||||
|
# bash scripts/quality/cppcheck.sh --strict # +style, exit 1 on any
|
||||||
|
# bash scripts/quality/cppcheck.sh --all # absolute everything,
|
||||||
|
# useful for diffs only
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 no findings at the chosen severity, or findings but not --strict
|
||||||
|
# 1 findings + --strict
|
||||||
|
# 2 prerequisite missing
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
|
command -v cppcheck >/dev/null 2>&1 || {
|
||||||
|
echo "FAIL: cppcheck not in PATH." >&2
|
||||||
|
echo " macOS: brew install cppcheck" >&2
|
||||||
|
echo " Linux: sudo apt install cppcheck" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
STRICT=0
|
||||||
|
ALL=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--strict) STRICT=1 ;;
|
||||||
|
--all) ALL=1 ;;
|
||||||
|
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ENABLE="warning"
|
||||||
|
if [ "$STRICT" -eq 1 ]; then ENABLE="warning,style"; fi
|
||||||
|
if [ "$ALL" -eq 1 ]; then ENABLE="all"; fi
|
||||||
|
|
||||||
|
BUILD_DIR="build-cppcheck"
|
||||||
|
LOG="$BUILD_DIR/cppcheck.log"
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
|
echo "cppcheck ($(cppcheck --version 2>&1 | head -1))"
|
||||||
|
echo " enable: $ENABLE"
|
||||||
|
echo " log: $LOG"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Suppress noise classes that are not actionable in our project:
|
||||||
|
# missingIncludeSystem — CGAL/Eigen/Boost headers are intentionally
|
||||||
|
# included implicitly; cppcheck cannot resolve.
|
||||||
|
# unmatchedSuppression — cosmetic.
|
||||||
|
# unusedFunction — header-only; many `inline` helpers ARE used,
|
||||||
|
# cppcheck can't see across TUs.
|
||||||
|
# normalCheckLevelMaxBranches — informational, not a finding.
|
||||||
|
#
|
||||||
|
# We point cppcheck at code/include/ only. The deps tree is third-party
|
||||||
|
# code and out of scope.
|
||||||
|
|
||||||
|
cppcheck \
|
||||||
|
--enable="$ENABLE" \
|
||||||
|
--std=c++17 \
|
||||||
|
--quiet \
|
||||||
|
--error-exitcode=2 \
|
||||||
|
--inline-suppr \
|
||||||
|
--suppress=missingIncludeSystem \
|
||||||
|
--suppress=unmatchedSuppression \
|
||||||
|
--suppress=unusedFunction \
|
||||||
|
--suppress=normalCheckLevelMaxBranches \
|
||||||
|
-I code/include \
|
||||||
|
code/include 2>&1 | tee "$LOG"
|
||||||
|
rc=$?
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "── Summary ──"
|
||||||
|
n=$(grep -cE "\[(error|warning|style|performance|portability)\]" "$LOG" || true)
|
||||||
|
echo " total findings: $n"
|
||||||
|
echo " full log: $LOG"
|
||||||
|
|
||||||
|
if [ "$STRICT" -eq 1 ] && [ "$n" -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$rc" -eq 2 ] && [ "$STRICT" -ne 1 ]; then
|
||||||
|
# cppcheck signalled "error" severity but caller didn't ask --strict.
|
||||||
|
echo
|
||||||
|
echo "NOTE: cppcheck reported an `error`-severity finding. Even"
|
||||||
|
echo " without --strict, please review the log."
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
FAST=0
|
FAST=0
|
||||||
[ "${1:-}" = "--fast" ] && FAST=1
|
[ "${1:-}" = "--fast" ] && FAST=1
|
||||||
@@ -39,6 +39,8 @@ GATES_FAST=(
|
|||||||
"clang-format drift | bash scripts/quality/clang-format.sh"
|
"clang-format drift | bash scripts/quality/clang-format.sh"
|
||||||
"cmake-format/-lint | bash scripts/quality/cmake-format.sh"
|
"cmake-format/-lint | bash scripts/quality/cmake-format.sh"
|
||||||
"codespell | bash scripts/quality/codespell.sh"
|
"codespell | bash scripts/quality/codespell.sh"
|
||||||
|
"shellcheck | bash scripts/quality/shellcheck.sh"
|
||||||
|
"cppcheck | bash scripts/quality/cppcheck.sh"
|
||||||
"Markdown links | python3 scripts/check-markdown-links.py"
|
"Markdown links | python3 scripts/check-markdown-links.py"
|
||||||
"Sanitizers | bash scripts/quality/sanitizers.sh"
|
"Sanitizers | bash scripts/quality/sanitizers.sh"
|
||||||
"clang-tidy | bash scripts/quality/clang-tidy.sh"
|
"clang-tidy | bash scripts/quality/clang-tidy.sh"
|
||||||
@@ -67,6 +69,7 @@ echo "============================================================"
|
|||||||
|
|
||||||
results=""
|
results=""
|
||||||
failed=0
|
failed=0
|
||||||
|
skipped=0
|
||||||
i=0
|
i=0
|
||||||
for entry in "${GATES[@]}"; do
|
for entry in "${GATES[@]}"; do
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
@@ -79,12 +82,21 @@ for entry in "${GATES[@]}"; do
|
|||||||
log="$LOG_DIR/$slug.log"
|
log="$LOG_DIR/$slug.log"
|
||||||
echo
|
echo
|
||||||
echo "──── [$i/${#GATES[@]}] $name ────"
|
echo "──── [$i/${#GATES[@]}] $name ────"
|
||||||
if eval "$cmd" >"$log" 2>&1; then
|
eval "$cmd" >"$log" 2>&1
|
||||||
|
rc=$?
|
||||||
|
# Exit code 2 from any of our gate scripts = "tool not installed".
|
||||||
|
# Treat as SKIP rather than FAIL so a partial dev environment can
|
||||||
|
# still run the rest of the sweep.
|
||||||
|
if [ "$rc" -eq 2 ] && head -3 "$log" | grep -qE "FAIL:.*not (in PATH|installed|found)"; then
|
||||||
|
echo " SKIP (tool not installed — see $log)"
|
||||||
|
results="${results} SKIP $name (missing tool)
|
||||||
|
"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
elif [ "$rc" -eq 0 ]; then
|
||||||
echo " OK ($log)"
|
echo " OK ($log)"
|
||||||
results="${results} PASS $name
|
results="${results} PASS $name
|
||||||
"
|
"
|
||||||
else
|
else
|
||||||
rc=$?
|
|
||||||
echo " FAIL (rc=$rc) — see $log"
|
echo " FAIL (rc=$rc) — see $log"
|
||||||
echo " last 20 lines:"
|
echo " last 20 lines:"
|
||||||
tail -20 "$log" | sed 's/^/ /'
|
tail -20 "$log" | sed 's/^/ /'
|
||||||
@@ -100,5 +112,7 @@ echo " Summary"
|
|||||||
echo "============================================================"
|
echo "============================================================"
|
||||||
printf "%s" "$results"
|
printf "%s" "$results"
|
||||||
echo
|
echo
|
||||||
echo " failed: $failed / ${#GATES[@]}"
|
echo " passed: $((${#GATES[@]} - failed - skipped)) / ${#GATES[@]}"
|
||||||
|
echo " skipped: $skipped (tool not installed; gate is local-only)"
|
||||||
|
echo " failed: $failed"
|
||||||
exit $failed
|
exit $failed
|
||||||
|
|||||||
@@ -57,11 +57,18 @@ echo "Using CXX = $CXX_BIN ($("$CXX_BIN" --version | head -1))"
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
# ── Configure ────────────────────────────────────────────────────────────────
|
# ── Configure ────────────────────────────────────────────────────────────────
|
||||||
|
# CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST: without this,
|
||||||
|
# gtest_discover_tests runs the (sanitizer-instrumented) test binary at
|
||||||
|
# *build* time to enumerate test cases. ASan aborts that subprocess
|
||||||
|
# the moment it sees any allocation in static-init, which fails the
|
||||||
|
# build before we can even get to ctest. PRE_TEST defers discovery to
|
||||||
|
# `ctest` invocation, which is exactly what we want.
|
||||||
cmake -S code -B "$BUILD_DIR" \
|
cmake -S code -B "$BUILD_DIR" \
|
||||||
-DCMAKE_CXX_COMPILER="$CXX_BIN" \
|
-DCMAKE_CXX_COMPILER="$CXX_BIN" \
|
||||||
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
||||||
-DCMAKE_BUILD_TYPE=Debug \
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \
|
||||||
-Wno-dev
|
-Wno-dev
|
||||||
|
|
||||||
# ── Build the fast (non-CGAL) tests only ────────────────────────────────────
|
# ── Build the fast (non-CGAL) tests only ────────────────────────────────────
|
||||||
|
|||||||
79
scripts/quality/shellcheck.sh
Executable file
79
scripts/quality/shellcheck.sh
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/quality/shellcheck.sh
|
||||||
|
#
|
||||||
|
# Run shellcheck across every Bash script we own (scripts/**/*.sh).
|
||||||
|
# Skips the macOS duplicate artefacts (` 2.sh`).
|
||||||
|
#
|
||||||
|
# Local-only. CI promotion once every script is shellcheck-clean.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash scripts/quality/shellcheck.sh # warn + advisory exit 0
|
||||||
|
# bash scripts/quality/shellcheck.sh --strict # fail on any finding
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 no findings, or findings but --strict not set
|
||||||
|
# 1 --strict was set and shellcheck reported findings
|
||||||
|
# 2 prerequisite missing
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
cd "$ROOT" || exit 2
|
||||||
|
|
||||||
|
command -v shellcheck >/dev/null 2>&1 || {
|
||||||
|
echo "FAIL: shellcheck not in PATH." >&2
|
||||||
|
echo " macOS: brew install shellcheck" >&2
|
||||||
|
echo " Linux: sudo apt install shellcheck" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
STRICT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--strict) STRICT=1 ;;
|
||||||
|
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
FILES="$(find scripts -name "*.sh" -type f 2>/dev/null \
|
||||||
|
| grep -v " 2\.sh" \
|
||||||
|
| sort)"
|
||||||
|
|
||||||
|
if [ -z "$FILES" ]; then
|
||||||
|
echo "FAIL: no shell scripts found under scripts/" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "shellcheck ($(shellcheck --version | sed -n '2p'))"
|
||||||
|
echo "Scanning shell scripts under scripts/"
|
||||||
|
echo
|
||||||
|
|
||||||
|
n_total=0
|
||||||
|
n_with_findings=0
|
||||||
|
total_findings=0
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -z "$f" ] && continue
|
||||||
|
n_total=$((n_total + 1))
|
||||||
|
# -S style: warnings + above (skip "info" and "style" noise).
|
||||||
|
out="$(shellcheck --severity=warning --shell=bash "$f" 2>&1)"
|
||||||
|
if [ -n "$out" ]; then
|
||||||
|
echo "── $f ──"
|
||||||
|
echo "$out"
|
||||||
|
echo
|
||||||
|
n_with_findings=$((n_with_findings + 1))
|
||||||
|
# rough count: one finding per "In <file> line N:" block
|
||||||
|
cnt=$(printf '%s' "$out" | grep -c "^In .* line")
|
||||||
|
total_findings=$((total_findings + cnt))
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
$FILES
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "── Summary ──"
|
||||||
|
echo " scripts scanned: $n_total"
|
||||||
|
echo " scripts with issues: $n_with_findings"
|
||||||
|
echo " total findings: $total_findings"
|
||||||
|
|
||||||
|
if [ "$STRICT" -eq 1 ] && [ "$total_findings" -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user