CMake简单教程
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- CMake
- 常用命令及语法
- message()
- set()/unset()
- list()
- 控制流程
- 条件流程控制
- 循环控制
- Scope作用域
- 函数作用域
- 文件夹作用域
- 函数
- 宏
- 构建项目
- 直接写入源码路径
- 基本CMakeeLists构建
- 基本项目结构
- 基础配置
- 添加库
- 添加子目录(模块化)
- 构建与编译
- CMakeLists嵌套构建
- 复杂项目结构
- 根目录 CMakeLists.txt
- 核心库 (src/core/CMakeLists.txt)
- 命令行工具 (src/cli/CMakeLists.txt)
- 单元测试 (tests/CMakeLists.txt)
- 处理第三方依赖(以 JSON 库为例)
- 跨平台特殊处理
- 构建与安装
CMake
常用命令及语法
- CMake中的变量分为两种:CMake提供;自定义
- CMake变量的命名区分大小写
- CMake中的变量在存储时都是字符串
- CMake获取变量:${变量名}
- 变量的基础操作是set()与unset(),但也可以用list或是string操作变量
- 路径分隔符在 Windows 上是 ;,而在 Unix 上是 :
message()
打印调试信息,支持message(XXX)、message(“XXX”)、message([[XXX]]),跨行不能用第一种,message结尾自带换行
set()/unset()
set() 可定义变量
# 语法
set(<variable> <value>...[PARENT__SCOPE]) # 不加PARENT__SCOPE只在当前作用域生效,类似值传递,加了后可从函数内修改外部变量# 用例
set(CMAKE_CXX_STANDARD 11) # 定义变量CMAKE_CXX_STANDARD并赋值为1
set("My Var" YYY) # 设置变量My Var并赋值为YY,某些平台可能要对"做转义
set([[My Var]] ZZZ) # 设置变量My Var并赋值为ZZ。
# 对于含有空格的变量读取时要转义:message(${My\ Var}) 故变量名尽量不要带空格# 设置多个值
set(LISTVALUE a1 a2) # 内部存储时使用了;分割,定义时也可写成set(LISTVALUE a1;a2)
message(${LISTVALUE}) # 打印结果为a1a2
可重复设值,会覆盖原值。
set可以给一个变量设置多个值,此时相当于list,变量内部存储时使用”; ”分割,但显示时直接连接显示。
unset() 用于移除普通或缓存变量
# 语法
unset(<variable> [CACHE | PARENT_SCOPE])# 用例
unset(MY_VARIABLE) # 移除普通变量
unset(MY_CACHE_VARIABLE CACHE) # CMake缓存中移除指定的变量
unset(MY_PARENT_VARIABLE PARENT_SCOPE) # 从父作用域中移除指定变量
# 移除不存在的变量不会报错
设置环境变量
在 CMake 中,环境变量可以通过 $ENV{VAR_NAME} 进行读取,并通过 set(ENV{VAR_NAME} value) 进行临时修改(仅在当前 CMake 进程中有效)。
message($ENV{PATH}) # 打印PATH环境变量
set(ENV{CXX} "g++") # 临时指定C++编译器为g++
message($ENV{CXX}) # 打印结果g++
unset(ENV{CXX}) # 清除C++的环境变量
message($ENV{CXX}) # 此时再打印会报错该行,因为CXX变成空值
list()
遍历文件目录通常会得到一个list
# 语法,ACTION为操作类型,<list>为列表变量名
list(ACTION <list> [args...])list(APPEND <list> [<element>...]) # 列表添加元素
list(APPEND LIST1 ${LIST2}) # 合并 LIST2 到 LIST1
list(REMOVE_ITEM <list> <value> [value...]) # 列表删除元素
list(LENGTH <list> <output variable>) # 获取列表元素个数并返回到out variable中(如果提供了)
list(FIND <list> <value> <out-var>) # 在列表中查找元素并返回索引
list(INSERT <list> <index> [<element>...]) # 在index位置插入
list(REVERSE <list>) # 反转list
list(SORT <list> [...]) # 排序list(按字符串排序)# 用例
list(APPEND port p1 p2 p3) # 定义列表port={p1,p2,p3},和set(port p1 p2 p3)结果一致
list(LENGTH port len) # 读取列表port的元素个数并返回到len,此时len=3
list(FIND port p2 index) # 从列表port中查找元素p2,若找到则返回索引到index,此时index=1
list(REMOVE_ITEM port p2) # 从列表port中删除元素p2,此时port={p1,p3}
list(INSERT port 1 p2) # 在索引1位置插入元素p2,此时port={p1,p2,p3},也可将常量1替换为变量{index}
控制流程
条件流程控制
if…else…
# 语法
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else() # 括号别忘了
<commands>
endif()# 用例
set(VARBOOL TRUE)
if(NOT VARBOOL OR VARBOOL) # 必为真,AND则必为假。还有LESS、EQUAL等message(TRUE)
else()message(FALSE)
endif()
# 打印结果为TRUE
循环控制
for
foreach(<loop_var> RANGE <max>) # 定义循环变量loop_var,从0到max<commands>
endforeach()foreach(<loop_var> RANGE <min> <max> [<step>]) # 从min到max,不写step默认步长为1foreach(<loop_variable> IN [LISTS <lists>] [lTEMS <items>]) # 先遍历列表,再遍历ITEMS后的变量# 用例
foreach(VAR RANGE 3)message(&{VAR})
endforeach()
# VAR从0到3,打印结果为0 1 2 3,有换行foreach(VAR IN LISTS MY_LIST ITEMS 4 f) # VAR先遍历取值LISTS,再遍历4、fmessage(${VAR})
endforeach()
# 打印结果为1 2 3 4 f
while
while(<condition>)<commands>
endwhile()
Scope作用域
函数作用域
类似C/C++等编程语言的作用域概念,函数内的值操作默认不改变外部环境,定义的变量只在当前作用域(函数体)内有效。
文件夹作用域
子文件夹CMakeLists.txt可以继承使用父文件夹CMakeLists.txt的变量
函数
# 定义函数
function(<name> [<argument>...]) # 第一个参数name为函数名称,必写,后续为参数<commands> # 可以用 return() 提前返回
endfunction()# 调用函数
<name>(<argument>...)
参数列表在定义时也可以不写,传入时照样传,然后用ARGV0、ARGV1、ARGV2来使用,定义时写的话等于定义了一个形参
function(MyFunc FirstArg)message("MyFunc Name: ${CMAKE_CURRENT_FUNCTION}") # 打印当前函数名message("FirstArg: ${FirstArg}") # 打印FirstArg(FirstArg接收了调用时传入的第一个参数)set(FirstArg "new value") # 修改函数内的局部变量message("FirstArg again: ${FirstArg}") # 再次打印FirstArg,结果为New valuemessage("ARGVO: ${ARGVO}") # 打印第一个参数,并非打印形参FirstArg,ARGV0是直接对实参取值(也是值传递)message("ARGV1: ${ARGV1}") # 打印第二个参数,若没传则为空message("ARGV2: ${ARGV2}") # 打印第三个参数,同上
endfunction()set(Arg "first value")
MyFunc(${Arg} "value")
message("Arg: ${Arg}")
# 打印结果为
MyFunc Name: MyFunc
FirstArg: first value
FirstArg again: new value
ARGVO: first value # 打印第一个实参,而非FirstArg
ARGV1: value # 传入的第二个实参
ARGV2: # 没传打印空
Arg: first value # 函数内部没有改变外部变量
${Arg}是值传递,MyFunc(Arg “value”)是显式传递变量,同时用set的PARENT_SCOPE才可影响外部,只有其一还不行。
宏
# 语法
macro(<name> [<argument>...])<commands>
endmacro()# 调用, 无返回值,可通过修改外部变量实现类似效果
<name>(<argument>...)
类似C++中的宏函数,是文本替换(等于引用传递),没有独立作用域,直接修改参数会影响外部变量,适合全局变量或简单代码,不建议,直接用函数。
构建项目
目前有四种方式:直接写入源码路径(基本)、调用子目录的cmake脚本、CMakeLists嵌套(常用)、Object Library。这里只介绍基本的和常用的方法。
直接写入源码路径
add_executable() 添加可执行文件,在源码引入头文件需写明相对路径。
# 语法
add_executable(<target_name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
# <target_name>:可执行文件的名称。[]为配置属性。source 源文件,直接列出所有 .cpp/.c 文件(或通过变量传递)。# 用例
add_executable(my_app src/main.cpp src/helper.cpp)
# 编译src下的main.cpp和helper.cpp文件,将编译生成的目标文件链接在一起,最终生成my_app可执行文件set(SOURCESsrc/main.cppsrc/helper.cpp
) # 换行写更直观,add_executable()内也可换行写
add_executable(my_app ${SOURCES}) # 使用变量传递源文件
基本CMakeeLists构建
CMake 构建一个项目通常包括以下步骤:从项目配置、源代码管理、构建目标定义,到最终的编译和安装。
基本项目结构
my_project/
├── CMakeLists.txt # 根配置文件
├── include/ # 公共头文件
│ └── mylib.h
├── src/ # 源代码
│ ├── main.cpp
│ └── mylib.cpp
└── build/ # 构建目录(推荐)
基础配置
cmake_minimum_required(VERSION 3.10) # 最低 CMake 版本
project(MyProject VERSION 1.0 # 项目名称和版本DESCRIPTION "A simple project"LANGUAGES CXX) # 语言:CXX(C++), Cset(CMAKE_CXX_STANDARD 11) # 设置 C++ 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 添加可执行文件
add_executable(my_app src/main.cpp)# 添加头文件路径
target_include_directories(my_app PRIVATE include)# 可选:添加编译选项
target_compile_options(my_app PRIVATE -Wall -Wextra)
添加库
# 创建静态库
add_library(mylib STATIC src/mylib.cpp)
target_include_directories(mylib PUBLIC include) # PUBLIC 头文件对外可见# 将库链接到可执行文件
target_link_libraries(my_app PRIVATE mylib)
添加子目录(模块化)
如果项目有子模块:
add_subdirectory(lib) # 子目录需有自己的 CMakeLists.txt
构建与编译
生成构建系统
在项目根目录下:
mkdir build && cd build # 推荐外部构建(保持源码干净)
cmake .. # 生成构建文件(Makefile/MSVC等)
编译项目
cmake --build . # 跨平台编译(等效于 make/ninja)
# 或直接调用原生工具:
make # Linux/macOS
msbuild MyProject.sln # Windows (Visual Studio)
运行程序
./my_app # Linux/macOS
.\Debug\my_app.exe # Windows (默认 Debug 模式)
CMakeLists嵌套构建
复杂项目结构
data_tool/
├── CMakeLists.txt # 根配置
├── cmake/ # 自定义 CMake 模块
│ └── FindMathFunctions.cmake
├── include/
│ └── data_tool/
│ ├── core.h # 核心库头文件
│ └── utils.h
├── src/
│ ├── core/ # 核心库代码
│ │ ├── CMakeLists.txt
│ │ └── core.cpp
│ ├── cli/ # 命令行工具
│ │ ├── CMakeLists.txt
│ │ └── main.cpp
│ └── utils/ # 工具函数
│ ├── CMakeLists.txt
│ └── utils.cpp
├── tests/ # 单元测试
│ ├── CMakeLists.txt
│ └── test_core.cpp
├── thirdparty/ # 第三方依赖(可选)
└── build/ # 构建目录
根目录 CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(DataToolVERSION 1.0.0DESCRIPTION "A data processing tool with core library and CLI"LANGUAGES CXX
)# 设置 C++17 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 全局编译选项
if(MSVC)add_compile_options(/W4 /WX)
else()add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()# 启用测试
option(BUILD_TESTING "Build unit tests" ON)
if(BUILD_TESTING)enable_testing()include(CTest)
endif()# 添加子目录
add_subdirectory(src/core)
add_subdirectory(src/utils)
add_subdirectory(src/cli)if(BUILD_TESTING)add_subdirectory(tests)
endif()# 安装配置
install(DIRECTORY include/data_toolDESTINATION includeFILES_MATCHING PATTERN "*.h"
)# 导出库配置(供其他项目使用)
include(CMakePackageConfigHelpers)
configure_package_config_file(cmake/DataToolConfig.cmake.in${CMAKE_CURRENT_BINARY_DIR}/DataToolConfig.cmakeINSTALL_DESTINATION lib/cmake/DataTool
)install(FILES ${CMAKE_CURRENT_BINARY_DIR}/DataToolConfig.cmakeDESTINATION lib/cmake/DataTool
)
核心库 (src/core/CMakeLists.txt)
# 创建动态库(将 SHARED 改成 STATIC 即为静态库)
add_library(data_tool_core SHAREDcore.cpp
)# 头文件路径(PUBLIC 表示依赖此库的目标自动包含头文件)
target_include_directories(data_tool_corePUBLIC${CMAKE_CURRENT_SOURCE_DIR}/../../include
)# 链接其他内部库(如 utils)
target_link_libraries(data_tool_corePRIVATEdata_tool_utils
)# 安装库和头文件
install(TARGETS data_tool_coreEXPORT DataToolTargetsLIBRARY DESTINATION lib # 动态库(Linux/Mac)ARCHIVE DESTINATION lib # 静态库RUNTIME DESTINATION bin # Windows 动态库
)
命令行工具 (src/cli/CMakeLists.txt)
# 创建可执行文件
add_executable(data_tool_climain.cpp
)# 链接核心库
target_link_libraries(data_tool_cliPRIVATEdata_tool_core
)# 安装到 bin 目录
install(TARGETS data_tool_cliDESTINATION bin
)
单元测试 (tests/CMakeLists.txt)
# 使用 GoogleTest(通过 FetchContent 自动下载)
include(FetchContent)
FetchContent_Declare(googletestGIT_REPOSITORY https://github.com/google/googletest.gitGIT_TAG release-1.11.0
)
FetchContent_MakeAvailable(googletest)# 添加测试可执行文件
add_executable(test_coretest_core.cpp
)# 链接核心库和 GTest
target_link_libraries(test_corePRIVATEdata_tool_coreGTest::GTestGTest::Main
)# 注册测试
add_test(NAME core_test COMMAND test_core)
处理第三方依赖(以 JSON 库为例)
# 方法1:使用 find_package(需提前安装)
find_package(nlohmann_json 3.10 REQUIRED)# 方法2:直接嵌入源码(通过 FetchContent)
include(FetchContent)
FetchContent_Declare(jsonURL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz
)
FetchContent_MakeAvailable(json)target_link_libraries(data_tool_corePRIVATEnlohmann_json::nlohmann_json
)
跨平台特殊处理
# Windows 特定配置
if(WIN32)target_compile_definitions(data_tool_corePRIVATE_CRT_SECURE_NO_WARNINGS)
endif()# MacOS 动态库安装路径
if(APPLE)set_target_properties(data_tool_corePROPERTIESINSTALL_RPATH "@loader_path/../lib")
endif()
构建与安装
# 生成构建系统
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local# 编译
cmake --build . --config Release# 运行测试
ctest --output-on-failure# 安装到系统
cmake --install .