Introduction: Why a Monorepo for C++?
In modern C++ development, especially in medium and large-sized projects, code organization becomes a critically important task. A monorepo is an approach where the code for all components, libraries, and services is stored in a single repository. Unlike multi-repositories, a monorepo offers a single entry point, simplifies refactoring, and streamlines dependency management. In practice, this means you can change a library's API and immediately see how it affects all dependent modules.
In this article, we will break down the practical aspects of organizing C++ code in a monorepo: from folder structure to setting up CI/CD. You will learn how to avoid common mistakes and make the build process fast and predictable.
1. Monorepo Structure for C++
A proper folder structure is the foundation of a successful monorepo. Let's look at a recommended hierarchy for C++ projects.
Basic Directory Hierarchy
my_project/├── CMakeLists.txt # Root build file├── cmake/ # Custom CMake modules│ └── FindMyLib.cmake├── libs/ # Internal libraries│ ├── 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/ # Executable applications│ ├── server/│ │ ├── CMakeLists.txt│ │ └── src/│ │ └── main.cpp│ └── cli/│ ├── CMakeLists.txt│ └── src/│ └── main.cpp├── tests/ # Integration tests│ ├── CMakeLists.txt│ └── test_integration.cpp├── third_party/ # External dependencies (vendoring)│ ├── fmt/│ └── spdlog/├── .github/ # CI/CD configuration│ └── workflows/│ └── build.yml└── README.mdKey Principles:
- libs/ — all internal libraries. Each library has its own folder with a
CMakeLists.txt. - apps/ — executable files. They depend on libraries from
libs/. - third_party/ — vendored dependencies (git submodules or copied sources).
- tests/ — integration tests that verify the interaction of multiple libraries.
Example CMakeLists.txt for a Library
# 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)Important: Use PUBLIC and PRIVATE for proper dependency propagation. This is a key point — if library A uses library B only in its source files, make B PRIVATE.
2. Dependency Management in a Monorepo
One of the main challenges of monorepos is "dependency hell." In C++, this is solved through CMake target-oriented design.
Rule: One Target — One Library
Each library should export exactly one CMake target. This simplifies linking and prevents circular dependencies.
# Root CMakeLists.txtcmake_minimum_required(VERSION 3.20)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# Include all librariesadd_subdirectory(libs/core)add_subdirectory(libs/network)