cmake - 01 - Instalação e Build

cmake -  Instalação e Build

Index Chapter 01 02 03 04

Tip

Antes de começar tenha atenção em caixinhas como esta, porque elas sinalizam os comandos que são usados com mais frequência no cmake.

Instalação no Ubuntu

Baixe do site: https://cmake.org/download/.

Para instalar a versão 3.25 que é a versão corrente na da data 18/11/2022.

sudo apt purge cmake
wget https://github.com/Kitware/CMake/releases/download/v3.25.0/cmake-3.25.0.tar.gz
tar xzf cmake-3.25.0
cd cmake-3.25.0
sudo ./bootstrap
sudo make
sudo make install

Verifique a instalação com o comando version

cmake --version

Onde make version <maj.min.rev>.

Se estiver rodando linux e quiser instalar as ferramentas padrão para gerar o build execute:

sudo apt install build-essential

Docker

Caso prefira usar o docker eu disponibilizei um link com um toolchain que oferece a versão 3.2.0 para executar os exemplos deste tutorial.

docker run -it swidzinski/cmake:toolchain

CMakeLists.txt

É o arquivo de configuração do cmake e deve existir um arquivo desse na raíz do seu projeto ou no local onde você indicar com cmake -S na primeira etapa de configuração do build que será visto mais a frente cmake - Instalação e Build > 1. Configuração do Build

Formato mínimo de um arquivo CMakeLists.txt

Se seu cmake for a versão 3.2.2 você poderia usar

	cmake_minimum_required(VERSION 3.2.2)
	project(
	alo-mundo
	VERSION 1.0
	DESCRIPTION "Projeto de aprendizado"
	LANGUAGES CXX
)

Estes são os campos obrigatórios para configuração mínima do arquivo CMakeLists.txt. Uma informação importante é que os comandos no cmake são case insensitive porém os parâmetros são case sensitive. Portanto, o parâmetro logo abaixo VERSION precisará ser escrito em caixa-alta.

cmake_minimum_required(VERSION <número-da-versão>)

Será onde aplicaremos a primeira política de versão ao nosso projeto. Como o cmake tem mais de 20 anos, algumas mudanças de versão não garantem necessariamente retrocompatibilidade, por isso, este comando é obrigatório. No mínimo, aconselho usar 3.1, pois abaixo desta versão target_sources não estará disponível. É obrigatório existir esta definição em todos os arquivos CMakelists.txt.

project(<nome-do-projeto>)

Com este comando é possível versionar o projeto, atribuir um nome, uma descrição, além de selecionar a linguagem de programação utilizada (LANGUAGES CXX significa que o projeto irá ser compilado para C++). É recomendável que cada projeto use esta definição no arquivo CMakelists.txt.

A configuração linguagem de programação também poderia ser declarada com o comando:

target_compile_features(<target> <escopo> cxx_std_YY)

Onde YY será a versão C++. Por exemplo se quiser usar a versão 11 então seria cxx_std_11. A definição de <escopo> pode ser PRIVATE, PUBLIC ou INTERFACE. Leia mais a frente a declaração do comando target_sources para enteder o significado da declaração de escopo.

Outro comando extremamente importante

add_executable(<target>)

Este comando é responsável por criar o target executável. O nome do target será também o nome do binário executável. Esse comando cria um target que possibilita a criação de vínculos com bibliotecas dinâmicas ou estáticas como veremos mais a frente.

Uma dúvida comum é o que seria um target? Conceitualmente targets podem ser entendidos como unidades lógicas que se organizam a fim de de gerar um build. Esses targets poderiam, por exemplo, se transformar em executáveis ou bibliotecas.

Target, portanto, estará sempre ligado a essa ideia de artefato(s) que se (unem e se) transforma(m) em outro(s) artefato(s). Por exemplo, se o comando add_executable não fosse adicionado ao arquivo CMakeLists.txt, o target resultante do build seriam arquivos objetos compilados com extensão .o.

Arquivos fontes podem ser adicionados diretamente com o comando add_executable ou separadamente com o target_sources.

target_sources(<target> <escopo> <arquivos .cpp>)

Este comando vincula o código-fonte com o target. A definição de <escopo> pode ser PRIVATE, PUBLIC ou INTERFACE. Se for PRIVATE então o fonte será usado para construir este target e mais nenhum outro target dependente. Use PUBLIC quando o target for usado pelo projeto atual e por outro target que se vincule a ele. Em geral este campo será na maioria das vezes PRIVATE. Não confunda o conceito de private, public de classe com dependência de target. Além de PRIVATE, existem PUBLIC e INTERFACE.

Para evitar problemas, quando trabalhar com vários projetos ou projetos complexos, utilize sempre caminhos absolutos através das variáveis CMAKE_CURRENT_SOURCE_DIR ou CMAKE_CURRENT_LIST_DIR prefixado ao arquivo quando for acrescentar no target_sources. A diferença entre eles é que:

CMAKE_CURRENT_SOURCE_DIR usa o diretório onde esta localizado o arquivo CMakeLists.txt.

CMAKE_CURRENT_LIST_DIR utiliza o diretório onde o script esta sendo executado.

Apesar dos dois comandos geralmente produzirem um resultado semelhante, opte pelo primeiro. Exemplo:

target_sources(
	lib_escola
	PUBLIC
	{CMAKE_CURRENT_SOURCE_DIR}/monitor.cpp
)

Outra informação importante é que se a definição de <escopo> campo for omitida, o cmake consulta a variável BUILD_SHARED_LIBDS para determinar o tipo de lib que será gerado

A vantagem é que target_sources permite explicitamente definir o escopo do arquivo fonte (private, interface, etc..)

Ao utilizar o target_sources você terá a garantia de que qualquer um que utilize em seu código #include <nome-do-arquivo.hpp> irá funcionar. Embora com aspas duplas também deva funcionar.

target_compile_features(<target> <escopo> cxx_std_YY)

Com este comando é possível atribuir a versão C++ que será usada para compilar o projeto. Onde YY deverá ser substituido pela versão da linguagem C++11, 14, 17, 20. Interessante que você pode ter targets compilados com versões diferentes.

Se quiser uma lista de features disponíveis consulte a documentação oficial.

Para incluir um fonte no path do projeto use o comando target_include_directories ou include_directories.

target_include_directories(<target> <escopo> <diretório>) ou include_directories(<diretório>) )

Estes dois comandos permitem incluir um fonte no path. O comando include_directories, porém, tem escopo global enquanto o target_include_directories permite atribuir um target, além do escopo, portanto, dê preferência a este último. Se o projeto for uma lib, então mantenha o escopo como PUBLIC para que qualquer outro projeto que faça referência tenha a segurança de ter os arquivos no path durante a compilação

Se quiser ler sobre definição de escopo.

Etapas do build

Podemos dividir o processo em 3 etapas principais:

  1. Configuração do build
  2. Geração do build
  3. Compilação do projeto ou build

1. Configuração do Build

Frequentemente, a etapa 1 é consolidada com a etapa 2, caso tenha necessidade de realizar mais alguma configuração você pode configurar variáveis de cache usando o comando cmake -D ou os utilitários ccmake ou cmake-gui, vamos falar mais adiante.

2. Geração do Build

A sintaxe para geração do build é essa:

cmake -S <código-fonte> -B <buildtree>

Evite poluir o seu repositório com código gerado pelo build.

Caso o seu build esteja mesclado com seu código, o que não é considerado uma boa prática, considere adicionar, pelo menos, um .gitignore para que o os artefatos e binários gerados resultantes do builds não sejam acidentalmente comitados dentro do seu versionador, poluindo o código fonte.

Em contraste com outras ferramentas, cmake encoraja a produção de artefatos (out of the source building) separado do código.

cmake -S ./src -B ./output

Este comando irá gerar o sistema de build em output e e o código fonte será analisado a partir de ./src.

No local onde se localiza o arquivo de código-fonte, ou seja, no local onde você indicou -S deve conter obrigatoriamente um arquivo de configuração CMakeLists.txt, caso contrário, você receberá uma mensagem de erro informado que o arquivo não foi encontrado.

Generators

Generators são particularmente importantes no cmake pois especificam que tipo de build você quer atribuir para sua instalação.

Usuários do linux estão mais inclinados a escolherem Unix Makefiles ou Ninja, enquanto usuários do windows tem a tendência de escolherem build para a sua IDE favorita. Podemos ter uma lista dos generators suportados pelo cmake com executando o comando

cmake --help 

Embora não esteja nesta lista o visual studio é suportado nas versões mais recentes do cmake.

Uma consideração importante na hora de gerar o build é que por padrão a compilação gera código debug. Para compilar código de produção, release você precisará alterar a variável CMAKE_BUILD_TYPE. Isso pode ser incluído no próprio build com o parâmetro -D que permite manipular variáveis do cache,

cmake -S . -B build -D CMAKE_BUILD_TYPE=Release

Onde CMAKE_BUILD_TYPE pode assumir os seguintes valores:

Você pode até listá-las depois com o comando:

cmake -L[A][H] <path-do-bluid>

A saída deverá se parece com

Você também pode configurar níveis de log, combinando com a função da linguagem cmake message() para qualquer listfiles (arquivos com extensão cmake), para exibir mensagens para o usuário de acordo com o level definido pelo usuário.

message([<level>] "menssagem de texto" ...)
cmake --log-level=<level>

onde level pode ser:

Da mesma forma pode ser atribuído de forma permanente na variável de cache
CMAKE_MESSAGE_LOG_LEVEL

3. Compilando do projeto ou build

Você precisa indicar onde o build foi gerado

cmake --build <buildtree> [<options>] [-- <build-tool-options>]

Para paralelizar e acelerar o build você pode utilizar quaisquer uns desses parâmetros

cmake --build <dir> --parallel [<number-of-jobs>]
cmake --build <dir> -j [<number-of-jobs>]

Antes que pergunte, eles são equivalentes. Alternativamente também é possível atribuir a variável CMAKE_BUILD_PARALLEL_LEVEL

Nos próximos tutoriais veremos como adicionar dependências estáticas e dinâmicas e exemplos mais avançados.

Parte 2 - Interagindo com outros projetos e bibliotecas