Monorepo with C++: Code Organization and Best Practices
Developing large C++ projects often faces the problem of code fragmentation: libraries are scattered across different repositories, versions are not synchronized, and integration becomes a headache. The solution is a monorepo. This is an approach where all code, including libraries, utilities, and tests, is stored in a single repository. In this article, we will analyze how to properly organize a monorepo for C++ projects using modern tools (CMake, Conan, Git) and avoiding typical mistakes.
1. Directory Structure: The Foundation of Order
The first step is to create a unified, clear folder structure. Here is a recommended scheme for a C++ monorepo:
my-monorepo/├── cmake/ # Common CMake modules├── libs/ # Libraries│ ├── core/ # Core (logging, allocators)│ ├── math/ # Math library│ └── network/ # Network library├── apps/ # Executable applications│ ├── server/ # Server application│ └── client/ # Client application├── tests/ # Integration tests├── scripts/ # Build and deployment scripts├── third_party/ # External dependencies (submodules)├── CMakeLists.txt # Root CMake file├── conanfile.txt # Dependency manager (optional)└── .github/ # CI/CD (GitHub Actions)Why does this work?
- libs/ — all libraries in one place, easy to reuse.
- apps/ — applications that depend on libs.
- third_party/ — external dependencies (via git submodules or CMake FetchContent).
- cmake/ — custom functions (e.g.,
add_library_with_tests).
2. CMake: Configuring the Root Project
CMake is the standard for C++ builds in monorepos. Configure the root CMakeLists.txt so that it includes all subprojects:
cmake_minimum_required(VERSION 3.20)project(MyMonorepo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Common optionsoption(BUILD_TESTS "Build tests" ON)
# Include librariesadd_subdirectory(libs/core)add_subdirectory(libs/math)add_subdirectory(libs/network)
# Include applicationsadd_subdirectory(apps/server)add_subdirectory(apps/client)
# Include testsif(BUILD_TESTS) enable_testing() add_subdirectory(tests)endif()Important points:
- Use
target_link_librariesto explicitly specify dependencies between libraries. - For external dependencies (e.g., Boost, fmt), use
FetchContentor Conan. - Avoid global
include_directories— usetarget_include_directorieswithPUBLICorPRIVATE.
3. Managing Dependencies Inside the Monorepo
One of the main problems of a monorepo is circular dependencies. The solution: a strict hierarchy. For example:
- core (depends on nothing except the standard library)
- math (depends on core)
- network (depends on core and math)
- server (depends on core, math, network)
Example CMakeLists.txt for the math library:
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 # Explicitly specify de