Монорепозиторий с C++: организация кода и лучшие практики
Разработка крупных проектов на C++ часто сталкивается с проблемой фрагментации кода: библиотеки разбросаны по разным репозиториям, версии не синхронизированы, а интеграция превращается в головную боль. Решение — монорепозиторий (monorepo). Это подход, при котором весь код, включая библиотеки, утилиты и тесты, хранится в одном репозитории. В этой статье мы разберем, как правильно организовать монорепозиторий для C++ проектов, используя современные инструменты (CMake, Conan, Git) и избегая типичных ошибок.
1. Структура каталогов: основа порядка
Первый шаг — создать единую, понятную структуру папок. Вот рекомендуемая схема для C++ монорепозитория:
my-monorepo/
├── cmake/ # Общие CMake-модули
├── libs/ # Библиотеки
│ ├── core/ # Ядро (логирование, аллокаторы)
│ ├── math/ # Математическая библиотека
│ └── network/ # Сетевая библиотека
├── apps/ # Исполняемые приложения
│ ├── server/ # Серверное приложение
│ └── client/ # Клиентское приложение
├── tests/ # Интеграционные тесты
├── scripts/ # Скрипты сборки и деплоя
├── third_party/ # Внешние зависимости (submodules)
├── CMakeLists.txt # Корневой CMake-файл
├── conanfile.txt # Менеджер зависимостей (опционально)
└── .github/ # CI/CD (GitHub Actions)
Почему это работает?
- libs/ — все библиотеки в одном месте, легко переиспользовать.
- apps/ — приложения, которые зависят от libs.
- third_party/ — внешние зависимости (через git submodules или CMake FetchContent).
- cmake/ — кастомные функции (например,
add_library_with_tests).
2. CMake: настройка корневого проекта
CMake — стандарт для C++ сборки в монорепозиториях. Настройте корневой CMakeLists.txt так, чтобы он подключал все подпроекты:
cmake_minimum_required(VERSION 3.20)
project(MyMonorepo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Общие опции
option(BUILD_TESTS "Build tests" ON)
# Подключаем библиотеки
add_subdirectory(libs/core)
add_subdirectory(libs/math)
add_subdirectory(libs/network)
# Подключаем приложения
add_subdirectory(apps/server)
add_subdirectory(apps/client)
# Подключаем тесты
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
Важные моменты:
- Используйте
target_link_librariesдля явного указания зависимостей между библиотеками. - Для внешних зависимостей (например, Boost, fmt) используйте
FetchContentили Conan. - Избегайте глобальных
include_directories— используйтеtarget_include_directoriesсPUBLICилиPRIVATE.
3. Управление зависимостями внутри монорепозитория
Одна из главных проблем монорепозитория — циклические зависимости. Решение: строгая иерархия. Например:
- core (не зависит ни от чего, кроме стандартной библиотеки)
- math (зависит от core)
- network (зависит от core и math)
- server (зависит от core, math, network)
Пример CMakeLists.txt для библиотеки math:
add_library(math
src/vector.cpp
src/matrix.cpp
include/math/vector.h
include/math/matrix.h
)
target_include_directories(math
PUBLIC include
PRIVATE src
)
target_link_libraries(math
PUBLIC core # Явно указываем зависимость
)
Совет: Используйте INTERFACE для библиотек, которые содержат только заголовки (header-only). Это ускоряет сборку.
4. CI/CD и автоматизация сборки
Монорепозиторий требует продуманного CI/CD. Основные принципы:
- Инкрементальная сборка: собирайте только те компоненты, которые изменились. Используйте
ccacheдля ускорения. - Матрица сборок: тестируйте на разных компиляторах (GCC, Clang, MSVC) и платформах (Linux, Windows, macOS).
- Линтинг и форматирование: запускайте clang-tidy и clang-format на каждом PR.
Пример workflow для GitHub Actions (файл .github/workflows/build.yml):
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure CMake
run: cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DBUILD_TESTS=ON
- name: Build
run: cmake --build build --parallel
- name: Test
run: ctest --test-dir build --output-on-failure
Важно: В больших монорепозиториях (100+ библиотек) используйте инструменты вроде Bazel или Buck2, которые умеют кэшировать результаты сборки на уровне файлов. Но для средних проектов CMake + ccache — оптимальный выбор.
Заключение
Монорепозиторий для C++ — это не просто модный тренд, а реальный способ ускорить разработку и уменьшить хаос. Главное — придерживаться четкой структуры (libs/apps/tests), использовать современные CMake-практики (target-based, FetchContent) и автоматизировать CI/CD. Начните с малого: перенесите