A02 - Síntaxe CMake

Questions

  • Como podemos alcançar mais controle sobre o sistema de construção gerado por cmake?

  • É possível deixar o usuário decidir o que gerar?

Objectives

  • Aprenda a definir variáveis com set e usá-los com o operador ${} para referenciar variáveis.

  • Aprenda a sintaxe para condicionais em cmake: if - elseif - else - endif

  • Aprenda a sintaxe para loops em cmake: foreach

  • Aprenda como as estruturas CMAKE constroem artefatos.

  • Aprenda a imprimir mensagens úteis.

  • Aprenda a lidar com as opções voltadas para o usuário: option e o papel do cache do CMAKE.

CMAKE Oferece um idioma específico de domínio (do ingles, domain-specific language, DSL) para descrever como gerar um sistema de compilação nativo de uma plataforma específica que você pode deseja executar. Neste tópico, vamos nos familiarizar com sua sintaxe.

The CMake DSL

Lembre-se de que o DSL é insensível a maiúsculas e minúsculas. Agora vamos dar uma olhada nos seus principais elementos.

Variáveis.

Há variáveis definidas pelo cmake ou pelo usuário. Pode-se obter a lista de variáveis definidas pelo CMAKE com:

$ cmake --help-variable-list

Você pode criar uma nova variável com o comando set:

Variáveis no Cmake são sempre de tipo string, mas certos comandos podem interpretá-los como outros tipos. Se você quiser declarar uma variável list, você terá que fornecer-lhe como uma string separada por ponto-e-virgula. Listas podem ser manipuladas com a comando list.

Você pode inspecionar o valor de qualquer variável por desreferenciação com o operador ${}, assim como no bash. Por exemplo, o trecho a seguir define o conteúdo da variável hello e, em seguida, imprime seu conteúdo:

set(hello "world")
message("hello ${hello}")

Duas coisas para observar:

  • Se a variável dentro do operator $ {}’ não estiver definida, você receberá uma string vazia.

  • Você pode referenciar variáveis aninhadas: $ {Outer_${inner_variable} _variable} Elas serão avaliadas de dentro para fora.

Um dos aspectos mais confusos no CMAKE é a escopo das variáveis. Existem três escopos de variáveis:

  • Função. Escopo quando uma variável é definida (comando set) em uma função:

    a variável será visível dentro da função, mas não fora.

  • Diretório. Escopo ao processar um CMakeLists.txt em um diretório: Variáveis na pasta pai estarão disponíveis, mas qualquer um que é definida na pasta atual não será propagada para o pai.

  • Cache. Essas variáveis são persistentes através de chamadas para cmake e disponível para todos os escopos no projeto. Modificar uma variável de cache requer o uso de uma forma especial da função set:

Aqui está uma lista de algumas variáveis definidas pela CMAKE:

Para obter ajuda sobre uma variável CMAKE faça:

$ cmake --help-variable PROJECT_BINARY_DIR

Comandos

São blocos essenciais do CMAKE, pois permitem manipular variáveis. Eles incluem construções de fluxo de controle e familas de comando target_* . Você pode encontrar uma lista completa de comandos disponíveis com:

$ cmake --help-command-list

Funções e macros são construídos no topo dos comandos e são definidos pelo usuário ou pré-definidos pelo cmake Estes são úteis para evitar a scripts CMAke repetitivos.

A diferença entre uma função e uma macro é seu escopo:

  1. Funções têm seu próprio escopo: variáveis definidas dentro de uma função não são propagadas de volta ao chamador.

  2. Macros não têm seu próprio escopo: as variáveis do escopo pai podem ser modificadas e novas variáveis no escopo pai podem ser definidas.

Obter ajuda em um comando específico, função ou macro pode ser obtido com:

$ cmake --help-command target_link_libraries

Módulos

São coleções de funções e macros definidos pelo usuário ou pelo cmake. Cmake vem com um rico ecossistema de módulos e você provavelmente escreverá alguns para encapular funções ou macros freqüentemente usados em seus scripts cmake.

Para usar um módulo, voce deverá usar o comando include. Veja o exemplo abaixo:

include(CMakePrintHelpers)

A lista completa de módulos internos está disponível com:

$ cmake --help-module-list

Ajuda em um módulo integrado específico pode ser obtido com:

$ cmake --help-module CMakePrintHelpers

A árvore de construção

É intuitivo navegar na pasta Build do projeto:

$ tree -L 2 build

build
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.18.4
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── compute-areas.dir
│   ├── geometry.dir
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── compute-areas
├── libgeometry.a
└── Makefile

Note que:

  • O projeto foi configurado com o gerador makefile.

  • A cache é um arquivo texto CMakeCache.txt.

  • Para cada alvo (target)no projeto, CMake criará uma subpasta <target>.dir em CMakeFiles. Os arquivos de objeto intermediários são armazenado nessas pastas, juntamente com flags do compilador e linha de links.

  • Os artefatos de construção, compute-areas e libgeometry.a, são armazenados na raiz da árvore de construção (build).

Controle de fluxo

Os comandos if e foreach estão disponíveis como construções de controle de fluxo no Cmake e você certamente está familiarizado com seu uso em outras linguagens de programação

Desde que todas as variáveis no cmake são strings, a sintaxe para if e | foreach | aparecem em algumas variantes.

O valor da verdade das condições no blocos if e elseif é determinado por operadores booleanos. No CMAKE:

  • Verdadeiro é qualquer expressão avaliado para: 1, ON, TRUE, YES, e Y.

  • Falso é qualquer expressão avaliado para: 0, OFF, FALSE, NO, N, IGNORE, e NOTFOUND.

Cmake oferece operador booleano para comparações de string, tal como STREQUAL por igualdade de strings, e para comparações de versão, como VERSION_EQUAL.

Expansões de variáveis em condicionais

O comando if expande o conteúdo das variáveis antes de avaliar seu valor de verdade. Veja a documentação oficial para mais detalhes.

Exercício 2: Condicionais no Cmake

Modique o CMakeLists.txt``do exercicio anterior para contruir uma biblioteca *statica* ou *compartilhada*  dependendo do valor do booleano ``MAKE_SHARED_LIBRARY:

  1. Define a variável MAKE_SHARED_LIBRARY.

  2. Escreva uma verificação condicional da variável. Dependendo da condição, chame adequadamente a funçao add_library .

Você pode encontrar um projeto de base em content/code/day-1/02_conditionals-cxx. Uma solução está na subpasta solution.

Você pode executar a mesma operação em uma coleção de itens com foreach:

A lista de itens pode ser sepradados por espaço ou ponto-e-virgula. break() e continue() também estão disponíveis.

Mensagens de impressão

Você provavelmente terá que depurar seus scripts CMAKE em algum momento. Acreditamos que a depuração de impressão é a maneira mais eficaz de fazê-lo e para isso podemos usar o coamnndo message:

O comando message pode ser um pouco estranho para se trabalhar, especialmente quando você deseja imprimir o nome e valor de uma variável. Incluindo o módulo embutido CMakePrintHelpers vai facilitar sua vida ao depurar, já que fornece o função cmake_print_variables:

Este comando aceita um número arbitrário de variáveis e imprime seu nome e valor para a saída padrão.

Por exemplo:

include(CMakePrintHelpers)

cmake_print_variables(CMAKE_C_COMPILER CMAKE_MAJOR_VERSION DOES_NOT_EXIST)

dá:

-- CMAKE_C_COMPILER="/usr/bin/gcc" ; CMAKE_MAJOR_VERSION="2" ; DOES_NOT_EXIST=""

Controlando a construção com opções

Nós mencionamos anteriormente que o -D empregado ma interface da linha de comando (CLI) do comando cmake``pode ser usado para passar opções. Entretanto, como definimos estas opções no nosso ``cmakelists.txt.

É aí que o comando option entra em jogo!

Importando o modulo CMakeDependentOption,você pode lidar com casos onde as opções são relevantes apenas se outras opções já estão definidas para valores específicos:

Exercício 4: Opções voltadas para o usuário

Neste exercício, vamos trabalhar com option e cmake_dependent_option. Queremos permitir que o usuário decida se deve construir uma biblioteca e se deve ser estática ou compartilhada.

  1. Adicione a opção USE_LIBRARY.

  2. Adicione opções dependentes MAKE_STATIC_LIBRARY e MAKE_SHARED_LIBRARY. Eles só serão apresentados se USE_LIBRARY for verdadeiro.

  3. Use condicionais para orquestrar a construção da biblioteca estática/compartilhada.

Você pode encontrar um projeto base na pasta content/code/day-1/04_options-cxx. Uma solução está na subpasta solution.

Keypoints

  • Cmake oferece um DSL completo que capacita a escrita de CMakeLists.txt complexos.

  • Variáveis têm regras de escopo.

  • A estrutura do projeto é espelhada na pasta Build.