What is a monorepo and why is it needed in C++?
A monorepo is an approach to code management where all project components, libraries, utilities, and even documentation are stored in a single repository. For C++ projects, this is especially relevant, as the language traditionally requires a clear file and dependency structure.
Advantages of a monorepo for C++:
- Single version of dependencies — all modules use the same version of libraries, eliminating ABI conflicts.
- Simplified build — one CMakeLists.txt or build system manages the entire project.
- Atomic commits — changes in multiple modules can be combined into a single commit, simplifying code review.
- Easy refactoring — changing an API in one place is immediately visible in all dependent modules.
However, a monorepo is not suitable for everyone: if teams are completely independent and use different compiler versions, a multirepo is better to consider. For beginners, though, a monorepo is an excellent way to avoid the headache of configuring paths and dependencies.
Main part: creating a monorepo for a C++ project
1. Directory structure
A proper structure is the foundation of any monorepo. The recommended template for C++:
my_monorepo/├── CMakeLists.txt # Root CMake file├── cmake/ # Auxiliary CMake modules│ └── helpers.cmake├── libs/ # Libraries│ ├── core/ # Project core│ │ ├── CMakeLists.txt│ │ ├── include/core/│ │ └── src/│ └── utils/ # Utilities│ ├── CMakeLists.txt│ ├── include/utils/│ └── src/├── apps/ # Executable applications│ ├── main_app/│ │ ├── CMakeLists.txt│ │ └── main.cpp│ └── test_app/│ ├── CMakeLists.txt│ └── main.cpp├── tests/ # Tests (if not built into modules)│ └── test_core.cpp├── scripts/ # Build and CI scripts│ └── build.sh├── .gitignore└── README.mdKey rule: each module (library or application) must be self-contained. It contains its own CMakeLists.txt, header files in include/, and source files in src/.
2. Configuring the root CMakeLists.txt
The root CMakeLists.txt manages the entire build. It should define common compiler settings, the C++ standard, and include child modules:
cmake_minimum_required(VERSION 3.20)project(MyMonorepo VERSION 1.0.0 LANGUAGES CXX)
# Set a single C++17 standard for the entire projectset(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
# Option for building testsoption(BUILD_TESTS "Build tests" OFF)
# Include auxiliary modulesinclude(cmake/helpers.cmake)
# Add subdirectories with librariesadd_subdirectory(libs/core)add_subdirectory(libs/utils)
# Add applicationsadd_subdirectory(apps/main_app)add_subdirectory(apps/test_app)
# If tests are enabled, include themif(BUILD_TESTS) enable_testing() add_subdirectory(tests)endif()3. Example library: libs/core
Consider the core library, which contains a class for working with numbers. File structure:
libs/core/├── CMakeLists.txt├── include/core/│ └── calculator.h└── src/ └── calculator.cppContents of include/core/calculator.h:
#ifndef CORE_CALCULATOR_H#define CORE_CALCULATOR_H
namespace core {
class Calculator {public: Calculator() = default; int add(int a, int b); int multiply(int a, int b);};
} // namespace core
#endif // CORE_CALCULATOR_H