广告

C++ CMakeLists.txt 编写教程:从入门到实战的 CMake 项目构建指南

1. 入门准备:理解 CMake 的角色与工作流

为什么选择 CMake

CMake 是一个跨平台的构建系统,它通过一组中性描述文件将源代码转化为本地构建系统的配置,例如 Makefile、Visual Studio 工解决方案或 Ninja 构建文件。对于 CMakeLists.txt 的设计,核心思想是将构建逻辑从具体编译器和平台中分离,从而在不同系统上保持一致的项目描述。

在实际开发中,跨平台兼容性、可维护性和可重复性 是选择 CMake 的主要原因。通过一个统一的 CMakeLists.txt,团队成员可以在 Windows、macOS、Linux 上使用相同的编译流程,减少环境差异带来的问题。

你的第一个 CMakeLists.txt 示例

一个简短的 CMakeLists.txt 能帮助你快速进入实战。它展示了最基本的配置:指定最小版本、项目名称、C++ 标准以及可执行文件的构建。这是进入 CMake 的第一步

下面给出一个最小可运行的示例,帮助你理解 CMake 的语法结构与目标声明的关系。

cmake_minimum_required(VERSION 3.14)
project(MyApp LANGUAGES CXX)# 将 C++ 标准设为 17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 指定源文件并构建可执行文件
add_executable(MyApp src/main.cpp)

2. 基础结构与最小 CMakeLists.txt

最小可用示例

最小示例明确了构建的核心要素:配置版本、项目名、语言以及可执行目标。通过理解这些要素,你可以逐步扩展复杂性。

理解 add_executable 如何将源文件与输出二进制绑定,是后续集成库、定义编译选项的重要前提。

cmake_minimum_required(VERSION 3.15)
project(SampleApp LANGUAGES CXX)add_executable(SampleApp src/main.cpp)

编译与运行

在本地构建时,推荐使用分离式构建目录,这样可以避免污染源码树。这也是跨平台构建的一大好处:通过 cmake -S . -B build 生成构建系统,再通过 cmake --build build 编译。

运行阶段,直接执行生成的输出即可。如果你在 Windows 上使用 Visual Studio,生成的解决方案会在 build 目录中打开,直接点击运行按钮即可。

# 生成构建系统
cmake -S . -B build# 编译
cmake --build build

3. 配置 C++ 标准与编译器选项

设置 C++ 标准

明确指定 C++ 标准可以避免不同编译器的实现差异带来的隐藏问题。常用做法是设定 CMAKE_CXX_STANDARDCMAKE_CXX_STANDARD_REQUIREDCMAKE_CXX_EXTENSIONS

通过这些设置,项目会强制使用你期望的语言特性集,并在不支持时给出明确错误,而不是在链接阶段才暴露问题。

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

编译器警告与优化

良好的编译选项能帮助你在开发阶段早发现问题。使用 目标级别的编译选项,可以避免全局影响,并且便于不同目标的调试配置。

典型做法包括开启警告、启用额外警告、以及为调试和发布分别设定优化等级。

add_executable(MyApp src/main.cpp)# 开启警告并启用额外警告
target_compile_options(MyApp PRIVATE -Wall -Wextra -Wpedantic)# 优化与调试配置示例
if(NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE Release)
endif()

4. 新手友好:项目结构与跨目录构建

组织源文件与头文件

实际项目通常包含多层目录与多种目标。推荐的做法是对源文件清单进行显式管理,而非使用 glob 通配符,确保变更时构建系统能正确检测到依赖。

使用 target_sources vs file(GLOB ...) 的组合,可以更清晰地表达文件分组与模块边界。

add_executable(MyApp)
target_sources(MyApp PRIVATEsrc/main.cppsrc/app/logic.cppinclude/app/logic.h
)

跨目录包含目录与头文件查找

对中大型项目,通常需要将头文件提供给各个目标。推荐的实践是使用 target_include_directories 来绑定头文件路径,避免全局污染。

此外,借助 include_directories 通常不推荐用于现代 CMake,优先使用目标导向的包含路径。

add_executable(MyApp src/main.cpp)
target_include_directories(MyApp PRIVATE include)

5. 引入外部依赖:第三方库与 FetchContent

使用 find_package 与 FetchContent

引入第三方库时,find_package 可以在系统中搜索现有的安装包,若找不到则借助 FetchContent 进行按需下载与编译。两者组合能让你的项目对外部依赖更加鲁棒。

通过 FetchContent,可以在构建时自动获取依赖源码并编译成库,然后通过 target_link_libraries 关联到可执行文件。

C++ CMakeLists.txt 编写教程:从入门到实战的 CMake 项目构建指南

include(FetchContent)
FetchContent_Declare(fmtGIT_REPOSITORY https://github.com/fmtlib/fmt.gitGIT_TAG        9.1.0)
FetchContent_MakeAvailable(fmt)add_executable(MyApp src/main.cpp)
target_link_libraries(MyApp PRIVATE fmt::fmt)

整合第三方库的示例

以 fmt 为例,FetchContent 自动拉取并构建 fmt 库,之后使用 fmt::fmt 作为链接目标。这样可以实现跨平台的一致性构建。

你还可以结合 find_package(OpenCVThreads、以及平台特定的工具链配置),实现更丰富的依赖体系。

# 通过 FetchContent 获取 fmt
include(FetchContent)
FetchContent_Declare(fmtGIT_REPOSITORY https://github.com/fmtlib/fmt.gitGIT_TAG        9.1.0)
FetchContent_MakeAvailable(fmt)add_executable(MyApp src/main.cpp)
target_link_libraries(MyApp PRIVATE fmt::fmt)

6. 测试、安装与打包

添加测试

测试是持续集成的重要环节。将 CTest 集成到你的构建系统,可以方便地定义测试用例并通过命令执行。

常见做法是使用 enable_testing()add_test,将测试可执行文件注册到 CTest 管理中,以实现自动化测试。

include(CTest)
enable_testing()add_executable(test_math tests/test_math.cpp)
add_test(NAME test_math_run COMMAND test_math)

安装目标与安装路径

为了将构建产物分发给终端用户,安装目标(install) 的配置是必要的。通过 install 指令,你可以指定二进制、头文件、资源等的安装路径与权限。

典型的做法是公开一个可执行文件,并将头文件安装到 include 目录,方便其他项目通过 find_package 进行集成。

install(TARGETS MyAppRUNTIME DESTINATION binLIBRARY DESTINATION libARCHIVE DESTINATION lib
)install(DIRECTORY include/ DESTINATION include)

7. 实战案例:从头实现一个小型应用的完整 CMake 构建流程

项目概览

在这个实战环节中,我们将把前面的知识整合成一个具备源代码、头文件、第三方依赖、测试和安装的完整示例。目标是让一个简单的控制台应用在任意主流平台上可构建、可测试、可安装。

你将看到一个逐步演练的流程:从目录结构设计到 CMakeLists.txt 的编写,再到实际构建与运行的全流程。本文以 CMakeLists.txt 编写教程:从入门到实战的 CMake 项目构建指南 为主线,覆盖关键思路与典型模式。

完整 CMakeLists.txt 实现

下面给出一个较完整的 CMakeLists.txt 范例,展示如何组合多目标、头文件分发、外部依赖和测试。请注意,这只是一个示意,实际项目中你可以按需调整路径和依赖。

cmake_minimum_required(VERSION 3.15)
project(HelloCMake LANGUAGES CXX)# C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)# 查找并添加 fmt( FetchContent 示例)
include(FetchContent)
FetchContent_Declare(fmtGIT_REPOSITORY https://github.com/fmtlib/fmt.gitGIT_TAG        9.1.0)
FetchContent_MakeAvailable(fmt)# 选项与调试信息
option(BUILD_TESTS "Build tests" ON)# 源文件组织
set(SRCsrc/main.cppsrc/app/logic.cpp
)set(HEADERinclude/app/logic.h
)# 生成可执行文件
add_executable(HelloCMake ${SRC} ${HEADER})# 编译选项
target_compile_options(HelloCMake PRIVATE -Wall -Wextra)# 连接外部依赖
target_link_libraries(HelloCMake PRIVATE fmt::fmt)# 包含目录
target_include_directories(HelloCMake PRIVATE include)# 测试
if(BUILD_TESTS)enable_testing()add_executable(test_logic tests/test_logic.cpp)target_link_libraries(test_logic PRIVATE fmt::fmt)add_test(NAME TestLogic COMMAND test_logic)
endif()# 安装
install(TARGETS HelloCMake RUNTIME DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)

后续拓展:当你的项目规模继续扩大时,可以引入如 CPack 进行打包、版本控制的构建策略、以及针对不同平台的定制构建脚本。例如,在继续优化时你可以尝试将测试、打包与文档构建分离到独立的 CMake 配置中,以提升可维护性。

广告

后端开发标签