Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,17 +20,53 @@ 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 $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
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DNEURON_IDE_ENABLE_COVERAGE=ON
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 $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
run: cmake --build build --target neuronide_proto

- name: Run 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
41 changes: 36 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`) |
Expand Down Expand Up @@ -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
Expand All @@ -444,11 +473,13 @@ ctest -L component --output-on-failure
```bash
cmake --build build --target format
```
- 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
Expand Down
18 changes: 12 additions & 6 deletions cmake/StaticAnalysis.cmake
Original file line number Diff line number Diff line change
@@ -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 ---
Expand Down