Introdução: por que um monorepositório para C++?
No desenvolvimento moderno de C++, especialmente em projetos de médio e grande porte, a organização do código se torna uma tarefa criticamente importante. Monorepositório (monorepo) é uma abordagem onde o código de todos os componentes, bibliotecas e serviços é armazenado em um único repositório. Ao contrário dos multirrepositórios, o monorepositório oferece um único ponto de entrada, simplifica a refatoração e o gerenciamento de dependências. Na prática, isso significa que você pode alterar a API de uma biblioteca e ver imediatamente como isso afetará todos os módulos dependentes.
Neste artigo, abordaremos os aspectos práticos da organização de código C++ em um monorepositório: desde a estrutura de pastas até a configuração de CI/CD. Você aprenderá como evitar erros típicos e tornar a compilação rápida e previsível.
1. Estrutura do monorepositório para C++
Uma estrutura de pastas correta é a base de um monorepositório bem-sucedido. Vamos considerar a hierarquia recomendada para projetos C++.
Hierarquia básica de diretórios
my_project/├── CMakeLists.txt # Arquivo raiz de compilação├── cmake/ # Módulos CMake personalizados│ └── FindMyLib.cmake├── libs/ # Bibliotecas internas│ ├── 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/ # Aplicações executáveis│ ├── server/│ │ ├── CMakeLists.txt│ │ └── src/│ │ └── main.cpp│ └── cli/│ ├── CMakeLists.txt│ └── src/│ └── main.cpp├── tests/ # Testes de integração│ ├── CMakeLists.txt│ └── test_integration.cpp├── third_party/ # Dependências externas (vendoring)│ ├── fmt/│ └── spdlog/├── .github/ # Configuração de CI/CD│ └── workflows/│ └── build.yml└── README.mdPrincípios-chave:
- libs/ — todas as bibliotecas internas. Cada biblioteca tem sua própria pasta com
CMakeLists.txt. - apps/ — arquivos executáveis. Eles dependem das bibliotecas em
libs/. - third_party/ — dependências de fornecedores (git submodules ou códigos-fonte copiados).
- tests/ — testes de integração que verificam a interação de várias bibliotecas.
Exemplo de CMakeLists.txt para uma biblioteca
# 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)Importante: Use PUBLIC e PRIVATE para o encaminhamento correto de dependências. Este é um ponto-chave — se a biblioteca A usa a biblioteca B apenas em seus próprios fontes, torne B PRIVATE.
2. Gerenciamento de dependências no monorepositório
Um dos principais problemas dos monorepositórios é o "inferno das dependências". Em C++, isso é resolvido através do CMake target-oriented design (design orientado a alvos).
Regra: um alvo — uma biblioteca
Cada biblioteca deve exportar exatamente um alvo CMake. Isso simplifica a vinculação e previne dependências cíclicas.
# CMakeLists.txt raizcmake_minimum_required(VERSION 3.20)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# Inclui todas as bibliotecasadd_subdirectory(libs/core)add_subdirectory(libs/network)