Einführung: Warum ein Monorepo für C++?
In der modernen C++-Entwicklung, insbesondere bei mittleren und großen Projekten, wird die Code-Organisation zu einer kritischen Aufgabe. Ein Monorepository (Monorepo) ist ein Ansatz, bei dem der Code aller Komponenten, Bibliotheken und Dienste in einem einzigen Repository gespeichert wird. Im Gegensatz zu Multi-Repositories bietet ein Monorepo einen einzigen Einstiegspunkt, vereinfacht Refactoring und Abhängigkeitsverwaltung. In der Praxis bedeutet dies, dass Sie die API einer Bibliothek ändern und sofort sehen können, wie sich dies auf alle abhängigen Module auswirkt.
In diesem Artikel werden wir die praktischen Aspekte der Organisation von C++-Code in einem Monorepo behandeln: von der Ordnerstruktur bis zur CI/CD-Konfiguration. Sie erfahren, wie Sie typische Fehler vermeiden und den Build schnell und vorhersagbar gestalten.
1. Struktur eines Monorepos für C++
Die richtige Ordnerstruktur ist die Grundlage eines erfolgreichen Monorepos. Betrachten wir die empfohlene Hierarchie für C++-Projekte.
Basisverzeichnishierarchie
my_project/├── CMakeLists.txt # Root-Build-Datei├── cmake/ # Benutzerdefinierte CMake-Module│ └── FindMyLib.cmake├── libs/ # Interne Bibliotheken│ ├── 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/ # Ausführbare Anwendungen│ ├── server/│ │ ├── CMakeLists.txt│ │ └── src/│ │ └── main.cpp│ └── cli/│ ├── CMakeLists.txt│ └── src/│ └── main.cpp├── tests/ # Integrationstests│ ├── CMakeLists.txt│ └── test_integration.cpp├── third_party/ # Externe Abhängigkeiten (Vendoring)│ ├── fmt/│ └── spdlog/├── .github/ # CI/CD-Konfiguration│ └── workflows/│ └── build.yml└── README.mdWichtige Prinzipien:
- libs/ — alle internen Bibliotheken. Jede Bibliothek hat ihren eigenen Ordner mit
CMakeLists.txt. - apps/ — ausführbare Dateien. Sie hängen von den Bibliotheken in
libs/ab. - third_party/ — Vendor-Abhängigkeiten (git submodules oder kopierte Quellen).
- tests/ — Integrationstests, die das Zusammenspiel mehrerer Bibliotheken prüfen.
Beispiel CMakeLists.txt für eine Bibliothek
# libs/core/CMakeLists.txtadd_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)Wichtig: Verwenden Sie PUBLIC und PRIVATE für die korrekte Weitergabe von Abhängigkeiten. Dies ist ein entscheidender Punkt — wenn Bibliothek A Bibliothek B nur in ihren eigenen Quelldateien verwendet, machen Sie B zu PRIVATE.
2. Abhängigkeitsverwaltung im Monorepo
Eines der Hauptprobleme von Monorepos ist der „Abhängigkeits-Overhead“. In C++ wird dies durch CMake target-oriented design gelöst.
Regel: Ein Target — eine Bibliothek
Jede Bibliothek sollte genau ein CMake-Target exportieren. Dies vereinfacht das Linken und verhindert zyklische Abhängigkeiten.
# Root CMakeLists.txtcmake_minimum_required(VERSION 3.20)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# Alle Bibliotheken einbindenadd_subdirectory(libs/core)add_subdirectory(libs/network)