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
13 changes: 13 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
Checks: >
bugprone-*,
clang-analyzer-*,
-clang-analyzer-cplusplus.NewDelete,
-clang-analyzer-cplusplus.NewDeleteLeaks,
-bugprone-reserved-identifier,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions

WarningsAsErrors: "*"
HeaderFilterRegex: "src/.*"
ExcludeHeaderFilterRegex: ".*(qcustomplot|ui_[a-z]+).h"
99 changes: 51 additions & 48 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,69 @@ on:
- master

jobs:
coverage:
# coverage:
# runs-on: ubuntu-latest
# container: docker://jgeudens/qt-linux:6.8.3_build_1
# steps:
# - name: Checkout
# uses: actions/checkout@v6.0.0

# - name: Start display server
# run: bash /start.sh &

# - name: configure safe directory for git
# run: git config --global --add safe.directory "${GITHUB_WORKSPACE}"

# - name: Prepare environment for Ninja build with coverage
# run: |
# cmake . -GNinja -DUSE_GCOV=ON
# ninja

# - name: Generate gcovr coverage report
# run: |
# ctest
# gcovr --gcov-ignore-parse-errors --cobertura cobertura.xml

# - name: Run codacy-coverage-reporter
# uses: codacy/codacy-coverage-reporter-action@v1
# with:
# api-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
# language: 'CPP'
# coverage-reports: cobertura.xml
# env:
# PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
# if: ${{ !!env.PROJECT_TOKEN }}

clazy:
runs-on: ubuntu-latest
container: docker://jgeudens/qt-linux:6.8.3_build_1
steps:
- name: Checkout
uses: actions/checkout@v6.0.0

- name: Start display server
run: bash /start.sh &
- uses: actions/checkout@v6.0.0

- name: configure safe directory for git
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}

- name: Prepare environment for Ninja build with coverage
run: |
cmake . -GNinja -DUSE_GCOV=ON
ninja
- name: Configure safe directory for git
run: git config --global --add safe.directory "${GITHUB_WORKSPACE}"

- name: Generate gcovr coverage report
- name: Install clazy
run: |
ctest
gcovr --gcov-ignore-parse-errors --cobertura cobertura.xml
apt-get update
apt-get install -y clazy

- name: Run codacy-coverage-reporter
uses: codacy/codacy-coverage-reporter-action@v1
with:
api-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
language: 'CPP'
coverage-reports: cobertura.xml
env:
PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
if: ${{ !!env.PROJECT_TOKEN }}
- name: Run clazy
run: bash scripts/run_clazy.sh

sonarqube:
clang-tidy:
runs-on: ubuntu-latest
container: docker://jgeudens/qt-linux:6.8.3_build_1
env:
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6.0.0
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0

- name: Install Build Wrapper
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v7.0
- name: Run build-wrapper
- name: Configure safe directory for git
run: git config --global --add safe.directory "${GITHUB_WORKSPACE}"

- name: Install clang-tidy
run: |
# here goes your compilation wrapped with build-wrapper; See https://docs.sonarcloud.io/advanced-setup/languages/c-c-objective-c/#analysis-steps-using-build-wrapper for more information
cmake . -GNinja
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ninja
if: ${{ !!env.SONAR_TOKEN && !!env.GITHUB_TOKEN }}
apt-get update
apt-get install -y clang-tidy

- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v7.0
with:
# Consult https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/scanners/sonarscanner/ for more information and options
args: >
--define "sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json"
if: ${{ !!env.SONAR_TOKEN && !!env.GITHUB_TOKEN }}
- name: Run clang-tidy
run: bash scripts/run_clang_tidy.sh
67 changes: 67 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ModbusScope — Claude Code Guide

## Build

```bash
mkdir -p build && cd build
cmake -GNinja ..
ninja
```

Run tests:

```bash
ctest --output-on-failure
```

Run pre-commit checks:

```bash
./scripts/pre-commit.sh
```

**Requirements:** Qt 6, C++20, Ninja. Compiler flags: `-Wall -Wextra -Werror`.

## Project Structure

```
src/
├── communication/ Modbus protocol layer (ModbusPoll, ModbusMaster, ModbusConnection)
├── models/ Data models (SettingsModel, GraphDataModel, DiagnosticModel, Device, Connection)
├── datahandling/ Expression parsing, graph data processing
├── importexport/ CSV export, MBS project files, MBC device config import
├── dialogs/ UI dialogs and MainWindow
├── customwidgets/ Qt custom widgets
├── graphview/ Graph rendering (qcustomplot)
└── util/ Logging (ScopeLogging), formatting helpers
tests/ Google Test + Qt Test; test files named tst_*.cpp
libraries/ Vendored: qcustomplot, muparser
```

## Architecture

- **SettingsModel** is the central config store — connections, devices, poll time
- **ModbusPoll** orchestrates polling: iterates connections, delegates to **ModbusMaster** per connection
- **ModbusMaster** manages one connection's read cycle via **ModbusConnection** (Qt Modbus)
- **GraphDataHandler** processes raw Modbus results and applies user expressions
- Models emit Qt signals; UI layers observe them — no direct model→UI calls

## Code Style

Enforced by `.clang-format` (Mozilla-based, C++20):

- 4 spaces, no tabs; 120-char line limit
- Braces on new line for classes/functions
- Pointer left-aligned: `Type* ptr`
- Private members: `_camelCase`; pointer members: `_pName`
- Classes: `PascalCase`; methods: `camelCase`
- Use Qt doxygen style for comments

## Key Conventions

- Prefer readability and maintainability over using the latest C++ features (avoid syntax sugar that may be less familiar to new contributors).
- Tests use `QCOMPARE` / `QTEST_GUILESS_MAIN` (Qt Test)
- Make sure to document public APIs with brief Doxygen comments
- Update json-rpc schema spec when updating json-rpc related code
- Only use early return for error handling, avoid deep nesting
- When fixing a bug, add a test that reproduces the issue before implementing the fix.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(CMAKE_AUTORCC ON)

option(USE_GCOV "Enable gcov support" OFF)
Expand Down
29 changes: 29 additions & 0 deletions scripts/run_clang_format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
set -euo pipefail

CHECK_MODE=false
if [ "${1:-}" = "--check" ]; then
CHECK_MODE=true
fi

CHANGED_FILES=$(
{ git diff --name-only --diff-filter=d HEAD; git ls-files --others --exclude-standard; } \
| grep -E '\.(cpp|h)$' \
| sort -u \
|| true
)

if [ -z "$CHANGED_FILES" ]; then
echo "No changed C++ files."
exit 0
fi

echo "=== Files ==="
echo "$CHANGED_FILES"
echo "=== Running clang-format ==="

if [ "$CHECK_MODE" = true ]; then
echo "$CHANGED_FILES" | xargs clang-format --dry-run --Werror
else
echo "$CHANGED_FILES" | xargs clang-format -i
fi
17 changes: 17 additions & 0 deletions scripts/run_clang_tidy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
set -euo pipefail

BUILD_DIR="${1:-build}"
QT_PREFIX="${2:-/opt/Qt/6.8.3/gcc_64}"

echo "=== Configuring (compile_commands.json) ==="
cmake -GNinja \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_PREFIX_PATH="${QT_PREFIX}" \
-B "${BUILD_DIR}"

echo "=== Generating autogen headers ==="
ninja -C "${BUILD_DIR}" src/ScopeSource_autogen

echo "=== Running clang-tidy ==="
run-clang-tidy -quiet -p "${BUILD_DIR}" -j "$(nproc)" "$(pwd)/src/.*\.cpp\$" 2>/dev/null
18 changes: 18 additions & 0 deletions scripts/run_clazy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail

BUILD_DIR="${1:-build}"
QT_PREFIX="${2:-/opt/Qt/6.8.3/gcc_64}"

echo "=== Configuring (compile_commands.json) ==="
cmake -GNinja \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_PREFIX_PATH="${QT_PREFIX}" \
-B "${BUILD_DIR}"
Comment on lines +8 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Anchor all paths to the repository root instead of the caller’s working directory.

Line 8 configures CMake without an explicit source dir, and Line 17 scans $(pwd)/src. This breaks when the script is launched outside the repo root.

🔧 Proposed fix
 #!/bin/bash
 set -euo pipefail
 
-BUILD_DIR="${1:-build}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+BUILD_DIR="${1:-build}"
 QT_PREFIX="${2:-/opt/Qt/6.8.3/gcc_64}"
+
+if [[ "${BUILD_DIR}" != /* ]]; then
+  BUILD_DIR="${REPO_ROOT}/${BUILD_DIR}"
+fi
 
 echo "=== Configuring (compile_commands.json) ==="
 cmake -GNinja \
     -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
     -DCMAKE_PREFIX_PATH="${QT_PREFIX}" \
+    -S "${REPO_ROOT}" \
     -B "${BUILD_DIR}"
@@
 echo "=== Running clazy ==="
-find "$(pwd)/src" -name "*.cpp" -print0 | \
+find "${REPO_ROOT}/src" -name "*.cpp" -print0 | \
     xargs -0 -P "$(nproc)" -I{} clazy-standalone --only-qt --header-filter="src/.*" -p "${BUILD_DIR}" {}

Also applies to: 17-18

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_clazy.sh` around lines 8 - 11, The script uses the caller's
working directory for paths (cmake -B "${BUILD_DIR}" and scanning $(pwd)/src),
which breaks when run outside the repo root; compute a repository root (e.g.,
REPO_ROOT via git rev-parse --show-toplevel or dirname "$0") and anchor all
paths to it: pass the repo root (or repo_root/src) as cmake's source dir and
prefix BUILD_DIR and any scans (replace $(pwd)/src) with
"${REPO_ROOT}/${BUILD_DIR}" and "${REPO_ROOT}/src" respectively, keeping
QT_PREFIX usage unchanged.


echo "=== Generating autogen headers ==="
ninja -C "${BUILD_DIR}" src/ScopeSource_autogen

echo "=== Running clazy ==="
find "$(pwd)/src" -name "*.cpp" -print0 | \
xargs -0 -P "$(nproc)" -I{} clazy-standalone --only-qt --header-filter="src/.*" -p "${BUILD_DIR}" {}
29 changes: 29 additions & 0 deletions scripts/run_precommit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}/.."
PASS=0
FAIL=0

run_check()
{
local name="$1"
shift
echo "=== $name ==="
if "$@"; then
echo "--- $name: PASS"
((PASS++))
else
echo "--- $name: FAIL"
((FAIL++))
fi
echo ""
}

run_check "clang-format" bash "${SCRIPT_DIR}/run_clang_format.sh"
run_check "clang-tidy" bash "${SCRIPT_DIR}/run_clang_tidy.sh"
run_check "clazy" bash "${SCRIPT_DIR}/run_clazy.sh"

echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
[ "${FAIL}" -eq 0 ]
18 changes: 8 additions & 10 deletions src/communication/communicationstats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@

const uint32_t CommunicationStats::_cUpdateTime = 500;

CommunicationStats::CommunicationStats(GraphDataModel* pGraphDataModel, quint32 sampleCalculationSize, QObject *parent)
: QObject{parent}, _pGraphDataModel (pGraphDataModel), _sampleCalculationSize(sampleCalculationSize)
CommunicationStats::CommunicationStats(GraphDataModel* pGraphDataModel, quint32 sampleCalculationSize, QObject* parent)
: QObject{ parent }, _pGraphDataModel(pGraphDataModel), _sampleCalculationSize(sampleCalculationSize)
{
connect(this, &CommunicationStats::triggerRunTimeUpdate, _pGraphDataModel, &GraphDataModel::communicationTimeStatsChanged);
connect(this, &CommunicationStats::triggerRunTimeUpdate, _pGraphDataModel,
&GraphDataModel::communicationTimeStatsChanged);
}

void CommunicationStats::updateTimingInfo()
{
QList<double> diffList;
quint32 timeMedian;

if (_pGraphDataModel->size() == 0u)
{
timeMedian = 0u;
}
else if (_pGraphDataModel->dataMap(0)->size() <= 1)
if (_pGraphDataModel->size() == 0u || _pGraphDataModel->dataMap(0)->size() <= 1)
{
timeMedian = 0u;
}
Expand Down Expand Up @@ -71,7 +68,8 @@ void CommunicationStats::stop()

void CommunicationStats::incrementCommunicationStats(quint32 successes, quint32 errors)
{
_pGraphDataModel->setCommunicationStats(_pGraphDataModel->communicationSuccessCount() + successes, _pGraphDataModel->communicationErrorCount() + errors);
_pGraphDataModel->setCommunicationStats(_pGraphDataModel->communicationSuccessCount() + successes,
_pGraphDataModel->communicationErrorCount() + errors);
}

void CommunicationStats::updateRuntime()
Expand All @@ -88,7 +86,7 @@ void CommunicationStats::updateCommunicationStats(ResultDoubleList resultList)
{
quint32 error = 0;
quint32 success = 0;
for(const auto &result: resultList)
for (const auto& result : resultList)
{
result.isValid() ? success++ : error++;
}
Expand Down
Loading