12、CMake基础语法:cmake_minimum_required、project、add_executable、add_library

说实话,很多初学者一上来就写CMakeLists.txt,结果编译报错一脸懵。我当年也是这样,照着网上的模板抄,抄完发现根本跑不起来。后来我才明白,CMake其实就那么几个核心命令,搞懂了它们,剩下的都是锦上添花。

今天咱们就聊聊最基础的四个命令:cmake_minimum_requiredprojectadd_executableadd_library。这四个命令,说白了就是CMake世界的"四梁八柱"。你想想看,任何一个CMake项目,几乎都离不开它们。

12.1 cmake_minimum_required:版本声明

这个命令写在CMakeLists.txt的第一行。它的作用很简单——告诉CMake:"嘿,我的项目至少需要你哪个版本才能编译。"

cmake_minimum_required(VERSION 3.10)

为什么要写这个?我在项目中遇到过好几次,同事把CMakeLists.txt从旧项目拷过来,结果新环境CMake版本太低,某些新特性不支持,编译直接挂掉。有了这行声明,CMake会主动检查版本,不满足就直接报错退出,省得你排查半天。

注意:版本号不要写得太高。比如你明明只用到了3.10的特性,却写了3.20,那别人低版本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.cutils.c

你可能会问:"如果源文件很多怎么办?难道要一个一个写?" 当然不是。你可以用变量来收集源文件:

set(SOURCES
    main.c
    utils.c
    network.c
    protocol.c
    logger.c
)

add_executable(my_app ${SOURCES})

我个人习惯把源文件按模块分组,用不同的变量名。比如 NETWORK_SRCSUTIL_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 知识体系总览

下面这张图把今天讲的四个命令以及它们之间的关系梳理了一下。你可以把它当作一个快速参考:

CMake基础语法核心命令 cmake_minimum_required project add_executable add_library 声明最低CMake版本 定义项目名称、版本、语言 生成可执行文件 生成静态/动态库 执行顺序与依赖关系 cmake_minimum_required → project → add_executable / add_library cmake_minimum_required 必须写在最前面 project 通常在第二行,之后才能使用 PROJECT_NAME 等变量 add_executable 和 add_library 可以任意顺序,但必须在 target_link_libraries 之前

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