Configurando um projeto
Poderíamos usar os arquivos tasks.json
e launch.json
para compilar e executar o código, como fizemos anteriormente.
Entretanto, para projetos maiores, o CMake permite uma maior escalabilidade.
Além disso, ele permite definir instruções de compilação diferentes para vários sistemas operacionais, além de argumentos opcionais, como flags de compilação e definições de pré-processador.
Projeto
Seção intitulada “Projeto”Para não bagunçar o projeto oriented_graph
, vamos copiá-lo para uma nova pasta chamada oriented_graph_with_cmake
.
cd ~/dev/c_cpp_projectscp -r oriented_graph oriented_graph_with_cmakecd oriented_graph_with_cmake
Também podemos adotar a convenção de colocar todos os arquivos de código-fonte dentro de uma pasta chamada src
.
mkdir -p srcmv main.cpp srcmv node srcmv edge srcmv graph src
Configuração geral
Seção intitulada “Configuração geral”O principal arquivo de configuração do CMake é o CMakeLists.txt
.
Ele é responsável por definir as configurações do projeto e as dependências necessárias para a compilação.
Crie um arquivo chamado CMakeLists.txt
na raiz da pasta oriented_graph. Atente-se à letras maiúsculas e minúsculas no nome do arquivo, ele precisa estar escrito exatamente como acima.
cd ~/dev/c_cpp_projects/oriented_graph_with_cmaketouch CMakeLists.txtcode .
Vamos preencher trecho por trecho do arquivo CMakeLists.txt
para entender o que cada parte faz.
Versões
Seção intitulada “Versões”Primeiramente, é necessário definir a versão do CMake que será utilizada.
Na época de escrita deste guia, a versão 3.31.6
era a mais recente disponível.
Nós podemos definir um intervalo de versões suportadas, para garantir que o projeto seja compilado corretamente em diferentes ambientes.
Isso é feito com o comando cmake_minimum_required
.
## Versioncmake_minimum_required(VERSION 3.12...3.31)
Em seguida, usamos um comando para garantir que versões anteriores à 3.12
passem por uma política de compatibilidade para conseguir realizar a compilação.
if (${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})endif ()
Projeto
Seção intitulada “Projeto”Agora, vamos definir o nome do projeto e a linguagem utilizada, que é o C++.
## Projectproject(oriented_graph LANGUAGES CXX)
Ambiente
Seção intitulada “Ambiente”O C++ tem várias versões, e pode ser customizado para diferentes ambientes.
No nosso caso, queremos usar uma versão mais recente (no momento a 23
) e compatível com o padrão oficial.
Para isso, usamos comandos iniciados em set
para definir as variáveis de ambiente.
## Environmentset(CMAKE_CXX_STANDARD 23)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
Flags de compilação
Seção intitulada “Flags de compilação”As flags de compilação são opções que podem ser passadas para o compilador para alterar o comportamento da compilação.
No nosso caso, queremos ativar todos os avisos de compilação. Além disso, é importante manter a compatibilidade com outros compiladores, como o GCC, o Apple Clang e MSVC.
Para isso, usamos o comando add_compile_options
.
## Warningsif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") add_compile_options( -Wall -Wextra # Reasonable warnings -Wpedantic # Strict ISO C++ compliance -Wconversion # Warn on implicit type conversions -Wsign-conversion # Warn on signed/unsigned mismatches )elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_compile_options( /W4 # High warning level /permissive- # Enforce standards compliance )endif()
Diretórios
Seção intitulada “Diretórios”Nosso projeto guarda o código-fonte na pasta src
.
Para que o CMake consiga encontrar os arquivos dentro dela, precisamos listá-la no arquivo CMakeLists.txt
.
## Directoriesadd_subdirectory(src)
Outras pastas comumente usadas são:
include
: para arquivos de cabeçalho (.h
ou.hpp
);lib
: para bibliotecas externas;tests
: para testes automatizados;docs
: para documentação; eassets
: para arquivos estáticos, como imagens e fontes.
Nosso projeto não possui essas pastas, mas caso você venha a precisar em outros projetos, você usaria o comando add_subdirectory
para cada uma delas.
Resultado
Seção intitulada “Resultado”No final, o arquivo CMakeLists.txt
deve ficar da seguinte forma.
## Versioncmake_minimum_required(VERSION 3.12...3.31)
if (${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})endif ()
## Projectproject(oriented_graph LANGUAGES CXX)
## Environmentset(CMAKE_CXX_STANDARD 23)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
## Warningsif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") add_compile_options( -Wall -Wextra # Reasonable warnings -Wpedantic # Strict ISO C++ compliance -Wconversion # Warn on implicit type conversions -Wsign-conversion # Warn on signed/unsigned mismatches )elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_compile_options( /W4 # High warning level /permissive- # Enforce standards compliance )endif()
## Directoriesadd_subdirectory(src)
Código-fonte
Seção intitulada “Código-fonte”Agora que já temos o arquivo CMakeLists.txt
configurado, precisamos adicionar os arquivos de código-fonte, que estão na pasta src
.
Para isso, vamos criar um novo arquivo CMakeLists.txt
dentro da pasta src
.
touch src/CMakeLists.txt
Executável
Seção intitulada “Executável”Dentro deste novo arquivo src/CMakeLists.txt
, precisamos definir o nome do arquivo executável que será gerado após a compilação.
No nosso caso, o nome do executável será oriented_graph
, e ele será gerado a partir do arquivo main.cpp
.
## Executableadd_executable(oriented_graph main.cpp)
Diretórios
Seção intitulada “Diretórios”Agora, precisamos adicionar os módulos que estão dentro da pasta src
.
Para isso, usamos o comando add_subdirectory
para cada um deles.
## Directoriesadd_subdirectory(node)add_subdirectory(edge)add_subdirectory(graph)
Dependências
Seção intitulada “Dependências”O arquivo principal, main.cpp
, depende da classe Graph
.
Isso é definido pela importação do arquivo de cabeçalho graph.hpp
, que está dentro da pasta graph
.
#include "graph/graph.hpp"
Para que o CMake consiga vincular esse arquivo, precisamos adicionar a pasta graph
como um diretório de inclusão.
Isso é feito com o comando target_include_directories
.
Ele vai incluir o diretório graph
de forma privada ao executável oriented_graph
.
Aqui decidimos usar a keyword PRIVATE
para que os outros módulos que main.cpp
importa não sejam disponibilizados para arquivos que venham a incluir main.cpp
.
Isso é importante para evitar que outros módulos acessem arquivos que não deveriam.
## Linking librariestarget_link_libraries(oriented_graph PRIVATE graph)
Resultado
Seção intitulada “Resultado”No final, o arquivo src/CMakeLists.txt
deve ficar da seguinte forma.
## Executableadd_executable(oriented_graph main.cpp)
## Directoriesadd_subdirectory(node)add_subdirectory(edge)add_subdirectory(graph)
## Linking librariestarget_link_libraries(oriented_graph PRIVATE graph)
Módulos
Seção intitulada “Módulos”A forma de criar diferentes módulos no CMake é definir cada um como uma biblioteca estática.
Elas são montadas por um arquivo *.hpp
que contém funções e variáveis que podem ser utilizadas por outros arquivos do projeto.
Assim como fizemos para a pasta src
, precisamos criar um arquivo CMakeLists.txt
para cada módulo.
Esses arquivos são responsáveis por definir as configurações específicas de cada um.
touch src/node/CMakeLists.txttouch src/edge/CMakeLists.txttouch src/graph/CMakeLists.txt
Biblioteca
Seção intitulada “Biblioteca”Vamos tomar como exemplo o módulo edge
, precisamos definir o nome da biblioteca, que será edge
.
Para isso, acesse o arquivo src/edge/CMakeLists.txt
, e utilize o comando add_library
.
## Libraryadd_library(edge)
Código-fonte
Seção intitulada “Código-fonte”Em seguida, precisamos adicionar os arquivos de código-fonte e de cabeçalho que fazem parte do módulo.
O módulo edge
possui apenas um arquivo de cabeçalho, o edge.hpp
, e um arquivo de código-fonte, o edge.cpp
.
Usaremos o comando target_sources
para adicionar esses arquivos à biblioteca.
Os arquivos de cabeçalho são adicionados como públicos para que eles possam ser incluídos por outros módulos. Já os arquivos de código-fonte são adicionados como privados, de forma que outros módulos precisem utilizar o arquivo de cabeçalho para conseguir acessá-los.
## Sourcestarget_sources(edge PUBLIC edge.hpp PRIVATE edge.cpp)
Diretórios
Seção intitulada “Diretórios”Agora, precisamos definir o diretório edge
como um diretório de inclusão, para que ele possa ser acessado por outros módulos.
Isso é feito com o comando target_include_directories
.
O CMake vai incluir o diretório edge
de forma pública à biblioteca edge
.
É necessário usar a keyword PUBLIC
para que outros arquivos consigam incluir a classe Edge
, definida no arquivo edge.hpp
.
## Define moduletarget_include_directories(edge PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
Dependências
Seção intitulada “Dependências”O módulo edge
depende da classe Node
.
Isso é definido pela importação do arquivo de cabeçalho node.hpp
, que está dentro da pasta node
.
#include "../node/node.hpp"
Para que o CMake consiga vincular esse arquivo, precisamos adicionar a pasta node
como um diretório de inclusão.
Isso é feito com o comando target_link_libraries
.
Da mesma forma que fizemos para o executável oriented_graph
, vamos incluir o diretório node
de forma privada à biblioteca edge
.
## Link librariestarget_link_libraries(edge PRIVATE node)
O módulo node
não possui dependências, então não precisamos nos preocupar com isso.
Já o módulo graph
depende do módulo node
e do módulo edge
.
Dessa forma, devemos incluir ambos na lista de dependências do comando target_link_libraries
.
Resultado
Seção intitulada “Resultado”No final, o arquivo src/node/CMakeLists.txt
deve ficar da seguinte forma.
## Libraryadd_library(node)
## Sourcestarget_sources(node PUBLIC node.hpp PRIVATE node.cpp)
## Define moduletarget_include_directories(node PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
O arquivo src/edge/CMakeLists.txt
deve ficar da seguinte forma.
## Libraryadd_library(edge)
## Sourcestarget_sources(edge PUBLIC edge.hpp PRIVATE edge.cpp)
## Define moduletarget_include_directories(edge PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
## Link librariestarget_link_libraries(edge PRIVATE node)
O arquivo src/graph/CMakeLists.txt
deve ficar da seguinte forma.
## Libraryadd_library(graph)
## Sourcestarget_sources(graph PUBLIC graph.hpp PRIVATE graph.cpp)
## Define moduletarget_include_directories(graph PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
## Link librariestarget_link_libraries(graph PRIVATE node edge)
Por fim, vamos fazer stage das modificações e realizar um novo commit.
git add .git commit -m "Configure CMake"