diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 41090d7b..8e0b9220 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -34,6 +34,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ yq \ ca-certificates \ clang-format \ + ccache \ clang-tidy \ graphviz \ plantuml \ diff --git a/.devcontainer/setup-env.sh b/.devcontainer/setup-env.sh index b0774967..36b26c4f 100755 --- a/.devcontainer/setup-env.sh +++ b/.devcontainer/setup-env.sh @@ -5,6 +5,10 @@ set -e grep -Fxq "source /opt/ros/jazzy/setup.bash" ~/.bashrc || echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc grep -Fxq "source /opt/ros/jazzy/setup.zsh" ~/.zshrc || echo "source /opt/ros/jazzy/setup.zsh" >> ~/.zshrc +# ccache configuration for PCH compatibility +grep -Fxq 'export CCACHE_SLOPPINESS=pch_defines,time_macros' ~/.bashrc || echo 'export CCACHE_SLOPPINESS=pch_defines,time_macros' >> ~/.bashrc +grep -Fxq 'export CCACHE_SLOPPINESS=pch_defines,time_macros' ~/.zshrc || echo 'export CCACHE_SLOPPINESS=pch_defines,time_macros' >> ~/.zshrc + # Source the current shell to apply changes immediately if [ -n "$ZSH_VERSION" ]; then # Running in zsh @@ -22,6 +26,12 @@ if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then fi rosdep update +# Install pre-commit hooks (pre-commit for local, pre-push for clang-tidy) +if command -v pre-commit &>/dev/null; then + pre-commit install + pre-commit install --hook-type pre-push +fi + # Test installations echo "Testing installations..." echo "ROS2 Jazzy: $(test -f /opt/ros/jazzy/setup.bash && echo 'Installed' || echo 'Not found')" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 973eea61..685a5bc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,6 @@ jobs: fail-fast: false matrix: include: - - ros_distro: jazzy - os_image: ubuntu:noble - ros_distro: humble os_image: ubuntu:jammy - ros_distro: rolling @@ -41,6 +39,17 @@ jobs: with: required-ros-distributions: ${{ matrix.ros_distro }} + - name: Install ccache + run: apt-get install -y ccache + + - name: Cache ccache + uses: actions/cache@v4 + with: + path: /root/.cache/ccache + key: ccache-${{ matrix.ros_distro }}-${{ github.sha }} + restore-keys: | + ccache-${{ matrix.ros_distro }}- + - name: Install cpp-httplib from source (Humble) if: matrix.ros_distro == 'humble' run: | @@ -58,13 +67,9 @@ jobs: run: | apt-get update apt-get install -y ros-${{ matrix.ros_distro }}-test-msgs - # Linter tools only needed on Jazzy (clang versions differ across Ubuntu releases) - if [ "${{ matrix.ros_distro }}" = "jazzy" ]; then - apt-get install -y clang-format clang-tidy - fi source /opt/ros/${{ matrix.ros_distro }}/setup.bash rosdep update - # On Humble, skip the libcpp-httplib-dev rosdep key — the apt version (0.10.3) + # On Humble, skip the libcpp-httplib-dev rosdep key - the apt version (0.10.3) # is too old; cpp-httplib v0.14.3 is built from source in an earlier step. if [ "${{ matrix.ros_distro }}" = "humble" ]; then rosdep install --from-paths src --ignore-src -r -y --skip-keys="libcpp-httplib-dev" @@ -73,25 +78,207 @@ jobs: fi - name: Build packages + env: + CCACHE_DIR: /root/.cache/ccache + CCACHE_MAXSIZE: 500M + CCACHE_SLOPPINESS: pch_defines,time_macros run: | source /opt/ros/${{ matrix.ros_distro }}/setup.bash colcon build --symlink-install \ --cmake-args -DCMAKE_BUILD_TYPE=Release \ --event-handlers console_direct+ + ccache -s - - name: Run linters (clang-format, clang-tidy, etc.) - if: matrix.ros_distro == 'jazzy' + - name: Run unit and integration tests + timeout-minutes: 15 run: | source /opt/ros/${{ matrix.ros_distro }}/setup.bash source install/setup.bash + colcon test --return-code-on-test-failure \ + --ctest-args -LE linter \ + --event-handlers console_direct+ + + - name: Show test results + if: always() + run: colcon test-result --verbose + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.ros_distro }} + path: | + log/ + build/*/test_results/ + + jazzy-build: + runs-on: ubuntu-latest + container: + image: ubuntu:noble + timeout-minutes: 30 + defaults: + run: + shell: bash + + steps: + - name: Install Git + run: | + apt-get update + apt-get install -y git + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up ROS 2 Jazzy + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: jazzy + + - name: Install ccache + run: apt-get install -y ccache + + - name: Cache ccache + uses: actions/cache@v4 + with: + path: /root/.cache/ccache + key: ccache-jazzy-${{ github.sha }} + restore-keys: | + ccache-jazzy- + + - name: Install dependencies + run: | + apt-get update + apt-get install -y ros-jazzy-test-msgs clang-format clang-tidy + source /opt/ros/jazzy/setup.bash + rosdep update + rosdep install --from-paths src --ignore-src -y + + - name: Build packages + env: + CCACHE_DIR: /root/.cache/ccache + CCACHE_MAXSIZE: 500M + CCACHE_SLOPPINESS: pch_defines,time_macros + run: | + source /opt/ros/jazzy/setup.bash + colcon build --symlink-install \ + --cmake-args -DCMAKE_BUILD_TYPE=Release -DENABLE_CLANG_TIDY=ON \ + --event-handlers console_direct+ + ccache -s + + - name: Package build artifacts + run: tar cf /tmp/jazzy-build.tar build/ install/ + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: jazzy-build + path: /tmp/jazzy-build.tar + retention-days: 1 + + jazzy-lint: + needs: jazzy-build + runs-on: ubuntu-latest + container: + image: ubuntu:noble + timeout-minutes: 30 + defaults: + run: + shell: bash + + steps: + - name: Install Git + run: | + apt-get update + apt-get install -y git + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up ROS 2 Jazzy + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: jazzy + + - name: Install dependencies + run: | + apt-get update + apt-get install -y clang-format clang-tidy + source /opt/ros/jazzy/setup.bash + rosdep update + rosdep install --from-paths src --ignore-src -y + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: jazzy-build + + - name: Extract build artifacts + run: tar xf jazzy-build.tar && rm jazzy-build.tar + + - name: Run linters + run: | + source /opt/ros/jazzy/setup.bash + source install/setup.bash colcon test --return-code-on-test-failure \ --ctest-args -L linter \ --event-handlers console_direct+ + - name: Show test results + if: always() + run: colcon test-result --verbose + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-jazzy-lint + path: | + log/ + build/*/test_results/ + + jazzy-test: + needs: jazzy-build + runs-on: ubuntu-latest + container: + image: ubuntu:noble + timeout-minutes: 15 + defaults: + run: + shell: bash + + steps: + - name: Install Git + run: | + apt-get update + apt-get install -y git + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up ROS 2 Jazzy + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: jazzy + + - name: Install dependencies + run: | + apt-get update + apt-get install -y ros-jazzy-test-msgs + source /opt/ros/jazzy/setup.bash + rosdep update + rosdep install --from-paths src --ignore-src -y + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: jazzy-build + + - name: Extract build artifacts + run: tar xf jazzy-build.tar && rm jazzy-build.tar + - name: Run unit and integration tests - timeout-minutes: 15 run: | - source /opt/ros/${{ matrix.ros_distro }}/setup.bash + source /opt/ros/jazzy/setup.bash source install/setup.bash colcon test --return-code-on-test-failure \ --ctest-args -LE linter \ @@ -105,7 +292,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-${{ matrix.ros_distro }} + name: test-results-jazzy-test path: | log/ build/*/test_results/ @@ -133,6 +320,17 @@ jobs: with: required-ros-distributions: jazzy + - name: Install ccache + run: apt-get install -y ccache + + - name: Cache ccache + uses: actions/cache@v4 + with: + path: /root/.cache/ccache + key: ccache-coverage-${{ github.sha }} + restore-keys: | + ccache-coverage- + - name: Install dependencies run: | apt-get update @@ -142,11 +340,16 @@ jobs: rosdep install --from-paths src --ignore-src -r -y - name: Build packages with coverage + env: + CCACHE_DIR: /root/.cache/ccache + CCACHE_MAXSIZE: 500M + CCACHE_SLOPPINESS: pch_defines,time_macros run: | source /opt/ros/jazzy/setup.bash colcon build --symlink-install \ --cmake-args -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON \ --event-handlers console_direct+ + ccache -s - name: Run unit and integration tests for coverage run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a243c88..80693442 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,3 +60,15 @@ repos: language: system types_or: [c, c++, python, cmake] exclude: /vendored/ + + # ── Incremental clang-tidy (pre-push only) ──────────────────────── + # Requires: pre-commit install --hook-type pre-push + - repo: local + hooks: + - id: clang-tidy-diff + name: clang-tidy (changed files only) + entry: ./scripts/clang-tidy-diff.sh + language: system + types: [c++] + exclude: /vendored/ + stages: [pre-push] diff --git a/README.md b/README.md index 9b8acad4..df887788 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,13 @@ This section is for contributors and developers who want to build and test ros2_ ### Pre-commit Hooks This project uses [pre-commit](https://pre-commit.com/) to automatically run -`clang-format`, `flake8`, and other checks on staged files before each commit. +`clang-format`, `flake8`, and other checks on staged files before each commit, +plus an incremental clang-tidy check on `git push`. ```bash pip install pre-commit pre-commit install +pre-commit install --hook-type pre-push ``` To run all hooks against every file (useful after first setup): @@ -137,36 +139,42 @@ colcon build --symlink-install ### Testing -Run all tests: +Use the `scripts/test.sh` convenience script: ```bash source install/setup.bash -colcon test -colcon test-result --verbose +./scripts/test.sh # unit tests only (default) +./scripts/test.sh integ # integration tests only +./scripts/test.sh lint # linters (excluding clang-tidy) +./scripts/test.sh tidy # clang-tidy only (slow, ~8-10 min) +./scripts/test.sh all # everything +./scripts/test.sh # single test by CTest name regex ``` -Run linters: +You can pass extra colcon arguments after the preset: ```bash -source install/setup.bash -colcon test --ctest-args -L linters -colcon test-result --verbose +./scripts/test.sh unit --packages-select ros2_medkit_gateway ``` -Run only unit tests (everything except integration): +### Pre-push Hook (clang-tidy) + +An incremental clang-tidy check runs automatically on `git push` via pre-commit, analyzing only changed `.cpp` files. Typical run takes 5-30s vs 8-10 min for a full analysis. + +Setup: ```bash -source install/setup.bash -colcon test --ctest-args -E test_integration -colcon test-result --verbose +# Install the pre-push hook (if not already done above) +pre-commit install --hook-type pre-push + +# Build the merged compile_commands.json (required once after build) +./scripts/merge-compile-commands.sh ``` -Run only integration tests: +To run manually without pushing: ```bash -source install/setup.bash -colcon test --ctest-args -R test_integration -colcon test-result --verbose +./scripts/clang-tidy-diff.sh ``` ### Code Coverage @@ -205,18 +213,23 @@ Then open `coverage_html/index.html` in your browser. ### CI/CD All pull requests and pushes to main are automatically built and tested using GitHub Actions. -The CI workflow runs a build matrix across **ROS 2 Jazzy** (Ubuntu 24.04), **ROS 2 Humble** (Ubuntu 22.04), and **ROS 2 Rolling** (Ubuntu 24.04, allow-failure) and consists of the following jobs: +The CI workflow tests across **ROS 2 Jazzy** (Ubuntu 24.04), **ROS 2 Humble** (Ubuntu 22.04), and **ROS 2 Rolling** (Ubuntu 24.04, allow-failure): -**build-and-test** (matrix: Jazzy + Humble + Rolling): +**build-and-test** (matrix: Humble + Rolling): -- Full build and unit/integration tests on all distros +- Full build with ccache and unit/integration tests - Rolling jobs are allowed to fail (best-effort forward-compatibility) -- Code linting and formatting checks (clang-format, clang-tidy) — Jazzy only + +**jazzy-build** / **jazzy-lint** / **jazzy-test**: + +- `jazzy-build` compiles all packages with ccache and clang-tidy enabled +- `jazzy-lint` and `jazzy-test` run in parallel after the build completes +- Linting includes clang-format, clang-tidy, copyright, cmake-lint, and more **coverage** (Jazzy only): -- Builds with coverage instrumentation (Debug mode) -- Runs unit tests only (for stable coverage metrics) +- Builds with coverage instrumentation (Debug mode, ccache-enabled) +- Runs unit and integration tests (excluding linters) - Generates lcov coverage report (available as artifact) - Uploads coverage to Codecov (only on push to main) diff --git a/cmake/ROS2MedkitCcache.cmake b/cmake/ROS2MedkitCcache.cmake new file mode 100644 index 00000000..4a2f5bce --- /dev/null +++ b/cmake/ROS2MedkitCcache.cmake @@ -0,0 +1,38 @@ +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_guard(GLOBAL) + +# Auto-detect and enable ccache if available. +# Include this module early in CMakeLists.txt (before add_library/add_executable). +# +# Override with: -DCMAKE_CXX_COMPILER_LAUNCHER= / -DCMAKE_C_COMPILER_LAUNCHER= +# +# When using with precompiled headers (PCH), set the environment variable: +# export CCACHE_SLOPPINESS=pch_defines,time_macros +# Without this, ccache will have poor hit rates on PCH-using targets. + +if(NOT CMAKE_C_COMPILER_LAUNCHER OR NOT CMAKE_CXX_COMPILER_LAUNCHER) + find_program(_CCACHE ccache) + if(_CCACHE) + if(NOT CMAKE_C_COMPILER_LAUNCHER) + set(CMAKE_C_COMPILER_LAUNCHER "${_CCACHE}" CACHE STRING "C compiler launcher") + endif() + if(NOT CMAKE_CXX_COMPILER_LAUNCHER) + set(CMAKE_CXX_COMPILER_LAUNCHER "${_CCACHE}" CACHE STRING "C++ compiler launcher") + endif() + message(STATUS "ccache found: ${_CCACHE}") + endif() + unset(_CCACHE) +endif() diff --git a/cmake/ROS2MedkitLinting.cmake b/cmake/ROS2MedkitLinting.cmake new file mode 100644 index 00000000..c10f33b7 --- /dev/null +++ b/cmake/ROS2MedkitLinting.cmake @@ -0,0 +1,50 @@ +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_guard(GLOBAL) + +# Shared linting configuration for ros2_medkit packages. +# Include this in CMakeLists.txt BEFORE the if(BUILD_TESTING) block +# (alongside include(ROS2MedkitCcache) - CMAKE_MODULE_PATH is already set). +# +# Provides: +# option ENABLE_CLANG_TIDY (default OFF) +# function ros2_medkit_clang_tidy([HEADER_FILTER ] [TIMEOUT ]) + +option(ENABLE_CLANG_TIDY "Register clang-tidy as a CTest target" OFF) + +# Capture at include-time: inside a function CMAKE_CURRENT_LIST_DIR resolves to the caller. +set(_ROS2_MEDKIT_CLANG_TIDY_CONFIG "${CMAKE_CURRENT_LIST_DIR}/../.clang-tidy") + +function(ros2_medkit_clang_tidy) + if(NOT ENABLE_CLANG_TIDY) + return() + endif() + + cmake_parse_arguments(ARG "" "HEADER_FILTER;TIMEOUT" "" ${ARGN}) + + find_package(ament_cmake_clang_tidy REQUIRED) + + set(_args "${CMAKE_CURRENT_BINARY_DIR}" CONFIG_FILE "${_ROS2_MEDKIT_CLANG_TIDY_CONFIG}") + + if(ARG_HEADER_FILTER) + list(APPEND _args HEADER_FILTER "${ARG_HEADER_FILTER}") + endif() + + if(ARG_TIMEOUT) + list(APPEND _args TIMEOUT "${ARG_TIMEOUT}") + endif() + + ament_clang_tidy(${_args}) +endfunction() diff --git a/scripts/clang-tidy-diff.sh b/scripts/clang-tidy-diff.sh new file mode 100755 index 00000000..3221ba24 --- /dev/null +++ b/scripts/clang-tidy-diff.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Incremental clang-tidy: only analyses C++ source files passed as arguments. +# Designed for pre-commit which passes staged filenames. +# Requires: merged compile_commands.json (run ./scripts/merge-compile-commands.sh after build). +# +# Usage (standalone): +# ./scripts/clang-tidy-diff.sh src/ros2_medkit_gateway/src/config.cpp +# +# Usage (via pre-commit): automatic - pre-commit passes changed files. + +set -euo pipefail + +COMPILE_DB="build/compile_commands.json" + +if [ ! -f "$COMPILE_DB" ]; then + echo "Warning: No merged compile_commands.json found." + echo "Run: colcon build && ./scripts/merge-compile-commands.sh" + exit 0 +fi + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +CLANG_TIDY_CONFIG=".clang-tidy" +ERRORS=0 + +for file in "$@"; do + # Only process C++ source files (.cpp). Headers are checked transitively + # through their including .cpp files. CI runs full clang-tidy on all files. + case "$file" in + *.cpp) ;; + *) continue ;; + esac + + if [[ "$file" == *"/vendored/"* ]]; then + continue + fi + + # Resolve to absolute path for matching against compile_commands.json + ABS_FILE="$REPO_ROOT/$file" + if [ ! -f "$ABS_FILE" ]; then + ABS_FILE="$(realpath "$file" 2>/dev/null || echo "$file")" + fi + + echo "clang-tidy: $file" + if ! clang-tidy -p "$(dirname "$COMPILE_DB")" --config-file="$CLANG_TIDY_CONFIG" "$ABS_FILE"; then + ERRORS=$((ERRORS + 1)) + fi +done + +if [ "$ERRORS" -gt 0 ]; then + echo "clang-tidy found issues in $ERRORS file(s)" + exit 1 +fi diff --git a/scripts/merge-compile-commands.sh b/scripts/merge-compile-commands.sh new file mode 100755 index 00000000..697f5e4a --- /dev/null +++ b/scripts/merge-compile-commands.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Merges per-package compile_commands.json files into a single database. +# Run after colcon build to enable project-wide clang-tidy analysis. +# +# Usage: ./scripts/merge-compile-commands.sh +# Output: build/compile_commands.json + +set -euo pipefail + +MERGED="build/compile_commands.json" + +# Find all per-package compile_commands.json files +DATABASES=(build/*/compile_commands.json) + +if [ ${#DATABASES[@]} -eq 0 ] || [ ! -f "${DATABASES[0]}" ]; then + echo "Error: No compile_commands.json found in build/*/." + echo "Run 'colcon build' first." + exit 1 +fi + +# Merge using jq if available, otherwise use Python +if command -v jq &>/dev/null; then + jq -s 'add' "${DATABASES[@]}" > "$MERGED" +else + python3 -c " +import json, sys +entries = [] +for f in sys.argv[2:]: + with open(f) as fh: + entries.extend(json.load(fh)) +with open(sys.argv[1], 'w') as fh: + json.dump(entries, fh, indent=2) +" "$MERGED" "${DATABASES[@]}" +fi + +if command -v jq &>/dev/null; then + COUNT=$(jq length "$MERGED") +else + COUNT=$(python3 -c "import json; print(len(json.load(open('$MERGED'))))" 2>/dev/null || echo '?') +fi +echo "Merged ${#DATABASES[@]} databases into $MERGED ($COUNT entries)" diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 00000000..1b44a664 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Copyright 2026 bburda +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +# Quick-test presets for ros2_medkit development. +# Usage: ./scripts/test.sh [preset] [extra colcon test args...] +# +# Presets: +# unit - Unit tests only, no linters, no integration (DEFAULT) +# integ - Integration tests only +# lint - Linters only (clang-format, copyright, cmake-lint; NO clang-tidy) +# tidy - clang-tidy only (slow, ~8-10 min) +# all - Everything (equivalent to bare colcon test) +# - Run a single test by CTest name regex +# +# Examples: +# ./scripts/test.sh # unit tests only +# ./scripts/test.sh integ # integration tests only +# ./scripts/test.sh lint # fast linters only +# ./scripts/test.sh test_health_handler # single test +# ./scripts/test.sh unit --packages-select ros2_medkit_gateway + +PRESET="${1:-unit}" +shift 2>/dev/null || true + +COMMON_ARGS=(--event-handlers console_direct+ --parallel-workers "$(nproc)" --return-code-on-test-failure) + +# Run tests, capture exit code so we always show results even on failure. +set +e +case "$PRESET" in + unit) + echo "==> Running unit tests (no linters, no integration)" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" -LE "linter|integration" \ + "$@" + ;; + integ) + echo "==> Running integration tests only" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" -L integration \ + "$@" + ;; + lint) + echo "==> Running linters (excluding clang-tidy)" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" -L linter -E "clang_tidy" \ + "$@" + ;; + tidy) + echo "==> Running clang-tidy (this will take a while)" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" -R "clang_tidy" \ + "$@" + ;; + all) + echo "==> Running all tests" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" \ + "$@" + ;; + *) + echo "==> Running tests matching: $PRESET" + colcon test "${COMMON_ARGS[@]}" \ + --ctest-args -j "$(nproc)" -R "$PRESET" \ + "$@" + ;; +esac +TEST_EXIT=$? +set -e + +echo "" +echo "==> Results:" +colcon test-result --verbose || true +exit "$TEST_EXIT" diff --git a/src/ros2_medkit_diagnostic_bridge/CMakeLists.txt b/src/ros2_medkit_diagnostic_bridge/CMakeLists.txt index 3899734d..f392b14f 100644 --- a/src/ros2_medkit_diagnostic_bridge/CMakeLists.txt +++ b/src/ros2_medkit_diagnostic_bridge/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2026 mfaferek93 +# Copyright 2026 mfaferek93, bburda # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,10 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") +include(ROS2MedkitCcache) +include(ROS2MedkitLinting) + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion) endif() @@ -98,10 +102,11 @@ if(BUILD_TESTING) find_package(launch_testing_ament_cmake REQUIRED) set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format") - set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint) + list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint ament_cmake_clang_tidy) ament_lint_auto_find_test_dependencies() + ros2_medkit_clang_tidy() + # Unit tests ament_add_gtest(test_diagnostic_bridge test/test_diagnostic_bridge.cpp) target_link_libraries(test_diagnostic_bridge diagnostic_bridge_lib) diff --git a/src/ros2_medkit_fault_manager/CMakeLists.txt b/src/ros2_medkit_fault_manager/CMakeLists.txt index 6b49ce5d..7b7e3329 100644 --- a/src/ros2_medkit_fault_manager/CMakeLists.txt +++ b/src/ros2_medkit_fault_manager/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2025 mfaferek93 +# Copyright 2025-2026 mfaferek93, bburda # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Multi-distro compatibility (Humble / Jazzy / Rolling) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") include(ROS2MedkitCompat) +include(ROS2MedkitCcache) +include(ROS2MedkitLinting) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion) @@ -118,10 +120,11 @@ if(BUILD_TESTING) find_package(launch_testing_ament_cmake REQUIRED) set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format") - set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint) + list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint ament_cmake_clang_tidy) ament_lint_auto_find_test_dependencies() + ros2_medkit_clang_tidy() + # Unit tests ament_add_gtest(test_fault_manager test/test_fault_manager.cpp) target_link_libraries(test_fault_manager fault_manager_lib) diff --git a/src/ros2_medkit_fault_reporter/CMakeLists.txt b/src/ros2_medkit_fault_reporter/CMakeLists.txt index 66debb79..3d7fc61f 100644 --- a/src/ros2_medkit_fault_reporter/CMakeLists.txt +++ b/src/ros2_medkit_fault_reporter/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2025 mfaferek93 +# Copyright 2025-2026 mfaferek93, bburda # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,10 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") +include(ROS2MedkitCcache) +include(ROS2MedkitLinting) + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion) endif() @@ -85,10 +89,11 @@ if(BUILD_TESTING) find_package(launch_testing_ament_cmake REQUIRED) set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format") - set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint) + list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint ament_cmake_clang_tidy) ament_lint_auto_find_test_dependencies() + ros2_medkit_clang_tidy() + # Unit tests ament_add_gtest(test_local_filter test/test_local_filter.cpp) target_link_libraries(test_local_filter fault_reporter_lib) diff --git a/src/ros2_medkit_gateway/CMakeLists.txt b/src/ros2_medkit_gateway/CMakeLists.txt index 3010a408..9be63260 100644 --- a/src/ros2_medkit_gateway/CMakeLists.txt +++ b/src/ros2_medkit_gateway/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Multi-distro compatibility (Humble / Jazzy / Rolling) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") include(ROS2MedkitCompat) +include(ROS2MedkitCcache) +include(ROS2MedkitLinting) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion) @@ -162,6 +164,25 @@ target_link_libraries(gateway_lib ${CMAKE_DL_LIBS} ) +# Precompiled headers - heavy headers used across most translation units. +# Speeds up full builds by ~30-40%. Disable with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON. +target_precompile_headers(gateway_lib PRIVATE + + + + + + + + + + + + + + +) + # Gateway node executable add_executable(gateway_node src/main.cpp) target_link_libraries(gateway_node gateway_lib) @@ -192,14 +213,8 @@ install(PROGRAMS scripts/get_type_schema.py scripts/generate_dev_certs.sh if(BUILD_TESTING) # Linting and code quality checks find_package(ament_lint_auto REQUIRED) - find_package(ament_cmake_clang_tidy REQUIRED) - - # Use custom clang-format and clang-tidy configs from repo root + # Use custom clang-format config from repo root set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format") - set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - - # Limit clang-tidy to only report issues from our source files (not vendored deps) - set(ament_cmake_clang_tidy_HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/(include|src|test)/") # Exclude linters that don't work well with vendored dependencies: # - uncrustify/cpplint: conflicts with clang-format @@ -244,12 +259,8 @@ if(BUILD_TESTING) CONFIG_FILE "${ament_cmake_clang_format_CONFIG_FILE}" ) - # Configure clang-tidy manually with increased timeout (1500s instead of default 300s) - # This is needed because the project has many files and clang-tidy analysis takes time - ament_clang_tidy( - "${CMAKE_CURRENT_BINARY_DIR}" - CONFIG_FILE "${ament_cmake_clang_tidy_CONFIG_FILE}" - HEADER_FILTER "${ament_cmake_clang_tidy_HEADER_FILTER}" + ros2_medkit_clang_tidy( + HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/(include|src|test)/" TIMEOUT 1500 ) diff --git a/src/ros2_medkit_serialization/CMakeLists.txt b/src/ros2_medkit_serialization/CMakeLists.txt index 654d49e4..47f4b5c2 100644 --- a/src/ros2_medkit_serialization/CMakeLists.txt +++ b/src/ros2_medkit_serialization/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Multi-distro compatibility (Humble / Jazzy / Rolling) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") include(ROS2MedkitCompat) +include(ROS2MedkitCcache) +include(ROS2MedkitLinting) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) @@ -105,7 +107,6 @@ install(DIRECTORY include/ if(BUILD_TESTING) find_package(ament_cmake_gtest REQUIRED) find_package(ament_lint_auto REQUIRED) - find_package(ament_cmake_clang_tidy REQUIRED) find_package(std_msgs REQUIRED) find_package(std_srvs REQUIRED) find_package(geometry_msgs REQUIRED) @@ -140,11 +141,7 @@ if(BUILD_TESTING) "test/test_type_cache.cpp" ) - # Configure clang-tidy manually for non-vendored files only - set(_clang_tidy_config "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - ament_clang_tidy( - "${CMAKE_CURRENT_BINARY_DIR}" - CONFIG_FILE "${_clang_tidy_config}" + ros2_medkit_clang_tidy( HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/include/ros2_medkit_serialization/[^v].*" TIMEOUT 300 )