5、目标与属性:理解add_executable与add_library、目标属性(Target Properties)的查看与设置、set_target_properties实战

这一章,我们聊聊CMake里最核心的两个概念:目标属性

说白了,CMake的一切工作,最终都是为了生成一个“目标”——要么是一个可执行文件,要么是一个库。而属性,就是附着在目标上的各种配置信息。你想想看,一个目标需要知道它的源文件是谁、头文件路径在哪、链接什么库、编译选项是什么……这些信息,全都存在属性里。

我个人习惯把目标想象成一个“对象”,属性就是它的成员变量。嗯,这个比喻虽然不严谨,但很实用。

5.1 创建目标:add_executable 与 add_library

这是CMake里最基础的两个命令。你几乎每个项目都会用到它们。

5.1.1 add_executable

用来生成一个可执行文件。语法很简单:

add_executable(<目标名> [源文件列表...])

举个例子:

add_executable(my_app main.cpp utils.cpp)

这行代码会创建一个名为 my_app 的目标,它由 main.cpputils.cpp 编译链接而成。最终生成的可执行文件,在Linux下叫 my_app,在Windows下叫 my_app.exe

小提示: 目标名在整个CMake工程里必须是唯一的。我见过有人把目标名和文件名搞混,结果链接时报错半天找不到原因。目标名只是一个逻辑标识,跟最终生成的文件名没有直接关系——当然,默认情况下它们是一样的。

5.1.2 add_library

用来生成一个库。语法:

add_library(<目标名> [STATIC | SHARED | MODULE] [源文件列表...])

三种库类型:

  • STATIC:静态库。在Linux下是 .a,Windows下是 .lib。链接时会把代码直接复制到可执行文件里。
  • SHARED:动态库。Linux下是 .so,Windows下是 .dll(同时会生成一个 .lib 导入库)。运行时加载。
  • MODULE:插件库。跟SHARED类似,但不参与链接,通常用 dlopenLoadLibrary 手动加载。

举个例子:

add_library(my_lib STATIC math.cpp io.cpp)

这会生成一个静态库 libmy_lib.a(Linux)或 my_lib.lib(Windows)。

注意: 如果不指定库类型,默认是STATIC还是SHARED?其实CMake没有默认值——你必须显式指定,或者通过 BUILD_SHARED_LIBS 变量来控制。我曾经在一个跨平台项目里忘了指定类型,结果Linux下编译正常,Windows下却生成了DLL,导致链接错误。嗯,从那以后我每次都写清楚类型。

5.2 目标属性:查看与设置

目标创建好了,但它只是一个空壳。真正让目标“活”起来的,是属性。

5.2.1 什么是目标属性?

每个目标都有一堆属性,控制着它的编译、链接、安装等行为。常见的属性有:

属性名 作用
INCLUDE_DIRECTORIES 头文件搜索路径
COMPILE_DEFINITIONS 编译宏定义
COMPILE_OPTIONS 编译选项(如 -Wall
LINK_LIBRARIES 链接的库
OUTPUT_NAME 最终生成的文件名
VERSION / SOVERSION 库的版本号(仅SHARED库有效)

你可以用 get_target_property 来查看属性值:

get_target_property(my_include_dirs my_app INCLUDE_DIRECTORIES)
message("my_app 的头文件路径: ${my_include_dirs}")

如果属性没被设置过,会返回 NOTFOUND。嗯,这个细节容易踩坑——我建议你总是先检查一下。

5.2.2 设置属性:set_target_properties

这是最常用的设置属性的命令。语法:

set_target_properties(<目标1> [<目标2> ...]
                      PROPERTIES
                      <属性名1> <值1>
                      [<属性名2> <值2> ...])

可以同时设置多个目标、多个属性。看个实战例子:

add_library(my_math STATIC math.cpp)
set_target_properties(my_math
    PROPERTIES
    OUTPUT_NAME "math_core"
    VERSION "1.2.0"
    SOVERSION "1"
    COMPILE_DEFINITIONS "USE_FAST_MATH"
)

这段代码做了几件事:

  • 把库的输出文件名改为 math_core(而不是默认的 my_math
  • 设置版本号为1.2.0,SO版本为1(Linux下会生成 libmath_core.so.1.2.0 和软链接)
  • 添加了一个编译宏 USE_FAST_MATH
核心要点: set_target_properties 是“覆盖式”赋值。如果你多次设置同一个属性,后面的值会覆盖前面的。但有些属性是列表型的(比如 INCLUDE_DIRECTORIES),你需要用 target_include_directories 来追加,而不是直接覆盖。

5.3 实战:用属性控制一个库的方方面面

光说不练假把式。我们来看一个完整的例子,把今天讲的东西串起来。

假设我们要构建一个图像处理库 imgproc,它需要:

  • 以动态库形式构建
  • 输出文件名改为 imgproc_core
  • 版本号 2.0.1,SO版本 2
  • 添加一个宏 IMGPROC_ENABLE_SSE
  • 链接一个第三方库 png

CMakeLists.txt 可以这样写:

cmake_minimum_required(VERSION 3.16)
project(ImageProcessor)

add_library(imgproc SHARED
    filter.cpp
    transform.cpp
    io.cpp
)

# 设置各种属性
set_target_properties(imgproc
    PROPERTIES
    OUTPUT_NAME "imgproc_core"
    VERSION "2.0.1"
    SOVERSION "2"
    COMPILE_DEFINITIONS "IMGPROC_ENABLE_SSE"
)

# 链接png库(假设已经用find_package找到了)
target_link_libraries(imgproc PRIVATE png)

# 查看一下最终输出文件名
get_target_property(out_name imgproc OUTPUT_NAME)
message("库的输出文件名: ${out_name}")

编译后,在Linux下你会得到 libimgproc_core.so.2.0.1,同时有 libimgproc_core.so.2libimgproc_core.so 两个软链接。Windows下则是 imgproc_core.dllimgproc_core.lib

个人经验: 我建议把 VERSIONSOVERSION 分开设置。很多新手只设 VERSION,结果发现动态库的SO版本没变,导致运行时加载了错误的库。记住:SOVERSION 控制的是二进制兼容性,VERSION 只是完整版本号。

5.4 知识体系:目标与属性核心逻辑

下面这张图,帮你理清本章的核心脉络:

CMake 目标 add_executable add_library STATIC SHARED MODULE 目标属性 (Properties) OUTPUT_NAME VERSION / SOVERSION COMPILE_DEFINITIONS set_target_properties 查看: get_target_property

这张图展示了:目标分为可执行文件和库两大类,库又有三种类型。每个目标都挂载着若干属性,而 set_target_properties 就是用来配置这些属性的核心命令。

5.5 避坑指南

最后,分享几个我踩过的坑:

  • 属性名拼写错误: CMake不会报错,只会默默忽略。我曾经把 OUTPUT_NAME 写成 OUTPUT_NAME(少了个T),结果折腾了半小时才发现。建议用 cmake --help-property-list 查看所有合法属性名。
  • 列表属性用等号覆盖: 比如 set_target_properties(... PROPERTIES INCLUDE_DIRECTORIES "/my/path") 会清空之前所有的头文件路径。正确做法是用 target_include_directories 追加。
  • 忘记设置 SOVERSION 动态库的SO版本不设置,默认是0。如果你的库做了不兼容的修改,但忘了改SO版本,用户更新后程序会直接崩溃。嗯,这个坑我帮别人排查过很多次。

好了,这一章的内容就到这里。目标与属性是CMake的基石,理解了它们,后面的构建配置就会顺畅很多。


公众号:蓝海资料掘金营,微信deep3321