6、CMake目标与属性:目标概念、目标属性、set_target_properties、get_target_properties、目录属性
好,咱们今天聊点实在的。CMake 里有个核心概念,叫「目标」。说白了,目标就是你最终要生成的那个东西——可执行文件、静态库、动态库,甚至是一些自定义的构建动作。我刚开始用 CMake 时,总觉得这东西就是个脚本,写几个 add_executable 就完事了。后来项目一复杂,才发现不懂目标,根本玩不转。
什么是目标?
目标,英文叫 target。你可以把它理解成一个「构建单元」。每个目标都有自己的名字、类型,还有一堆属性。比如:
add_executable(my_app main.cpp utils.cpp)
add_library(my_lib STATIC math.cpp io.cpp)
这里 my_app 和 my_lib 就是两个目标。前者是可执行文件,后者是静态库。嗯,就这么简单。
但真正有意思的,是目标身上的那些属性。
目标属性:目标的「身份证」
每个目标都有一堆属性。这些属性控制着编译、链接、安装等各个环节的行为。比如:
- INCLUDE_DIRECTORIES:头文件搜索路径
- COMPILE_DEFINITIONS:编译宏定义
- COMPILE_OPTIONS:编译选项
- LINK_LIBRARIES:链接的库
- OUTPUT_NAME:输出文件名
- VERSION 和 SOVERSION:共享库版本号
我举个例子你就明白了。假设你有个库,想控制它的输出文件名:
add_library(my_math STATIC math.cpp)
set_target_properties(my_math PROPERTIES OUTPUT_NAME "math_core")
这样编译出来的库文件就叫 libmath_core.a,而不是 libmy_math.a。我在项目中遇到过类似需求——客户要求库名必须符合他们的命名规范,用 set_target_properties 一行就搞定了。
set_target_properties:批量设置属性
这个命令是咱们的常用工具。它的语法很直接:
set_target_properties(target1 target2 ...
PROPERTIES
prop1 value1
prop2 value2
...)
你可以一次设置多个目标的多个属性。比如:
set_target_properties(my_app my_lib
PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON)
这里我同时给两个目标设置了 C++17 标准,并要求位置无关代码。你想想看,如果不用 set_target_properties,你得在每个目标后面单独写 target_compile_features,多麻烦。
get_target_property:把属性读出来
有设置就有读取。get_target_property 可以帮你拿到目标的某个属性值:
get_target_property(out_var target property)
举个例子:
add_executable(my_app main.cpp)
get_target_property(app_type my_app TYPE)
message("my_app 的类型是: ${app_type}")
输出会是:
my_app 的类型是: EXECUTABLE
这个命令在调试时特别有用。我记得有一次,一个库的链接总是失败,我怀疑是属性没传过去。用 get_target_property 一查,发现 LINK_LIBRARIES 是空的——原来我忘了 target_link_libraries。嗯,这种坑踩过一次就记住了。
目录属性:作用域的力量
除了目标属性,CMake 还有目录属性。目录属性作用于整个 CMakeLists.txt 所在的目录及其子目录。常用的有:
- INCLUDE_DIRECTORIES:目录级别的头文件路径
- COMPILE_DEFINITIONS:目录级别的编译宏
- COMPILE_OPTIONS:目录级别的编译选项
设置方式类似:
set_directory_properties(PROPERTIES
INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include"
COMPILE_DEFINITIONS "USE_OPENSSL")
但说实话,我个人不太推荐滥用目录属性。为什么呢?因为它的作用域太宽了。你在这个目录下定义了一个宏,子目录里所有目标都会受影响。万一子目录里有个目标不需要这个宏,你就得手动取消,很麻烦。
我更喜欢用 target_include_directories 和 target_compile_definitions 这种目标级别的命令。它们更精确,不会「误伤」其他目标。你想想看,一个大型项目里,不同模块的编译需求千差万别,用目录属性很容易造成「牵一发而动全身」的局面。
一张图看懂目标与属性
下面这张图总结了目标、属性、目录属性之间的关系:
实战:一个完整的例子
咱们来写一个完整的 CMakeLists.txt,把今天讲的东西串起来:
cmake_minimum_required(VERSION 3.16)
project(MyProject VERSION 1.0.0)
# 添加一个库目标
add_library(math_ops STATIC
src/add.cpp
src/sub.cpp
src/mul.cpp
)
# 设置库的属性
set_target_properties(math_ops PROPERTIES
OUTPUT_NAME "math_core"
VERSION 1.0.0
SOVERSION 1
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# 添加可执行文件目标
add_executable(my_app
src/main.cpp
src/calc.cpp
)
# 链接库
target_link_libraries(my_app PRIVATE math_ops)
# 设置可执行文件的属性
set_target_properties(my_app PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
# 读取属性做检查
get_target_property(app_out_dir my_app RUNTIME_OUTPUT_DIRECTORY)
message(STATUS "可执行文件输出目录: ${app_out_dir}")
get_target_property(lib_name math_ops OUTPUT_NAME)
message(STATUS "库的输出名称: ${lib_name}")
这个例子涵盖了目标创建、属性设置、属性读取的全流程。我在实际项目中就是这么用的——先定义目标,再精细化控制每个目标的编译和链接行为。
- 目标是 CMake 构建的基本单元,分为可执行文件、静态库、共享库等类型
- 目标属性控制编译、链接、输出等行为,用 set_target_properties 设置
- get_target_property 用于调试和检查属性值
- 目录属性作用域更宽,适合全局设置,但容易造成意外影响
- 优先使用目标级别的命令(如 target_include_directories),而不是目录属性
好了,关于目标和属性,今天就聊到这儿。这些东西看着简单,但用好了能让你的 CMake 脚本清晰又灵活。下次遇到复杂的构建需求,记得先想想:我能不能用目标属性来解决?
公众号:蓝海资料掘金营,微信deep3321