8、Cmake构建过程详解
约 2332 字大约 8 分钟
2025-04-26
CMake 构建过程分为多个阶段,每个阶段生成不同类型的文件。让我详细说明:
一、CMake 构建流程概览
源代码 → CMake配置 → 构建系统生成 → 编译 → 链接 → 安装
(Configure) (Generate) (Build) (Install)二、各阶段详解
阶段1:配置阶段 (Configure)
主要操作:
- 读取
CMakeLists.txt文件 - 检测系统环境、编译器、库文件
- 设置各种变量和选项
- 检查依赖关系
生成的文件:
| 文件类型 | 后缀 | 作用 |
|---|---|---|
| CMakeCache.txt | .txt | 缓存配置结果,记录变量值,下次配置会读取此文件 |
| CMakeFiles/ | 目录 | 存储配置过程中的临时文件和脚本 |
| cmake_install.cmake | .cmake | 安装脚本,定义了如何安装文件 |
| CTestTestfile.cmake | .cmake | CTest 测试配置文件 |
| CPackConfig.cmake | .cmake | CPack 打包配置文件 |
| CMakeOutput.log | .log | 配置过程的输出日志 |
| CMakeError.log | .log | 配置过程的错误日志 |
阶段2:生成阶段 (Generate)
主要操作:
- 根据配置结果生成本地构建系统文件
- 生成 Makefile、Visual Studio 解决方案等
- 生成编译规则和依赖关系
生成的文件:
| 构建系统 | 生成文件 | 后缀 | 作用 |
|---|---|---|---|
| Make | Makefile | 无后缀 | make 工具的构建脚本 |
| Make | CMakeFiles/Makefile2 | 无后缀 | 内部使用的 Makefile |
| Ninja | build.ninja | .ninja | Ninja 构建系统的脚本 |
| Visual Studio | *.sln | .sln | Visual Studio 解决方案文件 |
| Visual Studio | *.vcxproj | .vcxproj | Visual Studio 项目文件 |
| Xcode | *.xcodeproj | .xcodeproj | Xcode 项目文件 |
阶段3:编译阶段 (Build)
主要操作:
- 预处理:处理宏定义、头文件包含
- 编译:将源文件编译成目标文件
- 生成汇编代码和对象文件
生成的文件:
| 文件类型 | 后缀 | 生成位置 | 作用 |
|---|---|---|---|
| 预处理文件 | .i (C) / .ii (C++) | CMakeFiles/ | 预处理后的源代码,展开所有宏和头文件 |
| 汇编文件 | .s / .asm | CMakeFiles/ | 汇编代码,用于调试优化问题 |
| 对象文件 | .o (Linux/Mac) / .obj (Windows) | CMakeFiles/ | 编译后的二进制目标文件,未链接 |
| 依赖文件 | .d | CMakeFiles/ | 记录头文件依赖关系,用于增量编译 |
编译阶段示例:
# GCC 编译过程
g++ -E main.cpp -o main.i # 预处理
g++ -S main.i -o main.s # 编译成汇编
g++ -c main.s -o main.o # 汇编成目标文件阶段4:链接阶段 (Link)
主要操作:
- 收集所有目标文件
- 解析符号引用
- 合并目标文件生成最终的可执行文件或库
生成的文件:
| 文件类型 | 后缀 | 平台 | 作用 |
|---|---|---|---|
| 可执行文件 | 无后缀 / .exe | Linux/Unix/Windows | 可直接运行的程序 |
| 静态库 | .a (Linux/Mac) / .lib (Windows) | 所有平台 | 静态链接库,编译时链接 |
| 动态库 | .so (Linux) / .dylib (Mac) / .dll (Windows) | 所有平台 | 动态链接库,运行时加载 |
| 导入库 | .lib (Windows) | Windows | Windows 下动态库的导入库 |
| 调试符号 | .pdb (Windows) / .dSYM (Mac) | Windows/Mac | 调试符号文件 |
| 导出文件 | .exp (Windows) | Windows | 动态库导出文件 |
阶段5:测试阶段 (Test)
主要操作:
- 运行 CTest
- 执行单元测试
- 收集测试结果
生成的文件:
| 文件类型 | 后缀 | 作用 |
|---|---|---|
| 测试日志 | .log | 测试执行的详细日志 |
| 测试结果 | .xml (JUnit) | 可集成的测试报告 |
| 覆盖率报告 | .gcov / .info | 代码覆盖率信息 |
阶段6:安装阶段 (Install)
主要操作:
- 复制文件到安装目录
- 创建目录结构
- 设置文件权限
生成的文件:
| 文件类型 | 安装位置 | 作用 |
|---|---|---|
| 可执行文件 | bin/ | 用户可直接运行的程序 |
| 库文件 | lib/ 或 lib64/ | 供其他程序链接的库 |
| 头文件 | include/ | 供其他程序包含的头文件 |
| 配置文件 | etc/ 或 share/ | 程序配置文件 |
| 文档文件 | share/doc/ | 文档和帮助文件 |
| CMake 配置 | lib/cmake/ | 供 find_package 使用的配置 |
| pkg-config 文件 | lib/pkgconfig/ | 供 pkg-config 工具使用 |
阶段7:打包阶段 (Package)
主要操作:
- 运行 CPack
- 创建分发包
- 压缩和打包
生成的文件:
| 文件类型 | 后缀 | 作用 |
|---|---|---|
| 源码包 | .tar.gz / .tar.bz2 / .zip | 源代码分发包 |
| 二进制包 | .tar.gz / .zip | 二进制分发包 |
| 安装程序 | .exe (NSIS) / .msi | Windows 安装程序 |
| Debian 包 | .deb | Debian/Ubuntu 安装包 |
| RPM 包 | .rpm | Red Hat/Fedora 安装包 |
| DMG 镜像 | .dmg | macOS 磁盘镜像 |
三、完整示例演示
案例详细过程请参考:cmake 编译项目案例展示
项目结构
myproject/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── lib.cpp
│ └── lib.h
└── build/项目代码
main.cpp:
#include "lib.h"
#include <iostream>
#include <iomanip>
int main() {
std::cout << "=== Calculator Application ===" << std::endl;
std::cout << std::endl;
// 显示库信息
Calculator::printInfo();
std::cout << std::endl;
// 测试基本运算
std::cout << "Basic Operations:" << std::endl;
std::cout << "10 + 5 = " << Calculator::add(10, 5) << std::endl;
std::cout << "10 - 5 = " << Calculator::subtract(10, 5) << std::endl;
std::cout << "10 * 5 = " << Calculator::multiply(10, 5) << std::endl;
std::cout << "10 / 3 = " << std::fixed << std::setprecision(2)
<< Calculator::divide(10.0, 3.0) << std::endl;
std::cout << std::endl;
// 测试斐波那契数列
std::cout << "Fibonacci Sequence (first 10 numbers):" << std::endl;
auto fib = Calculator::fibonacci(10);
for (size_t i = 0; i < fib.size(); i++) {
std::cout << fib[i];
if (i < fib.size() - 1) std::cout << ", ";
}
std::cout << std::endl << std::endl;
// 测试质数
std::cout << "Prime Numbers between 1 and 30:" << std::endl;
for (int i = 1; i <= 30; i++) {
if (Calculator::isPrime(i)) {
std::cout << i << " ";
}
}
std::cout << std::endl;
return 0;
}lib.sh:
#ifndef LIB_H
#define LIB_H
#include <string>
#include <vector>
class Calculator {
public:
// 基本数学运算
static int add(int a, int b);
static int subtract(int a, int b);
static int multiply(int a, int b);
static double divide(double a, double b);
// 高级功能
static std::vector<int> fibonacci(int n);
static bool isPrime(int n);
static std::string getVersion();
// 调试信息
static void printInfo();
};
#endif // LIB_Hlib.cpp:
#include "lib.h"
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
int Calculator::add(int a, int b) {
return a + b;
}
int Calculator::subtract(int a, int b) {
return a - b;
}
int Calculator::multiply(int a, int b) {
return a * b;
}
double Calculator::divide(double a, double b) {
if (b == 0) {
std::cerr << "Error: Division by zero!" << std::endl;
return 0;
}
return a / b;
}
std::vector<int> Calculator::fibonacci(int n) {
std::vector<int> result;
if (n <= 0) return result;
result.push_back(0);
if (n == 1) return result;
result.push_back(1);
for (int i = 2; i < n; i++) {
result.push_back(result[i-1] + result[i-2]);
}
return result;
}
bool Calculator::isPrime(int n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
std::string Calculator::getVersion() {
return "1.0.0";
}
void Calculator::printInfo() {
std::cout << "Calculator Library v" << getVersion() << std::endl;
std::cout << "Features:" << std::endl;
std::cout << " - Basic arithmetic operations" << std::endl;
std::cout << " - Fibonacci sequence generator" << std::endl;
std::cout << " - Prime number checking" << std::endl;
}CMakeLists.txt:
cmake_minimum_required(VERSION 3.16.3)
project(MyProject VERSION 1.0.0)
# 配置库
add_library(mylib STATIC src/lib.cpp src/lib.h)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
# 配置可执行文件
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# 安装配置
install(TARGETS myapp mylib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
# 打包配置
include(CPack)执行过程和生成文件:
# 1. 配置阶段
cd build
cmake ..
# 生成文件:
# CMakeCache.txt - 缓存
# CMakeFiles/ - 临时文件目录
# cmake_install.cmake - 安装脚本
# Makefile - 构建脚本
# 2. 编译阶段
make
# 编译生成:
# CMakeFiles/mylib.dir/src/lib.cpp.o - 目标文件
# CMakeFiles/myapp.dir/src/main.cpp.o - 目标文件
# 3. 链接阶段
# 生成:
# libmylib.a - 静态库
# myapp - 可执行文件
# 4. 安装阶段
make install
# 安装到指定位置:
# /usr/local/bin/myapp - 可执行文件
# /usr/local/lib/libmylib.a - 静态库
# 5. 打包阶段
make package
# 生成:
# MyProject-1.0.0-Linux.tar.gz - 二进制包
# MyProject-1.0.0-Source.tar.gz - 源码包四、文件类型详细说明
1. 目标文件 (.o / .obj)
# 查看目标文件信息
objdump -t file.o # 查看符号表
nm file.o # 列出符号
size file.o # 查看节大小2. 静态库 (.a / .lib)
# 查看静态库内容
ar -t libmylib.a # 列出包含的目标文件
ar -x libmylib.a # 解压目标文件
nm libmylib.a # 查看所有符号3. 动态库 (.so / .dylib / .dll)
# Linux 动态库
ldd libmylib.so # 查看依赖
objdump -p libmylib.so # 查看导出符号
# Windows 动态库
dumpbin /exports mylib.dll # 查看导出函数
dumpbin /dependents mylib.dll # 查看依赖4. CMake 缓存文件
# CMakeCache.txt 示例内容
CMAKE_BUILD_TYPE:STRING=Release
CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++
CMAKE_INSTALL_PREFIX:PATH=/usr/local5. 生成器表达式文件
# build.ninja (Ninja 构建文件) 示例
rule CXX_COMPILER
command = g++ $DEFINES $INCLUDES $FLAGS -MD -MT $out -MF $out.d -c $in -o $out
description = Building CXX object $out五、构建目录结构示例
build/ # 构建目录
├── CMakeCache.txt # CMake 缓存
├── CMakeFiles/ # CMake 内部文件
│ ├── 3.20.0/ # CMake 版本目录
│ │ ├── CMakeSystem.cmake # 系统检测结果
│ │ ├── CMakeCXXCompiler.cmake # 编译器检测结果
│ │ └── ...
│ ├── myapp.dir/ # myapp 目标相关文件
│ │ ├── src/
│ │ │ └── main.cpp.o # 目标文件
│ │ ├── main.cpp.o.d # 依赖文件
│ │ └── build.make # 构建规则
│ └── mylib.dir/ # mylib 目标相关文件
│ ├── src/
│ │ └── lib.cpp.o # 目标文件
│ └── ...
├── Makefile # 主 Makefile
├── cmake_install.cmake # 安装脚本
├── CTestTestfile.cmake # 测试配置
├── CPackConfig.cmake # 打包配置
├── myapp # 可执行文件(Linux)
├── myapp.exe # 可执行文件(Windows)
├── libmylib.a # 静态库
├── libmylib.so # 动态库(Linux)
├── mylib.dll # 动态库(Windows)
├── mylib.lib # 导入库(Windows)
└── Testing/ # 测试结果目录
└── Temporary/
└── LastTest.log # 测试日志六、优化和调试技巧
1. 查看详细编译信息
# 查看详细编译命令
make VERBOSE=1
cmake --build . --verbose
# 查看 CMake 内部变量
cmake -LAH # 列出所有变量2. 清理构建文件
# 清理编译产物
make clean
# 完全清理重新配置
rm -rf CMakeCache.txt CMakeFiles/
cmake ..3. 不同构建类型
# Debug 构建
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
# Release 构建
cmake -DCMAKE_BUILD_TYPE=Release ..
make
# 对比生成文件
# Debug: 包含调试符号,未优化
# Release: 已优化,无调试符号4. 分离构建目录
# 源码和构建分离
mkdir -p build/release build/debug
cd build/release
cmake -DCMAKE_BUILD_TYPE=Release ../..
make这个详细的阶段分析展示了 CMake 从配置到最终安装的完整流程,每个阶段生成的文件都有其特定用途,理解这些有助于更好地掌握 CMake 构建系统。