引言:为什么要为 C++ 使用 Monorepo?
在现代 C++ 开发中,尤其是在中大型项目中,代码组织成为一项至关重要的任务。Monorepo 是一种将所有组件、库和服务的代码存储在单一仓库中的方法。与多仓库不同,monorepo 提供了单一入口点,简化了重构,并简化了依赖管理。在实践中,这意味着你可以更改库的 API,并立即查看它如何影响所有依赖模块。
在本文中,我们将详细讲解在 monorepo 中组织 C++ 代码的实践方面:从文件夹结构到设置 CI/CD。你将学习如何避免常见错误,并使构建过程快速且可预测。
1. C++ 的 Monorepo 结构
正确的文件夹结构是成功 monorepo 的基础。让我们看看为 C++ 项目推荐的分层结构。
基本目录层次结构
my_project/├── CMakeLists.txt # 根构建文件├── cmake/ # 自定义 CMake 模块│ └── FindMyLib.cmake├── libs/ # 内部库│ ├── core/│ │ ├── CMakeLists.txt│ │ ├── include/│ │ │ └── core/│ │ │ ├── utils.h│ │ │ └── types.h│ │ └── src/│ │ ├── utils.cpp│ │ └── types.cpp│ └── network/│ ├── CMakeLists.txt│ ├── include/│ │ └── network/│ │ └── http_client.h│ └── src/│ └── http_client.cpp├── apps/ # 可执行应用程序│ ├── server/│ │ ├── CMakeLists.txt│ │ └── src/│ │ └── main.cpp│ └── cli/│ ├── CMakeLists.txt│ └── src/│ └── main.cpp├── tests/ # 集成测试│ ├── CMakeLists.txt│ └── test_integration.cpp├── third_party/ # 外部依赖(供应商代码)│ ├── fmt/│ └── spdlog/├── .github/ # CI/CD 配置│ └── workflows/│ └── build.yml└── README.md关键原则:
- libs/ — 所有内部库。每个库都有自己的文件夹,包含一个
CMakeLists.txt。 - apps/ — 可执行文件。它们依赖于
libs/中的库。 - third_party/ — 供应商依赖(git 子模块或复制的源代码)。
- tests/ — 验证多个库交互的集成测试。
库的 CMakeLists.txt 示例
# libs/core/CMakeLists.txtadd_library(core src/utils.cpp src/types.cpp)
target_include_directories(core PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>)
target_link_libraries(core PUBLIC fmt::fmt spdlog::spdlog)重要提示: 使用 PUBLIC 和 PRIVATE 来正确传播依赖关系。这是一个关键点——如果库 A 仅在其源文件中使用库 B,则将 B 设为 PRIVATE。
2. Monorepo 中的依赖管理
Monorepo 的主要挑战之一是“依赖地狱”。在 C++ 中,这通过 CMake 面向目标的设计 来解决。
规则:一个目标——一个库
每个库应只导出一个 CMake 目标。这简化了链接并防止了循环依赖。
# 根 CMakeLists.txtcmake_minimum_required(VERSION 3.20)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# 包含所有库add_subdirectory(libs/core)add_subdirectory(libs/network)