CMake构建系统入门
·
CMake构建系统入门
CMake是现代C/C++项目构建的事实标准。从KDE4到LLVM,从OpenCV到TensorFlow,无数知名项目都在使用CMake管理构建流程。本文将从基础概念到高级技巧,全面解析CMake构建系统。
一、CMake概述
1.1 什么是CMake
CMake(Cross-platform Make)是一个开源的跨平台构建系统生成器。它不直接构建项目,而是生成平台特定的构建文件:
| 平台 | 生成的构建文件 |
|---|---|
| Linux/Unix | Makefile |
| Windows | Visual Studio项目文件(.sln) |
| macOS | Xcode项目 |
| 跨平台 | Ninja文件 |
1.2 为什么需要CMake
传统makefile的痛点:
- 编写复杂,容易出错
- 平台相关性,移植困难
- 依赖管理麻烦
- 大型项目维护成本高
CMake的优势:
- 跨平台:一份配置,多平台构建
- 开源自软件:BSD许可证
- 高效:比autotools快40%(KDE官方数据)
- 可扩展:支持自定义模块
- 简化流程:cmake + make两步构建
1.3 CMake构建流程
CMakeLists.txt → cmake → 平台构建文件 → make/编译器 → 可执行文件
典型工作流:
# 1. 编写CMakeLists.txt
# 2. 生成构建文件
cmake .
# 3. 编译
make
# 4. 运行
./myapp
推荐的外部构建方式:
mkdir build && cd build
cmake ..
make
二、CMake基础语法
2.1 语法特点
- 变量引用:使用
${}取值,IF语句中直接使用变量名 - 指令格式:
command(arg1 arg2 ...),参数用空格或分号分隔 - 大小写:指令不区分大小写,变量区分大小写,推荐指令全大写
- 注释:使用
#
2.2 第一个CMakeLists.txt
# 指定CMake最低版本
cmake_minimum_required(VERSION 3.10)
# 定义项目名称和语言
project(HelloWorld C CXX)
# 添加可执行文件
add_executable(hello main.cpp)
2.3 常用变量
CMake预定义了许多变量,掌握它们是高效使用CMake的关键:
路径相关变量:
| 变量 | 说明 |
|---|---|
PROJECT_SOURCE_DIR |
项目根目录 |
PROJECT_BINARY_DIR |
构建输出目录 |
CMAKE_CURRENT_SOURCE_DIR |
当前CMakeLists.txt所在目录 |
CMAKE_SOURCE_DIR |
顶层CMakeLists.txt所在目录 |
CMAKE_BINARY_DIR |
构建根目录 |
输出目录变量:
# 设置可执行文件输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
# 设置动态库输出位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
# 设置静态库输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
编译相关变量:
# C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 编译类型
set(CMAKE_BUILD_TYPE Release) # Debug, Release, RelWithDebInfo
# 编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O2")
三、常用指令详解
3.1 project() - 定义项目
# 基本形式
project(MyProject)
# 指定版本
project(MyProject VERSION 1.0.0)
# 指定语言
project(MyProject C CXX)
# 指定描述和主页
project(MyProject
VERSION 1.0.0
DESCRIPTION "My awesome project"
LANGUAGES CXX
)
3.2 add_executable() - 添加可执行目标
# 基本形式
add_executable(myapp main.cpp)
# 多文件
add_executable(myapp
main.cpp
utils.cpp
network.cpp
)
# 使用变量
set(SOURCES main.cpp utils.cpp)
add_executable(myapp ${SOURCES})
3.3 add_library() - 添加库目标
# 静态库
add_library(mylib STATIC lib.cpp)
# 动态库
add_library(mylib SHARED lib.cpp)
# 默认(根据BUILD_SHARED_LIBS变量决定)
add_library(mylib lib.cpp)
3.4 target_link_libraries() - 链接库
add_executable(myapp main.cpp)
target_link_libraries(myapp
PRIVATE mylib # 仅当前目标使用
PUBLIC sharedlib # 当前目标和依赖者都使用
INTERFACE headeronly # 仅依赖者使用
)
可见性说明:
PRIVATE:仅当前目标使用PUBLIC:当前目标和依赖者都使用INTERFACE:仅依赖者使用
3.5 include_directories() - 添加头文件目录
# 全局添加(不推荐)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 针对目标添加(推荐)
target_include_directories(myapp
PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
3.6 link_directories() - 添加库搜索路径
# 全局添加(不推荐,需在add_executable之前)
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 针对目标添加(推荐)
target_link_directories(myapp PRIVATE ${PROJECT_SOURCE_DIR}/lib)
3.7 set() - 设置变量
# 设置变量
set(MY_VAR "hello")
# 设置列表
set(SOURCES
main.cpp
utils.cpp
network.cpp
)
# 设置缓存变量(可被cmake命令行修改)
set(MY_OPTION ON CACHE BOOL "Enable my option")
# 设置环境变量
set(ENV{PATH} "/usr/local/bin:$ENV{PATH}")
四、文件操作与源文件管理
4.1 file()命令
# 递归收集所有.cpp文件
file(GLOB_RECURSE CPP_SOURCES
${PROJECT_SOURCE_DIR}/src/*.cpp
)
# 收集当前目录的.cpp文件(不递归)
file(GLOB CPP_SOURCES *.cpp)
# 复制文件
file(COPY ${PROJECT_SOURCE_DIR}/config.txt
DESTINATION ${PROJECT_BINARY_DIR})
# 创建目录
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/output)
4.2 aux_source_directory()
# 收集指定目录下的源文件(不递归)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SOURCES)
add_executable(myapp ${SOURCES})
注意:aux_source_directory只收集当前目录,不会递归子目录。
4.3 list()操作
# 定义列表
set(MY_LIST a b c d)
# 追加元素
list(APPEND MY_LIST e f)
# 删除元素
list(REMOVE_ITEM MY_LIST c)
# 获取长度
list(LENGTH MY_LIST LEN)
# 访问元素
list(GET MY_LIST 0 FIRST_ITEM)
# 排序
list(SORT MY_LIST)
# 示例:从源文件列表中删除特定文件
file(GLOB_RECURSE CPPS ${PROJECT_SOURCE_DIR}/src/*.cpp)
list(REMOVE_ITEM CPPS
${PROJECT_SOURCE_DIR}/src/main.cpp
)
五、条件判断与循环
5.1 if()条件判断
# 基本形式
if(DEFINED MY_VAR)
message(STATUS "MY_VAR is defined")
endif()
# 比较操作
if(MY_VAR EQUAL 10)
# ...
elseif(MY_VAR LESS 5)
# ...
else()
# ...
endif()
# 逻辑操作
if(WIN32 AND NOT CYGWIN)
# Windows原生环境
endif()
# 字符串比较
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# 调试模式
endif()
# 检查变量是否定义
if(DEFINED MY_VAR)
# 已定义
endif()
# 检查变量真假
if(MY_VAR)
# 非空、非0、非FALSE
endif()
5.2 循环
# foreach循环
foreach(FILE ${SOURCES})
message(STATUS "Processing ${FILE}")
endforeach()
# 带范围的循环
foreach(i RANGE 1 10)
message(STATUS "i = ${i}")
endforeach()
# while循环
set(COUNT 0)
while(COUNT LESS 5)
message(STATUS "Count: ${COUNT}")
math(EXPR COUNT "${COUNT} + 1")
endwhile()
六、函数与宏
6.1 function()函数
# 定义函数
function(add_my_executable TARGET_NAME)
# ARGN包含所有额外参数
add_executable(${TARGET_NAME} ${ARGN})
target_link_libraries(${TARGET_NAME} PRIVATE mylib)
message(STATUS "Created target: ${TARGET_NAME}")
endfunction()
# 调用
add_my_executable(myapp main.cpp utils.cpp)
6.2 macro()宏
# 定义宏
macro(print_info VAR)
message(STATUS "${VAR} = ${${VAR}}")
endmacro()
# 调用
set(MY_VAR "hello")
print_info(MY_VAR)
函数与宏的区别:
- 函数有自己的作用域,宏在调用者作用域执行
- 函数内修改变量不影响外部,宏会影响
- 推荐使用函数,作用域更清晰
七、依赖管理
7.1 find_package()查找依赖
# 查找必需依赖
find_package(OpenCV REQUIRED)
# 查找可选依赖
find_package(Boost COMPONENTS filesystem system)
# 使用依赖
if(OpenCV_FOUND)
target_include_directories(myapp PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS})
endif()
# 指定查找路径
set(OpenCV_DIR "/path/to/opencv")
find_package(OpenCV REQUIRED)
常见变量:
<Package>_FOUND:是否找到<Package>_INCLUDE_DIRS:头文件目录<Package>_LIBRARIES:库文件<Package>_VERSION:版本号
7.2 FetchContent下载依赖
CMake 3.11+提供了现代化的依赖管理方式:
include(FetchContent)
# 声明依赖
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
# 获取依赖
FetchContent_MakeAvailable(googletest)
# 使用依赖
enable_testing()
add_executable(mytest test.cpp)
target_link_libraries(mytest PRIVATE GTest::gtest_main)
7.3 add_subdirectory()添加子项目
# 添加子目录
add_subdirectory(lib/mylib)
# 指定二进制输出目录
add_subdirectory(lib/mylib ${PROJECT_BINARY_DIR}/mylib_build)
# 子项目的CMakeLists.txt
add_library(mylib STATIC lib.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 主项目使用
target_link_libraries(myapp PRIVATE mylib)
7.4 include()包含其他CMake文件
# 包含自定义CMake模块
include(${PROJECT_SOURCE_DIR}/cmake/Utils.cmake)
# 包含CMake自带的模块
include(CMakePrintHelpers)
cmake_print_variables(CMAKE_CXX_COMPILER)
八、编译选项与定义
8.1 target_compile_definitions()
# 添加编译宏定义
target_compile_definitions(myapp
PRIVATE
DEBUG_MODE=1
PROJECT_NAME="MyApp"
PUBLIC
API_EXPORTS
)
# 等价于编译选项 -DDEBUG_MODE=1 -DPROJECT_NAME="MyApp"
8.2 target_compile_options()
# 添加编译选项
target_compile_options(myapp
PRIVATE
-Wall
-Wextra
$<$<CONFIG:Debug>:-g -O0> # 仅Debug模式
$<$<CONFIG:Release>:-O3> # 仅Release模式
)
8.3 target_compile_features()
# 要求特定C++特性
target_compile_features(myapp PRIVATE cxx_std_17)
九、安装与打包
9.1 install()安装规则
# 安装可执行文件
install(TARGETS myapp
RUNTIME DESTINATION bin
)
# 安装库文件
install(TARGETS mylib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# 安装头文件
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
)
# 安装配置文件
install(FILES config/settings.json
DESTINATION etc/myapp
)
9.2 CPack打包
# 包含CPack模块
include(CPack)
# 设置包信息
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My awesome application")
set(CPACK_PACKAGE_VENDOR "MyCompany")
# 设置生成器
set(CPACK_GENERATOR "DEB;RPM;TGZ")
# DEB包特定设置
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
# 生成包
# cpack
十、实战示例
10.1 多目录项目结构
MyProject/
├── CMakeLists.txt
├── include/
│ └── myapp.h
├── src/
│ ├── main.cpp
│ ├── utils.cpp
│ └── network.cpp
├── lib/
│ └── thirdparty/
│ └── CMakeLists.txt
└── tests/
└── CMakeLists.txt
根CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
# 收集源文件
file(GLOB_RECURSE SOURCES ${PROJECT_SOURCE_DIR}/src/*.cpp)
# 创建可执行文件
add_executable(myapp ${SOURCES})
target_include_directories(myapp
PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
# 添加第三方库
add_subdirectory(lib/thirdparty)
target_link_libraries(myapp PRIVATE thirdparty)
# 启用测试
enable_testing()
add_subdirectory(tests)
10.2 条件编译
# 添加选项
option(ENABLE_CUDA "Enable CUDA support" OFF)
option(BUILD_TESTS "Build test programs" ON)
if(ENABLE_CUDA)
find_package(CUDA REQUIRED)
add_definitions(-DUSE_CUDA)
target_link_libraries(myapp PRIVATE ${CUDA_LIBRARIES})
endif()
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
10.3 自定义CMake模块
cmake/FindMyLib.cmake:
find_path(MYLIB_INCLUDE_DIR mylib.h
PATHS /usr/local/include /usr/include
)
find_library(MYLIB_LIBRARY NAMES mylib
PATHS /usr/local/lib /usr/lib
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
DEFAULT_MSG
MYLIB_LIBRARY MYLIB_INCLUDE_DIR
)
if(MyLib_FOUND)
set(MYLIB_LIBRARIES ${MYLIB_LIBRARY})
set(MYLIB_INCLUDE_DIRS ${MYLIB_INCLUDE_DIR})
endif()
mark_as_advanced(MYLIB_INCLUDE_DIR MYLIB_LIBRARY)
使用:
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package(MyLib REQUIRED)
target_link_libraries(myapp PRIVATE ${MYLIB_LIBRARIES})
十一、最佳实践
1. 使用现代CMake(目标导向)
# 旧方式(不推荐)
include_directories(/path/to/include)
link_libraries(mylib)
# 新方式(推荐)
target_include_directories(myapp PRIVATE /path/to/include)
target_link_libraries(myapp PRIVATE mylib)
2. 外部构建
mkdir build && cd build
cmake ..
make
3. 使用target_*系列命令
优先使用 target_* 命令而非全局命令,作用域更清晰。
4. 合理组织项目结构
project/
├── CMakeLists.txt
├── cmake/ # 自定义模块
├── include/ # 公共头文件
├── src/ # 源代码
├── tests/ # 测试
└── docs/ # 文档
5. 使用变量简化配置
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
set(SRC_DIR ${PROJECT_SOURCE_DIR}/src)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)