强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 18 章:开发环境与构建系统

第 18 章:开发环境与构建系统

“好的开发环境让你专注于代码,而不是环境问题。”


18.1 Docker 开发环境

18.1.1 快速启动

# 使用预构建的 LLVM Docker 镜像
docker pull ubuntu:22.04

# 创建 Dockerfile
cat > Dockerfile << 'EOF'
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# 安装 LLVM 工具链
RUN apt-get update && apt-get install -y \
    wget gnupg2 software-properties-common \
    build-essential cmake ninja-build python3 \
    git curl ca-certificates \
    && wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | \
       tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
    && echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main" | \
       tee /etc/apt/sources.list.d/llvm.list \
    && apt-get update && apt-get install -y \
    llvm-18-dev clang-18 lld-18 lldb-18 \
    clang-tools-18 libclang-18-dev libc++-18-dev \
    && rm -rf /var/lib/apt/lists/*

ENV PATH="/usr/lib/llvm-18/bin:${PATH}"
ENV LLVM_DIR="/usr/lib/llvm-18/lib/cmake/llvm"
ENV CC=clang-18
ENV CXX=clang++-18

WORKDIR /workspace
CMD ["/bin/bash"]
EOF

# 构建并运行
docker build -t llvm-dev .
docker run -it -v $(pwd):/workspace llvm-dev

18.1.2 多版本 Docker 环境

# docker-compose.yml
version: '3'
services:
  llvm17:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        LLVM_VERSION: 17
    volumes:
      - .:/workspace
    environment:
      - LLVM_VERSION=17

  llvm18:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        LLVM_VERSION: 18
    volumes:
      - .:/workspace
    environment:
      - LLVM_VERSION=18

  llvm19:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        LLVM_VERSION: 19
    volumes:
      - .:/workspace
    environment:
      - LLVM_VERSION=19
# Dockerfile — 参数化版本
ARG LLVM_VERSION=18
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    wget gnupg2 software-properties-common \
    build-essential cmake ninja-build python3 git \
    && wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | \
       tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
    && echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-${LLVM_VERSION} main" | \
       tee /etc/apt/sources.list.d/llvm.list \
    && apt-get update && apt-get install -y \
    llvm-${LLVM_VERSION}-dev clang-${LLVM_VERSION} \
    lld-${LLVM_VERSION} lldb-${LLVM_VERSION} \
    && rm -rf /var/lib/apt/lists/*

ENV PATH="/usr/lib/llvm-${LLVM_VERSION}/bin:${PATH}"
WORKDIR /workspace

18.2 CMake 集成

18.2.1 使用 find_package

# CMakeLists.txt — 使用系统安装的 LLVM
cmake_minimum_required(VERSION 3.20)
project(MyLLVMProject)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 LLVM
find_package(LLVM REQUIRED CONFIG)

message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

# 添加 LLVM 头文件路径
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

# 查找 LLVM 库
llvm_map_components_to_libnames(LLVM_LIBS
    core
    support
    analysis
    instcombine
    transformutils
    scalaropts
)

# 构建可执行文件
add_executable(my-tool main.cpp)
target_link_libraries(my-tool ${LLVM_LIBS})

18.2.2 使用 Clang 库

# 查找 Clang
find_package(Clang REQUIRED CONFIG)

include_directories(${CLANG_INCLUDE_DIRS})
add_definitions(${CLANG_DEFINITIONS})

add_executable(my-clang-tool clang_tool.cpp)

target_link_libraries(my-clang-tool
    clangTooling
    clangASTMatchers
    clangFrontend
    clangSerialization
    clangDriver
    clangParse
    clangSema
    clangAnalysis
    clangAST
    clangBasic
    clangEdit
    clangLex
    clangRewrite
)

18.2.3 查找 LLVM 和 Clang 的完整模板

# cmake/FindLLVM.cmake (自定义查找)
# 或使用系统自带的 Config 模式

# LLVM 查找优先级:
# 1. CMAKE_PREFIX_PATH 环境变量
# 2. LLVM_DIR CMake 变量
# 3. 系统默认路径

# 使用示例
cmake -DLLVM_DIR=/opt/llvm/lib/cmake/llvm \
      -DClang_DIR=/opt/llvm/lib/cmake/clang \
      -S . -B build

18.2.4 生成 LLVM IR 的 CMake 工具

# 添加自定义命令生成 LLVM IR
function(add_llvm_ir_target target source)
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.ll
        COMMAND ${CMAKE_C_COMPILER} -S -emit-llvm
                -o ${CMAKE_CURRENT_BINARY_DIR}/${target}.ll
                ${CMAKE_CURRENT_SOURCE_DIR}/${source}
        DEPENDS ${source}
        COMMENT "Generating LLVM IR for ${source}"
    )
    add_custom_target(${target}_ir DEPENDS
        ${CMAKE_CURRENT_BINARY_DIR}/${target}.ll)
endfunction()

18.3 CI/CD 配置

18.3.1 GitHub Actions

# .github/workflows/llvm-test.yml
name: LLVM Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test-matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        llvm-version: [17, 18, 19]
        build-type: [Release, Debug]

    steps:
    - uses: actions/checkout@v4

    - name: Install LLVM ${{ matrix.llvm-version }}
      run: |
        wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | \
          sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
        echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ matrix.llvm-version }} main" | \
          sudo tee /etc/apt/sources.list.d/llvm.list
        sudo apt-get update
        sudo apt-get install -y \
          llvm-${{ matrix.llvm-version }}-dev \
          clang-${{ matrix.llvm-version }} \
          lld-${{ matrix.llvm-version }}

    - name: Configure
      run: |
        cmake -G Ninja -S . -B build \
          -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
          -DLLVM_DIR=/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/llvm \
          -DClang_DIR=/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/clang

    - name: Build
      run: ninja -C build

    - name: Test
      run: ninja -C build test

  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Install clang-tidy
      run: sudo apt-get install -y clang-tidy-18
    - name: Run clang-tidy
      run: |
        cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S . -B build
        clang-tidy-18 -p build src/*.cpp

18.3.2 GitLab CI

# .gitlab-ci.yml
stages:
  - build
  - test
  - lint

variables:
  LLVM_VERSION: "18"

.build_template: &build_template
  image: ubuntu:22.04
  before_script:
    - apt-get update
    - apt-get install -y wget gnupg2 cmake ninja-build build-essential
    - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
    - echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-${LLVM_VERSION} main" > /etc/apt/sources.list.d/llvm.list
    - apt-get update
    - apt-get install -y llvm-${LLVM_VERSION}-dev clang-${LLVM_VERSION} lld-${LLVM_VERSION}

build-release:
  <<: *build_template
  stage: build
  script:
    - cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
    - ninja -C build
  artifacts:
    paths:
      - build/

test:
  <<: *build_template
  stage: test
  script:
    - ninja -C build test
  dependencies:
    - build-release

18.4 项目结构模板

my-llvm-project/
├── CMakeLists.txt              # 顶层 CMake
├── README.md
├── .github/workflows/          # CI/CD
├── .clang-format               # 代码格式配置
├── .clang-tidy                 # 静态分析配置
├── cmake/
│   ├── FindLLVM.cmake          # LLVM 查找
│   └── utils.cmake             # 工具函数
├── include/
│   └── myproject/
│       ├── Pass.h              # Pass 头文件
│       └── Utils.h
├── lib/
│   ├── Pass.cpp                # Pass 实现
│   └── Utils.cpp
├── tools/
│   ├── my-opt/                 # 自定义 opt
│   │   ├── CMakeLists.txt
│   │   └── main.cpp
│   └── my-tool/                # 其他工具
├── test/
│   ├── lit.cfg.py              # lit 测试配置
│   └── passes/
│       ├── my-pass.ll          # IR 测试
│       └── my-pass.test
├── docs/
└── benchmarks/

18.4.1 顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(MyLLVMProject VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)

include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

add_subdirectory(lib)
add_subdirectory(tools)
add_subdirectory(test)

18.5 测试框架 (lit)

LLVM 使用 lit 作为集成测试框架:

# test/lit.cfg.py
import lit.formats

config.name = "MyProject"
config.test_format = lit.formats.ShTest(True)
config.suffixes = ['.ll', '.c', '.cpp', '.test']
config.test_source_root = os.path.dirname(__file__)

# 设置工具路径
config.substitutions.append(('%my-opt', 'my-opt'))
config.substitutions.append(('%clang', 'clang-18'))
; test/passes/my-pass.ll
; RUN: my-opt -passes='my-pass' %s | FileCheck %s

; CHECK-LABEL: @test_function
; CHECK: %result = add i32 %a, %b
define i32 @test_function(i32 %a, i32 %b) {
entry:
  %result = add i32 %a, %b
  ret i32 %result
}
# 运行测试
lit test/ -v

# 运行单个测试
lit test/passes/my-pass.ll -v

# 运行并显示输出
lit test/ -v --show-unsupported

18.6 代码格式和风格

# .clang-format
BasedOnStyle: LLVM
IndentWidth: 2
ColumnLimit: 80
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
PointerAlignment: Left
# .clang-tidy
Checks: >
  -*,
  bugprone-*,
  performance-*,
  readability-*,
  modernize-*,
  -modernize-use-trailing-return-type
WarningsAsErrors: ''
# 格式化代码
clang-format -i $(find . -name '*.cpp' -o -name '*.h')

# 检查代码
clang-tidy -p build src/*.cpp

18.7 本章小结

工具用途
Docker统一开发环境
CMake构建系统
GitHub ActionsCI/CD
lit集成测试
clang-format代码格式化
clang-tidy静态检查

扩展阅读

  1. LLVM CMake — CMake 构建参考
  2. LLVM lit — lit 测试框架
  3. LLVM Coding Standards — 编码规范

下一章: 第 19 章:故障排查 — 常见问题、IR 验证和 Pass 调试。