Введение: Зачем нужен монорепозиторий в C++?
Разработка крупных C++ проектов часто сталкивается с проблемой организации кода. Команды выбирают между монорепозиторием (один репозиторий для всего кода) и мультирепозиторием (отдельные репозитории для каждой библиотеки). В контексте C++ монорепозиторий предлагает ряд преимуществ: упрощение рефакторинга, единая версия зависимостей и централизованная система сборки.
Эта статья проведет вас по ключевым аспектам организации монорепозитория для C++: от структуры папок до настройки CMake и управления зависимостями. Вы узнаете, как сделать проект масштабируемым и удобным для команды.
1. Структура монорепозитория для C++
Правильная структура — основа успешного монорепозитория. Рекомендуется использовать иерархию, разделяющую библиотеки, приложения и внешние зависимости.
Пример базовой структуры
my_monorepo/
├── CMakeLists.txt # Корневой CMake-файл
├── cmake/ # Вспомогательные CMake-модули
├── libs/ # Библиотеки проекта
│ ├── core/ # Ядро (логика, утилиты)
│ │ ├── include/
│ │ │ └── core/
│ │ ├── src/
│ │ └── CMakeLists.txt
│ ├── network/ # Сетевые модули
│ │ ├── include/
│ │ ├── src/
│ │ └── CMakeLists.txt
│ └── utils/ # Вспомогательные утилиты
│ ├── include/
│ ├── src/
│ └── CMakeLists.txt
├── apps/ # Исполняемые приложения
│ ├── server/
│ │ ├── src/
│ │ └── CMakeLists.txt
│ └── client/
│ ├── src/
│ └── CMakeLists.txt
├── tests/ # Интеграционные тесты
├── third_party/ # Внешние зависимости (git submodules)
│ ├── fmt/
│ └── spdlog/
└── scripts/ # Скрипты для CI/CD и разработки
Ключевые принципы
- libs/: Каждая библиотека — самодостаточный модуль со своим CMakeLists.txt.
- apps/: Приложения используют библиотеки через
target_link_libraries. - third_party/: Внешние зависимости добавляются через git submodules или FetchContent.
- tests/: Отдельная директория для тестов, которые проверяют интеграцию компонентов.
2. Система сборки: CMake для монорепозитория
CMake — стандартный инструмент для C++ проектов. В монорепозитории он позволяет управлять зависимостями между библиотеками и приложениями.
Корневой CMakeLists.txt
Создайте корневой файл, который подключает все подпроекты:
cmake_minimum_required(VERSION 3.20)
project(MyMonorepo VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Подключаем внешние зависимости
add_subdirectory(third_party/fmt)
add_subdirectory(third_party/spdlog)
# Подключаем библиотеки
add_subdirectory(libs/core)
add_subdirectory(libs/network)
add_subdirectory(libs/utils)
# Подключаем приложения
add_subdirectory(apps/server)
add_subdirectory(apps/client)
# Подключаем тесты
enable_testing()
add_subdirectory(tests)
Пример CMakeLists.txt для библиотеки
# libs/core/CMakeLists.txt
add_library(core
src/core_module.cpp
src/utilities.cpp
)
target_include_directories(core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(core
PUBLIC
fmt::fmt
spdlog::spdlog
)
Сборка и тестирование
Для сборки всего проекта используйте:
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel
ctest --output-on-failure
3. Управление зависимостями в монорепозитории
Одна из главных проблем монорепозитория — контроль версий зависимостей. В C++ есть несколько подходов.
Вариант 1: Git submodules
Добавьте внешние библиотеки как submodules:
git submodule add https://github.com/fmtlib/fmt.git third_party/fmt
git submodule add https://github.com/gabime/spdlog.git third_party/spdlog
Плюсы: Простота, фиксация версий.
Минусы: Необходимость обновлять submodules вручную.
Вариант 2: FetchContent (рекомендуется)
CMake может автоматически загружать зависимости:
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.0
)
FetchContent_MakeAvailable(fmt)
Преимущества: Автоматизация, централизованное управление версиями.
Вариант 3: Conan или vcpkg
Менеджеры пакетов (Conan, vcpkg) также хорошо интегрируются с CMake:
# conanfile.txt
[requires]
fmt/10.1.0
spdlog/1.12.0
[generators]
CMakeDeps
4. Организация тестов и CI/CD
Монорепозиторий требует продуманной стратегии тестирования и непрерывной интеграции.
Структура тестов
Разделите тесты на модульные (внутри каждой библиотеки) и интеграционные (в tests/):
tests/
├── CMakeLists.txt
├── test_integration.cpp
└── test_network.cpp
Пример CMakeLists.txt для тестов:
add_executable(test_integration
test_integration.cpp
)
target_link_libraries(test_integration
PRIVATE
core
network
GTest::GTest
)
add_test(NAME IntegrationTest COMMAND test_integration)