C/C++ Linux 开发教程(GCC + CMake) / CMake 基础(add_executable/target_link)
CMake 基础(add_executable/target_link)
CMake 是目前最流行的跨平台构建系统生成器。它不直接构建项目,而是生成 Makefile、Ninja 文件或 IDE 项目文件。本文从零开始讲解 CMake 的核心概念和使用方法。
CMakeLists.txt 基本结构
最小 CMake 项目
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyApp VERSION 1.0.0 LANGUAGES C CXX)
add_executable(app main.c)
# 构建步骤
cmake -B build # 配置(生成构建系统)
cmake --build build # 构建(编译链接)
./build/app # 运行
三行核心命令
| 命令 | 作用 | 必须 |
|---|
cmake_minimum_required | 指定最低 CMake 版本 | ✅ |
project | 定义项目名称和属性 | ✅ |
add_executable / add_library | 定义构建目标 | ✅ |
project() 命令
# 基本用法
project(MyApp)
# 完整用法
project(MyApp
VERSION 2.1.0 # 版本号
DESCRIPTION "My Application" # 描述
HOMEPAGE_URL "https://example.com" # 主页
LANGUAGES C CXX # 使用的语言
)
# project() 会自动定义以下变量:
# PROJECT_NAME = "MyApp"
# PROJECT_VERSION = "2.1.0"
# MyApp_VERSION = "2.1.0"
# PROJECT_SOURCE_DIR / MyApp_SOURCE_DIR
# PROJECT_BINARY_DIR / MyApp_BINARY_DIR
添加构建目标
add_executable — 可执行文件
# 基本用法
add_executable(app main.c utils.c)
# 使用变量
set(SOURCES main.c utils.c parser.c)
add_executable(app ${SOURCES})
# 使用 file(GLOB) 收集源文件(不推荐用于生产项目)
file(GLOB SOURCES "src/*.c")
add_executable(app ${SOURCES})
# 指定输出目录
set_target_properties(app PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
add_library — 库文件
# 静态库(默认)
add_library(mylib STATIC src/mylib.c)
# 动态库(共享库)
add_library(mylib SHARED src/mylib.c)
# 对象库(只编译不链接,适合复用编译结果)
add_library(mylib_obj OBJECT src/mylib.c)
# 接口库(只有头文件,不编译)
add_library(mylib_headers INTERFACE)
# 使用源文件列表
set(LIB_SOURCES
src/string_utils.c
src/file_utils.c
src/math_utils.c
)
add_library(utils STATIC ${LIB_SOURCES})
链接与包含
target_link_libraries — 链接库
add_executable(app main.c)
# 链接外部库
target_link_libraries(app PRIVATE m) # 数学库
target_link_libraries(app PRIVATE pthread) # 线程库
# 链接自己编译的库
add_library(utils STATIC src/utils.c)
target_link_libraries(app PRIVATE utils)
# 关键字说明:
# PRIVATE — 仅当前目标使用
# INTERFACE — 仅传递给依赖者
# PUBLIC — 当前目标和依赖者都使用
# 链接多个库
target_link_libraries(app
PRIVATE
utils
m
pthread
OpenSSL::SSL
)
target_include_directories — 头文件路径
add_library(mylib src/mylib.c)
# 添加公共头文件路径(PUBLIC = 当前目标和依赖者都可用)
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src # 私有头文件
)
# 使用库时无需手动指定 -I 路径
add_executable(app main.c)
target_link_libraries(app PRIVATE mylib) # 自动获得 include 路径
target_compile_definitions — 预处理宏
# 添加预处理宏
target_compile_definitions(app PRIVATE
VERSION="1.0"
ENABLE_FEATURE_X
$<$<CONFIG:Debug>:DEBUG_MODE> # 仅 Debug 模式
)
target_compile_options — 编译选项
# 添加编译选项
target_compile_options(app PRIVATE
-Wall -Wextra -Wpedantic
$<$<CONFIG:Debug>:-O0 -g>
$<$<CONFIG:Release>:-O2 -DNDEBUG>
)
变量与缓存
普通变量
# 设置变量
set(MY_VAR "hello")
set(SOURCES main.c utils.c)
set(CMAKE_C_STANDARD 11)
# 使用变量
message(STATUS "MY_VAR = ${MY_VAR}")
# 列表操作
list(APPEND SOURCES parser.c)
list(REMOVE_ITEM SOURCES utils.c)
list(LENGTH SOURCES SOURCE_COUNT)
list(SORT SOURCES)
# 环境变量
set(ENV{PATH} "/usr/local/bin:$ENV{PATH}")
message(STATUS "HOME = $ENV{HOME}")
缓存变量
# 缓存变量(用户可通过 -D 修改)
set(BUILD_TESTS ON CACHE BOOL "Build test programs")
set(OUTPUT_DIR "bin" CACHE STRING "Output directory")
set(USE_OPENSSL ON CACHE BOOL "Use OpenSSL for TLS")
# 带高级标记(GUI 中默认不显示)
set(INTERNAL_VAR "" CACHE STRING "" INTERNAL)
# 使用 cache 变量
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
# 在命令行设置缓存变量
cmake -B build -DBUILD_TESTS=OFF -DOUTPUT_DIR="dist"
CMake 内置变量
| 变量 | 说明 |
|---|
CMAKE_SOURCE_DIR | 顶层源码目录 |
CMAKE_BINARY_DIR | 顶层构建目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前 CMakeLists.txt 所在目录 |
CMAKE_CURRENT_BINARY_DIR | 当前构建目录 |
CMAKE_C_COMPILER | C 编译器路径 |
CMAKE_CXX_COMPILER | C++ 编译器路径 |
CMAKE_BUILD_TYPE | 构建类型 (Debug/Release/…) |
CMAKE_INSTALL_PREFIX | 安装前缀 |
CMAKE_SYSTEM_NAME | 目标系统名 |
CMAKE_C_FLAGS | 全局 C 编译标志 |
message 输出
# 不同类型的 message
message(STATUS "Status message") # 普通状态信息
message(WARNING "Warning message") # 警告
message(FATAL_ERROR "Fatal error") # 致命错误(停止配置)
message(SEND_ERROR "Send error") # 错误(继续配置)
message(AUTHOR_WARNING "Author warning") # 作者警告
message(TRACE "Trace message") # 跟踪信息
message(DEBUG "Debug message") # 调试信息(CMake 3.15+)
# 打印变量
message(STATUS "Source dir: ${CMAKE_SOURCE_DIR}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Compiler: ${CMAKE_C_COMPILER_ID}")
# 打印所有变量(调试用)
get_cmake_property(_vars VARIABLES)
foreach(_var ${_vars})
message(STATUS "${_var} = ${${_var}}")
endforeach()
条件与循环
if/else/endif
# 基本条件判断
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug build")
add_definitions(-DDEBUG)
else()
message(STATUS "Release build")
endif()
# 逻辑运算
if(BUILD_TESTS AND ENABLE_COVERAGE)
add_subdirectory(tests)
endif()
if(NOT WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Non-Windows or Linux")
endif()
# 数值比较
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
message(STATUS "Modern CMake")
endif()
# 文件/目录检查
if(EXISTS "${CMAKE_SOURCE_DIR}/config.h")
message(STATUS "config.h exists")
endif()
if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/third_party")
add_subdirectory(third_party)
endif()
# 变量检查
if(DEFINED MY_VAR)
message(STATUS "MY_VAR is defined: ${MY_VAR}")
endif()
if(MY_VAR)
message(STATUS "MY_VAR is truthy")
endif()
# 字符串匹配
if(MY_STRING MATCHES "^hello.*")
message(STATUS "Matches pattern")
endif()
foreach/endforeach
# 遍历列表
set(SOURCES main.c utils.c parser.c)
foreach(src ${SOURCES})
message(STATUS "Processing: ${src}")
endforeach()
# 遍历范围
foreach(i RANGE 1 10)
message(STATUS "i = ${i}")
endforeach()
# 遍历多个列表
foreach(file IN LISTS HEADERS SOURCES)
message(STATUS "File: ${file}")
endforeach()
# while 循环
set(counter 0)
while(counter LESS 5)
math(EXPR counter "${counter} + 1")
message(STATUS "counter = ${counter}")
endwhile()
构建流程
配置、构建、安装三步走
# 第一步: 配置(生成构建系统)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local
# 第二步: 构建(编译链接)
cmake --build build --parallel $(nproc)
# 第三步: 安装(可选)
cmake --install build
# 或
sudo cmake --install build # 安装到系统路径
构建类型
| 构建类型 | 编译标志 | 说明 |
|---|
Debug | -O0 -g | 调试模式 |
Release | -O3 -DNDEBUG | 发布模式 |
RelWithDebInfo | -O2 -g -DNDEBUG | 带调试信息的发布 |
MinSizeRel | -Os -DNDEBUG | 最小体积 |
# 指定构建类型
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
install 规则
# 安装可执行文件
install(TARGETS app RUNTIME DESTINATION bin)
# 安装库文件
install(TARGETS mylib
LIBRARY DESTINATION lib # .so
ARCHIVE DESTINATION lib # .a
RUNTIME DESTINATION bin # .dll (Windows)
)
# 安装头文件
install(DIRECTORY include/ DESTINATION include)
install(FILES include/mylib.h DESTINATION include)
# 安装配置文件
install(FILES config.ini DESTINATION etc/myapp)
ctest 测试
# 启用测试
enable_testing()
# 添加测试
add_executable(test_utils test/test_utils.c)
target_link_libraries(test_utils PRIVATE utils)
add_test(NAME test_utils COMMAND test_utils)
# 带参数的测试
add_test(NAME test_integration
COMMAND ${CMAKE_BINARY_DIR}/bin/app --test
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# 设置测试超时(秒)
set_tests_properties(test_utils PROPERTIES TIMEOUT 30)
# 运行测试
cd build
ctest
ctest --verbose
ctest --output-on-failure
ctest -R "test_utils" # 只运行匹配的测试
ctest -j$(nproc) # 并行运行
生成器
Makefile 生成器(默认)
# 使用 Makefile 生成器
cmake -G "Unix Makefiles" -B build
cmake --build build --parallel 4
# 手动调用 make
cd build
make -j$(nproc)
make install
Ninja 生成器(推荐)
# 使用 Ninja 生成器(更快)
cmake -G "Ninja" -B build
cmake --build build
# 或使用 ninja
cd build
ninja
ninja install
生成器对比
| 特性 | Makefile | Ninja |
|---|
| 并行构建 | make -j | 自动并行 |
| 增量构建 | 较慢 | 更快 |
| 依赖追踪 | 基于时间戳 | 更精确 |
| 输出 | 详细 | 简洁 |
| 可用性 | 广泛 | 需安装 |
| 推荐场景 | 兼容性 | 性能关键 |
CMake 预设(CMakePresets.json)
基本预设配置
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 21,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"displayName": "Default Config",
"description": "Default build using Ninja",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release Build",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "debug-asan",
"displayName": "Debug with AddressSanitizer",
"inherits": "default",
"cacheVariables": {
"ENABLE_ASAN": "ON"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"output": {
"outputOnFailure": true
}
}
]
}
# 使用预设
cmake --preset default
cmake --build --preset default
ctest --preset default
# 查看可用预设
cmake --list-presets
cmake --list-presets --workflow
实际项目结构
多目录项目
my_project/
├── CMakeLists.txt # 顶层 CMakeLists
├── src/
│ ├── CMakeLists.txt
│ ├── main.c
│ └── app.c
├── include/
│ └── app/
│ ├── app.h
│ └── utils.h
├── lib/
│ ├── CMakeLists.txt
│ ├── stringlib.c
│ └── iolib.c
└── tests/
├── CMakeLists.txt
└── test_app.c
# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0 LANGUAGES C)
# 全局设置
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # 生成 compile_commands.json
# 添加子目录
add_subdirectory(lib)
add_subdirectory(src)
add_subdirectory(tests)
# lib/CMakeLists.txt
add_library(utils STATIC stringlib.c iolib.c)
target_include_directories(utils
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_compile_options(utils PRIVATE -Wall -Wextra)
# src/CMakeLists.txt
add_executable(app main.c app.c)
target_link_libraries(app PRIVATE utils pthread)
# tests/CMakeLists.txt
enable_testing()
add_executable(test_app test_app.c)
target_link_libraries(test_app PRIVATE utils)
add_test(NAME test_app COMMAND test_app)
⚠️ 注意点
- 版本要求:始终在文件开头指定
cmake_minimum_required - 使用 target_*:避免使用全局的
include_directories() / add_definitions() / link_libraries(),使用 target 版本 - file(GLOB):不推荐收集源文件——新增/删除文件时 CMake 不会自动重新配置
- 构建目录:永远在源码外部构建(out-of-source build),不要在源码目录内运行 cmake
- 变量展开:引用变量用
${VAR},但在 if() 中可以直接用变量名
💡 提示
- 生成 compile_commands.json:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON),配合 clangd 使用 - 并行构建:
cmake --build build -j$(nproc) 加速编译 - verbose 构建:
cmake --build build --verbose 查看完整编译命令 - 清除缓存:删除
build/CMakeCache.txt 可重置所有配置 - 查看构建命令:
cmake --build build -- VERBOSE=1 (Makefile) 或 -v (Ninja)
工程场景
场景 1:CMake 管理依赖库
# 查找系统库
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
target_link_libraries(app PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
)
场景 2:切换编译器
# 使用 Clang
cmake -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
# 使用交叉编译工具链
cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake
# 使用 ccache 加速
cmake -B build -DCMAKE_C_COMPILER_LAUNCHER=ccache
场景 3:调试 CMake 问题
# 查看所有缓存变量
cmake -B build -LAH
# 开启调试输出
cmake -B build --log-level=VERBOSE
# 追踪 CMake 执行
cmake -B build --trace --trace-expand 2>&1 | tee cmake_trace.log
扩展阅读