Monorepo mit C++: Code-Organisation und Best Practices
Die Entwicklung großer C++-Projekte steht oft vor dem Problem der Code-Fragmentierung: Bibliotheken sind über verschiedene Repositories verstreut, Versionen sind nicht synchronisiert und die Integration wird zum Kopfschmerz. Die Lösung ist ein Monorepo. Dies ist ein Ansatz, bei dem der gesamte Code, einschließlich Bibliotheken, Hilfsprogrammen und Tests, in einem einzigen Repository gespeichert wird. In diesem Artikel werden wir untersuchen, wie man ein Monorepo für C++-Projekte richtig organisiert, moderne Tools (CMake, Conan, Git) verwendet und typische Fehler vermeidet.
1. Verzeichnisstruktur: Die Grundlage der Ordnung
Der erste Schritt besteht darin, eine einheitliche, klare Ordnerstruktur zu erstellen. Hier ist ein empfohlenes Schema für ein C++-Monorepo:
my-monorepo/├── cmake/ # Gemeinsame CMake-Module├── libs/ # Bibliotheken│ ├── core/ # Kern (Logging, Allokatoren)│ ├── math/ # Mathematik-Bibliothek│ └── network/ # Netzwerk-Bibliothek├── apps/ # Ausführbare Anwendungen│ ├── server/ # Server-Anwendung│ └── client/ # Client-Anwendung├── tests/ # Integrationstests├── scripts/ # Build- und Deployment-Skripte├── third_party/ # Externe Abhängigkeiten (Submodules)├── CMakeLists.txt # Root-CMake-Datei├── conanfile.txt # Abhängigkeitsmanager (optional)└── .github/ # CI/CD (GitHub Actions)Warum funktioniert das?
- libs/ — alle Bibliotheken an einem Ort, leicht wiederverwendbar.
- apps/ — Anwendungen, die von libs abhängen.
- third_party/ — externe Abhängigkeiten (über git submodules oder CMake FetchContent).
- cmake/ — benutzerdefinierte Funktionen (z.B.
add_library_with_tests).
2. CMake: Einrichtung des Root-Projekts
CMake ist der Standard für C++-Builds in Monorepos. Konfigurieren Sie die root CMakeLists.txt so, dass sie alle Unterprojekte einbindet:
cmake_minimum_required(VERSION 3.20)project(MyMonorepo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Gemeinsame Optionenoption(BUILD_TESTS "Build tests" ON)
# Bibliotheken einbindenadd_subdirectory(libs/core)add_subdirectory(libs/math)add_subdirectory(libs/network)
# Anwendungen einbindenadd_subdirectory(apps/server)add_subdirectory(apps/client)
# Tests einbindenif(BUILD_TESTS) enable_testing() add_subdirectory(tests)endif()Wichtige Punkte:
- Verwenden Sie
target_link_libraries, um Abhängigkeiten zwischen Bibliotheken explizit anzugeben. - Verwenden Sie für externe Abhängigkeiten (z.B. Boost, fmt)
FetchContentoder Conan. - Vermeiden Sie globale
include_directories— verwenden Sietarget_include_directoriesmitPUBLICoderPRIVATE.
3. Abhängigkeitsmanagement innerhalb des Monorepos
Eines der Hauptprobleme eines Monorepos sind zirkuläre Abhängigkeiten. Lösung: eine strenge Hierarchie. Zum Beispiel:
- core (hängt von nichts außer der Standardbibliothek ab)
- math (hängt von core ab)
- network (hängt von core und math ab)
- server (hängt von core, math, network ab)
Beispiel einer CMakeLists.txt für die Bibliothek 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 # Abhängigkeit explizit angeben