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

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

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

Начать курс

Введение: почему монорепозиторий для C++?

В современной C++ разработке, особенно в проектах среднего и крупного размера, организация кода становится критически важной задачей. Монорепозиторий (monorepo) — это подход, при котором код всех компонентов, библиотек и сервисов хранится в одном репозитории. В отличие от мультирепозиториев, монорепозиторий предлагает единую точку входа, упрощает рефакторинг и управление зависимостями. На практике это означает, что вы можете изменять API библиотеки и сразу видеть, как это повлияет на все зависимые модули.

В этой статье мы разберем практические аспекты организации C++ кода в монорепозитории: от структуры папок до настройки CI/CD. Вы узнаете, как избежать типичных ошибок и сделать сборку быстрой и предсказуемой.

1. Структура монорепозитория для C++

Правильная структура папок — основа успешного монорепозитория. Рассмотрим рекомендуемую иерархию для 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/            # Внешние зависимости (vendoring)
│   ├── fmt/
│   └── spdlog/
├── .github/                # CI/CD конфигурация
│   └── workflows/
│       └── build.yml
└── README.md

Ключевые принципы:

  • libs/ — все внутренние библиотеки. Каждая библиотека имеет свою папку с CMakeLists.txt.
  • apps/ — исполняемые файлы. Они зависят от библиотек из libs/.
  • third_party/ — вендорные зависимости (git submodules или скопированные исходники).
  • tests/ — интеграционные тесты, которые проверяют взаимодействие нескольких библиотек.

Пример CMakeLists.txt для библиотеки

# libs/core/CMakeLists.txt
add_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. Управление зависимостями в монорепозитории

Одна из главных проблем монорепозиториев — «ад зависимостей». В C++ она решается через CMake target-oriented design (проектирование через цели).

Правило: одна цель — одна библиотека

Каждая библиотека должна экспортировать ровно один CMake target. Это упрощает линковку и предотвращает циклические зависимости.

# Корневой CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# Подключаем все библиотеки
add_subdirectory(libs/core)
add_subdirectory(libs/network)

# Подключаем приложения
add_subdirectory(apps/server)
add_subdirectory(apps/cli)

# Тесты
enable_testing()
add_subdirectory(tests)

Работа с внешними зависимостями

Для внешних библиотек используйте FetchContent или vendoring. Пример с FetchContent:

# cmake/fetch_dependencies.cmake
include(FetchContent)

FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.1.0
)

FetchContent_Declare(
    spdlog
    GIT_REPOSITORY https://github.com/gabime/spdlog.git
    GIT_TAG v1.12.0
)

FetchContent_MakeAvailable(fmt spdlog)

Совет: Для стабильности лучше зафиксировать версии через GIT_TAG. Это гарантирует, что сборка не сломается из-за изменений в upstream.

3. CI/CD для C++ монорепозитория

Автоматизация сборки и тестирования — обязательный элемент. Рассмотрим настройку GitHub Actions.

Пример workflow: сборка и тестирование

# .github/workflows/build.yml
name: Build and Test

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
      with:
        submodules: recursive

    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install -y cmake g++

    - name: Configure CMake
      run: cmake -B build -DCMAKE_BUILD_TYPE=Release

    - name: Build
      run: cmake --build build --parallel

    - name: Run tests
      run: |
        cd build
        ctest --output-on-failure

Инкрементальная сборка с кэшированием

Для ускорения сборки используйте ccache

Блоги

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