Pular para o conteúdo

Configurando um projeto

Poderíamos usar os arquivos tasks.json e launch.json para compilar e executar o código, como fizemos anteriormente. Contudo, 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.

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.

Fedora, WSL ou Mint
cd ~/dev/c_cpp_projects/oriented_graph
touch CMakeLists.txt
code .

A estrutura do projeto ficará como a seguir.

  • Directorysrc
    • Directoryedge
      • edge.cpp
      • edge.hpp
    • Directorygraph
      • graph.cpp
      • graph.hpp
    • Directorynode
      • node.cpp
      • node.hpp
    • main.cpp
  • .clang-format
  • .clang-tidy
  • .gitignore
  • CMakeLists.txt

Vamos preencher trecho por trecho do arquivo CMakeLists.txt para entender o que cada parte faz.

Primeiramente, é necessário definir a versão do CMake que será utilizada. Na época de escrita deste guia, a versão 3.31.11 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.

CMakeLists.txt
## Version
cmake_minimum_required(VERSION 3.28...3.31)

Agora, vamos definir o nome do projeto e a linguagem utilizada, que é o C++. O CMake define a palavra-chave CXX para representar isso.

CMakeLists.txt
## Project
project(oriented_graph
LANGUAGES CXX
)

O C++ tem várias versões, e pode ser customizado para diferentes ambientes. No nosso caso, queremos usar uma versão mais recente e compatível com o padrão oficial.

No momento, a melhor opção é a versão 23. Para isso, usamos comandos iniciados em set para definir as variáveis de ambiente.

CMakeLists.txt
## Environment
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

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 alguns os warnings (ou avisos) de compilação. Vários desses avisos são definidos pelo compilador GCC, e respeitados pelo Clang.

Você pode encontrar uma lista dessas flags na documentação do GCC. Além disso, a documentação do Clang mostra quais flags estão implementadas no Clang.

Para definir avisos para esses dois compiladores, abrimos uma seção que foca no GCC, no Clang e no Apple Clang. Então, usamos o comando add_compile_options.

CMakeLists.txt
## Warnings
if (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
)
endif()

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.

CMakeLists.txt
## Directories
add_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; e
  • assets: 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.

No final, o arquivo CMakeLists.txt deve ficar da seguinte forma.

CMakeLists.txt
## Version
cmake_minimum_required(VERSION 3.28...3.31)
## Project
project(oriented_graph
LANGUAGES CXX
)
## Environment
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
## Warnings
if (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
)
endif()
## Directories
add_subdirectory(src)

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.

Fedora, WSL ou Mint
touch src/CMakeLists.txt

A estrutura do projeto ficará como a seguir.

  • Directorysrc
    • Directoryedge
      • edge.cpp
      • edge.hpp
    • Directorygraph
      • graph.cpp
      • graph.hpp
    • Directorynode
      • node.cpp
      • node.hpp
    • CMakeLists.txt
    • main.cpp
  • .clang-format
  • .clang-tidy
  • .gitignore
  • CMakeLists.txt

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.

src/CMakeLists.txt
## Executable
add_executable(oriented_graph
main.cpp
)

Agora, precisamos adicionar os módulos que estão dentro da pasta src. Para isso, usamos o comando add_subdirectory para cada um deles.

src/CMakeLists.txt
## Directories
add_subdirectory(node)
add_subdirectory(edge)
add_subdirectory(graph)

O arquivo principal, main.cpp, depende da classe Graph. Para que o CMake consiga incluir esse arquivo corretamente, precisamos vincular a pasta graph.

Isso é feito com o comando target_link_directories. Ele vai tornar o módulo graph disponível para o executável oriented_graph.

src/CMakeLists.txt
## Link libraries
target_link_libraries(oriented_graph
PUBLIC
graph
)

No final, o arquivo src/CMakeLists.txt deve ficar da seguinte forma.

src/CMakeLists.txt
## Executable
add_executable(oriented_graph
main.cpp
)
## Directories
add_subdirectory(node)
add_subdirectory(edge)
add_subdirectory(graph)
## Link libraries
target_link_libraries(oriented_graph
PUBLIC
graph
)

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.

Fedora, WSL ou Mint
touch src/node/CMakeLists.txt
touch src/edge/CMakeLists.txt
touch src/graph/CMakeLists.txt

A estrutura do projeto ficará como a seguir.

  • Directorysrc
    • Directoryedge
      • CMakeLists.txt
      • edge.cpp
      • edge.hpp
    • Directorygraph
      • CMakeLists.txt
      • graph.cpp
      • graph.hpp
    • Directorynode
      • CMakeLists.txt
      • node.cpp
      • node.hpp
    • CMakeLists.txt
    • main.cpp
  • .clang-format
  • .clang-tidy
  • .gitignore
  • CMakeLists.txt

Vamos tomar como exemplo o módulo edge. A lógica dos demais módulos é similar, e apresentamos o código completo de cada ao final da seção.

Precisamos definir o nome da biblioteca, que será edge. Para isso, acesse o arquivo src/edge/CMakeLists.txt, e inclua o comando do CMake add_library.

src/edge/CMakeLists.txt
## Library
add_library(edge)

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. Usemos 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.

src/edge/CMakeLists.txt
## Sources
target_sources(edge
PUBLIC
edge.hpp
PRIVATE
edge.cpp
)

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.

src/edge/CMakeLists.txt
## Define module
target_include_directories(edge
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)

O módulo edge depende da classe Node. Para que o CMake consiga incluir esse arquivo corretamente, precisamos vincular a pasta node.

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 pública à biblioteca edge.

src/edge/CMakeLists.txt
## Link libraries
target_link_libraries(edge
PRIVATE
node
)

No final, o arquivo src/node/CMakeLists.txt deve ficar da seguinte forma.

src/node/CMakeLists.txt
## Library
add_library(node)
## Sources
target_sources(node
PUBLIC
node.hpp
PRIVATE
node.cpp
)
## Define module
target_include_directories(node
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)

O arquivo src/edge/CMakeLists.txt deve ficar da seguinte forma.

src/edge/CMakeLists.txt
## Library
add_library(edge)
## Sources
target_sources(edge
PUBLIC
edge.hpp
PRIVATE
edge.cpp
)
## Define module
target_include_directories(edge
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
## Link libraries
target_link_libraries(edge
PUBLIC
node
)

O arquivo src/graph/CMakeLists.txt deve ficar da seguinte forma.

src/graph/CMakeLists.txt
## Library
add_library(graph)
## Sources
target_sources(graph
PUBLIC
graph.hpp
PRIVATE
graph.cpp
)
## Define module
target_include_directories(graph
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
## Link libraries
target_link_libraries(graph
PUBLIC
node
edge
)

Por fim, vamos fazer stage das modificações e realizar um novo commit.

Fedora, WSL ou Mint
git add .
git commit -m "Configure CMake"