2. CMake基础语法:CMakeLists.txt核心指令、变量与缓存、条件与循环

好,咱们直接进入正题。CMake 这东西,说白了就是一套“工程描述语言”。你写的 CMakeLists.txt,就是告诉编译器:你的项目有哪些源文件、依赖什么库、怎么链接。我刚开始接触 CMake 时,觉得它比 QMake 啰嗦多了。但用久了你会发现,它的灵活性和跨平台能力,确实值得你花点时间学。

2.1 核心指令:项目骨架

每个 CMake 项目,都从 cmake_minimum_required 和 project 开始。这两行,就像盖房子打地基,缺一不可。

cmake_minimum_required(VERSION 3.16)
project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)

cmake_minimum_required 指定了最低 CMake 版本。我个人习惯写 3.16,因为这是 Qt6 官方推荐的最低版本。你写低了,有些新特性用不了;写高了,同事的旧环境可能跑不起来。

project 指令定义了项目名、版本号和语言。这里我指定了 CXX(C++),如果你用 C 语言,可以加上 C。嗯,这里要注意:项目名后面可以跟一堆参数,但 VERSION 和 LANGUAGES 是最常用的。

接下来是添加可执行文件或库:

add_executable(MyApp main.cpp mainwindow.cpp)
add_library(MyLib STATIC utils.cpp utils.h)

add_executable 生成可执行文件,add_library 生成库文件。STATIC 表示静态库,SHARED 是动态库。我在项目中遇到过一个问题:忘记把头文件加进 add_library 的列表里,结果 IDE 里跳转不到定义。虽然编译能过,但开发体验很差。所以,头文件也列进去吧。

2.2 变量与缓存:CMake 的“内存”

变量是 CMake 的灵魂。你定义变量,传递参数,控制流程,都靠它。

set(SOURCES main.cpp mainwindow.cpp)
set(HEADERS mainwindow.h)
add_executable(MyApp ${SOURCES} ${HEADERS})

用 set 定义变量,用 ${变量名} 引用。注意,变量名大小写敏感。我习惯用大写表示全局变量,小写表示局部变量,这样一眼就能看出来。

缓存变量,是 CMake 里一个容易让人迷惑的东西。说白了,它就是把变量值存到 CMakeCache.txt 里,下次运行 CMake 时还能读到。

set(MY_OPTION "ON" CACHE BOOL "是否启用某个功能")

这里 CACHE 关键字告诉 CMake:把这个变量缓存起来。后面的 BOOL 是类型,"是否启用某个功能" 是描述。用户可以通过 cmake -DMY_OPTION=OFF 来修改它。

小技巧: 用 cmake -L 可以列出所有缓存变量。调试时特别有用。

还有一种特殊的变量:CMAKE_PREFIX_PATH。这玩意儿用来告诉 CMake 去哪里找第三方库。比如你安装了 Qt 到自定义路径,就得设置它。

set(CMAKE_PREFIX_PATH "/opt/Qt/6.5.0/gcc_64")

我曾经因为忘记设置这个路径,折腾了一下午,死活找不到 Qt 库。后来发现是路径写错了。嗯,细节决定成败。

2.3 条件与循环:让 CMakeLists.txt 活起来

条件判断,让 CMake 能根据不同的平台、编译器、选项,执行不同的逻辑。

if(WIN32)
    message("当前是 Windows 平台")
elseif(APPLE)
    message("当前是 macOS 平台")
else()
    message("当前是 Linux 平台")
endif()

if 后面跟条件表达式。常用的有:

  • WIN32:Windows 平台
  • APPLE:macOS 或 iOS
  • UNIX:类 Unix 系统(包括 Linux、macOS)
  • MSVC:Visual Studio 编译器
  • CMAKE_BUILD_TYPE:构建类型(Debug、Release 等)

循环,最常用的是 foreach。比如你要处理一堆源文件:

set(SOURCES a.cpp b.cpp c.cpp)
foreach(file ${SOURCES})
    message("处理文件: ${file}")
endforeach()

还有一种 while 循环,但用得少。我个人觉得,foreach 已经能覆盖 90% 的场景了。

避坑指南: 在 if 条件里,变量名不要加 ${}。比如 if(WIN32) 是对的,if(${WIN32}) 是错的。为什么?因为 CMake 会自动展开变量,你再加一层展开,就变成 if(TRUE) 或 if(FALSE),逻辑就乱了。

2.4 知识体系总览

下面这张图,帮你把本章的核心内容串起来。你可以把它当作一个“思维导图”,随时回来对照。

CMake 基础语法 核心指令 cmake_minimum_required project add_executable / add_library 变量与缓存 set / ${变量} CACHE 变量 CMAKE_PREFIX_PATH 条件与循环 if / elseif / else / endif foreach / endforeach while / endwhile 核心思想:用 CMakeLists.txt 描述项目结构 变量控制逻辑,条件适应平台,循环简化重复

2.5 实战小例子:一个完整的 CMakeLists.txt

光说不练假把式。咱们写一个完整的例子,把上面讲的东西串起来。

cmake_minimum_required(VERSION 3.16)
project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 Qt 库
find_package(Qt6 REQUIRED COMPONENTS Widgets)

# 源文件列表
set(SOURCES main.cpp mainwindow.cpp)
set(HEADERS mainwindow.h)

# 根据平台添加额外源文件
if(WIN32)
    list(APPEND SOURCES win_utils.cpp)
elseif(APPLE)
    list(APPEND SOURCES mac_utils.cpp)
else()
    list(APPEND SOURCES linux_utils.cpp)
endif()

# 生成可执行文件
add_executable(MyApp ${SOURCES} ${HEADERS})

# 链接 Qt 库
target_link_libraries(MyApp PRIVATE Qt6::Widgets)

# 设置输出目录(可选)
set_target_properties(MyApp PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

这个例子,你直接复制到项目根目录,就能用。它做了几件事:

  • 指定 CMake 最低版本和项目信息
  • 设置 C++17 标准
  • 查找 Qt6 的 Widgets 模块
  • 根据平台添加不同的源文件
  • 生成可执行文件并链接 Qt 库
重点: find_package 是 CMake 查找外部库的核心指令。它依赖 CMAKE_PREFIX_PATH 或系统默认路径。如果你遇到“找不到 Qt”的错误,先检查这个路径对不对。

好了,这一章的内容就到这里。CMake 的语法其实不复杂,复杂的是怎么用好它。你多写几个项目,自然就熟练了。记住:变量是基础,条件判断是灵魂,循环是效率工具。下一章,咱们聊聊更高级的“函数与宏定义”,让 CMakeLists.txt 更模块化。

可可精品资料库