CMake 系列教程二基础命令详解从单文件到多目标掌握 CMake 核心命令一、回顾与准备上一篇我们完成了一个最简 CMake 项目。现实项目远不止一个main.cpp——多文件、多目标、头文件搜索路径、链接第三方库……这些都离不开 CMake 的核心命令。本文所有示例基于CMake 3.20推荐使用现代 CMake 风格。二、项目定义命令2.1cmake_minimum_requiredcmake_minimum_required(VERSION 3.20)必须写在CMakeLists.txt的最开头project()之前设置策略Policy行为到对应版本避免旧版兼容行为可选加FATAL_ERROR3.x 中已无实际区别2.2project# 完整形式 project(MyApp VERSION 1.0.0 DESCRIPTION A demo application LANGUAGES CXX C )project()做了很多事设置的变量值PROJECT_NAMEMyAppPROJECT_VERSION1.0.0PROJECT_DESCRIPTIONA demo applicationCMAKE_PROJECT_NAMEMyApp顶层项目名CMAKE_CXX_COMPILER自动检测的 C 编译器⚠️LANGUAGES不写时默认为C CXX。纯 C 项目记得写LANGUAGES C可加速配置。三、目标定义命令3.1add_executable# 基本用法 add_executable(myapp main.cpp) # 多个源文件 add_executable(myapp main.cpp app.cpp utils.cpp ) # 使用 GLOB 自动收集源文件不推荐见后文说明 file(GLOB SOURCES src/*.cpp) add_executable(myapp ${SOURCES})3.2add_library# 静态库默认 add_library(math STATIC math/add.cpp math/sub.cpp ) # 动态库共享库 add_library(utils SHARED utils/logger.cpp utils/config.cpp ) # 接口库仅头文件库无 .cpp 文件 add_library(fmt INTERFACE)类型关键字产出说明静态库STATIC.a/.lib编译时链接到可执行文件中动态库SHARED.so/.dll运行时加载接口库INTERFACE无产物仅传播头文件路径和编译选项3.3 为什么不用file(GLOB)收集源文件# ❌ 不推荐 file(GLOB MY_SOURCES src/*.cpp) add_executable(myapp ${MY_SOURCES})原因CMake 的配置阶段不会自动重新运行。新增.cpp文件后CMake 不知道需要重新配置新文件不会被编译。除非你每次手动cmake -B build否则构建会悄悄跳过新文件。正确做法显式列出每个源文件。四、目标属性命令现代 CMake 核心这是现代 CMake 最重要的部分——以目标为中心设置属性而非全局变量。4.1target_include_directories控制头文件搜索路径add_library(math STATIC math/add.cpp math/sub.cpp) # PRIVATE仅 math 目标本身可见 target_include_directories(math PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/math ) # PUBLICmath 目标可见链接 math 的目标也可见 target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/math ) # INTERFACEmath 目标本身不可见仅传播给链接它的目标 # 适用于纯头文件库 target_include_directories(fmt INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include )三种可见性关键字自己链接自己的目标典型场景PRIVATE✅❌内部实现细节PUBLIC✅✅对外公开的头文件INTERFACE❌✅仅头文件库记忆口诀PRIVATE 是我的PUBLIC 是大家的INTERFACE 是只给别人的。4.2target_link_libraries链接库并传播属性add_executable(myapp main.cpp) # 链接 math 库 target_link_libraries(myapp PRIVATE math) # 链接系统库如 pthread target_link_libraries(myapp PRIVATE pthread) # 链接第三方库通过 find_package 找到的后文详解 target_link_libraries(myapp PRIVATE fmt::fmt)关键理解target_link_libraries不仅链接库文件还会传播该库的PUBLIC/INTERFACE属性头文件路径、编译选项等。# 假设 math 声明了 PUBLIC include 路径 target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/math) # myapp 链接 math 后自动获得 math 的 PUBLIC include 路径 target_link_libraries(myapp PRIVATE math) # ✅ 无需再手动 target_include_directories4.3target_compile_options设置编译选项# 开启 C17警告全部打开 target_compile_options(myapp PRIVATE -stdc17 -Wall -Wextra -Wpedantic ) # 跨平台写法避免在 MSVC 上传 GCC 选项 target_compile_options(myapp PRIVATE $$CXX_COMPILER_ID:GNU,Clang:-Wall -Wextra $$CXX_COMPILER_ID:MSVC:/W4 )$$...:...是生成器表达式Generator Expression后续文章会详细讲解。4.4target_compile_features更优雅地指定 C 标准# 等价于 -stdc17但跨平台 target_compile_features(myapp PRIVATE cxx_std_17)这是设置 C 标准的推荐方式比全局设置CMAKE_CXX_STANDARD更精确。4.5 属性传播总结┌──────────────────────┐ │ math (库目标) │ │ │ │ PUBLIC include: math/ │ │ PUBLIC compile: -O2 │ └──────────┬───────────┘ │ target_link_libraries(myapp PRIVATE math) │ ▼ ┌──────────────────────┐ │ myapp (可执行) │ │ │ │ ✅ 获得 math/ 路径 │ │ ✅ 获得 -O2 选项 │ └──────────────────────┘五、实战多目标项目5.1 项目结构calculator/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── core/ │ │ ├── calc.h │ │ └── calc.cpp │ └── utils/ │ ├── logger.h │ └── logger.cpp └── include/ └── calc/ └── calc.h # 对外公开头文件5.2 CMakeLists.txtcmake_minimum_required(VERSION 3.20) project(Calculator VERSION 1.0.0 LANGUAGES CXX) # --- 库core --- add_library(calc STATIC src/core/calc.cpp ) target_include_directories(calc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # 对外公开 ) target_include_directories(calc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core # 内部使用 ) target_compile_features(calc PUBLIC cxx_std_17) # --- 库utils --- add_library(utils STATIC src/utils/logger.cpp ) target_include_directories(utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/utils ) target_compile_features(utils PRIVATE cxx_std_17) # --- 可执行文件 --- add_executable(calculator src/main.cpp) target_link_libraries(calculator PRIVATE calc utils) # calculator 自动获得 calc 和 utils 的 PUBLIC 属性5.3 关键要点库目标自己声明自己需要的头文件路径而非让消费者手动添加PUBLIC和PRIVATE精确控制传播范围target_compile_features(calc PUBLIC cxx_std_17)— 库要求 C17链接它的目标自动继承此要求消费者calculator只需target_link_libraries无需关心依赖细节六、构建类型6.1 四种标准构建类型类型优化调试信息用途Debug❌✅开发调试Release✅❌正式发布RelWithDebInfo✅✅发布但保留调试信息MinSizeRel✅体积优先❌嵌入式/体积敏感场景6.2 设置构建类型# 配置时指定cmake-Bbuild-DCMAKE_BUILD_TYPERelease cmake--buildbuild# CMakeLists.txt 中设置默认值 if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING Build type FORCE) endif()⚠️单配置生成器Makefile、Ninja通过CMAKE_BUILD_TYPE切换需要重新配置。多配置生成器Visual Studio、Xcode在构建时指定cmake-Bbuild-GVisual Studio 17 2022cmake--buildbuild--configRelease七、子目录与多 CMakeLists.txt7.1add_subdirectory当项目变大可以拆分为多个子目录每个子目录有自己的CMakeLists.txtproject/ ├── CMakeLists.txt # 顶层 ├── src/ │ ├── CMakeLists.txt # 定义可执行目标 │ └── main.cpp └── libs/ ├── math/ │ ├── CMakeLists.txt # 定义 math 库目标 │ └── math.cpp └── utils/ ├── CMakeLists.txt # 定义 utils 库目标 └── logger.cpp顶层 CMakeLists.txtcmake_minimum_required(VERSION 3.20) project(MyProject LANGUAGES CXX) add_subdirectory(libs/math) add_subdirectory(libs/utils) add_subdirectory(src)libs/math/CMakeLists.txtadd_library(math STATIC math.cpp) target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_features(math PUBLIC cxx_std_17)src/CMakeLists.txtadd_executable(myapp main.cpp) target_link_libraries(myapp PRIVATE math utils)7.2 重要的目录变量变量含义CMAKE_CURRENT_SOURCE_DIR当前CMakeLists.txt所在目录源码侧CMAKE_CURRENT_BINARY_DIR当前CMakeLists.txt对应的构建目录CMAKE_SOURCE_DIR最顶层CMakeLists.txt所在目录CMAKE_BINARY_DIR最顶层的构建目录PROJECT_SOURCE_DIR最近一次project()调用所在目录最佳实践优先使用CMAKE_CURRENT_SOURCE_DIR和PROJECT_SOURCE_DIR避免使用CMAKE_SOURCE_DIR后者在项目被其他项目通过add_subdirectory引入时会指错位置。八、常用辅助命令速查命令用途示例message输出信息message(STATUS Configuring...)set设置变量set(SOURCES a.cpp b.cpp)list列表操作list(APPEND SOURCES c.cpp)option定义开关option(ENABLE_TESTS Build tests ON)configure_file生成配置头文件configure_file(config.h.in config.h)add_custom_command自定义构建步骤代码生成、预处理等install安装规则安装到系统路径小结核心命令作用关键点add_executable定义可执行目标显式列出源文件add_library定义库目标STATIC / SHARED / INTERFACEtarget_include_directories头文件搜索路径PRIVATE / PUBLIC / INTERFACEtarget_link_libraries链接库 传播属性优先用 PRIVATEtarget_compile_features设置 C 标准比全局变量更精确add_subdirectory添加子目录模块化管理现代 CMake 的核心思想所有属性挂载在目标上通过target_*命令设置通过链接关系自动传播。减少全局变量让依赖关系显式、可追溯。下一期预告《CMake 系列教程三变量、条件与控制流》—— 讲解 CMake 的变量机制、条件判断、循环、函数与宏让你的构建脚本具备复杂逻辑处理能力。