12、CMake基础语法:cmake_minimum_required、project、add_executable、add_library
说实话,很多初学者一上来就写CMakeLists.txt,结果编译报错一脸懵。我当年也是这样,照着网上的模板抄,抄完发现根本跑不起来。后来我才明白,CMake其实就那么几个核心命令,搞懂了它们,剩下的都是锦上添花。
今天咱们就聊聊最基础的四个命令:cmake_minimum_required、project、add_executable 和 add_library。这四个命令,说白了就是CMake世界的"四梁八柱"。你想想看,任何一个CMake项目,几乎都离不开它们。
12.1 cmake_minimum_required:版本声明
这个命令写在CMakeLists.txt的第一行。它的作用很简单——告诉CMake:"嘿,我的项目至少需要你哪个版本才能编译。"
cmake_minimum_required(VERSION 3.10)
为什么要写这个?我在项目中遇到过好几次,同事把CMakeLists.txt从旧项目拷过来,结果新环境CMake版本太低,某些新特性不支持,编译直接挂掉。有了这行声明,CMake会主动检查版本,不满足就直接报错退出,省得你排查半天。
另外,这个命令还有一个隐藏参数 FATAL_ERROR,不过现在基本不用了。因为从CMake 2.6开始,版本不匹配默认就是致命错误。
12.2 project:项目名称与语言
project 命令用来定义项目名称,顺便还能指定编程语言。它的基本用法是这样的:
project(MyProject VERSION 1.0.0 LANGUAGES C CXX)
这里我定义了项目名叫 MyProject,版本号 1.0.0,语言是C和C++。你可能会问:"为什么要指定语言?" 嗯,这里要注意——如果你不写 LANGUAGES,CMake默认会启用C和C++。如果你的项目只用C,那可以写成 LANGUAGES C,这样CMake就不会去检测C++编译器了,能省一点配置时间。
我个人习惯在项目一开始就把版本号写在这里。为什么呢?因为CMake会自动生成几个变量:
| 变量名 | 含义 |
|---|---|
PROJECT_NAME |
项目名称,这里是 MyProject |
PROJECT_VERSION |
完整版本号,1.0.0 |
PROJECT_VERSION_MAJOR |
主版本号,1 |
PROJECT_VERSION_MINOR |
次版本号,0 |
PROJECT_VERSION_PATCH |
补丁版本号,0 |
这些变量后面写安装脚本、生成版本头文件时特别有用。我曾经为了给固件打版本号,手动在代码里维护一个宏定义,每次发版都要改。后来改用 project 的版本号,配合 configure_file 自动生成头文件,再也没出过版本号写错的问题。
12.3 add_executable:生成可执行文件
这个命令用来告诉CMake:"我要编译一个可执行程序,源文件是这些。"
add_executable(my_app main.c utils.c)
上面这行代码会生成一个叫 my_app 的可执行文件(Windows下是 my_app.exe),源文件是 main.c 和 utils.c。
你可能会问:"如果源文件很多怎么办?难道要一个一个写?" 当然不是。你可以用变量来收集源文件:
set(SOURCES
main.c
utils.c
network.c
protocol.c
logger.c
)
add_executable(my_app ${SOURCES})
我个人习惯把源文件按模块分组,用不同的变量名。比如 NETWORK_SRCS、UTIL_SRCS,最后再合并。这样后期维护时,一眼就能看出哪些文件属于哪个模块。
file(GLOB ...) 来收集源文件,比如 file(GLOB SOURCES "src/*.c"),要注意——CMake不会自动检测新增文件。你添加了新源文件后,必须重新运行 cmake 命令才能生效。我建议还是手动列出源文件,更可靠。
12.4 add_library:生成库文件
库文件有两种:静态库和动态库。add_library 就是用来生成它们的。
add_library(my_lib STATIC lib.c lib_utils.c)
这里 STATIC 表示生成静态库(Linux下是 libmy_lib.a,Windows下是 my_lib.lib)。如果想生成动态库,把 STATIC 换成 SHARED:
add_library(my_lib SHARED lib.c lib_utils.c)
动态库在Linux下是 libmy_lib.so,Windows下是 my_lib.dll。
你可能会想:"那我到底该用静态库还是动态库?" 这个问题其实没有标准答案。我个人的经验是:
- 静态库:适合小项目、工具类库。编译出来的可执行文件是自包含的,部署简单,不用担心动态库缺失。
- 动态库:适合大型项目、公共库。多个程序可以共享同一个动态库,节省磁盘和内存空间。但要注意版本兼容问题——我曾经因为动态库版本不匹配,调试了整整一个下午。
另外,CMake 3.1之后还支持 OBJECT 库类型,它不生成最终的库文件,而是把目标文件(.o)收集起来,供其他目标使用。这个后面讲到高级用法时再细说。
12.5 知识体系总览
下面这张图把今天讲的四个命令以及它们之间的关系梳理了一下。你可以把它当作一个快速参考:
12.6 一个完整的例子
说了这么多,咱们来看一个完整的CMakeLists.txt长什么样:
cmake_minimum_required(VERSION 3.10)
project(Calculator VERSION 1.0.0 LANGUAGES C)
# 收集源文件
set(CALC_SRCS
main.c
add.c
subtract.c
multiply.c
divide.c
)
# 生成可执行文件
add_executable(calculator ${CALC_SRCS})
# 生成静态库(供其他项目使用)
add_library(calc_lib STATIC
add.c
subtract.c
multiply.c
divide.c
)
这个例子定义了一个计算器项目。它既生成了一个可执行文件 calculator,又生成了一个静态库 calc_lib。你可能会问:"为什么要同时生成两个?" 嗯,在实际项目中,有时候你需要一个可执行程序做测试,同时把核心算法打包成库,方便其他模块调用。我之前的项目就是这么干的——主程序用可执行文件,算法模块用静态库,两边互不干扰。
cmake_minimum_required必须写在第一行,声明CMake最低版本。project定义项目名称、版本和语言,会生成一系列PROJECT_*变量。add_executable生成可执行文件,源文件建议用变量管理。add_library生成静态库(STATIC)或动态库(SHARED),根据项目需求选择。
好了,今天的内容就到这里。这四个命令是CMake的基石,搞懂了它们,后面的学习就会顺畅很多。记住,写CMakeLists.txt就像搭积木——先把地基打牢,再往上添砖加瓦。
公众号:蓝海资料掘金营,微信deep3321