Monorepositório com C++: organização de código e melhores práticas
O desenvolvimento de grandes projetos em C++ frequentemente enfrenta o problema de fragmentação de código: bibliotecas espalhadas por diferentes repositórios, versões não sincronizadas e a integração se torna uma dor de cabeça. A solução é o monorepositório (monorepo). Esta é uma abordagem onde todo o código, incluindo bibliotecas, utilitários e testes, é armazenado em um único repositório. Neste artigo, vamos analisar como organizar corretamente um monorepositório para projetos C++, usando ferramentas modernas (CMake, Conan, Git) e evitando erros típicos.
1. Estrutura de diretórios: a base da organização
O primeiro passo é criar uma estrutura de pastas única e clara. Aqui está um esquema recomendado para um monorepositório C++:
my-monorepo/├── cmake/ # Módulos CMake comuns├── libs/ # Bibliotecas│ ├── core/ # Núcleo (logging, alocadores)│ ├── math/ # Biblioteca matemática│ └── network/ # Biblioteca de rede├── apps/ # Aplicações executáveis│ ├── server/ # Aplicação servidora│ └── client/ # Aplicação cliente├── tests/ # Testes de integração├── scripts/ # Scripts de build e deploy├── third_party/ # Dependências externas (submodules)├── CMakeLists.txt # Arquivo CMake raiz├── conanfile.txt # Gerenciador de dependências (opcional)└── .github/ # CI/CD (GitHub Actions)Por que isso funciona?
- libs/ — todas as bibliotecas em um só lugar, fáceis de reutilizar.
- apps/ — aplicações que dependem de libs.
- third_party/ — dependências externas (via git submodules ou CMake FetchContent).
- cmake/ — funções personalizadas (exemplo:
add_library_with_tests).
2. CMake: configurando o projeto raiz
CMake é o padrão para build C++ em monorepositórios. Configure o CMakeLists.txt raiz para que ele inclua todos os subprojetos:
cmake_minimum_required(VERSION 3.20)project(MyMonorepo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Opções comunsoption(BUILD_TESTS "Build tests" ON)
# Inclui as bibliotecasadd_subdirectory(libs/core)add_subdirectory(libs/math)add_subdirectory(libs/network)
# Inclui as aplicaçõesadd_subdirectory(apps/server)add_subdirectory(apps/client)
# Inclui os testesif(BUILD_TESTS) enable_testing() add_subdirectory(tests)endif()Pontos importantes:
- Use
target_link_librariespara indicar explicitamente as dependências entre bibliotecas. - Para dependências externas (exemplo: Boost, fmt), use
FetchContentou Conan. - Evite
include_directoriesglobais — usetarget_include_directoriescomPUBLICouPRIVATE.
3. Gerenciamento de dependências dentro do monorepositório
Um dos principais problemas do monorepositório são as dependências cíclicas. Solução: hierarquia rigorosa. Exemplo:
- core (não depende de nada, exceto da biblioteca padrão)
- math (depende de core)
- network (depende de core e math)
- server (depende de core, math, network)
Exemplo de CMakeLists.txt para a biblioteca 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 # Indica explicitamente a de