Clang-tidy on Windows

As I am

  • not that proficient in PowerShell

  • prefer to reuse as much as possible existing solutions

  • have a bash shell on Windows thanks to Cygwin

I decided to use clang-tidy from Cygwin. Unfortunately, it is not packaged for it.

But Cygwin provides the necessary tools for interacting with "native" Windows executables, and as it turns out, it can be done easily.

First, download and install the LLVM suite on Windows. If you can, use the 64-bit executable, and I would not recommend adding the tools to the path.

The Python script run-clang-tidy.py is missing but can be downloaded from github.

In my current configuration, I have the official Python3 port for Windows, and not the Python version packaged for Cygwin (as I like to avoid having duplicate programs).

Now that we have all the tools, it’s time to see how to put them together.

From a Cygwin prompt, let’s check what checks are available:

'C:/Program Files/LLVM/bin/clang-tidy.exe' --list-checks -checks='*'

As a first test, let’s use modernize-use-nullptr.

run-clang-tidy.py is flexible and can, fortunately, accept toolchains outside of PATH. As I’m using the Windows version of Python (the non-Cygwin version), I need to use Windows paths:

python3 "<path to run-clang-tidy.py>" -clang-apply-replacements 'C:/Program Files/LLVM/bin/clang-apply-replacements.exe' -clang-tidy-binary 'C:/Program Files/LLVM/bin/clang-tidy.exe' -header-filter='.*' -checks='-*,modernize-use-nullptr' -p <dir-containing-compile_commands.json> -fix

Note: on a GNU/Linux distribution, with all tools installed from the package manager, the command would look like

run-clang-tidy -header-filter='.*' -checks='-*,modernize-use-nullptr' -p <dir-containing-compile_commands.json> -fix

To simplify the execution of clang-tidy, it is possible to add a script similar to the following in your $PATH:

Save as run-clang-tidy in your Cygwin $PATH
#!/bin/sh

clang_bin_dir='C:/Program Files/LLVM/bin'
clang_apply_replacements="$clang_bin_dir/clang-apply-replacements.exe";
clang_tidy_binary="$clang_bin_dir/clang-tidy.exe";
python3 "<path to run-clang-tidy.py>" -clang-apply-replacements "$clang_apply_replacements" -clang-tidy-binary "$clang_tidy_binary" "$@";

Note that edits are made at the end, thus better not to touch any file in the meantime.

Unfortunately, this process can take a lot of time on bigger projects, unless you have multiple copies of your code, you might want to take a break. Thus I prefer to save the result on an external file:

cd <directory where compile_commands.json is located>
python3 "<path to run-clang-tidy.py>" -clang-apply-replacements 'C:/Program Files/LLVM/bin/clang-apply-replacements.exe' -clang-tidy-binary 'C:/Program Files/LLVM/bin/clang-tidy.exe' -header-filter='.*' -checks='-*,modernize-use-nullptr' -export-fixes ./modernize-use-nullptr.yml

# or, thanks to the script
run-clang-tidy -header-filter='.*' -checks='-*,modernize-use-nullptr' -export-fixes ./modernize-use-nullptr.yml

Notice that in this case, you might need to install the optional dependency pyyaml.

It can be installed through pip from a PowerShell console (administrator rights are not required, but in that case, you might need to adjust some paths)

& 'C:\Program Files\Python38\python.exe' -m pip install pyyaml

Again, the output is written at the end of the process. But at least it is possible to

  • continue working/editing our source files. At worst, some proposed fix will not apply cleanly

  • review the changes before applying them

Of course, it is possible to execute more than just one type of analysis:

cd <directory where compile_commands.json is located>
run-clang-tidy -header-filter='.*' -checks='-*,modernize*' -export-fixes ./modernize.yml

How to skip folders or restrict changes to a given folder?

clang-tidy uses as input compile_command.json. This means that by editing it, it is possible to exclude files. For example, we do not want to spend time analyzing files we cannot change, like the dependencies of our project.

Taking advantage of a revision control system and undoing changes to files we do not want to edit is suboptimal as we are still spending time analyzing those files. For a bigger project with many dependencies, this could take a lot of time.

Editing the compile_command.json file by hand is a tedious and error-prone task, fortunately, we can use jq (already packaged in Cygwin), for this task.

Supposing that our external dependencies are all located in an extern folder, we can filter those .json entries out:

jq -c '[.[] | select(.file | contains("extern") | not)]'

Notice that clang-tidy currently accepts only compile_command.json as a compilation database name. So either save a copy somewhere else or overwrite the file.

cat compile_command.json | jq -c '[.[] | select(.file | contains("extern") | not)]' > /tmp/compile_command.json

# or

cat compile_command.json | jq -c '[.[] | select(.file | contains("extern") | not)]' | sponge compile_command.json

If we want to analyze only a specific folder, for example, we want only to apply changes to a library located in the folder libfoo:

cat compile_command.json | jq -c '[.[] | select(.file | contains("libfoo"))]' > /tmp/compile_command.json

Bonus

Cppcheck can use the compile_command.json database too, so being able to edit it efficiently is a must when using both analyzers.

IDE integration:

Integrating it with the IDE has the advantage that it can make some interactive tasks easier. Nevertheless, I prefer to run the analysis from the command line, as it is generally an intensive task, and in the meantime, I would like to use the IDE for something else, because it makes it trivial to automate tasks.

QtCreator 🗄️, MSVC 🗄️ and other IDE and text-editors 🗄️ provide the possibility to integrate clang-tidy (and possibly other static-analyzers).

The great thing about QtCreator is that it makes it possible to import the .yml file generated with run-clang-tidy. I do not know if another IDE provides a similar feature.

QtCreator also has another advantage; it makes it possible to create a compile_command.json file (at least for CMake projects), even for Visual Studio Solutions. Normally it is possible to create it directly with CMake (with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, which is ignored when creating a visual studio solution) or with other programs.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.