Cenário 1: Projeto de Módulo Único (Estrutura Simples)
Em projetos menores, onde não há uma hierarquia complexa de submódulos, a configuração do CMake é direta. Considere a seguinte estrutura de diretórios:
CoreSystem/
├── CMakeLists.txt
├── include/
│ └── core_engine.h
├── src/
│ ├── app_runner.cpp
│ └── core_engine.cpp
└── build/
Comandos Fundamentais
Versão Mínima e Nome do Projeto
cmake_minimum_required(VERSION 3.15): Define a versão mínima do CMake necessária para processar o script.
project(CoreSystem): Estabelece o nome do projeto e inicializa variáveis internas do sistema.
Definição de Alvos (Targets)
add_executable(app_runner src/app_runner.cpp): Cria um alvo executável chamado app_runner a partir do arquivo fonte especificado.
add_library(core_engine STATIC src/core_engine.cpp): Gera uma biblioteca estática denominada core_engine.
Diretórios de Inclusão e Vinculação
target_include_directories(core_engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include): Adiciona o diretório include ao caminho de busca de cabeçalhos. O uso de PUBLIC garante que qualquer alvo que vincule core_engine também herde esse caminho.
target_link_libraries(app_runner PRIVATE core_engine): Vincula a biblioteca core_engine ao executável app_runner, resolvendo dependências de símbolos em tempo de linkagem.
Gerenciamento de Dependências Externas (Exemplo com OpenCV)
set(OpenCV_DIR "/opt/opencv4/lib/cmake/opencv4"): Aponta para o diretório onde os arquivos de configuração do CMake da biblioteca externa estão localizados.
find_package(OpenCV 4.5 REQUIRED): Busca a instalação do OpenCV. A palavra-chave REQUIRED interrompe a configuração caso a biblioteca não seja encontrada. Após a execução, variáveis como OpenCV_LIBS e OpenCV_INCLUDE_DIRS são populadas automaticamente.
Para aplicar essas dependências ao alvo:
target_include_directories(app_runner PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(app_runner PRIVATE ${OpenCV_LIBS})
Configuração de Diretórios de Saída
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin): Direciona todos os executáveis gerados para a pasta bin dentro do diretório de build.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib): Define o local de saída para bibliotecas estáticas.
Cenário 2: Projeto Multi-Módulo (Estrutura Hierárquica)
Para sistemas maiores, é recomendável dividir o código em submódulos, cada um com seu próprio arquivo de configuração.
PlatformSuite/
├── CMakeLists.txt # Configuração raiz
├── src/
│ └── main_app.cpp # Fonte principal
├── network_module/
│ ├── CMakeLists.txt # Configuração do submódulo de rede
│ ├── src/
│ │ └── net_handler.cpp
│ └── include/
│ └── net_handler.h
└── data_module/
├── CMakeLists.txt # Configuração do submódulo de dados
├── src/
│ └── db_connector.cpp
└── include/
└── db_connector.h
Configuração do Módulo Raiz
O arquivo principal é responsável por orquestrar a compilação e conectar os submódulos.
cmake_minimum_required(VERSION 3.15)
project(PlatformSuite)
# Integra os subdiretórios ao sistema de build
add_subdirectory(network_module)
add_subdirectory(data_module)
# Define o executável principal
add_executable(platform_app src/main_app.cpp)
# Vincula as bibliotecas dos submódulos e propaga seus cabeçalhos
target_link_libraries(platform_app PRIVATE net_lib data_lib)
Nota sobre add_subdirectory: É possível compilar diretórios externos especificando um caminho de build distinto, por exemplo: add_subdirectory(../external_deps ext_build).
Configuração dos Submódulos
Cada submódulo gerencia suas próprias fontes e expõe suas interfaces.
Exemplo para network_module/CMakeLists.txt:
project(NetworkModule)
add_library(net_lib STATIC src/net_handler.cpp)
# Expõe o diretório de inclusão para o módulo pai e outros consumidores
target_include_directories(net_lib PUBLIC
$<build_interface:>
$<include>
)
</include></build_interface:>
Propagação de Variáveis para o Escopo Pai
Quando um submódulo precisa compartilhar variáveis (como caminhos de bibliotecas de terceiros) com o módulo raiz, utiliza-se a diretiva PARENT_SCOPE.
set(EXTERNAL_MATH_LIB "${CMAKE_CURRENT_SOURCE_DIR}/third_party/mathlib/libmath.a")
set(EXTERNAL_MATH_LIB ${EXTERNAL_MATH_LIB} PARENT_SCOPE)
set(EXTERNAL_MATH_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/third_party/mathlib/include")
set(EXTERNAL_MATH_INCLUDE ${EXTERNAL_MATH_INCLUDE} PARENT_SCOPE)
Transitividade e Palavras-chave de Visibilidade
O uso correto de PUBLIC em target_include_directories elimina a necessidade de redeclarar caminhos de cabeçalhos no módulo raiz. Se net_lib for marcado como PUBLIC, o platform_app herdará automaticamente os include directories ao usar target_link_libraries.
- Apenas
target_include_directories: A compilação passa, mas a linkagem falha com erros de "referência indefinida" (undefined reference). - Apenas
target_link_libraries: A linkagem encontra a biblioteca, mas a compilação falha por não encontrar os arquivos de cabeçalho.
Ambos os aspectos (cabeçalhos e binários) devem ser configurados, seja explicitamente ou através da transitividade do CMake moderno.
Diferenças entre Comandos de Escopo Global e por Alvo
No CMake moderno, prefira sempre os comandos prefixados com target_:
- Erro de
#includenão encontrado: Utilizetarget_include_directoriespara informar ao compilador onde buscar os cabeçalhos. - Erro de linkagem (símbolo indefinido): Utilize
target_link_librariespara instruir o linker sobre quais bibliotecas vincular. - Evite
target_link_directories: A menos que estritamente necessário, evite usar este comando. Prefira passar caminhos absolutos ou usarfind_packagepara resolver dependências de forma robusta.
Comandos globais legados como include_directories e link_libraries poluem o escopo e afetam todos os alvos subsequentes no mesmo diretório, o que é considerado uma má prática em projetos complexos.
Cofniguração para Compilação Cruzada (Cross-Compiling)
Ao compilar para uma arquitetura diferente da máquina hospedeira (ex: ARM a partir de x86), um arquivo toolchain (como arm_toolchain.cmake) é utilizado.
# Definição dos compiladores
set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc")
set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++")
# Diretório raiz do sistema de destino (sysroot)
set(CMAKE_SYSROOT "/opt/cross-root/aarch64")
# Ajuste dos caminhos de busca
set(CMAKE_FIND_ROOT_PATH "/opt/cross-root/aarch64")
# Regras de busca para o CMake
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Explicação das variáveis de modo de busca:
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER: O CMake buscará programas (como compiladores ou ferramentas de build) apenas no sistema hospedeiro, ignorando o sysroot.CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY: Restringe a busca de pacotes (viafind_package) exclusivamente ao ambiente do sysroot.CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLYeCMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY: Garantem que bibliotecas e cabeçalhos sejam extraídos apenas do sistema de destino, evitando incompatibilidades de arquitetura.