Whats the idiomatic way to run clang-tidy on header files with CMake?

I would like to run clang-tidy on header files. I would also like to apply the fixits that are automatically generated. This is not trivial, because my header files are included many times throughout my codebase, so my fixits tend to get applied many times which leads to nonsense code (NULL → nullptrnullptr, and similar).

CMake seems to expect me to use CMAKE_<LANG>_CLANG_TIDY to point to the clang-tidy binary itself, and CMAKE_<LANG>_CLANG_TIDY_EXPORT_FIXES_DIR to dump the fixits to a set of yaml files. There doesn’t seem to be a wrapper for then applying those fixes, so I run clang-apply-replacements manually. Replacements in header files get applied many times.

LLVM seem to have a different solution. They supply a script called run-clang-tidy, which does three things:

  1. Runs clang-tidy (using the compile_commands.json file rather than running an entire build),
  2. Collects all the yaml files into one enormous yaml file,
  3. Calls clang-apply-replacements on the enormous yaml file, which seems to de-duplicate the fixes and apply them properly.

Unfortunately, the run-clang-tidy script always runs clang-tidy - there is no “only apply fixes” option. It therefore doesn’t play very nicely with CMake’s implementation.

It seems like CMake and LLVM have different opinions about how best to run clang-tidy in these circumstances. Have I missed something?

This seems like a useful feature to request.

compile_commands.json doesn’t really support a way to specify headers as they’re not compiled. CMake prefers to run clang-tidy with the compiler so that it is integrated into the build graph (e.g., a header might be generated and just running clang-tidy may fail if the header is not made up-to-date before running clang-tidy.

This seems like a useful feature to request.

Perhaps the first thing to do is to ask LLVM to look again at the scripting around clang-tidy. It seems like the obvious place for this de-duplication to take place is perhaps in clang-apply-replacements itself? Or maybe run-clang-tidy can be made more modular (there’s already a comment saying it should be merged with clang-tidy-diff.py…). Once we have the right tools on the LLVM end, integrating them into CMake becomes easier and both projects end up pulling in the same direction. I’ll raise something with LLVM tomorrow (unless you know an LLVM person on this forum you can tag?).

compile_commands.json doesn’t really support a way to specify headers

For clarity, what I think happens is that clang-tidy runs per Translation Unit. It’s able to relate different parts of the TU to source files. If you run clang-tidy on foo.cpp, clang-tidy can always “see” the code from the includes, and it decides whether to complain about it based on various HeaderFilter options.

Our build is very expensive, so being able to run clang-tidy without invoking the whole build is attractive.
I’m sure I’ve seen a request for some sort of “null” build before, where instead of invoking the compiler CMake just touches files and runs custom targets… This might be a bit of a side-line. Let’s talk to the LLVM people first.

LLVM discourse topic here

Thanks; I’ve subscribed myself.

That causes consistency problems because if you build after doing a “null” build, you end up with all kinds of missing symbols because the libraries are “nothing”. There have been thoughts of support along these lines, but it’s not clear when they’ll be done (ideally 2024).

Is there a workaround? Even the supposed workflow to run clang-tidy manually on sources given a compilation database and supply the path to a single header do not seem to work.

Have you tried the --header-filter= option?

No, I’ll give it a try and report back. Thanks for the tip!