工具链通关02-CMake进阶超100个文件

本文目录

  • [🔧 工具链通关 02:CMake 进阶 ------ 当你项目超过 100 个文件时怎么写](#🔧 工具链通关 02:CMake 进阶 —— 当你项目超过 100 个文件时怎么写)
    • [📐 1. 目标依赖树:CMake 的核心心智模型](#📐 1. 目标依赖树:CMake 的核心心智模型)
      • [1.1 新手写法 vs 进阶写法](#1.1 新手写法 vs 进阶写法)
      • [1.2 依赖树可视化](#1.2 依赖树可视化)
    • [📁 2. 多模块项目结构](#📁 2. 多模块项目结构)
      • [2.1 目录布局](#2.1 目录布局)
      • [2.2 顶层 CMakeLists.txt](#2.2 顶层 CMakeLists.txt)
      • [2.3 模块级 CMakeLists.txt(以 ui 为例)](#2.3 模块级 CMakeLists.txt(以 ui 为例))
    • [🔍 3. find_package 实战](#🔍 3. find_package 实战)
      • [3.1 find_package 做了什么](#3.1 find_package 做了什么)
      • [3.2 三种情况](#3.2 三种情况)
      • [3.3 自定义 FindRaylib.cmake](#3.3 自定义 FindRaylib.cmake)
    • [🎯 4. 生成器表达式](#🎯 4. 生成器表达式)
      • [4.1 什么是生成器表达式](#4.1 什么是生成器表达式)
      • [4.2 常用生成器表达式](#4.2 常用生成器表达式)
      • [4.3 实战:按配置和平台自动调整](#4.3 实战:按配置和平台自动调整)
    • [🧪 5. 测试集成](#🧪 5. 测试集成)
    • [📊 6. 编译速度优化](#📊 6. 编译速度优化)
    • [💡 7. 五条 CMake 进阶心法](#💡 7. 五条 CMake 进阶心法)
    • [🔜 系列结语](#🔜 系列结语)
    • [📚 工具链通关系列目录](#📚 工具链通关系列目录)

🎯 工具链通关系列第 2 篇 · 承接 一张图看懂 C/C++ 从源码到可执行,本篇讲 CMake 进阶。

📖 相关文章Qt Creator 极简安装与 CMake 配置指南


🔧 工具链通关 02:CMake 进阶 ------ 当你项目超过 100 个文件时怎么写


"CMake 不就是 cmake_minimum_required + add_executable 吗?"

"那你项目有 3 个模块、2 个第三方库、跨 3 个平台呢?"

"......我把所有 .c 文件列在 add_executable 里?"

"120 个文件名?"

小项目 CMake 随便写。但当文件超过 100 个、有多个模块和第三方依赖时,CMakeLists 的写法决定了编译速度、可维护性和跨平台能力。


📐 1. 目标依赖树:CMake 的核心心智模型

1.1 新手写法 vs 进阶写法

cmake 复制代码
# ❌ 新手写法:一个 CMakeLists 全搞定
add_executable(myapp
    src/main.c src/engine.c src/renderer.c src/game.c
    src/ui/button.c src/ui/menu.c src/ui/dialog.c
    src/net/client.c src/net/server.c
    src/utils/string.c src/utils/file.c src/utils/math.c
    # ... 还有 90 个 ...
)
target_include_directories(myapp PRIVATE
    src/ src/engine src/renderer src/ui src/net src/utils
    third_party/raylib/include third_party/cJSON
)
target_link_libraries(myapp raylib cJSON)

问题 :改一个 button.c,全部 120 个文件重编。

cmake 复制代码
# ✅ 进阶写法:拆成多个 target
add_library(engine     STATIC src/engine/*.c)
add_library(renderer   STATIC src/renderer/*.c)
add_library(ui         STATIC src/ui/*.c)
add_library(net        STATIC src/net/*.c)
add_library(utils      STATIC src/utils/*.c)

add_executable(myapp src/main.c)
target_link_libraries(myapp PRIVATE engine renderer ui net utils raylib cJSON)

效果 :改 button.c 只重编 ui 库 + 链接,30 秒搞定。

1.2 依赖树可视化

#mermaid-svg-vWtdvkJ450NFk8eN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vWtdvkJ450NFk8eN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vWtdvkJ450NFk8eN .error-icon{fill:#552222;}#mermaid-svg-vWtdvkJ450NFk8eN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vWtdvkJ450NFk8eN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vWtdvkJ450NFk8eN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vWtdvkJ450NFk8eN .marker.cross{stroke:#333333;}#mermaid-svg-vWtdvkJ450NFk8eN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vWtdvkJ450NFk8eN p{margin:0;}#mermaid-svg-vWtdvkJ450NFk8eN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vWtdvkJ450NFk8eN .cluster-label text{fill:#333;}#mermaid-svg-vWtdvkJ450NFk8eN .cluster-label span{color:#333;}#mermaid-svg-vWtdvkJ450NFk8eN .cluster-label span p{background-color:transparent;}#mermaid-svg-vWtdvkJ450NFk8eN .label text,#mermaid-svg-vWtdvkJ450NFk8eN span{fill:#333;color:#333;}#mermaid-svg-vWtdvkJ450NFk8eN .node rect,#mermaid-svg-vWtdvkJ450NFk8eN .node circle,#mermaid-svg-vWtdvkJ450NFk8eN .node ellipse,#mermaid-svg-vWtdvkJ450NFk8eN .node polygon,#mermaid-svg-vWtdvkJ450NFk8eN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vWtdvkJ450NFk8eN .rough-node .label text,#mermaid-svg-vWtdvkJ450NFk8eN .node .label text,#mermaid-svg-vWtdvkJ450NFk8eN .image-shape .label,#mermaid-svg-vWtdvkJ450NFk8eN .icon-shape .label{text-anchor:middle;}#mermaid-svg-vWtdvkJ450NFk8eN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vWtdvkJ450NFk8eN .rough-node .label,#mermaid-svg-vWtdvkJ450NFk8eN .node .label,#mermaid-svg-vWtdvkJ450NFk8eN .image-shape .label,#mermaid-svg-vWtdvkJ450NFk8eN .icon-shape .label{text-align:center;}#mermaid-svg-vWtdvkJ450NFk8eN .node.clickable{cursor:pointer;}#mermaid-svg-vWtdvkJ450NFk8eN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vWtdvkJ450NFk8eN .arrowheadPath{fill:#333333;}#mermaid-svg-vWtdvkJ450NFk8eN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vWtdvkJ450NFk8eN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vWtdvkJ450NFk8eN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vWtdvkJ450NFk8eN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vWtdvkJ450NFk8eN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vWtdvkJ450NFk8eN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vWtdvkJ450NFk8eN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vWtdvkJ450NFk8eN .cluster text{fill:#333;}#mermaid-svg-vWtdvkJ450NFk8eN .cluster span{color:#333;}#mermaid-svg-vWtdvkJ450NFk8eN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vWtdvkJ450NFk8eN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vWtdvkJ450NFk8eN rect.text{fill:none;stroke-width:0;}#mermaid-svg-vWtdvkJ450NFk8eN .icon-shape,#mermaid-svg-vWtdvkJ450NFk8eN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vWtdvkJ450NFk8eN .icon-shape p,#mermaid-svg-vWtdvkJ450NFk8eN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vWtdvkJ450NFk8eN .icon-shape .label rect,#mermaid-svg-vWtdvkJ450NFk8eN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vWtdvkJ450NFk8eN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vWtdvkJ450NFk8eN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vWtdvkJ450NFk8eN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 链接
链接
链接
链接
链接
链接
链接
依赖
依赖
依赖
依赖
myapp

(可执行)
engine

(静态库)
renderer

(静态库)
ui

(静态库)
net

(静态库)
utils

(静态库)
raylib

(第三方)
cJSON

(第三方)

🎯 CMake 的核心是 target,不是文件。 每个 target 是一个编译单元,有自己的 include 路径、编译选项、依赖关系。改一个 target 的文件,不会触发其他 target 重编。


📁 2. 多模块项目结构

2.1 目录布局

复制代码
myapp/
├── CMakeLists.txt              ← 顶层
├── cmake/                      ← CMake 模块
│   ├── FindRaylib.cmake        ← 自定义 find_package
│   └── Platform.cmake          ← 平台检测
├── src/
│   ├── main.c
│   └── CMakeLists.txt          ← 可执行 target
├── engine/
│   ├── engine.c engine.h
│   └── CMakeLists.txt          ← engine 静态库
├── renderer/
│   ├── renderer.c renderer.h
│   └── CMakeLists.txt          ← renderer 静态库
├── ui/
│   ├── button.c menu.c
│   └── CMakeLists.txt          ← ui 静态库
├── third_party/
│   ├── raylib/
│   └── cJSON/
└── tests/
    ├── test_engine.cpp
    └── CMakeLists.txt          ← Google Test

2.2 顶层 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(MyApp C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# 选项
option(BUILD_TESTS "Build unit tests" ON)
option(USE_RAYLIB  "Use Raylib renderer" ON)

# 平台检测
include(cmake/Platform.cmake)

# 第三方库
add_subdirectory(third_party/raylib)
add_subdirectory(third_party/cJSON)

# 项目模块
add_subdirectory(utils)
add_subdirectory(engine)
add_subdirectory(renderer)
add_subdirectory(ui)
add_subdirectory(net)
add_subdirectory(src)

# 测试
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

2.3 模块级 CMakeLists.txt(以 ui 为例)

cmake 复制代码
# ui/CMakeLists.txt
add_library(ui STATIC
    button.c
    menu.c
    dialog.c
    theme.c
)

target_include_directories(ui PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}   # ui/ 目录本身
)

target_link_libraries(ui PUBLIC
    utils                          # ui 依赖 utils
)

# 只对 ui 开启的编译选项
if(MSVC)
    target_compile_options(ui PRIVATE /W4)
else()
    target_compile_options(ui PRIVATE -Wall -Wextra)
endif()

💡 PUBLIC vs PRIVATE vs INTERFACE

  • PUBLIC:自己用 + 依赖者也用(ui 用 utils,ui 的使用者也要看到 utils 的头文件)
  • PRIVATE:自己用,依赖者不用(ui 的编译选项不传染给 myapp)
  • INTERFACE:自己不用,依赖者用(头文件库常用)
    #mermaid-svg-iu9ClJNXbLE3kMOc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iu9ClJNXbLE3kMOc .error-icon{fill:#552222;}#mermaid-svg-iu9ClJNXbLE3kMOc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iu9ClJNXbLE3kMOc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iu9ClJNXbLE3kMOc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iu9ClJNXbLE3kMOc .marker.cross{stroke:#333333;}#mermaid-svg-iu9ClJNXbLE3kMOc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iu9ClJNXbLE3kMOc p{margin:0;}#mermaid-svg-iu9ClJNXbLE3kMOc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster-label text{fill:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster-label span{color:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster-label span p{background-color:transparent;}#mermaid-svg-iu9ClJNXbLE3kMOc .label text,#mermaid-svg-iu9ClJNXbLE3kMOc span{fill:#333;color:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc .node rect,#mermaid-svg-iu9ClJNXbLE3kMOc .node circle,#mermaid-svg-iu9ClJNXbLE3kMOc .node ellipse,#mermaid-svg-iu9ClJNXbLE3kMOc .node polygon,#mermaid-svg-iu9ClJNXbLE3kMOc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iu9ClJNXbLE3kMOc .rough-node .label text,#mermaid-svg-iu9ClJNXbLE3kMOc .node .label text,#mermaid-svg-iu9ClJNXbLE3kMOc .image-shape .label,#mermaid-svg-iu9ClJNXbLE3kMOc .icon-shape .label{text-anchor:middle;}#mermaid-svg-iu9ClJNXbLE3kMOc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iu9ClJNXbLE3kMOc .rough-node .label,#mermaid-svg-iu9ClJNXbLE3kMOc .node .label,#mermaid-svg-iu9ClJNXbLE3kMOc .image-shape .label,#mermaid-svg-iu9ClJNXbLE3kMOc .icon-shape .label{text-align:center;}#mermaid-svg-iu9ClJNXbLE3kMOc .node.clickable{cursor:pointer;}#mermaid-svg-iu9ClJNXbLE3kMOc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iu9ClJNXbLE3kMOc .arrowheadPath{fill:#333333;}#mermaid-svg-iu9ClJNXbLE3kMOc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iu9ClJNXbLE3kMOc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iu9ClJNXbLE3kMOc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iu9ClJNXbLE3kMOc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iu9ClJNXbLE3kMOc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iu9ClJNXbLE3kMOc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster text{fill:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc .cluster span{color:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iu9ClJNXbLE3kMOc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iu9ClJNXbLE3kMOc rect.text{fill:none;stroke-width:0;}#mermaid-svg-iu9ClJNXbLE3kMOc .icon-shape,#mermaid-svg-iu9ClJNXbLE3kMOc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iu9ClJNXbLE3kMOc .icon-shape p,#mermaid-svg-iu9ClJNXbLE3kMOc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iu9ClJNXbLE3kMOc .icon-shape .label rect,#mermaid-svg-iu9ClJNXbLE3kMOc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iu9ClJNXbLE3kMOc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iu9ClJNXbLE3kMOc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iu9ClJNXbLE3kMOc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 私有不传染
    PRIVATE
    ui

compile: -Wall
myapp

不继承 -Wall
可见性传播
PUBLIC
PUBLIC
utils

include: utils/
ui

看到 utils/
myapp

看到 utils/ + ui/


🔍 3. find_package 实战

3.1 find_package 做了什么

cmake 复制代码
find_package(Raylib REQUIRED)
# 找到后提供:
#   Raylib_INCLUDE_DIRS  - 头文件路径
#   Raylib_LIBRARIES     - 库文件路径
#   Raylib_FOUND         - 是否找到

3.2 三种情况

情况 find_package 行为 你要做的
库装在系统里 自动找到 直接用
库在项目里(add_subdirectory) 不需要 find_package target_link_libraries 直接引用
库在自定义路径 需要写 FindXxx.cmake 放 cmake/ 目录,设 CMAKE_MODULE_PATH

3.3 自定义 FindRaylib.cmake

cmake 复制代码
# cmake/FindRaylib.cmake
find_path(RAYLIB_INCLUDE_DIR
    NAMES raylib.h
    PATHS ${CMAKE_SOURCE_DIR}/third_party/raylib/include
          /usr/include
          /usr/local/include
)

find_library(RAYLIB_LIBRARY
    NAMES raylib libraylib
    PATHS ${CMAKE_SOURCE_DIR}/third_party/raylib/lib
          /usr/lib
          /usr/local/lib
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Raylib
    REQUIRED_VARS RAYLIB_LIBRARY RAYLIB_INCLUDE_DIR
)

if(Raylib_FOUND)
    set(Raylib_INCLUDE_DIRS ${RAYLIB_INCLUDE_DIR})
    set(Raylib_LIBRARIES ${RAYLIB_LIBRARY})
    message(STATUS "Raylib found: ${RAYLIB_LIBRARY}")
endif()

mark_as_advanced(RAYLIB_INCLUDE_DIR RAYLIB_LIBRARY)
cmake 复制代码
# 顶层 CMakeLists.txt
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(Raylib REQUIRED)

🎯 4. 生成器表达式

4.1 什么是生成器表达式

cmake 复制代码
# 普通写法:只能选一个
if(MSVC)
    target_compile_options(myapp PRIVATE /W4)
else()
    target_compile_options(myapp PRIVATE -Wall)
endif()

# 生成器表达式:一条搞定
target_compile_options(myapp PRIVATE
    $<$<C_COMPILER_ID:MSVC>:/W4>
    $<$<C_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
)

4.2 常用生成器表达式

表达式 含义 示例
$<CONFIG:Debug> Debug 构建时为 1 $<$<CONFIG:Debug>:-O0 -g>
$<PLATFORM_ID:Windows> Windows 平台时为 1 $<$<PLATFORM_ID:Windows>:-lws2_32>
$<C_COMPILER_ID:GNU> GCC 编译器时为 1 $<$<C_COMPILER_ID:GNU>:-Wall>
$<TARGET_FILE:myapp> myapp 的输出文件路径 用于 post-build 复制
$<BOOL:${USE_RAYLIB}> 变量为真时为 1 条件链接

4.3 实战:按配置和平台自动调整

cmake 复制代码
target_compile_options(myapp PRIVATE
    # Debug: 关优化,开调试
    $<$<CONFIG:Debug>:-O0 -g3 -DDEBUG>
    # Release: 开优化
    $<$<CONFIG:Release>:-O2 -DNDEBUG>
    # GCC/Clang 专属
    $<$<C_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
    # MSVC 专属
    $<$<C_COMPILER_ID:MSVC>:/W4 /permissive->
)

target_link_libraries(myapp PRIVATE
    raylib
    cJSON
    # Windows 需要额外链接 winsock
    $<$<PLATFORM_ID:Windows>:ws2_32>
    # Linux 需要链接 pthread
    $<$<PLATFORM_ID:Linux>:pthread>
)

# 编译后自动复制 DLL
add_custom_command(TARGET myapp POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    $<TARGET_FILE:raylib>
    $<TARGET_FILE_DIR:myapp>
)

🧪 5. 测试集成

cmake 复制代码
# tests/CMakeLists.txt
find_package(GTest REQUIRED)

add_executable(run_tests
    test_engine.cpp
    test_renderer.cpp
    test_ui.cpp
)

target_link_libraries(run_tests PRIVATE
    engine renderer ui
    GTest::gtest_main
)

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

# 自定义测试:冒烟测试
add_test(NAME smoke_test
    COMMAND myapp --run-smoke-test
)
bash 复制代码
# 运行测试
cmake --build build
cd build && ctest --output-on-failure

📊 6. 编译速度优化

优化手段 效果 做法
拆 target 改一个文件不重编全部 每个模块一个 add_library
ccache 未改动的文件跳过编译 set(CMAKE_C_COMPILER_LAUNCHER ccache)
Ninja 生成器 比 Make 快 3-5 倍 cmake -G Ninja
** unity build** 减少 I/O set(CMAKE_UNITY_BUILD ON)
PCH 预编译头 加速头文件多的项目 target_precompile_headers(myapp PRIVATE pch.h)
cmake 复制代码
# 开启 ccache(Linux/macOS)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
    set(CMAKE_C_COMPILER_LAUNCHER ccache)
    set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
endif()

# 预编译头
target_precompile_headers(myapp PRIVATE
    <stdio.h> <stdlib.h> <string.h>
    "engine.h" "renderer.h"
)

🐛 Unity build 注意 :合并多个 .c 文件编译时,如果有同名静态函数或同名 typedef,会冲突。遇到就给那个 target 关掉:set_target_properties(ui PROPERTIES UNITY_BUILD OFF)


💡 7. 五条 CMake 进阶心法

① target 是核心,不是文件

把项目拆成多个 target(库),每个 target 独立编译。改一个只重编一个。
② PUBLIC/PRIVATE/INTERFACE 是传染控制

include 路径用 PUBLIC 传播,编译选项用 PRIVATE 不传播。搞混了就是"为什么 myapp 编译时看到了 ui 的内部头文件"。
③ find_package 优先,add_subdirectory 兜底

系统装了就 find_package,没装就在 third_party 里 add_subdirectory。不要手动写 include 路径和 link 路径。
④ 生成器表达式替代 if-else

平台/编译器/配置相关的差异,用 $<...> 一行搞定,比 if-else 更清晰且支持多配置生成器(如 Visual Studio)。
⑤ ccache + Ninja 是提速利器

ccache 让二次编译快 10 倍,Ninja 让依赖解析快 5 倍。两个一起用,120 文件项目增量编译 < 5 秒。


🔜 系列结语

工具链通关系列到此暂告一段落。核心内容:四阶段编译(01)+ CMake 进阶(02)。

后续可能补充:GDB 调试技巧、性能分析(perf/Valgrind)等。


📚 工具链通关系列目录

# 标题 状态
01 一张图看懂 C/C++ 从源码到可执行 ✅ 已发布
02 CMake 进阶:超 100 个文件怎么写 ← 本文 ✅ 已发布

👍 如果你觉得有帮助,点赞 + 收藏 + 关注,三连支持作者继续写下去 🚀

相关推荐
十五年专注C++开发1 天前
CMake基础:BUILD_INTERFACE 与 INSTALL_INTERFACE 完全解析
c++·cmake·build_interface
羑悻的小杀马特3 天前
百度ASR+BRPC+ETCD高并发高可用架构搭建,C++客户端API封装+语音识别子服务注册发现+全链路测试调优终极指南!
架构·sdk·etcd·cmake·百度智能云·brpc·语音识别子服务
_YouziTech_4 天前
VS Code环境下LVGL模拟器的配置与运行
visualstudio·mingw·cmake·lvgl·vs code·模拟器·sdl2
用户3979678611167 天前
CMake-构建目标与属性
cmake
用户39796786111610 天前
CMake常用指令-string
cmake
Shadow(⊙o⊙)1 个月前
CMake实现客户端、服务端交流
组件·cmake
繁重的秋春1 个月前
Qt6.0下的CMake的部署配置
qt·cmake
Lee_jerome1 个月前
《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》
c语言·开发语言·arm开发·c++·编译·cmake·cmakelists
开发者联盟league1 个月前
cmake 创建和使用静态库(极简版)
c++·cmake·cpp