Введение: Зачем нужен монорепозиторий в C++?
В мире C++ разработки рано или поздно встаёт вопрос: как организовать код, когда проектов становится много? Библиотеки, утилиты, исполняемые файлы — всё это нужно как-то связывать, версионировать и собирать. Традиционный подход с множеством отдельных репозиториев (мультирепозиторий) часто приводит к проблемам: сложно синхронизировать изменения, тяжело тестировать интеграцию, а зависимости между проектами превращаются в ад.
Альтернатива — монорепозиторий. Это единый репозиторий, в котором хранятся все компоненты вашего проекта: библиотеки, приложения, тесты, документация. Такой подход активно используется в Google, Facebook, Microsoft и многих других компаниях. Для C++ монорепозиторий даёт ряд преимуществ:
- Единая версия зависимостей — вы всегда знаете, какая версия библиотеки используется во всех проектах.
- Атомарные коммиты — изменение API библиотеки и обновление всех её потребителей происходит в одном коммите.
- Упрощённый CI/CD — один пайплайн для всех компонентов.
- Прозрачность — легко увидеть, как устроен весь проект.
В этой статье мы разберём практическую организацию монорепозитория для C++ с использованием CMake, покажем структуру папок и примеры кода.
1. Структура папок: основа порядка
Первый шаг к успешному монорепозиторию — продуманная структура директорий. Вот рекомендуемый шаблон для C++ проекта:
my_project/
├── CMakeLists.txt # Корневой CMake-файл
├── README.md
├── .gitignore
├── .clang-format
├── cmake/ # Вспомогательные CMake-модули
│ └── FindMyLib.cmake
├── libs/ # Библиотеки
│ ├── core/ # Библиотека core
│ │ ├── CMakeLists.txt
│ │ ├── include/
│ │ │ └── core/
│ │ │ ├── math.h
│ │ │ └── string_utils.h
│ │ └── src/
│ │ ├── math.cpp
│ │ └── string_utils.cpp
│ └── network/ # Библиотека network
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── network/
│ │ └── http_client.h
│ └── src/
│ └── http_client.cpp
├── apps/ # Исполняемые приложения
│ ├── server/
│ │ ├── CMakeLists.txt
│ │ └── src/
│ │ └── main.cpp
│ └── cli_tool/
│ ├── CMakeLists.txt
│ └── src/
│ └── main.cpp
├── tests/ # Тесты
│ ├── CMakeLists.txt
│ ├── test_core/
│ │ ├── CMakeLists.txt
│ │ └── test_math.cpp
│ └── test_network/
│ ├── CMakeLists.txt
│ └── test_http_client.cpp
└── third_party/ # Внешние зависимости (git submodules или FetchContent)
└── googletest/
Ключевые принципы:
- libs/ — все библиотеки. Каждая библиотека — отдельная папка со своим CMakeLists.txt.
- apps/ — все исполняемые файлы. Они зависят от библиотек из libs/.
- tests/ — тесты, организованные по библиотекам.
- third_party/ — внешние зависимости. Используйте
git submodulesилиFetchContentдля их подключения.
2. CMake: настройка сборки монорепозитория
CMake — идеальный инструмент для монорепозитория. Рассмотрим, как настроить корневой CMakeLists.txt и файлы для библиотек.
Корневой CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Опции для включения/отключения компонентов
option(BUILD_TESTS "Build tests" ON)
option(BUILD_APPS "Build applications" ON)
# Подключаем внешние зависимости
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
FetchContent_MakeAvailable(googletest)
# Добавляем поддиректории библиотек
add_subdirectory(libs/core)
add_subdirectory(libs/network)
# Добавляем приложения (если опция включена)
if(BUILD_APPS)
add_subdirectory(apps/server)
add_subdirectory(apps/cli_tool)
endif()
# Добавляем тесты (если опция включена)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
CMakeLists.txt для библиотеки (libs/core)
add_library(core
src/math.cpp
src/string_utils.cpp
)
target_include_directories(core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
# Указываем, что библиотека использует C++20
target_compile_features(core PUBLIC cxx_std_20)
Обратите внимание: мы используем PUBLIC для include-директорий. Это означает, что любой, кто подключает библиотеку core, автоматически получает доступ к её заголовочным файлам.
CMakeLists.txt для приложения (apps/server)
add_executable(server
src/main.cpp
)
target_link_libraries(server
PRIVATE
core
network
)
Приложение просто линкуется с нужными библиотеками. Всё просто и прозрачно.
3. Управление зависимостями внутри монорепозитория
Одна из главных задач — правильно организовать зависимости между библиотеками. Рассмотрим типичный сценарий: библиотека network зависит от core.
Пример: библиотека network использует core
Создадим простой класс HttpClient, который использует утилиты из core: