3、CMake基础回顾:CMakeLists.txt结构,project()、add_executable()、add_library()命令

好,咱们正式开始动手之前,得先把地基打牢。CPack 说到底还是 CMake 的打包模块,你要是连 CMakeLists.txt 都写不利索,那后面打包肯定要踩坑。我个人习惯是,每学一个新工具,先把它依赖的东西摸一遍,这样出了问题才知道去哪找原因。

这一节咱们就快速过一下 CMake 最核心的三个东西:CMakeLists.txt 的结构、project() 命令、以及add_executable() 和 add_library()。嗯,都是基本功,但有些细节你可能平时没注意。

3.1 CMakeLists.txt 的基本骨架

说白了,CMakeLists.txt 就是 CMake 的“剧本”。你告诉它项目叫什么、用什么语言、生成什么目标,它就去帮你生成 Makefile 或者 Visual Studio 工程。

一个最简单的 CMakeLists.txt 长这样:

cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)
add_executable(MyApp main.cpp)

你看,就三行。但实际项目里,结构会复杂一些。我一般会按这个顺序组织:

  1. 版本声明:cmake_minimum_required() 放在第一行,别问为什么,这是规矩。
  2. 项目定义:project() 紧接着,带上版本号和语言。
  3. 编译选项:比如 C++ 标准、编译警告开关。
  4. 目标定义:add_executable() 或 add_library()。
  5. 依赖链接:target_link_libraries() 这些。
  6. 安装与打包:install() 和 CPack 配置。

这个顺序不是强制的,但按这个来,别人看你的 CMakeLists.txt 会舒服很多。我曾经接手过一个项目,CMakeLists.txt 写得跟天书一样,add_executable 放在最后,install 命令插在中间……那叫一个乱。

3.2 project() 命令——项目的身份证

project() 是每个 CMakeLists.txt 里必须有的命令。它干了三件事:

  • 给项目起个名字
  • 设置版本号(可选)
  • 指定编程语言(可选)

基本用法:

project(MyProject VERSION 2.0.0 LANGUAGES C CXX)

这里 MyProject 是项目名,VERSION 2.0.0 定义了主版本、次版本、补丁号。之后你可以通过 PROJECT_VERSION、PROJECT_VERSION_MAJOR 这些变量来引用。

为什么要指定语言?因为 CMake 默认会检测 C 和 C++ 编译器。如果你只写 C 代码,可以写成 LANGUAGES C,这样 CMake 就不会去找 C++ 编译器了,配置速度会快一丢丢。嗯,其实差别不大,但养成好习惯总没错。

小技巧:如果你不确定项目里用了哪些语言,直接用 LANGUAGES C CXX 最保险。我一般就这么写,省得后面加个 .cpp 文件还得回来改。

3.3 add_executable()——生成可执行文件

这个命令用来告诉 CMake:“我要编译一个可执行程序”。语法很简单:

add_executable(<目标名> [源文件列表])

举个例子:

add_executable(MyApp main.cpp utils.cpp logger.cpp)

这里 MyApp 就是最终生成的可执行文件名(在 Windows 上会变成 MyApp.exe)。源文件可以一个一个列,也可以用变量:

set(SOURCES main.cpp utils.cpp logger.cpp)
add_executable(MyApp ${SOURCES})

我个人习惯用变量来管理源文件,尤其是项目大了以后,方便增删。你想想看,要是你有 50 个 .cpp 文件,全写在 add_executable 那一行里,看着都头疼。

注意:目标名在整个 CMake 工程里必须是唯一的。你不能同时定义一个叫 MyApp 的可执行文件和一个叫 MyApp 的库,CMake 会报错。

3.4 add_library()——生成库文件

库文件分三种:静态库、动态库、模块库。add_library() 的语法和 add_executable() 很像:

add_library(<目标名> [STATIC | SHARED | MODULE] [源文件列表])

看几个例子:

# 静态库,生成 libMyLib.a 或 MyLib.lib
add_library(MyLib STATIC mylib.cpp)

# 动态库,生成 libMyLib.so 或 MyLib.dll
add_library(MyLib SHARED mylib.cpp)

# 不指定类型,由 BUILD_SHARED_LIBS 变量决定
add_library(MyLib mylib.cpp)

这里有个坑:如果不指定 STATIC 或 SHARED,CMake 默认生成静态库。但如果你设置了 set(BUILD_SHARED_LIBS ON),那就会变成动态库。我曾经在一个跨平台项目里吃过这个亏——Linux 上编译好好的,Windows 上死活链接不过去,查了半天发现是 BUILD_SHARED_LIBS 在 Windows 上被默认打开了。

避坑指南:如果你要生成动态库,建议显式写成 SHARED,别依赖默认行为。不同平台、不同 CMake 版本的默认值可能不一样。

3.5 一个完整的示例

把上面这些串起来,一个稍微像样的 CMakeLists.txt 长这样:

cmake_minimum_required(VERSION 3.16)

project(MyTool VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 源文件列表
set(APP_SOURCES main.cpp tool.cpp)
set(LIB_SOURCES core.cpp utils.cpp)

# 定义目标
add_executable(MyTool ${APP_SOURCES})
add_library(CoreLib STATIC ${LIB_SOURCES})

# 链接库
target_link_libraries(MyTool PRIVATE CoreLib)

# 安装配置(后面章节会细讲)
install(TARGETS MyTool CoreLib
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

你看,结构是不是很清晰?从上到下,版本、选项、源文件、目标、链接、安装。嗯,这就是我推荐的组织方式。

3.6 小结一下

这一节咱们回顾了 CMakeLists.txt 的基本结构,以及三个最核心的命令:

命令 作用 关键参数
project() 定义项目名称、版本、语言 VERSION, LANGUAGES
add_executable() 生成可执行文件 目标名 + 源文件列表
add_library() 生成库文件(静态/动态/模块) STATIC, SHARED, MODULE

这些是 CMake 的基石,也是后面 CPack 打包的前提。你想想看,连目标都没有,你打包什么呢?所以别嫌基础,把这些搞扎实了,后面学 CPack 会顺畅很多。

下一节咱们就开始正式进入 CPack 的世界了,先看看 CPack 到底能帮我们做什么。