quality: 4 more gates + dependency audit; full --fast sweep 10/10 green
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 1m57s
API Docs / doc-build (pull_request) Successful in 48s
Markdown link check / check (pull_request) Successful in 51s
C++ Tests / test-cgal (pull_request) Failing after 12m23s

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:
Tarik Moussa
2026-05-24 09:56:40 +02:00
parent d3c08b3bc0
commit 1aa3493e7d
14 changed files with 463 additions and 10 deletions

View File

@@ -73,7 +73,12 @@ ignore-words-list = bessel,ist,sinces,nd,te,inout,nin,numer,neet,anc,sinks,doubl
labelled,labelling,labels,labelled,
fulfil,fulfils,fulfilled,fulfilling,
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).
# Keep empty for now; add as we hit real-but-not-flagged typos.

56
.editorconfig Normal file
View 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

View File

@@ -55,8 +55,26 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
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)
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
# configure time and hangs with ASan enabled).
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT BUILD_TESTING)

View 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.

View File

@@ -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 |
| `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` |
| `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` |
### Correctness / quality gates (run before tagging or reviewer demos)

View File

@@ -73,7 +73,11 @@ echo " versions tested:"
echo "$AVAILABLE" | sed 's/^/ /'
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
[ -z "$cgal_root" ] && continue
ver="$(basename "$cgal_root")"

View File

@@ -23,7 +23,7 @@
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
cd "$ROOT" || exit 2
command -v clang-format >/dev/null 2>&1 || {
echo "FAIL: clang-format not in PATH." >&2

View File

@@ -40,20 +40,39 @@ TARGET_DIR="${1:-code/include}"
[ -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).
# 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" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DWITH_CGAL_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-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"
: > "$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" \
\( -name "*.h" -o -name "*.hpp" \) \
-type f \
| grep -v "code/deps/" \
| grep -v " 2\." \
| grep -v "viewer_utils\.h$" \
| grep -v "mesh_utils\.hpp$" \
| sort)
echo "========================================"
@@ -72,6 +91,7 @@ for h in $HEADERS; do
# generated" boilerplate. Pipe through tee for the log file.
clang-tidy --quiet \
-p "$BUILD_DIR" \
"${EXTRA_ARGS[@]}" \
"$h" 2>&1 | tee -a "$LOG" || true
done

View File

@@ -19,7 +19,7 @@
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
cd "$ROOT" || exit 2
# Allow ~/.local/bin (pip-installed tools) in PATH.
export PATH="$HOME/.local/bin:$PATH"

View File

@@ -24,7 +24,7 @@
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
cd "$ROOT" || exit 2
command -v codespell >/dev/null 2>&1 || {
echo "FAIL: codespell not in PATH." >&2

97
scripts/quality/cppcheck.sh Executable file
View 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

View File

@@ -28,7 +28,7 @@
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
cd "$ROOT" || exit 2
FAST=0
[ "${1:-}" = "--fast" ] && FAST=1
@@ -39,6 +39,8 @@ GATES_FAST=(
"clang-format drift | bash scripts/quality/clang-format.sh"
"cmake-format/-lint | bash scripts/quality/cmake-format.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"
"Sanitizers | bash scripts/quality/sanitizers.sh"
"clang-tidy | bash scripts/quality/clang-tidy.sh"
@@ -67,6 +69,7 @@ echo "============================================================"
results=""
failed=0
skipped=0
i=0
for entry in "${GATES[@]}"; do
i=$((i + 1))
@@ -79,12 +82,21 @@ for entry in "${GATES[@]}"; do
log="$LOG_DIR/$slug.log"
echo
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)"
results="${results} PASS $name
"
else
rc=$?
echo " FAIL (rc=$rc) — see $log"
echo " last 20 lines:"
tail -20 "$log" | sed 's/^/ /'
@@ -100,5 +112,7 @@ echo " Summary"
echo "============================================================"
printf "%s" "$results"
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

View File

@@ -57,11 +57,18 @@ echo "Using CXX = $CXX_BIN ($("$CXX_BIN" --version | head -1))"
echo
# ── 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" \
-DCMAKE_CXX_COMPILER="$CXX_BIN" \
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \
-Wno-dev
# ── Build the fast (non-CGAL) tests only ────────────────────────────────────

79
scripts/quality/shellcheck.sh Executable file
View 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