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)
你看,就三行。但实际项目里,结构会复杂一些。我一般会按这个顺序组织:
- 版本声明:cmake_minimum_required() 放在第一行,别问为什么,这是规矩。
- 项目定义:project() 紧接着,带上版本号和语言。
- 编译选项:比如 C++ 标准、编译警告开关。
- 目标定义:add_executable() 或 add_library()。
- 依赖链接:target_link_libraries() 这些。
- 安装与打包: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 那一行里,看着都头疼。
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 到底能帮我们做什么。