🔧 现代CMake最佳实践:权限最小化与依赖管理完全指南

现代CMake(3.14+)的核心设计理念是基于目标的依赖管理,通过精确控制每个目标的依赖可见性,实现权限最小化、编译效率最大化。本文将系统讲解现代CMake的最佳实践,帮助你告别全局变量的混乱时代。

一、核心概念:PRIVATE/PUBLIC/INTERFACE

1. 三种依赖可见性详解

现代CMake通过三个关键字精确控制依赖的传播范围:

关键字 当前目标 依赖当前目标的其他目标 典型场景
PRIVATE ✅ 可见 ❌ 不可见 内部实现依赖,如第三方库、内部工具
PUBLIC ✅ 可见 ✅ 可见 接口依赖,如导出头文件中使用的库
INTERFACE ❌ 不可见 ✅ 可见 纯头文件库,仅对使用者生效

依赖传播示意图

1
2
3
A 依赖 B (PUBLIC)  →  使用 A 的目标也能看到 B
A 依赖 B (PRIVATE) → 使用 A 的目标看不到 B
A 依赖 B (INTERFACE) → A 自己看不到 B,但使用 A 的目标能看到 B

2. 依赖传播的实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
# libA 是一个网络库,内部使用 libuv 实现
add_library(libA STATIC libA.cpp)

# libuv 是内部实现细节,不应该暴露给 libA 的使用者
target_link_libraries(libA PRIVATE libuv)

# libA 的公共头文件中使用了 libB 的类型
# 所以 libB 必须是 PUBLIC 依赖
target_link_libraries(libA PUBLIC libB)

# libA 提供了一个纯头文件的插件接口
# 插件实现者需要这个接口,但 libA 自己不需要
target_link_libraries(libA INTERFACE libA_plugin_interface)

3. 包含目录的可见性控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add_library(mylib STATIC mylib.cpp)

# 私有包含目录 - 仅 mylib.cpp 可见
target_include_directories(mylib PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/internal
${CMAKE_CURRENT_SOURCE_DIR}/third_party
)

# 公共包含目录 - mylib 和使用 mylib 的目标都可见
target_include_directories(mylib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)

# 接口包含目录 - 仅使用 mylib 的目标可见
target_include_directories(mylib INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include/mylib/plugins
)

4. 编译定义的可见性控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add_library(mylib STATIC mylib.cpp)

# 私有定义 - 仅影响当前目标的编译
target_compile_definitions(mylib PRIVATE
DEBUG_MODE=1
INTERNAL_VERSION="2.0"
)

# 公共定义 - 当前目标和使用者都会获得
target_compile_definitions(mylib PUBLIC
MYLIB_API_VERSION=3
)

# 接口定义 - 仅使用者获得
target_compile_definitions(mylib INTERFACE
MYLIB_PLUGIN_SUPPORT
)

二、传统方式 vs 现代方式

❌ 传统方式(不推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 全局设置,影响所有后续目标
cmake_minimum_required(VERSION 3.10)
project(myproject)

# 全局包含目录 - 所有目标都能看到
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${CMAKE_SOURCE_DIR}/third_party/libuv/include)
include_directories(${CMAKE_SOURCE_DIR}/third_party/openssl/include)

# 全局库目录
link_directories(${CMAKE_SOURCE_DIR}/third_party/lib)
link_directories(/usr/local/lib)

# 全局编译选项
add_definitions(-DDEBUG)
add_definitions(-DVERSION="1.0")

# 全局编译标志
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -O2")

# 链接时不指定可见性
add_executable(myapp main.cpp)
target_link_libraries(myapp uv ssl crypto pthread)

传统方式的问题

  1. 污染全局命名空间 - 所有目标都继承相同的设置
  2. 隐藏依赖关系 - 无法从 CMakeLists.txt 看出真实依赖
  3. 编译效率低 - 修改任何头文件都会触发大量重编译
  4. 难以维护 - 多模块项目容易产生冲突
  5. 不可移植 - 硬编码路径在其他环境无法工作

✅ 现代方式(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
cmake_minimum_required(VERSION 3.14)
project(myproject VERSION 1.0.0 LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ========== 第三方库配置 ==========
find_package(OpenSSL REQUIRED)
find_package(Threads REQUIRED)

# ========== 内部库:网络模块 ==========
add_library(network STATIC
src/network/tcp_client.cpp
src/network/tcp_server.cpp
src/network/ssl_wrapper.cpp
)

target_include_directories(network
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/network>
$<INSTALL_INTERFACE:include/network>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/network/internal
)

target_link_libraries(network
PUBLIC
OpenSSL::SSL
OpenSSL::Crypto
PRIVATE
Threads::Threads
)

target_compile_features(network PUBLIC cxx_std_17)

# ========== 主程序 ==========
add_executable(myapp
src/main.cpp
src/app.cpp
)

target_include_directories(myapp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_link_libraries(myapp PRIVATE
network
)

target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-Wall -Wextra -g>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)

三、现代CMake项目结构示例

推荐的项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
myproject/
├── CMakeLists.txt # 根配置
├── CMakePresets.json # 预设配置(可选)
├── cmake/ # CMake 模块
│ ├── FindMyLib.cmake
│ └── MyProjectConfig.cmake.in
├── include/ # 公共头文件
│ └── mylib/
│ ├── api.h
│ └── types.h
├── src/ # 源代码
│ ├── CMakeLists.txt
│ ├── mylib/
│ │ ├── internal/ # 私有头文件
│ │ │ └── impl.h
│ │ └── lib.cpp
│ └── app/
│ └── main.cpp
├── tests/ # 测试代码
│ ├── CMakeLists.txt
│ └── test_main.cpp
└── third_party/ # 第三方库
└── googletest/

根目录 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
cmake_minimum_required(VERSION 3.14)

project(MyProject
VERSION 1.0.0
DESCRIPTION "A modern CMake project example"
LANGUAGES CXX
)

# 全局设置(仅设置必要的)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 设置默认构建类型
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# 添加子目录
add_subdirectory(src)
add_subdirectory(tests)

# 安装配置
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

# 安装头文件
install(DIRECTORY include/mylib
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# 生成版本配置文件
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)

# 安装 CMake 配置文件
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
)

库的 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# src/CMakeLists.txt

# 收集源文件
add_library(mylib
mylib/core.cpp
mylib/utils.cpp
mylib/network.cpp
)

# 设置目标属性
set_target_properties(mylib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
OUTPUT_NAME mylib
POSITION_INDEPENDENT_CODE ON
)

# 包含目录
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/mylib/internal
)

# 编译定义
target_compile_definitions(mylib
PUBLIC
MYLIB_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
MYLIB_VERSION_MINOR=${PROJECT_VERSION_MINOR}
PRIVATE
MYLIB_BUILDING
)

# 编译选项
target_compile_options(mylib
PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)

# 链接依赖
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)

target_link_libraries(mylib
PUBLIC
OpenSSL::SSL
PRIVATE
Threads::Threads
OpenSSL::Crypto
)

# 安装目标
install(TARGETS mylib
EXPORT mylibTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(EXPORT mylibTargets
FILE mylibTargets.cmake
NAMESPACE MyProject::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
)

测试的 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# tests/CMakeLists.txt

# 使用 FetchContent 获取 Google Test
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)

# 禁用 Google Test 的 C++17 警告
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# 测试可执行文件
add_executable(unit_tests
test_core.cpp
test_utils.cpp
test_network.cpp
)

target_include_directories(unit_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)

target_link_libraries(unit_tests PRIVATE
mylib
GTest::gtest
GTest::gtest_main
)

# 注册测试
include(GoogleTest)
gtest_discover_tests(unit_tests)

四、find_package 的现代用法

1. 使用导入目标(Imported Targets)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ✅ 推荐:使用导入目标
find_package(OpenSSL REQUIRED)
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)

# ✅ 推荐:使用导入目标
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE Threads::Threads)

# ✅ 推荐:Boost 使用导入目标
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)

# ❌ 不推荐:使用变量
find_package(OpenSSL REQUIRED)
target_link_libraries(myapp PRIVATE ${OPENSSL_LIBRARIES})
target_include_directories(myapp PRIVATE ${OPENSSL_INCLUDE_DIR})

2. 自定义 Find 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# cmake/FindMyLib.cmake
find_path(MYLIB_INCLUDE_DIR
NAMES mylib/api.h
PATHS
/usr/local/include
/opt/mylib/include
)

find_library(MYLIB_LIBRARY
NAMES mylib mylibd
PATHS
/usr/local/lib
/opt/mylib/lib
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
REQUIRED_VARS MYLIB_LIBRARY MYLIB_INCLUDE_DIR
VERSION_VAR MYLIB_VERSION
)

if(MyLib_FOUND AND NOT TARGET MyLib::MyLib)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MYLIB_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
)
endif()

mark_as_advanced(MYLIB_INCLUDE_DIR MYLIB_LIBRARY)

3. 使用 FetchContent 管理依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
include(FetchContent)

# 下载并配置第三方库
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
)

FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.12.0
)

# 一次性获取所有依赖
FetchContent_MakeAvailable(json spdlog)

# 使用
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json spdlog::spdlog)

五、生成器表达式

生成器表达式允许在构建时(而非配置时)计算值,是实现跨平台、跨配置构建的关键。

1. 常用生成器表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 根据构建类型设置编译选项
target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-g -O0 -Wall>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
$<$<CONFIG:RelWithDebInfo>:-O2 -g>
)

# 根据编译器设置选项
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -Weverything>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /utf-8>
)

# 根据平台设置选项
target_compile_definitions(myapp PRIVATE
$<$<PLATFORM_ID:Windows>:WINDOWS_BUILD>
$<$<PLATFORM_ID:Linux>:LINUX_BUILD>
$<$<PLATFORM_ID:Darwin>:MACOS_BUILD>
)

# 根据语言版本设置
target_compile_features(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:cxx_std_20>
)

# 条件链接库
target_link_libraries(myapp PRIVATE
$<$<PLATFORM_ID:Windows>:ws2_32>
$<$<PLATFORM_ID:Linux>:rt>
)

2. 包含目录的生成器表达式

1
2
3
4
5
6
target_include_directories(mylib PUBLIC
# 构建时使用源码目录
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
# 安装后使用安装目录
$<INSTALL_INTERFACE:include>
)

3. 自定义命令的条件执行

1
2
3
4
5
6
7
add_custom_command(TARGET myapp POST_BUILD
COMMAND $<$<CONFIG:Debug>:${CMAKE_COMMAND}>
-E copy_if_different
$<TARGET_FILE:mylib>
$<TARGET_FILE_DIR:myapp>
COMMENT "Copy library to output directory (Debug only)"
)

六、预编译头(PCH)

预编译头可以显著提高编译速度,特别是对于大型项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 添加预编译头
target_precompile_headers(mylib PRIVATE
<vector>
<string>
<map>
<memory>
<algorithm>
<iostream>
"mylib/common.h"
)

# 重用其他目标的预编译头
target_precompile_headers(myapp REUSE_FROM mylib)

# 禁用特定文件的预编译头
set_source_files_properties(special.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON)

七、CMakePresets.json

CMakePresets.json 是 CMake 3.19+ 引入的配置预设文件,可以统一管理构建配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "Default Config",
"description": "Default build configuration",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_STANDARD": "17",
"BUILD_TESTS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug Config",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS_DEBUG": "-Wall -Wextra -g -fsanitize=address"
}
},
{
"name": "release",
"displayName": "Release Config",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS_RELEASE": "-O3 -march=native"
}
},
{
"name": "msvc",
"displayName": "MSVC Config",
"inherits": "default",
"generator": "Visual Studio 17 2022",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "cl.exe"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
},
{
"name": "debug",
"configurePreset": "debug"
},
{
"name": "release",
"configurePreset": "release"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"output": {
"outputOnFailure": true
}
}
]
}

使用方式

1
2
3
4
5
6
7
8
# 配置
cmake --preset debug

# 构建
cmake --build --preset debug

# 测试
ctest --preset default

八、常见陷阱与解决方案

1. 循环依赖

1
2
3
4
5
6
7
8
# ❌ 错误:A 依赖 B,B 也依赖 A
target_link_libraries(A PUBLIC B)
target_link_libraries(B PUBLIC A)

# ✅ 解决:将公共部分提取为独立库
add_library(common STATIC common.cpp)
target_link_libraries(A PRIVATE common B)
target_link_libraries(B PRIVATE common)

2. 头文件路径问题

1
2
3
4
5
6
# ❌ 错误:使用相对路径
#include "mylib/internal/impl.h"

# ✅ 正确:使用从包含目录开始的路径
target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
#include "mylib/internal/impl.h"

3. 静态库的传递依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 静态库的 PRIVATE 依赖不会自动传递
add_library(static_lib STATIC lib.cpp)
target_link_libraries(static_lib PRIVATE dependency)

# 使用者需要显式链接
add_executable(app main.cpp)
target_link_libraries(app PRIVATE static_lib dependency)

# ✅ 更好的方式:使用对象库
add_library(obj_lib OBJECT lib.cpp)
target_link_libraries(obj_lib PUBLIC dependency)

add_library(static_lib STATIC $<TARGET_OBJECTS:obj_lib>)
target_link_libraries(static_lib PUBLIC dependency)

4. 全局变量污染

1
2
3
4
5
# ❌ 错误:修改全局变量影响所有目标
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

# ✅ 正确:使用 target_compile_options
target_compile_options(myapp PRIVATE -Wall)

九、最佳实践清单

✅ 必须做的

实践 说明
使用 target_* 命令 替代全局命令,精确控制依赖
明确指定依赖可见性 PRIVATE/PUBLIC/INTERFACE 三选一
使用导入目标 find_package 后使用 Lib::Lib 形式
设置 C++ 标准 使用 CMAKE_CXX_STANDARDtarget_compile_features
使用生成器表达式 实现跨平台、跨配置的构建

❌ 必须避免的

实践 替代方案
include_directories() target_include_directories()
link_directories() target_link_directories() 或使用导入目标
add_definitions() target_compile_definitions()
add_compile_options() target_compile_options()
全局修改 CMAKE_CXX_FLAGS 使用 target_compile_options()
aux_source_directory() 显式列出源文件
file(GLOB ...) 显式列出源文件(除非配合配置时检查)

📋 推荐的 CMakeLists.txt 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
cmake_minimum_required(VERSION 3.14)

project(MyProject
VERSION 1.0.0
LANGUAGES CXX
)

# ========== 全局配置 ==========
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# ========== 依赖 ==========
find_package(Threads REQUIRED)

# ========== 目标 ==========
add_library(mylib STATIC
src/lib.cpp
)

target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_compile_features(mylib PUBLIC cxx_std_17)

target_compile_options(mylib PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)

target_link_libraries(mylib
PUBLIC
Threads::Threads
)

# ========== 安装 ==========
include(GNUInstallDirs)
install(TARGETS mylib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/mylib
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

🏁 总结

现代 CMake 的核心理念是基于目标的依赖管理,通过精确控制每个目标的依赖可见性,实现:

  1. 权限最小化 - 只暴露必要的接口
  2. 编译效率最大化 - 减少不必要的重编译
  3. 依赖关系清晰 - 从 CMakeLists.txt 一目了然
  4. 可移植性强 - 使用导入目标,避免硬编码路径
  5. 易于维护 - 模块化设计,职责明确

记住核心原则:能用 target_ 就不用全局命令,能明确指定可见性就不用默认值*。

更多 C++ 开发经验分享,欢迎访问我的博客 xutopia77 - 见字如面