从零实现 CMakeLists:构建 C++ 项目的完整步骤
CMake 是一个非常强大的构建工具,广泛应用于 C++ 项目的构建、测试、安装和包管理。本文将通过从零开始的方式,带领大家实现一个 CMakeLists 文件,并解释每一行代码的含义与作用。通过这篇文章,你将学会如何创建一个适用于 C++20 项目的 CMake 构建配置,并理解如何通过 CMake 管理源文件、库、测试以及安装过程。
1. 初始化项目设置
首先,我们需要使用 cmake_minimum_required()
来指定我们所需的最低 CMake 版本,并使用 project()
设置项目名称和版本。
1 | cmake_minimum_required(VERSION 3.10) |
cmake_minimum_required(VERSION 3.10)
:指定 CMake 的最低版本为 3.10,确保使用该版本或更新版本时可以正常工作。project(reaction VERSION 1.0 LANGUAGES CXX)
:定义项目名称为reaction
,版本为1.0
,并指定该项目使用 C++ 语言。
2. 设置编译选项
我们根据操作系统的不同来设置编译选项。对于 MSVC(微软的 C++ 编译器),我们启用 /W4
且禁用 RTTI;对于其他平台,我们启用警告并禁用 RTTI。
1 | if(MSVC) |
add_compile_options()
:该命令设置了编译器的标志。对于 MSVC,我们禁用了 RTTI(/GR-
),并将警告等级设置为W4
;对于其他平台,我们启用了常见的编译器警告,并指定 C++20 标准。
3. 设置构建类型
接下来,我们设置默认的构建类型为 Debug
,如果用户没有指定构建类型的话。
1 | if(NOT CMAKE_BUILD_TYPE) |
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
:如果没有设置CMAKE_BUILD_TYPE
,我们将其强制设为Debug
,这样可以方便地进行调试构建。
4. 添加库与目标
在 CMake 中,我们创建一个库并为它添加接口头文件。这里我们创建的是一个 INTERFACE
类型的库,意味着它不产生实际的二进制文件,只提供头文件和相关接口。
1 | add_library(${PROJECT_NAME} INTERFACE) |
add_library(${PROJECT_NAME} INTERFACE)
:创建一个接口库,适用于 header-only 类型的库。它没有编译的源文件,只依赖于头文件。add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
:创建一个别名,使得我们在其他地方引用库时使用reaction::reaction
来替代reaction
,提高代码可读性。
5. 设置包含目录
我们使用 target_include_directories()
来指定库的头文件目录,确保当用户在其他项目中使用该库时,能够正确找到头文件。
1 | target_include_directories(${PROJECT_NAME} INTERFACE |
$<INSTALL_INTERFACE:include>
:指示安装目录中的头文件路径。$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
:指示构建过程中使用的头文件路径。
6. 处理源文件
我们通过 file(GLOB ...)
命令来收集头文件和源文件,并将它们添加到目标中。
1 | file(GLOB HEADERS_LIST "${CMAKE_CURRENT_SOURCE_DIR}/include/reaction/*.h") |
file(GLOB ...)
:这个命令会将指定目录下的所有头文件(.h
文件)列入列表。foreach(header_file ${HEADERS_LIST})
:遍历收集到的头文件,并将它们添加到目标中。
7. 示例程序
我们接着查找并编译示例程序文件,并将它们与主库连接。
1 | file(GLOB EXAMPLE_SOURCES ${PROJECT_SOURCE_DIR}/example/*.cpp) |
file(GLOB ...)
:收集示例程序的源文件。add_executable(${example_name} ${example_file})
:为每个示例文件创建一个可执行文件。target_link_libraries(${example_name} PRIVATE ${PROJECT_NAME})
:将库链接到每个示例程序中。
8. 单元测试
如果系统中有 GTest 库,我们将启用单元测试并构建测试执行文件。
1 | find_package(GTest) |
find_package(GTest)
:查找 GTest 库,如果找到则进行后续配置。enable_testing()
:启用 CTest 测试功能。add_test(NAME reactionTest COMMAND runTests)
:将测试目标添加到 CTest 测试系统。
9. 安装配置
最后,我们配置了安装路径和文件,使得用户能够将构建的项目安装到指定目录中。
1 | install( |
install(...)
:这个命令指定了如何安装头文件、库文件以及可执行文件。${CMAKE_INSTALL_INCLUDEDIR}
、${CMAKE_INSTALL_LIBDIR}
等:这些变量代表了 CMake 的安装目录,通常是/usr/local/include
或 Windows 下的其他目录。
10. 包配置文件
我们生成并安装了 CMake 包配置文件,使得其他项目可以方便地找到并使用我们的库。
1 | include(CMakePackageConfigHelpers) |
configure_package_config_file()
:使用模板文件生成实际的配置文件。write_basic_package_version_file()
:生成包的版本文件。
11. 总结
通过本文的讲解,我们从零开始一步步实现了一个 CMakeLists 文件。通过这个配置文件,我们不仅能编译项目,还能实现测试、安装、以及其他功能。每一行 CMake 代码都与项目构建、源文件管理、库配置密切相关,因此深入理解这些配置对于开发复杂项目至关重要。
下一篇,我们将继续深入讨论 CMake 的高级特性,如多平台支持、跨平台构建等内容,敬请期待!
通过这篇文章的讲解,读者将能够理解每一行 CMakeLists 文件的作用,以及如何使用 CMake 配置一个完整的 C++ 项目,涵盖从编译到安装的全过程。