CMake is a cross-platform, open-source build system generator that abstracts compiler and platform-specific details, and is the official standard build framework for the entire LLVM/Clang infrastructure. It enables consistent compilation, linking, and toolchain management across Linux, Windows, macOS, and different CPU architectures (x86, ARM, RISC-V).
CMake 是 LLVM 生态的官方标准构建工具,是一套声明式、目标导向、跨平台的构建系统生成器。它通过统一的 CMakeLists.txt 配置文件,屏蔽不同平台、编译器、架构的构建差异,完成从源码到可执行文件、静态 / 动态库、LLVM Pass 插件的全流程工程化构建。
CMake 核心基础与标准规范
核心设计原则与约束
CMake 有 4 条必须遵守的核心规则:
所有配置必须围绕
add_executable/add_library等生成的目标 (Target)展开。所有头文件、链接库、编译选项必须通过
target_*指令为目标显式声明,不允许隐式依赖。必须采用
out-of-source(源码与构建目录分离)的构建方式,避免构建产物污染源码目录。CMake 版本必须与目标 LLVM 版本兼容,LLVM 15 + 要求 CMake 最低版本为 3.20。
标准构建全流程
CMake 有标准的、可复现的构建流程,所有示例均可通过该流程执行:
# 创建与源码目录分离的构建目录
mkdir build && cd build
# 执行配置,生成对应平台的构建文件(Makefile/Ninja/VS 工程)
# .. 指向顶层 CMakeLists.txt 所在的源码目录
cmake ..
# 执行编译,-j 指定并行编译线程数,提升编译速度
make -j8
核心基础指令与最简可复现示例
源码结构
├── hello.c
└── CMakeLists.txthello.c
#include <stdio.h>
int main () {
printf ("Hello LLVM & CMake\n");
return 0;
}CMakeLists.txt
# 指定 CMake 最低兼容版本,必须放在文件最开头
cmake_minimum_required (VERSION 3.20)
# 定义项目名称与支持的编程语言
project (HelloLLVM C CXX)
# 定义构建目标:生成名为 hello 的可执行文件
add_executable (hello hello.c)核心指令解释表
CMake 与 LLVM 核心场景实践
通过 CMake 自动化生成 LLVM IR
替代手动执行 clang -S -emit-llvm 命令,通过 CMake 实现 C 代码到 LLVM IR(.ll 文本格式)的自动化生成,支持多文件、批量生成。
项目结构
├── main.c
└── CMakeLists.txt
main.c
int main () {
int a = 10;
int b = 20;
int c = a + b;
return c;
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.20)
project (GenLLVMIR C)
# 将 C 源码编译为未优化的 LLVM IR 文本文件
# OUTPUT:声明生成的产物 main.ll
# COMMAND:执行 clang IR 生成命令
# DEPENDS:声明依赖的源码,源码修改时会重新生成 IR
add_custom_command (
OUTPUT main.ll
COMMAND clang -S -emit-llvm -O0 ${CMAKE_SOURCE_DIR}/main.c -o main.ll
DEPENDS main.c
COMMENT "正在生成 LLVM IR 文件: main.ll"
)
# 自定义构建目标,ALL 表示默认执行该目标
add_custom_target (gen_ir ALL DEPENDS main.ll)
执行标准构建流程后,会在 build 目录下自动生成 main.ll 文件,与手动执行 clang 命令生成的 IR 完全一致,可直接用于后续优化、Pass 调试。
CMake 构建调用 LLVM API 的可执行程序
用于开发基于 LLVM API 的 IR 生成、内存地址计算、指令分析等工具,核心是通过 CMake 引入 LLVM 头文件与核心库。
前置要求:已安装 LLVM 开发环境,并配置
LLVM_DIR环境变量(指向 LLVM 安装目录下的lib/cmake/llvm文件夹,存放 LLVM 的 CMake 配置文件)。
项目结构
├── llvm_demo.cpp
└── CMakeLists.txt
llvm_demo.cpp
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
using namespace llvm;
int main () {
LLVMContext Context;
// 创建 Module
Module *M = new Module ("demo_module", Context);
IRBuilder<> Builder (Context);
// 函数类型定义:i32 (i32, i32)
FunctionType *FuncType = FunctionType::get (Builder.getInt32Ty (), {Builder.getInt32Ty (), Builder.getInt32Ty ()}, false);
Function *AddFunc = Function::Create (FuncType, Function::ExternalLinkage, "add", M);
// 创建入口基本块
BasicBlock *EntryBB = BasicBlock::Create (Context, "entry", AddFunc);
Builder.SetInsertPoint (EntryBB);
// 实现加法逻辑
Value *Arg1 = AddFunc->getArg (0);
Value *Arg2 = AddFunc->getArg (1);
Value *Sum = Builder.CreateAdd (Arg1, Arg2, "sum");
Builder.CreateRet (Sum);
// 打印生成的 IR 到控制台
M->print (outs (), nullptr);
return 0;
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.20)
project (LLVMDemo CXX)
# 查找系统中安装的 LLVM,REQUIRED 表示必须找到,否则配置失败
find_package (LLVM REQUIRED CONFIG)
# 打印 LLVM 版本信息,便于调试版本匹配问题
message (STATUS "找到 LLVM 版本: ${LLVM_PACKAGE_VERSION}")
message (STATUS "LLVM 安装路径: ${LLVM_INSTALL_PREFIX}")
# 为当前项目添加 LLVM 头文件搜索路径
include_directories (${LLVM_INCLUDE_DIRS})
# 添加 LLVM 预设的编译宏定义
add_definitions (${LLVM_DEFINITIONS})
# 设置 C++ 标准,LLVM 15 + 要求 C++17 及以上
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
# 定义可执行文件目标
add_executable (llvm_demo llvm_demo.cpp)
# 为目标链接 LLVM 核心库
target_link_libraries (llvm_demo PRIVATE ${LLVM_LIBRARIES})
构建完成后,运行 ./llvm_demo,会直接在控制台输出生成的 add 函数的 LLVM IR。
CMake 构建 LLVM New Pass Manager 插件
LLVM New Pass 插件的官方标准 CMake 构建方式,无需重新编译整个 LLVM 项目,即可实现插件的动态编译与加载。
项目结构
├── MyCustomPass.cpp
└── CMakeLists.txt
MyCustomPass.cpp
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
// 自定义 Pass,继承自 PassInfoMixin
struct MyCustomPass : public PassInfoMixin<MyCustomPass> {
PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) {
// 遍历函数,打印函数名,实现简单的插桩逻辑
errs () << "Pass 遍历到函数:" << F.getName () << "\n";
// 声明未修改 IR,保留所有分析结果
return PreservedAnalyses::all ();
}
// 注册 Pass 到 Pipeline
static bool isRequired () { return true; }
};
} //namespace
// 插件注册入口
extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo () {
return {
LLVM_PLUGIN_API_VERSION,
"MyCustomPass",
LLVM_VERSION_STRING,
[](sslocal://flow/file_open?url=PassBuilder+%26PB&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
PB.registerPipelineParsingCallback (
[](sslocal://flow/file_open?url=StringRef+Name%2C+FunctionPassManager+%26FPM%2C+ArrayRef%3CPassBuilder%3A%3APipelineElement%3E&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
if (Name == "my-custom-pass") {
FPM.addPass (MyCustomPass ());
return true;
}
return false;
}
);
}
};
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.20)
project (MyCustomPass CXX)
# 查找 LLVM 配置
find_package (LLVM REQUIRED CONFIG)
# 版本与 C++ 标准匹配
message (STATUS "LLVM 版本: ${LLVM_PACKAGE_VERSION}")
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
# 引入 LLVM 头文件与宏定义
include_directories (${LLVM_INCLUDE_DIRS})
add_definitions (${LLVM_DEFINITIONS})
# LLVM 官方提供的 Pass 插件构建指令,替代手动配置动态库编译规则
# 自动处理插件的符号导出、平台适配、LLVM 库依赖等问题
add_llvm_pass_plugin (MyCustomPass MyCustomPass.cpp)
执行标准构建流程,编译完成后会在 build 目录下生成 libMyCustomPass.so(Linux)/MyCustomPass.dylib(macOS)插件文件。
通过 Clang 加载插件,编译 C 代码时自动执行 Pass:
clang -fpass-plugin=./build/libMyCustomPass.so test.c -o test
执行后会在控制台打印出所有遍历到的函数名。