Cmake 入门指南

16 阅读3分钟

1 简介与全局配置

官方网址 CMake 是一个用于管理源代码构建的工具。最初,CMake 被设计为各种 Makefile 变体的生成器。如今 CMake 广泛应用于 C 和 C++ 语言,但它也可以用于构建其他语言的源代码。

  • 核心逻辑:CMakeLists.txt -> 生成器 (Ninja/Makefile/VS) -> 构建系统。建议使用 3.22...3.26 语法指定版本范围,这能让新版本 CMake 启用新特性,同时保持旧版本兼容。
  • 版本管理:cmake_minimum_required(VERSION 3.22) —— 必须置于文件首行。
  • 项目属性:project(MyAwesomeProject VERSION 1.0 LANGUAGES C CXX) —— 定义名称、版本和语言。
  • 语言标准精细控制:
    • CMAKE_CXX_STANDARD:可选 11, 14, 17, 20, 23。
    • CMAKE_CXX_EXTENSIONS:建议设为 OFF(禁用 GNU 扩展以确保标准跨平台)。
    • CMAKE_CXX_STANDARD_REQUIRED:建议设为 ON(强制编译器支持,不支持则报错)。

2 语法核心与底层逻辑

CMake 不是简单的配置,它是具有变量作用域的脚本语言。

2.1 变量系统

  • 普通变量 (Normal Variable):
    • 作用域:在当前文件夹及其子文件夹中有效。
    • 设置:set(MY_VAR "a" "b") 实际上是 set(MY_VAR "a;b")。
  • 缓存变量 (Cache Variable):
    • 命令:set(VAR VAL CACHE STRING "doc")。
    • 粘性:即便修改 CMakeLists.txt,只要 CMakeCache.txt 还在,它就不会变,除非手动 -D 修改。
    • -D 逻辑:命令行参数具有最高优先级,会直接写入缓存。
  • 内置变量 (关键路径):
    • CMAKE_SOURCE_DIR:工程根目录。
    • CMAKE_BINARY_DIR:构建输出根目录。
    • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在目录。
    • CMAKE_CURRENT_BINARY_DIR:当前对应的构建输出目录。

2.2 列表操作——伪数组

列表本质是带分号的字符串。使用 list() 命令操作:

  • list(LENGTH <out_var>):获取元素数量。
  • list(APPEND <elements...>):追加元素。
  • list(INSERT ):在指定位置插入。
  • list(REMOVE_ITEM ):根据值删除。
  • list(REVERSE ):反转列表。
  • 循环遍历:
    foreach(item IN LISTS my_list)
        message(STATUS "Processing: ${item}")
    endforeach()

2.3 流程控制与条件判定 (if)

if(...) 会经历两阶段流程:

  • 常量检查:匹配 ON, TRUE, YES, 1 (真) 或 OFF, FALSE, NO, 0, 空字符串, 以-NOTFOUND结尾 (假)。
  • 变量解引用:若非逻辑常量,则假设它是变量名并自动读取其值进行判断。

常用指令

  • STREQUAL:字符串全等比较。
  • DEFINED:变量是否存在(不关心值)。
  • MATCHES:正则匹配。
  • VERSION_LESS / VERSION_EQUAL:专门用于版本号对齐。
  • NOT / AND / OR:组合逻辑。

2.4 函数与宏

  • function(name [arg1...]):创建独立作用域。
    • 返回值:新版本使用 return(PROPAGATE var);旧版本使用 set(var val PARENT_SCOPE)。
  • macro(name [arg1...]):文本替换,不创建作用域。内部命令直接在调用处执行。
  • 可变参数:使用 ARGN 获取所有未命名的参数。

2.5 日志系统

使用message方法进,格式message(level_key content),日志关键字如下:

  • FATAL_ERROR:致命错误,立即停止配置。
  • SEND_ERROR:配置错误,继续脚本运行但不生成构建系统。
  • WARNING / AUTHOR_WARNING:警告信息。
  • STATUS:带 -- 前缀的常规信息。
  • VERBOSE:详细模式(需开启特定开关才显示)。

3 构建与生成

3.1 命令行参数

  • -S :指定源码目录。
  • -B :指定构建目录。
  • --build :触发实际编译。
  • --config :多配置生成器(如 VS)指定 Debug 或 Release。
  • --clean-first:构建前清理旧产物。
  • --install :执行安装逻辑。
  • -- :递参数给底层的 Ninja 或 Make(如开启 8 核并行):-- -j8

3.2 预设

通过 CMakePresets.json 将复杂的命令行参数持久化,属性以及意义如下

  • version:建议 3 以上。
  • configurePresets:预定义 generator, binaryDir, cacheVariables。
  • buildPresets:预定义 targets, configuration (Debug/Release)。
  • CMakeUserPresets.json:本地个人配置,不应提交版本控制。

3.3 脚本模式

cmake -P script.cmake:不执行配置生成流程,仅顺序执行 CMake 逻辑。不支持 project() 和 Target 相关的操作。

4 目标化构建指令

这是现代 CMake 的精髓,避免使用全局命令。

4.1 定义目标 (Add Target

  • add_executable(name sources...):可执行程序。
  • add_library(name [TYPE] sources...):
    • STATIC:静态库(包含在目标中)。
    • SHARED:共享库(运行时加载)。
    • MODULE:插件库(不支持链接,仅支持动态加载)。
    • OBJECT:对象库(仅编译 .o,不打包,用 $<TARGET_OBJECTS:name> 引用)。
    • INTERFACE:接口库(无源码,仅传递配置)。
    • IMPORTED:导入已有的外部库文件。

4.2 配置目标属性

这些指令支持作用域

  • PRIVATE:编译本目标时使用,不传递给依赖者。用于 .cpp 引用的私有头文件。
  • INTERFACE:编译本目标时不使用,仅传递给依赖者。用于 Header-only 库。
  • PUBLIC:双向有效。用于 .h 中暴露给外部调用的接口定义。

指令有:

  • target_sources():关联源文件。
  • target_include_directories():头文件搜索路径。
  • target_link_libraries():链接依赖。
  • target_compile_definitions():定义宏(如 -DDEBUG)。
  • target_compile_options():设置特定编译器参数(如警告等级)。
  • target_link_options():设置链接器参数。
  • target_precompile_headers():优化构建速度,预编译头文件。
  • 高级控制:set_target_properties() 设置 OUTPUT_NAME 或版本号。

4.3 FILE_SET (CMake 3.23+):取代了繁琐的 PUBLIC_HEADER 属性。 参数:

  • BASE_DIRS:指定相对路径的基准(如 include)。
  • FILES:列出所有头文件。
  • 优势:安装时会自动保持目录结构,并自动设置 target_include_directories。

5 安装

install() 命令将构建产物移动到标准化目录。产物主要有:

  • ARCHIVE:静态库/导入库(.a / .lib),建议目录${CMAKE_INSTALL_LIBDIR}
  • LIBRARY:共享库(.so / .dylib),建议目录 ${CMAKE_INSTALL_LIBDIR}
  • RUNTIME:可执行程序 / DLL(app / .dll),建议路径${CMAKE_INSTALL_BINDIR}
  • PUBLIC_HEADER:公开头文件(.h)建议路径${CMAKE_INSTALL_INCLUDEDIR}

GNU 规范路径: include(GNUInstallDirs) 后,可使用 ${CMAKE_INSTALL_BINDIR} 等变量,自动适配 /usr/bin 或 /usr/local/bin。

6 依赖查找

  • find_file() / find_path() / find_library() / find_program():查找具体的个体。
  • find_package(Name [version] [REQUIRED] [COMPONENTS...]):
    • 结果:成功后设置变量 <Name>_FOUND。
    • REQUIRED:找不到则终止配置。
    • QUIET:不显示非致命信息。

7 生成器表达式

格式:($<...>),在配置阶段之后、构建阶段之前求值。

  • 条件选择:$<IF:condition,true_val,false_val>。
  • 配置检查:$<CONFIG:Debug>。
  • 接口区分:
    • $<BUILD_INTERFACE:...>:仅在源码目录下构建时生效。
    • $<INSTALL_INTERFACE:...>:仅在安装到目标环境后生效。
  • 路径获取:$<TARGET_FILE:target>。

8 自动化与代码生成

分为两步走,不会主动运行,必须被目标依赖;

  1. add_custom_command:
    • OUTPUT:必须明确生成的文件名。
    • DEPENDS:文件变化时才会重新触发。
    • COMMAND:调用 python, sh 或 ${CMAKE_COMMAND} -E。
  2. add_custom_target:一个“伪目标”,总是被认为已过期。常用于跑单元测试、生成文档或触发代码混淆。

CMake 内部模式 使用 ${CMAKE_COMMAND} -E 调用内置跨平台命令: copy, make_directory, echo, touch, remove。保证了 Windows 和 Linux 下构建脚本的通用性。

如果在此文章中您有所收获,请给作者一个鼓励,点个赞,谢谢支持

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给予关注和点赞;如果文章存在错误,也请多多指教!