2

Overview

I am trying to run against my codebase, which is built using .

I am generating a compilation database as part of the build and passing this via the -p flag to clang-tidy - however, clang-tidy complains with error: 'static_1.h' file not found [clang-diagnostic-error] . This causes clang-tidy to exit with status 2, which fails my build.

I would like clang-tidy to be able to find all header files that are used as part of the build - ideally without using any additional tools other than CMake, or changing any INTERFACE targets to STATIC or SHARED.

Details

I have the following directory structure:

.
├── ignored
│   ├── include
│   │   └── ignored.h
│   ├── CMakeLists.txt
│   └── ignored.cpp
├── interface_1
│   ├── include
│   │   └── interface_1.h
│   └── CMakeLists.txt
├── static_1
│   ├── include
│   │   └── static_1.h
│   ├── CMakeLists.txt
│   └── static_1.cpp
├── CMakeLists.txt
└── main.cpp

And my CMake configuration looks like this: a CMake build graph showing that example is the top-level executable, which is linked privately to interface_1 (which is an interface target), which is linked via interface to static_1 (which is a static target). There is also the static target ignored, which is not linked to anything.

When I build the project using the following commands:

$ mkdir build
$ cmake -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles" -S . -B build
$ cd build
$ cmake --build . -- -j

The project compiles successfully, and there is the following compile_commands.json file inside build:

[
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/interface_1/include -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/example.dir/main.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/ignored",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/ignored/include -g -o CMakeFiles/ignored.dir/ignored.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/ignored/ignored.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/ignored/ignored.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/static_1",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/static_1.dir/static_1.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp"
}
]

However, when I run clang-tidy:

$ clang-tidy -p build main.cpp static_1/include/static_1.h static_1/static_1.cpp interface_1/include/interface_1.h

I get the following error:

/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/interface_1/include/interface_1.h:4:10: error: 'static_1.h' file not found [clang-diagnostic-error]
#include <static_1.h>
         ^~~~~~~~~~~~

Minimal example

Root directory

# CMakeLists.txt
cmake_minimum_required(VERSION 3.25.1)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

project(example)

add_subdirectory(ignored)
add_subdirectory(interface_1)
add_subdirectory(static_1)

add_executable(example main.cpp)
target_link_libraries(example PRIVATE interface_1)
// main.cpp
#include <interface_1.h>

int main() {
  return 0;
}

static_1

# static_1/CMakeLists.txt
add_library(static_1 STATIC static_1.cpp)
target_include_directories(static_1 PUBLIC include)
// static_1/static_1.cpp
#include <static_1.h>
// static_1/include/static_1.h
#ifndef STATIC_1_H
#define STATIC_1_H
#endif // STATIC_1_H

interface_1

# interface_1/CMakeLists.txt
add_library(interface_1 INTERFACE)
target_include_directories(interface_1 INTERFACE include)
target_link_libraries(interface_1 INTERFACE static_1)
// interface_1/include/interface_1.h
#ifndef INTERFACE_1_H
#define INTERFACE_1_H
#include <static_1.h>
#endif // INTERFACE_1_H

ignored

# ignored/CMakeLists.txt
add_library(ignored STATIC ignored.cpp)
target_include_directories(ignored PUBLIC include)
// ignored/ignored.cpp
#include <ignored.h>
// ignored/include/ignored.h
#ifndef IGNORED_H
#define IGNORED_H
#endif // IGNORED_H

What did I find / try?

I have found a few links that are along similar lines, but not exactly the same as my question:

  • These answers suggest:
    • using plain compiler includes, which does work - but I don't want to have to manually configure clang-tidy for this, I want it to derive the files automatically
    • using a compilation database, which I am already doing
    • disabling response files, which I tried but there was no change in output
  • These answers suggest:
    • using --header-filter - I tried --header-filter="*.h" with no effect

I also found these issues / MRs. They seem slightly related but I'm not sure they are the cause of the issue:

I have also found the tool compdb, which seems designed for this purpose. However, if possible, I would like to find a solution that only uses CMake, as I'm relying on the dependencies for my project being already packaged in Debian.

The confusing case of the ignored target

For extra information - I noticed the following, which unfortunately confused me even more.

Remove ignored target

There is the ignored folder above - when I delete this folder, the error disappears. Additionally, the compilation database becomes:

[
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/interface_1/include -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/example.dir/main.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/static_1",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/static_1.dir/static_1.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp"
}
]

Rename ignored target

Additionally, if I rename ignored to not_used throughout the codebase, the error also disappears.

[
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/interface_1/include -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/example.dir/main.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/not_used",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/not_used/include -g -o CMakeFiles/not_used.dir/not_used.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/not_used/not_used.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/not_used/not_used.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/static_1",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/static_1.dir/static_1.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp"
}
]

Make ignored an INTERFACE target

Finally, if I remove ignored.cpp and turn ignored into an INTERFACE target, the error also disappears.

[
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/interface_1/include -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/example.dir/main.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/main.cpp"
},
{
  "directory": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/build/static_1",
  "command": "/usr/bin/c++  -I/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/include -g -o CMakeFiles/static_1.dir/static_1.cpp.o -c /tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp",
  "file": "/tmp/tmp.XkimKdDJyo/clang-tidy-so-issue/static_1/static_1.cpp"
}
]

I presume these are all the results of interactions with the compilation database. However, I'm confused how this interaction would remove the error.

Versions

I am using the following versions:

Debian GNU/Linux 12 (bookworm)
cmake version 3.25.1
g++ (Debian 12.2.0-14) 12.2.0
Debian LLVM version 14.0.6 # (clang-tidy)
  • 1
    Very well-written question! I think the problem is you are passing `interface_1.h` on the `clang-tidy` command line. Since that file doesn't match anything in the compilation database, `clang-tidy` does not know what flags to use for it. Normally, you only pass the names of top-level source files (the `.cpp` files) to `clang-tidy`, and the headers get checked indirectly due to being included. Is there a specific reason for also passing the headers explicitly? – Scott McPeak Jul 15 '23 at 00:21
  • 1
    note: CMake has clang-tidy support built-in: https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_CLANG_TIDY.html – starball Jul 15 '23 at 08:42
  • Thanks both for your help. I tried with only the source files and this did work, although on experimenting I can create situations where an included header will not get checked by clang-tidy. I need more time to investigate this though, so @ScottMcPeak if you want to turn your comment into an answer, I'll be happy to accept it! – Matthew Fennell Jul 16 '23 at 16:58

1 Answers1

1

The main reason for the "file not found" error is you are passing the name of a header file directly on the clang-tidy command line.

That does not work because the file names passed to clang-tidy are looked up in compile_commands.json, but only top-level source files (.cpp files) have entries in that file. Since the header file is not found in the compilation database, clang-tidy does not use any -I flags, and consequently cannot find the things that header includes.

Instead, only pass the top-level file names to clang-tidy, which will also check the files that are included by it, transitively.

You've indicated (in a comment) that clang-tidy seems to be failing to report some issues in headers when it is used that way. Various checkers have heuristics and options regarding when to report things in header files, and that might be playing a role. You could ask a follow-up question with a specific instance in which this is happening.

Side note: When I reproduced the problem using your files, I found that cmake (version 3.15.3 in my case) did not generate a compile_commands.json file. Following a suggestion at CMake not generating compile_commands.json, I changed the operative line to:

set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")

and that worked. I mention this because, with the original line, cmake might not be consistently regenerating compile_commands.json, which could interfere with experimentation and diagnosis. (You already mentioned a mystery involving the ignored target, so perhaps that is related.)

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79