How to use the tool include-what-you-use together with CMake to detect unused headers?

CMake 3.3 introduced the new target property CXX_INCLUDE_WHAT_YOU_USE that can be set to the path of the program include-what-you-use. For instance this CMakeLists.txt

cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
add_executable(hello main.cc)

find_program(iwyu_path NAMES include-what-you-use iwyu)
if(NOT iwyu_path)
  message(FATAL_ERROR "Could not find the program include-what-you-use")
endif()

set_property(TARGET hello PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path})

is able to build the file main.cc

#include <iostream>
#include <vector>

int main() {
  std::cout << "Hello World!" << std::endl;
  return 0;
}

and at the same time have include-what-you-use give out a warning that the included header vector is not needed.

user@ubuntu:/tmp$ ls ~/hello
CMakeLists.txt  main.cc
user@ubuntu:/tmp$ mkdir /tmp/build
user@ubuntu:/tmp$ cd /tmp/build
user@ubuntu:/tmp/build$ ~/cmake-3.3.0-rc2-Linux-x86_64/bin/cmake ~/hello
-- The C compiler identification is GNU 4.9.2
-- The CXX compiler identification is GNU 4.9.2
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/build
user@ubuntu:/tmp/build$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/main.cc.o
Warning: include-what-you-use reported diagnostics:

/home/user/hello/main.cc should add these lines:

/home/user/hello/main.cc should remove these lines:
- #include <vector>  // lines 2-2

The full include-list for /home/user/hello/main.cc:
#include <iostream>  // for operator<<, basic_ostream, cout, endl, ostream
---

[100%] Linking CXX executable hello
[100%] Built target hello
user@ubuntu:/tmp/build$ ./hello 
Hello World!
user@ubuntu:/tmp/build$

If you want to pass custom options to include-what-you-use, like for instance --mapping_file you can do it via

set(iwyu_path_and_options
    ${iwyu_path}
    -Xiwyu
    --mapping_file=${my_mapping})

set_property(TARGET hello
    PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path_and_options})

CMake 3.18 introduced the new option REQUIRED to the find_program(), so it should be possible to remove the if statement if(NOT iwyu_path) above.


If you don't have access to CMake 3.3, include-what-you-use comes with a python tool called iwyu_tool.py which can do what you want.

It works by parsing a clang compilation database, which is easily produced with CMake.

Running the tool manually

Assuming you already have a CMake build dir for your project, you first need to tell CMake to produce the compilation database:

$ cd build
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .

This generates a file, compile_commands.json containing compiler invocations for every object file in your project. You don't need to rebuild the project.

You can now run include-what-you-use on your project by running the python tool on your build directory:

$ python /path/to/iwyu_tool.py -p .

Adding a custom target to your cmake project

The following snippet can be used to add an iwyu target to a cmake project.

# Generate clang compilation database
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(PythonInterp)
find_program(iwyu_tool_path NAMES iwyu_tool.py)
if (iwyu_tool_path AND PYTHONINTERP_FOUND)
  add_custom_target(iwyu
    ALL      # Remove ALL if you don't iwyu to be run by default.
    COMMAND "${PYTHON_EXECUTABLE}" "${iwyu_tool_path}" -p "${CMAKE_BINARY_DIR}"
    COMMENT "Running include-what-you-use tool"
    VERBATIM
  )
endif()

Notes

The include-what-you-use binary needs to be in your path for any of the above to work properly.


You can also enable it globally outside the cmake script by setting the cmake variable:

cmake -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu" <builddir> 

It will then call it on each CXX target.