文章

冗余头文件检查(三):include-what-you-use实战使用指南

冗余头文件检查(三):include-what-you-use实战使用指南

编译 IWYU

从源码编译

IWYU 推荐从源码编译,这样可以确保与你的 Clang 版本匹配。以下是官方推荐的编译步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 克隆仓库
git clone https://github.com/include-what-you-use/include-what-you-use.git
cd include-what-you-use

# 2. 创建构建目录
mkdir build
cd build

# 3. 配置 CMake
cmake .. -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang

# 4. 编译
make -j$(nproc)

# 5. 安装(可选)
sudo make install

常见编译问题

Clang 版本不匹配:IWYU 需要与 Clang 的版本匹配。常见的版本名为 libclang-XX,如 libclang-10

1
2
3
4
5
# 检查 Clang 版本
clang --version

# 如果版本不匹配,需要安装对应的 Clang 开发包
sudo apt-get install libclang-10-dev # Ubuntu/Debian

缺少依赖

1
2
3
4
5
# Ubuntu/Debian
sudo apt-get install clang-10 gcc-c++

# macOS (使用 Homebrew)
brew install llvm@10

基本使用

最简单的用法

1
2
3
4
5
# 分析单个文件
include-what-you-use main.cpp

# 分析多个文件
include-what-you-use src/*.cpp

IWYU 会输出两个部分:

  1. 给人类的可读建议
  2. fix_includes.py 脚本使用的机器可读格式

典型输出示例

1
$ include-what-you-use example.cpp

输出:

1
2
3
4
5
6
7
8
9
example.cpp should add these lines:
#include <string>

example.cpp should remove these lines:
- #include <vector>  // lines 3-3

The full include-list for example.cpp:
#include <iostream>
#include <string>

使用 fix_includes.py 自动修复

IWYU 输出的第二部分可以直接通过管道传输给 fix_includes.py 脚本:

1
include-what-you-use src/*.cpp | fix_includes.py

这个脚本会:

  • 读取 IWYU 的标准输出
  • 解析机器可读格式
  • 修改源文件,添加或删除 #include 指令

常用参数

控制输出格式

参数说明
--verbose显示详细的分析信息,有助于调试
--no_comments不输出人类可读的建议,只输出机器可读格式
--transitive_includes显示传递性包含关系
1
2
3
4
5
# 完整模式:显示所有细节
include-what-you-use --verbose example.cpp

# 纯机器格式:用于管道传输
include-what-you-use --no_comments example.cpp | fix_includes.py

指定包含路径

如果你的项目使用非标准的包含路径:

1
include-what-you-use -I./include -I./third_party/include src/*.cpp

定义宏

1
include-what-you-use -DPROJECT_EXPORTS src/*.cpp

排除特定文件

1
include-what-you-use --exclude_files '*_test.cpp' src/*.cpp

集成到构建系统

与 Make 集成

1
2
3
4
5
6
IWYU = include-what-you-use
LIBCLANG_INC = -I/usr/lib/llvm-10/include

iwyu_check:
    $(IWYU) $(CXXFLAGS) $(LIBCLANG_INC) $(SOURCES) 2>&1 | \
        fix_includes.py

与 CMake 集成

CMake 官方提供了与 IWYU 的集成支持。在你的 CMakeLists.txt 中添加:

1
2
3
4
5
6
7
# 启用 IWYU 支持
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "include-what-you-use")

# 设置 IWYU 参数
set(CMAKE_CXX_IWYU_OPTIONS
    "--mapping_file=${CMAKE_SOURCE_DIR}/iwyu_mappings.imp"
    "--verbose=1")

运行时,CMake 会自动调用 IWYU 分析每个源文件。

与 Bazel 集成

使用 bazel-compile-commands-extractor 生成 compile_commands.json,然后:

1
2
3
4
5
6
7
8
9
# 1. 生成 compile_commands.json
bazel query 'kind("cc_.*", //...)' \
    --output=xml > bazel_compile_commands.xml

# 2. 转换格式
python3 convert_compile_commands.py bazel_compile_commands.json

# 3. 运行 IWYU
iwyu_tool -j$(nproc) -p compile_commands.json

高级功能

映射文件(Mapping File)

IWYU 可以使用映射文件来覆盖其默认的头文件选择规则:

# mapping.imp

// 指定某个符号应该从哪个头文件获取
{ symbol: ["std::shared_ptr"], include: ["<memory>"] }

// 指定某个头文件应该被哪些类型包含
{ include: ["<boost/shared_ptr.hpp>"], private: ["boost::shared_ptr"] }

使用方式:

1
include-what-you-use --mapping_file=mapping.imp src/*.cpp

前向声明

IWYU 懂得”何时使用前向声明”:

1
2
3
4
5
6
7
8
9
// 原始代码
#include "ComplexClass.h"

void process(ComplexClass* obj);

// IWYU 建议
class ComplexClass;  // 前向声明就足够了

void process(ComplexClass* obj);

这是 IWYU 的一个重要特性,因为它可以减少不必要的编译依赖。

常见问题与解决方案

1. “IWYU 报告了很多误报”

原因:IWYU 的分析假设头文件自包含,或者某些依赖关系通过宏间接引入。

解决方案

  • 使用映射文件修正
  • 检查是否有不自包含的头文件

2. “IWYU 运行太慢”

原因:大型项目的完整 AST 分析耗时较长。

解决方案

  • 使用 iwyu_tool 并行处理文件
  • 分批次分析特定模块
  • 考虑使用 --no_comments 减少输出开销

3. “修复后编译错误”

原因:某些头文件虽然看似未被直接引用,但实际上提供了必要的宏定义或内联函数。

解决方案

  • 检查 IWYU 的详细输出,看看是否遗漏了间接依赖
  • 使用映射文件强制保留某些 #include

总结

最后总结一下,iwyu本身是可以先在小型模块上试验,确认效果后再推广到整个项目,同时将 IWYU 检查加入 CI 流程,防止引入新的冗余包含,并且在代码合并前运行,而不是等到问题积累到很多的时候再解决(PS.实际情况正好相反,使用iwyu的人一般是像批量解决问题的)

本文由作者按照 CC BY 4.0 进行授权