文章

冗余头文件检查(七):使用 Docker 构建可复现的 IWYU 环境

冗余头文件检查(七):使用 Docker 构建可复现的 IWYU 环境

为什么使用 Docker

在开发 IWYU 的扩展功能时,环境一致性是一个重要问题。不同的开发者可能在不同的操作系统、不同的 Clang 版本、不同的依赖库环境下工作。这种差异可能导致:

  1. 编译失败
  2. 运行时行为不一致
  3. CI 环境与本地开发环境不匹配
  4. 新成员加入项目时需要繁琐的环境配置

Docker 提供了一种轻量级的虚拟化方案,通过容器技术确保开发、测试和生产环境的一致性。

IWYU Docker镜像构建

基础镜像选择

IWYU 依赖 Clang/LLVM,因此我们需要一个包含基本构建工具和 Clang 的基础镜像:

1
2
3
4
5
# 使用 Ubuntu 20.04 作为基础镜像
FROM ubuntu:20.04

# 设置非交互式安装
ENV DEBIAN_FRONTEND=noninteractive

完整的 Dockerfile

以下是完整的 IWYU 开发环境 Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Dockerfile for IWYU Development Environment
FROM ubuntu:20.04

# 设置环境变量
ENV DEBIAN_FRONTEND=noninteractive
ENV LLVM_VERSION=10
ENV PATH="/usr/lib/llvm-${LLVM_VERSION}/bin:${PATH}"
ENV LD_LIBRARY_PATH="/usr/lib/llvm-${LLVM_VERSION}/lib:${LD_LIBRARY_PATH}"

# 安装基础依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    ninja-build \
    ccache \
    wget \
    curl \
    vim \
    && rm -rf /var/lib/apt/lists/*

# 安装 Clang/LLVM 10
RUN apt-get update && apt-get install -y \
    llvm-${LLVM_VERSION} \
    llvm-${LLVM_VERSION}-dev \
    clang-${LLVM_VERSION} \
    libclang-${LLVM_VERSION}-dev \
    libllvm${LLVM_VERSION} \
    && rm -rf /var/lib/apt/lists/*

# 创建工作目录
WORKDIR /workspace

# 克隆 IWYU 仓库
RUN git clone https://github.com/include-what-you-use/include-what-you-use.git

# 构建 Debug 版本的 IWYU
WORKDIR /workspace/include-what-you-use
RUN mkdir -p build-debug
WORKDIR /workspace/include-what-you-use/build-debug
RUN cmake -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_C_COMPILER=clang \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DLLVM_DIR=/usr/lib/llvm-${LLVM_VERSION}/lib/cmake/llvm \
    .. && \
    ninja -j$(nproc)

# 设置默认工作目录
WORKDIR /workspace

# 默认命令:显示 IWYU 版本
CMD ["/workspace/include-what-you-use/build-debug/bin/include-what-you-use", "--version"]

构建镜像

1
2
3
4
5
# 构建镜像
docker build -t iwyu-dev:latest .

# 查看构建的镜像
docker images | grep iwyu

使用 Docker 容器

基本运行

1
2
3
4
5
# 运行容器并进入 shell
docker run -it --rm iwyu-dev:latest bash

# 在容器中运行 IWYU
/workspace/include-what-you-use/build-debug/bin/include-what-you-use --help

挂载本地代码目录

为了能够使用容器中的 IWYU 分析本地代码,我们需要挂载本地目录:

1
2
3
4
5
6
7
8
9
# 运行容器并挂载本地项目目录
docker run -it --rm \
    -v /path/to/your/project:/workspace/project \
    iwyu-dev:latest bash

# 在容器中分析项目代码
/workspace/include-what-you-use/build-debug/bin/include-what-you-use \
    -I/workspace/project/include \
    /workspace/project/src/*.cpp

使用 docker run 的便捷方式

为了避免每次输入完整路径,可以在宿主机上创建一个 shell 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
#!/bin/bash
# iwyu-docker.sh - 使用 Docker 运行 IWYU

PROJECT_DIR=$(pwd)
IWYU_BINARY="/workspace/include-what-you-use/build-debug/bin/include-what-you-use"

docker run --rm \
    -v "${PROJECT_DIR}:/workspace/project" \
    -w /workspace/project \
    iwyu-dev:latest \
    $IWYU_BINARY "$@"

使用方式:

1
2
3
4
5
6
7
chmod +x iwyu-docker.sh

# 分析当前目录下的文件
./iwyu-docker.sh src/*.cpp -I./include

# 带调试输出
./iwyu-docker.sh src/main.cpp --verbose | fix_includes.py

Docker Compose 配置

对于更复杂的项目,可以使用 Docker Compose 管理多个服务或配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# docker-compose.yml
version: '3.8'

services:
  iwyu:
    build:
      context: .
      dockerfile: Dockerfile
    image: iwyu-dev:latest
    volumes:
      - ./project:/workspace/project
      - ./.config/iwyu:/root/.config/iwyu
    working_dir: /workspace/project
    command: ["/workspace/include-what-you-use/build-debug/bin/include-what-you-use", "--version"]

使用方式:

1
2
3
4
5
6
# 构建并启动服务
docker-compose up --build

# 执行 IWYU 分析
docker-compose exec iwyu /workspace/include-what-you-use/build-debug/bin/include-what-you-use \
    src/*.cpp -I include

VSCode 与 Docker 集成

安装 Dev Containers 扩展

在 VSCode 中,安装 Dev Containers (ms-vscode-remote.remote-containers) 扩展。

创建 .devcontainer 配置

在项目根目录下创建 .devcontainer/devcontainer.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    "name": "IWYU Development",
    "image": "iwyu-dev:latest",

    "features": {
        "ghcr.io/devcontainers/features/git:1": {}
    },

    "customizations": {
        "vscode": {
            "extensions": [
                "ms-vscode.cpptools",
                "ms-vscode.cmake-tools"
            ],
            "settings": {
                "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
            }
        }
    },

    "mounts": [
        "source=${localWorkspaceFolder},target=/workspace/project,type=bind,consistency=cached"
    ],

    "workspaceFolder": "/workspace/project",

    "remoteUser": "root"
}

在容器内进行开发

  1. 打开项目文件夹
  2. F1,选择 Dev Containers: Reopen in Container
  3. VSCode 会在容器内部启动,并挂载本地代码
  4. 可以像在本地开发一样使用 VSCode 的调试功能

调试环境中的 IWYU

使用 VSCode Remote SSH 调试

当使用 Dev Containers 时,VSCode 的调试配置 (.vscode/launch.json) 需要调整路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug IWYU (Container)",
            "type": "cppdbg",
            "request": "launch",
            "program": "/workspace/include-what-you-use/build-debug/bin/include-what-you-use",
            "args": [
                "-I/workspace/project/include",
                "/workspace/project/src/main.cpp",
                "--verbose"
            ],
            "stopAtEntry": false,
            "cwd": "/workspace/project",
            "environment": [],
            "externalConsole": false,
            "MIMode": "lldb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for lldb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

CI/CD 集成

GitHub Actions 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# .github/workflows/iwyu-check.yml
name: IWYU Check

on: [push, pull_request]

jobs:
  iwyu:
    runs-on: ubuntu-latest
    container:
      image: iwyu-dev:latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Run IWYU
        run: |
          /workspace/include-what-you-use/build-debug/bin/include-what-you-use \
            -I./include \
            src/*.cpp || true

      - name: Check for IWYU suggestions
        run: |
          # 如果 IWYU 有任何输出,则构建失败
          /workspace/include-what-you-use/build-debug/bin/include-what-you-use \
            -I./include --no_comments \
            src/*.cpp 2>&1 | tee /tmp/iwyu_output.txt
          if [ -s /tmp/iwyu_output.txt ]; then
            echo "IWYU found issues:"
            cat /tmp/iwyu_output.txt
            exit 1
          fi

优势总结

使用 Docker 构建 IWYU 环境的优势:

  1. 环境一致性:所有开发者使用相同的编译环境和依赖版本
  2. 快速上手:新成员只需要安装 Docker,无需手动配置复杂的开发环境
  3. 隔离性:容器与宿主机环境隔离,避免环境污染
  4. 可复现性:CI 环境与本地开发环境完全一致
  5. 版本切换方便:通过多标签镜像可以轻松切换不同版本的 IWYU 或 Clang

常见问题

容器启动失败

问题:容器启动时出现库找不到的错误。

解决:确保 Dockerfile 中正确设置了 LD_LIBRARY_PATH 环境变量。

性能慢

问题:在容器中编译 IWYU 速度较慢。

解决

  • 挂载本地目录时使用 consistency=cached 选项
  • 使用本地构建版本,减少容器镜像大小
1
2
3
4
5
6
# 在 Dockerfile 中
FROM iwyu-dev:latest as builder
RUN cd /workspace/include-what-you-use/build-debug && ninja

FROM ubuntu:20.04
COPY --from=builder /workspace /workspace

总结

通过 Docker,我们构建了一个完全可复现的 IWYU 开发环境。这不仅确保了不同开发者之间的一致性,也为 CI/CD 流程提供了可靠的基础。在接下来的文章中,我们将利用这个环境来实现 IWYU 的功能扩展。

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