Summary
The full clang-tidy gate (./scripts/test.sh tidy, i.e. the clang_tidy CTest test) analyses one translation unit at a time on any number of cores.
ament_clang_tidy defaults to --jobs 1, and our wrapper ros2_medkit_clang_tidy() in ros2_medkit_cmake/cmake/ROS2MedkitLinting.cmake passes neither a JOBS argument nor the ament_cmake_clang_tidy_JOBS variable that the upstream macro also honours. CTest-level parallelism does not compensate: each package registers a single clang_tidy test, so --ctest-args -j $(nproc) only overlaps packages, and the largest package still runs serially inside its own test.
Observed on a 16-core machine: the gateway package has 244 entries in its compile database, and a full run sits at 45+ minutes with a single clang-tidy process at 100% CPU while the other 15 cores idle.
Two paths are not affected and stay as they are:
- CI does not use this code path at all.
quality.yml drives run-clang-tidy (parallel by default) with clang-tidy-cache, and analyses only changed files on pull requests.
- The pre-push incremental script
scripts/clang-tidy-diff.sh loops serially, but pre-commit already invokes it with several file batches in parallel, so it is not the bottleneck.
So this is a local-developer cost only, paid every time someone runs the full gate before pushing.
Proposed solution
Pass the job count through the wrapper:
- add a
JOBS argument to ros2_medkit_clang_tidy() and a ROS2_MEDKIT_CLANG_TIDY_JOBS cache variable defaulting to the host core count;
- run the
tidy preset in scripts/test.sh with --ctest-args -j 1, because the parallelism now lives inside each package test - keeping both would multiply peak memory by the number of packages (7 packages register the test, and each clang-tidy process holds roughly half a gigabyte).
Additional context
The default stays conservative in the sense that it matches the machine it runs on, and a developer who wants a quieter box can set -DROS2_MEDKIT_CLANG_TIDY_JOBS=<n> at configure time.
Summary
The full clang-tidy gate (
./scripts/test.sh tidy, i.e. theclang_tidyCTest test) analyses one translation unit at a time on any number of cores.ament_clang_tidydefaults to--jobs 1, and our wrapperros2_medkit_clang_tidy()inros2_medkit_cmake/cmake/ROS2MedkitLinting.cmakepasses neither aJOBSargument nor theament_cmake_clang_tidy_JOBSvariable that the upstream macro also honours. CTest-level parallelism does not compensate: each package registers a singleclang_tidytest, so--ctest-args -j $(nproc)only overlaps packages, and the largest package still runs serially inside its own test.Observed on a 16-core machine: the gateway package has 244 entries in its compile database, and a full run sits at 45+ minutes with a single
clang-tidyprocess at 100% CPU while the other 15 cores idle.Two paths are not affected and stay as they are:
quality.ymldrivesrun-clang-tidy(parallel by default) withclang-tidy-cache, and analyses only changed files on pull requests.scripts/clang-tidy-diff.shloops serially, but pre-commit already invokes it with several file batches in parallel, so it is not the bottleneck.So this is a local-developer cost only, paid every time someone runs the full gate before pushing.
Proposed solution
Pass the job count through the wrapper:
JOBSargument toros2_medkit_clang_tidy()and aROS2_MEDKIT_CLANG_TIDY_JOBScache variable defaulting to the host core count;tidypreset inscripts/test.shwith--ctest-args -j 1, because the parallelism now lives inside each package test - keeping both would multiply peak memory by the number of packages (7 packages register the test, and eachclang-tidyprocess holds roughly half a gigabyte).Additional context
The default stays conservative in the sense that it matches the machine it runs on, and a developer who wants a quieter box can set
-DROS2_MEDKIT_CLANG_TIDY_JOBS=<n>at configure time.