From be36fa8798ee1fc3d58dab2989de1b965f653548 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 12 Oct 2026 22:11:30 +0200 Subject: [PATCH 1/3] ci: run clang-tidy as a parallel job only on files that have been changed --- .github/workflows/ci.yml | 47 +++++++++++++++++++++++++++++++++++--- README.md | 2 ++ cmake/StaticAnalysis.cmake | 18 ++++++++++----- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e9951c..2e7978b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,17 +17,58 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake clang-format clang-tidy-20 libsdl2-dev protobuf-compiler gcovr + sudo apt-get install -y cmake ninja-build clang-format libsdl2-dev protobuf-compiler gcovr - name: Check Code Formatting run: | find src include tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" \) | xargs clang-format --dry-run -Werror - name: Configure CMake - run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DNEURON_IDE_ENABLE_COVERAGE=ON + # clang-tidy runs in its own job below, so this build only compiles. + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DNEURON_IDE_ENABLE_COVERAGE=ON -DNEURON_IDE_ENABLE_CLANG_TIDY=OFF - name: Build - run: cmake --build build --config Release + run: cmake --build build --parallel - name: Run Tests & Check Coverage run: cmake --build build --target coverage + + clang-tidy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # On pull requests HEAD is the merge commit and HEAD^1 the base branch, which the + # changed-file list below diffs against. + fetch-depth: 2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build clang-tidy-20 libsdl2-dev protobuf-compiler + + - name: Configure CMake + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DNEURON_IDE_ENABLE_CLANG_TIDY=OFF + + - name: Generate protobuf headers + # Our sources include neuronide.pb.h, which exists only once this target is built. + run: cmake --build build --target neuronide_proto + + - name: Run clang-tidy + # Pull requests check only the files they change; pushes, and pull requests that change + # .clang-tidy itself, check every source file and through them every header they include + # (HeaderFilterRegex in .clang-tidy). + run: | + if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && git diff --quiet HEAD^1 HEAD -- .clang-tidy; then + files=$(git diff --name-only --diff-filter=d HEAD^1 HEAD -- \ + 'src/*.cpp' 'src/*.hpp' 'include/*.hpp' 'tests/*.cpp' 'tests/*.hpp') + else + files=$(git ls-files 'src/*.cpp' 'tests/*.cpp') + fi + if [ -z "$files" ]; then + echo "No C++ files changed." + exit 0 + fi + echo "$files" | xargs -P "$(nproc)" -n 1 clang-tidy-20 -p build --quiet diff --git a/README.md b/README.md index efa93db..00f11a3 100644 --- a/README.md +++ b/README.md @@ -444,6 +444,8 @@ ctest -L component --output-on-failure ```bash cmake --build build --target format ``` +- clang-tidy runs on every file as part of the build, which makes builds several times slower. + Configure with `-DNEURON_IDE_ENABLE_CLANG_TIDY=OFF` to skip it; CI checks it in a separate job. ### Coverage diff --git a/cmake/StaticAnalysis.cmake b/cmake/StaticAnalysis.cmake index 9b9d697..1accfe9 100644 --- a/cmake/StaticAnalysis.cmake +++ b/cmake/StaticAnalysis.cmake @@ -1,12 +1,18 @@ # cmake/StaticAnalysis.cmake # --- Clang-Tidy Configuration --- -find_program(CLANG_TIDY_EXE NAMES "clang-tidy-20") -if(CLANG_TIDY_EXE) - message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}") -else() - message(WARNING "clang-tidy not found.") +# Runs clang-tidy next to the compiler on every file, which costs several times more than the +# compile itself. CI turns it off and runs clang-tidy in a separate job instead. +option(NEURON_IDE_ENABLE_CLANG_TIDY "Run clang-tidy on every file as part of the build" ON) + +if(NEURON_IDE_ENABLE_CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES "clang-tidy-20") + if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}") + else() + message(WARNING "clang-tidy not found.") + endif() endif() # --- Clang-Format Target --- From e5a5fa06ca1dc9720a62bc0b20634c3f3e4f04cd Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 12 Oct 2026 22:22:57 +0200 Subject: [PATCH 2/3] ci: move shared dependencies to env --- .github/workflows/ci.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e7978b..9801b1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "main", "master" ] +env: + BUILD_DEPS: cmake ninja-build libsdl2-dev protobuf-compiler + jobs: build-and-test: runs-on: ubuntu-latest @@ -17,14 +20,13 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build clang-format libsdl2-dev protobuf-compiler gcovr + sudo apt-get install -y $BUILD_DEPS clang-format gcovr - name: Check Code Formatting run: | find src include tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" \) | xargs clang-format --dry-run -Werror - name: Configure CMake - # clang-tidy runs in its own job below, so this build only compiles. run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DNEURON_IDE_ENABLE_COVERAGE=ON -DNEURON_IDE_ENABLE_CLANG_TIDY=OFF - name: Build @@ -47,19 +49,15 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build clang-tidy-20 libsdl2-dev protobuf-compiler + sudo apt-get install -y $BUILD_DEPS clang-tidy-20 - name: Configure CMake run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DNEURON_IDE_ENABLE_CLANG_TIDY=OFF - name: Generate protobuf headers - # Our sources include neuronide.pb.h, which exists only once this target is built. run: cmake --build build --target neuronide_proto - name: Run clang-tidy - # Pull requests check only the files they change; pushes, and pull requests that change - # .clang-tidy itself, check every source file and through them every header they include - # (HeaderFilterRegex in .clang-tidy). run: | if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && git diff --quiet HEAD^1 HEAD -- .clang-tidy; then files=$(git diff --name-only --diff-filter=d HEAD^1 HEAD -- \ From 4ba60e894762c45870e3cbe188a86f974eccb9cf Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 13 Oct 2026 11:54:26 +0200 Subject: [PATCH 3/3] docs: update readme * recommend building using ninja * intorduce compile parameter to turn off clang-tidy during compile time --- README.md | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 00f11a3..959f8e6 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ EEG samples and the stimulus markers must share a **common clock domain** so tha | Concern | Choice | | ------------------------ | ---------------------------------------------------------------------- | | Language | C++20 | -| Build system | CMake (≥ 3.25), Ninja-friendly | +| Build system | CMake (≥ 3.25), Ninja recommended | | Experiment file format | Protocol Buffers (proto3) — see `protoFiles/neuronide.proto` | | Device config format | JSON (`config.json`) parsed with [nlohmann/json](https://github.com/nlohmann/json) | | EEG acquisition | [LSL — Lab Streaming Layer](https://github.com/sccn/liblsl) (`liblsl`) | @@ -407,21 +407,50 @@ All commands are run from the `Neuron-IDE-runtime/` directory. ```bash sudo apt update -sudo apt install cmake clang-format clang-tidy libsdl2-dev protobuf-compiler gcovr -# GTest, LSL and concurrentqueue are fetched automatically by CMake. +sudo apt install cmake ninja-build clang-format clang-tidy-20 libsdl2-dev protobuf-compiler gcovr +# GTest, LSL, concurrentqueue and nlohmann/json are fetched automatically by CMake. ``` ### Configure & build +Use the [Ninja](https://ninja-build.org/) generator: it builds on all CPU cores by default and +rebuilds only what changed. + ```bash -cmake -B build +cmake -B build -G Ninja cmake --build build ./build/src/NeuronIDE config.json experiment.neuroz # waits for the EEG stream, then records and runs the experiment ``` +`-G Ninja` is needed only the first time you configure a build directory; CMake remembers it. +A `build/` already configured with the default generator (Make) can't be switched, so delete it +first (`rm -rf build`). If you stay on Make, build with `cmake --build build --parallel`; +without `--parallel`, Make compiles one file at a time. + `NeuronIDE` takes the path to a device `config.json` (defaults to `config.json` in the working directory). +#### Faster builds without clang-tidy + +By default clang-tidy runs on every file during the build and takes several times longer than +compiling it. While iterating, turn it off. The setting is remembered in `build/`; configure +with `=ON` to turn it back on. + +```bash +cmake -B build -G Ninja -DNEURON_IDE_ENABLE_CLANG_TIDY=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +cmake --build build +``` + +CI still runs clang-tidy and fails on any warning, so run the same check before pushing. It +reads the `compile_commands.json` that `CMAKE_EXPORT_COMPILE_COMMANDS` writes: + +```bash +git ls-files 'src/*.cpp' 'tests/*.cpp' | xargs -P "$(nproc)" -n 1 clang-tidy-20 -p build --quiet +``` + +Lines like `43669 warnings generated.` count warnings in third-party headers, which +`.clang-tidy` filters out; only `error:` lines are problems. + ### Tests ```bash @@ -444,13 +473,13 @@ ctest -L component --output-on-failure ```bash cmake --build build --target format ``` -- clang-tidy runs on every file as part of the build, which makes builds several times slower. - Configure with `-DNEURON_IDE_ENABLE_CLANG_TIDY=OFF` to skip it; CI checks it in a separate job. +- clang-tidy runs as part of the build; see + [Faster builds without clang-tidy](#faster-builds-without-clang-tidy) to turn it off. ### Coverage ```bash -cmake -B build -DNEURON_IDE_ENABLE_COVERAGE=ON +cmake -B build -G Ninja -DNEURON_IDE_ENABLE_COVERAGE=ON cmake --build build cmake --build build --target coverage # runs tests + gcovr xdg-open build/coverage/index.html