CMake 从入门到精通:完整教程 / 第 5 章:目标与属性
第 5 章:目标与属性
5.1 目标(Target)概念
目标是现代 CMake 的核心概念。每个目标代表一个构建产物,并携带一组属性。
5.1.1 目标类型
| 类型 | 命令 | 产物 | 示例 |
|---|---|---|---|
| 可执行文件 | add_executable | 二进制文件 | 应用程序、测试程序 |
| 静态库 | add_library(... STATIC) | .a / .lib | 底层工具库 |
| 动态库 | add_library(... SHARED) | .so / .dll | 插件、共享库 |
| 对象库 | add_library(... OBJECT) | .o 文件集合 | 避免重复编译 |
| 接口库 | add_library(... INTERFACE) | 无物理产物 | 纯头文件库、配置聚合 |
| 模块库 | add_library(... MODULE) | .so / .dll | 运行时加载的插件 |
| 导入目标 | add_executable(... IMPORTED) | 已存在的二进制 | 外部预编译工具 |
| 别名目标 | add_executable(... ALIAS) | 指向另一个目标 | 命名空间化 |
5.1.2 目标的职责
一个目标携带以下信息:
Target "mylib"
├── 源文件 → SOURCES: src/a.cpp, src/b.cpp
├── 包含目录 → INCLUDE_DIRECTORIES: include/
├── 编译定义 → COMPILE_DEFINITIONS: MY_DEFINE=1
├── 编译选项 → COMPILE_OPTIONS: -Wall
├── 编译特性 → COMPILE_FEATURES: cxx_std_17
├── 链接库 → LINK_LIBRARIES: fmt::fmt, Threads::Threads
├── 链接选项 → LINK_OPTIONS: -fsanitize=address
├── 输出名称 → OUTPUT_NAME: mylib_v2
└── 位置属性 → BINARY_DIR, SOURCE_DIR
5.2 传播规则:PUBLIC / PRIVATE / INTERFACE
这是现代 CMake 中最关键的概念。
5.2.1 三种关键字
| 关键字 | 自己使用 | 传递给依赖者 |
|---|---|---|
PRIVATE | ✅ | ❌ |
PUBLIC | ✅ | ✅ |
INTERFACE | ❌ | ✅ |
5.2.2 target_link_libraries 传播
app → mylib → zlib (PUBLIC)
→ fmt (PRIVATE)
→ range-v3 (INTERFACE)
# mylib 的定义
add_library(mylib src/mylib.cpp)
target_link_libraries(mylib
PUBLIC zlib::zlib # mylib 和使用 mylib 的人都能用 zlib
PRIVATE fmt::fmt # 仅 mylib 内部使用 fmt
INTERFACE range-v3::range-v3 # 仅使用 mylib 的人能用 range-v3
)
# app 链接 mylib
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib)
# app 自动获得:zlib(PUBLIC 传递)和 range-v3(INTERFACE 传递)
# app 不获得:fmt(PRIVATE,不传递)
5.2.3 target_include_directories 传播
add_library(mylib src/mylib.cpp)
# public 目录:mylib 和依赖者都需要
target_include_directories(mylib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
# private 目录:仅 mylib 内部需要
target_include_directories(mylib PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src # 内部头文件
)
5.2.4 target_compile_definitions 传播
add_library(mylib src/mylib.cpp)
# PUBLIC 定义:mylib 和依赖者都会定义
target_compile_definitions(mylib PUBLIC HAS_FEATURE_X)
# PRIVATE 定义:仅 mylib 内部定义
target_compile_definitions(mylib PRIVATE MYLIB_INTERNAL_DEBUG)
5.2.5 target_compile_options 传播
add_library(mylib src/mylib.cpp)
# PRIVATE 选项:仅 mylib 编译时使用
target_compile_options(mylib PRIVATE -Wall -Wextra)
# PUBLIC 选项:mylib 和依赖者都使用
target_compile_options(mylib PUBLIC -fPIC)
5.2.6 实际传播效果
# 项目结构
add_library(core src/core.cpp)
target_include_directories(core PUBLIC include/core)
target_compile_definitions(core PUBLIC ENABLE_CORE)
target_link_libraries(core PUBLIC base)
target_compile_options(core PRIVATE -Wall)
add_library(app_lib src/app.cpp)
target_link_libraries(app_lib PUBLIC core)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE app_lib)
传播效果表:
| 属性 | core | app_lib | myapp |
|---|---|---|---|
include/core 目录 | ✅ | ✅ (PUBLIC) | ✅ (传递) |
ENABLE_CORE 定义 | ✅ | ✅ (PUBLIC) | ✅ (传递) |
base 库 | ✅ | ✅ (PUBLIC) | ✅ (传递) |
-Wall 选项 | ✅ | ❌ (PRIVATE) | ❌ |
5.3 目标属性(Properties)
5.3.1 设置属性
add_library(mylib src/mylib.cpp)
# 使用 target_* 命令(推荐)
target_include_directories(mylib PUBLIC include/)
target_compile_definitions(mylib PUBLIC MY_DEFINE)
target_compile_options(mylib PRIVATE -Wall)
target_compile_features(mylib PUBLIC cxx_std_17)
# 使用通用 set_target_properties
set_target_properties(mylib PROPERTIES
OUTPUT_NAME "mylib_v2"
VERSION "2.0.0"
SOVERSION "2"
POSITION_INDEPENDENT_CODE ON
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)
# 读取属性
get_target_property(lib_name mylib OUTPUT_NAME)
message("库输出名称: ${lib_name}") # mylib_v2
5.3.2 常用目标属性
| 属性 | 说明 | 示例 |
|---|---|---|
OUTPUT_NAME | 输出文件名 | mylib_v2 |
VERSION | 版本号 | 2.0.0 |
SOVERSION | SO 版本号 | 2 |
POSITION_INDEPENDENT_CODE | 生成 PIC 代码 | ON |
CXX_STANDARD | C++ 标准 | 17 |
CXX_STANDARD_REQUIRED | 是否强制要求标准 | ON |
CXX_EXTENSIONS | 是否使用编译器扩展 | OFF |
PREFIX | 输出文件前缀 | lib |
SUFFIX | 输出文件后缀 | .so |
DEBUG_POSTFIX | Debug 版后缀 | _d |
RUNTIME_OUTPUT_DIRECTORY | 可执行文件输出目录 | ${CMAKE_BINARY_DIR}/bin |
LIBRARY_OUTPUT_DIRECTORY | 库文件输出目录 | ${CMAKE_BINARY_DIR}/lib |
ARCHIVE_OUTPUT_DIRECTORY | 静态库输出目录 | ${CMAKE_BINARY_DIR}/lib |
5.3.3 目标属性分类
属性
├── 编译属性
│ ├── INCLUDE_DIRECTORIES
│ ├── COMPILE_DEFINITIONS
│ ├── COMPILE_OPTIONS
│ ├── COMPILE_FEATURES
│ └── COMPILE_FLAGS (已弃用)
├── 链接属性
│ ├── LINK_LIBRARIES
│ ├── LINK_DIRECTORIES
│ ├── LINK_OPTIONS
│ └── LINK_FLAGS (已弃用)
├── 输出属性
│ ├── OUTPUT_NAME
│ ├── PREFIX / SUFFIX
│ ├── VERSION / SOVERSION
│ └── *_OUTPUT_DIRECTORY
├── 语言属性
│ ├── CXX_STANDARD
│ ├── CXX_STANDARD_REQUIRED
│ └── CXX_EXTENSIONS
└── 位置属性
├── SOURCE_DIR
├── BINARY_DIR
└── SOURCES
5.4 别名目标(ALIAS)
5.4.1 基本用法
# 定义库
add_library(mylib src/mylib.cpp)
# 创建别名(注意命名空间风格)
add_library(MyLib::mylib ALIAS mylib)
# 使用别名
add_executable(app main.cpp)
target_link_libraries(app PRIVATE MyLib::mylib)
5.4.2 命名空间的好处
# 项目内部使用命名空间前缀
add_library(core src/core.cpp)
add_library(MyProject::core ALIAS core)
# 后续都用 MyProject::core 引用
# 下游项目使用时
target_link_libraries(app PRIVATE MyProject::core)
# 如果 MyProject::core 不存在,CMake 会尝试 find_package(MyProject)
# 这实现了"内部构建"和"系统安装"的无缝切换
5.4.3 别名限制
- 不能对
IMPORTED目标创建别名(CMake 3.18 之前) - 别名目标和原目标共享所有属性
- 不能对别名目标设置额外属性
- 可执行文件也可以创建别名
# 可执行文件别名
add_executable(myapp main.cpp)
add_executable(MyApp::myapp ALIAS myapp)
5.5 导入目标(IMPORTED)
5.5.1 概念
导入目标代表一个已经存在的外部文件,不是由当前项目构建的:
# 导入一个已存在的可执行文件
add_executable(MyTool::mytool IMPORTED)
set_target_properties(MyTool::mytool PROPERTIES
IMPORTED_LOCATION "/usr/local/bin/mytool"
)
# 导入一个已存在的库
add_library(ExternalLib::external SHARED IMPORTED)
set_target_properties(ExternalLib::external PROPERTIES
IMPORTED_LOCATION "/usr/lib/libexternal.so"
IMPORTED_SONAME "libexternal.so.2"
INTERFACE_INCLUDE_DIRECTORIES "/usr/include/external"
)
5.5.2 IMPORTED 与 GLOBAL
# 默认 IMPORTED 目标仅在当前目录可见
add_library(Ext::lib SHARED IMPORTED)
# 设置为 GLOBAL 使其在所有目录可见
add_library(Ext::lib SHARED IMPORTED GLOBAL)
5.5.3 find_package 创建导入目标
# find_package 通常会创建导入目标
find_package(OpenSSL REQUIRED)
# 检查目标是否存在
if(TARGET OpenSSL::SSL)
target_link_libraries(myapp PRIVATE OpenSSL::SSL)
endif()
5.6 接口库详解
5.6.1 纯头文件库
# 创建一个仅包含头文件的库
add_library(header_only_lib INTERFACE)
# 接口库只能使用 INTERFACE 关键字
target_include_directories(header_only_lib INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_compile_features(header_only_lib INTERFACE cxx_std_17)
# 使用
add_executable(app main.cpp)
target_link_libraries(app PRIVATE header_only_lib)
5.6.2 配置聚合
# 创建一个接口库来聚合编译选项
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# 创建一个接口库来聚合特性
add_library(project_features INTERFACE)
target_compile_features(project_features INTERFACE cxx_std_17)
target_compile_definitions(project_features INTERFACE
$<$<CONFIG:Debug>:DEBUG_MODE>
)
# 所有目标都可以方便地使用
add_executable(app main.cpp)
target_link_libraries(app PRIVATE
project_warnings
project_features
)
5.6.3 头文件集合(FILE_SET,CMake 3.23+)
add_library(mylib src/mylib.cpp)
# 使用 FILE_SET 管理头文件
target_sources(mylib PUBLIC
FILE_SET HEADERS
BASE_DIRS include
FILES
include/mylib/core.h
include/mylib/utils.h
include/mylib/types.h
)
# 安装时自动处理头文件
install(TARGETS mylib EXPORT MyTargets
FILE_SET HEADERS DESTINATION include
)
5.7 对象库详解
5.7.1 基本用法
# 对象库编译源文件为目标文件(.o/.obj)
add_library(mylib_objects OBJECT
src/a.cpp
src/b.cpp
src/c.cpp
)
# 设置公共属性
target_include_directories(mylib_objects PUBLIC include/)
target_compile_features(mylib_objects PUBLIC cxx_std_17)
# 使用对象文件创建静态库
add_library(mylib STATIC $<TARGET_OBJECTS:mylib_objects>)
target_link_libraries(mylib PUBLIC mylib_objects)
# 使用对象文件创建动态库
add_library(mylib_shared SHARED $<TARGET_OBJECTS:mylib_objects>)
5.7.2 对象库的优势
传统方式:
src/a.cpp ──编译──> a.o ──┐
src/b.cpp ──编译──> b.o ──┼──> libstatic.a (链接)
src/c.cpp ──编译──> c.o ──┘
src/a.cpp ──编译──> a.o ──┐
src/b.cpp ──编译──> b.o ──┼──> libshared.so (再编译一次!)
src/c.cpp ──编译──> c.o ──┘
使用对象库:
src/a.cpp ──编译──> a.o ──┐
src/b.cpp ──编译──> b.o ──┼──> OBJECT ──┬──> libstatic.a
src/c.cpp ──编译──> c.o ──┘ └──> libshared.so
(每个文件只编译一次!)
5.7.3 CMake 3.12+ 对象库直接链接
add_library(mylib_objects OBJECT src/a.cpp src/b.cpp)
target_include_directories(mylib_objects PUBLIC include)
# 3.12+ 可以直接链接对象库
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib_objects)
5.8 业务场景
场景一:大型项目的模块化
# 核心库
add_library(core OBJECT src/core/*.cpp)
target_include_directories(core PUBLIC include/core)
target_compile_features(core PUBLIC cxx_std_17)
# 网络模块
add_library(network OBJECT src/network/*.cpp)
target_include_directories(network PUBLIC include/network)
target_link_libraries(network PUBLIC core OpenSSL::SSL)
# 数据库模块
add_library(database OBJECT src/database/*.cpp)
target_include_directories(database PUBLIC include/database)
target_link_libraries(database PUBLIC core PostgreSQL::PostgreSQL)
# 聚合为单一共享库
add_library(myapp SHARED
$<TARGET_OBJECTS:core>
$<TARGET_OBJECTS:network>
$<TARGET_OBJECTS:database>
)
# 应用程序
add_executable(app main.cpp)
target_link_libraries(app PRIVATE myapp)
场景二:编译选项管理
# 创建项目级别的警告配置
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
$<$<CXX_COMPILER_ID:GNU>:
-Wall -Wextra -Wpedantic -Wshadow -Wnon-virtual-dtor
-Wold-style-cast -Wcast-align -Woverloaded-virtual
>
$<$<CXX_COMPILER_ID:Clang>:
-Wall -Wextra -Wpedantic -Wshadow -Wnon-virtual-dtor
>
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX /wd4251 /wd4275
>
)
# 每个目标都可以使用
add_library(mylib src/mylib.cpp)
target_link_libraries(mylib PRIVATE project_warnings)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE project_warnings mylib)
5.9 注意事项
| 问题 | 说明 |
|---|---|
| 混用新旧风格 | 避免使用 include_directories() 等全局命令 |
| 过度使用 PUBLIC | 只有真正需要传递的属性才用 PUBLIC |
| 忘记 PRIVATE | 大多数情况下,可执行文件应使用 PRIVATE 链接 |
| 对象库的 INCLUDE 目录 | CMake 3.12 前对象库不传递属性 |
| 接口库不能设 PRIVATE/PUBLIC | 接口库只能用 INTERFACE |
5.10 扩展阅读
上一章:第 4 章 — 变量系统 | 下一章:第 6 章 — 库的构建与使用 →