Монорепозиторий с C++: организация кода на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Зачем нужен монорепозиторий в 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:

Блоги

Рекомендаций по книгам