4、目标与可执行文件:add_executable()、add_library()、目标的概念与属性
好,咱们进入正题。这一章聊的是 CMake 里最核心的概念——目标(Target)。说白了,你写 CMakeLists.txt 就是在描述怎么把源代码变成一个个目标。可执行文件、库文件,都是目标。
我个人习惯把目标想象成一个「盒子」。这个盒子装着源文件、编译选项、链接库、头文件路径……所有你需要的东西。CMake 帮你管理这些盒子,最后生成你想要的产物。
4.1 可执行文件目标:add_executable()
这个命令最常用,也最简单。它的作用就是告诉 CMake:「嘿,我要生成一个可执行文件。」
add_executable(my_app main.cpp utils.cpp)
这里 my_app 就是目标的名字。你可以把它理解成这个盒子的标签。后面跟着的源文件,就是放进盒子里的原材料。
嗯,这里要注意:目标名字在整个 CMake 工程里必须是唯一的。你不能同时有两个 my_app,否则 CMake 会报错。
set_target_properties() 修改输出文件名。比如:
set_target_properties(my_app PROPERTIES OUTPUT_NAME "my_super_app")
这样生成的可执行文件就叫 my_super_app 了。
我在项目中遇到过一个问题:有人把源文件路径写错了,结果编译了半天全是链接错误。后来发现是 add_executable() 里漏了一个文件。所以,我建议你养成好习惯——每次添加新源文件,第一时间更新 CMakeLists.txt。
4.2 库文件目标:add_library()
库文件分三种:静态库、动态库、模块库。用 add_library() 来创建。
add_library(my_lib STATIC utils.cpp math.cpp) # 静态库
add_library(my_lib SHARED utils.cpp math.cpp) # 动态库
add_library(my_lib MODULE utils.cpp math.cpp) # 模块库(很少用)
如果不指定类型,默认是 STATIC 还是 SHARED?其实都不是。默认是 STATIC,但你可以通过 BUILD_SHARED_LIBS 变量来改变这个默认行为。
__declspec(dllexport) 和 __declspec(dllimport) 控制符号可见性。或者,更现代的做法是用 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 这个属性。
你想想看,为什么要有静态库和动态库的区别?静态库会把代码直接复制到可执行文件里,体积大但部署简单。动态库是运行时加载,体积小但需要处理依赖。我个人习惯:小工具用静态库,大型项目用动态库。
4.3 目标的概念:不只是个名字
目标在 CMake 里是一个「一等公民」。它不仅仅是名字和源文件的集合。每个目标都有一堆属性(Properties),控制着编译、链接、安装等行为。
举个例子,你可以给目标设置头文件搜索路径:
target_include_directories(my_app PRIVATE include/)
也可以设置链接库:
target_link_libraries(my_app PRIVATE my_lib)
还可以设置编译宏:
target_compile_definitions(my_app PRIVATE USE_NEW_FEATURE=1)
这些命令都是在往目标的「盒子」里添加东西。CMake 最终会把这些信息传递给编译器、链接器。
为什么会这样设计?因为目标之间可以形成依赖关系。比如 my_app 依赖 my_lib,那么 my_lib 的头文件路径、编译宏会自动传递给 my_app。这就是 CMake 的「传递性」——你不需要手动管理这些细节。
4.4 目标属性:藏在盒子里的秘密
每个目标都有几十个属性。常用的有这些:
| 属性名 | 作用 | 示例 |
|---|---|---|
OUTPUT_NAME |
修改输出文件名 | set_target_properties(my_app PROPERTIES OUTPUT_NAME "my_app_v2") |
VERSION |
设置库的版本号 | set_target_properties(my_lib PROPERTIES VERSION 1.2.3) |
LINK_FLAGS |
添加链接器选项 | set_target_properties(my_app PROPERTIES LINK_FLAGS "-static") |
COMPILE_FLAGS |
添加编译器选项 | set_target_properties(my_app PROPERTIES COMPILE_FLAGS "-Wall -O2") |
FOLDER |
在 IDE 中分组 | set_target_properties(my_app PROPERTIES FOLDER "Applications") |
嗯,这里要注意:COMPILE_FLAGS 和 LINK_FLAGS 是字符串形式的,容易写错。我更推荐用 target_compile_options() 和 target_link_options(),它们更安全、更现代。
4.5 实战:一个完整的小例子
咱们把上面的知识点串起来,写一个完整的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0)
# 添加一个库
add_library(math_lib STATIC math.cpp)
target_include_directories(math_lib PUBLIC include/)
target_compile_definitions(math_lib PRIVATE MATH_DEBUG=0)
# 添加一个可执行文件
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE math_lib)
target_compile_options(my_app PRIVATE -Wall -Wextra)
set_target_properties(my_app PROPERTIES
OUTPUT_NAME "my_super_app"
VERSION "1.0.0"
)
这个例子展示了:
- 如何创建库目标和可执行目标
- 如何设置头文件路径、编译宏、编译选项
- 如何链接库
- 如何修改输出文件名和版本号
我在项目中遇到过一个问题:target_include_directories 的 PUBLIC、PRIVATE、INTERFACE 搞混了。结果库的头文件没传递到可执行文件里,编译报错找不到头文件。记住这个口诀:自己的东西用 PRIVATE,给别人用的用 PUBLIC,只给别人用自己不用的用 INTERFACE。
4.6 避坑指南:目标相关的常见错误
我曾经在大型项目里犯过一个低级错误:两个子目录里定义了同名的目标。CMake 报错说目标重复,我找了半天才发现。解决方案很简单:给目标名字加前缀,比如 module_a_my_lib 和 module_b_my_lib。
还有一个坑:add_library() 和 add_executable() 必须在 target_link_libraries() 之前调用。否则 CMake 会报「目标不存在」的错误。这个顺序问题,新手经常遇到。
最后,别忘了 cmake_minimum_required() 和 project() 要写在最前面。没有它们,CMake 会一脸懵逼。
- 目标是 CMake 的核心抽象,管理源文件、编译选项、链接库
add_executable()生成可执行文件,add_library()生成库文件- 目标属性控制编译、链接、安装等行为
- 用
target_*系列命令设置属性,比直接操作变量更安全 - 注意目标名字唯一性、命令调用顺序、传递性规则
好,这一章就到这里。下一章咱们聊聊「变量与缓存」,看看 CMake 里那些让人头疼的变量作用域问题。