
1. Introdução
Este artigo discute o uso do sistema de compilação CMake usado em um grande número de projetos C / C ++. É altamente recomendável que você leia a primeira parte do manual para evitar entender mal a sintaxe da linguagem CMake, que aparece explicitamente ao longo do artigo.
Lançamento do CMake
A seguir, exemplos de uso da linguagem CMake que você deve praticar. Experimente o código-fonte modificando os comandos existentes e adicionando novos. Para executar esses exemplos, instale o CMake no site oficial .
Princípio de funcionamento
O sistema de criação do CMake é um invólucro de outros utilitários dependentes da plataforma (por exemplo, Ninja ou Make ). Assim, no próprio processo de montagem, por mais paradoxal que isso possa parecer, ele não participa diretamente.
O sistema de compilação CMake aceita um arquivo CMakeLists.txt
com uma descrição das regras de compilação na linguagem formal do CMake e, em seguida, gera arquivos de compilação intermediários e nativos no mesmo diretório aceito em sua plataforma.
Os arquivos gerados conterão nomes específicos de utilitários, diretórios e compiladores do sistema, enquanto os comandos do CMake usam apenas o conceito abstrato do compilador e não estão vinculados a ferramentas dependentes da plataforma que diferem bastante em diferentes sistemas operacionais.
Verificando a versão do CMake
O comando cmake_minimum_required
verifica a versão em execução do CMake: se for menor que o mínimo especificado, o CMake será encerrado com um erro fatal. Um exemplo que demonstra o uso típico desse comando no início de qualquer arquivo CMake:
Conforme observado nos comentários, o comando cmake_minimum_required
define todos os sinalizadores de compatibilidade (consulte cmake_policy
). Alguns desenvolvedores intencionalmente definem uma versão baixa do CMake e ajustam a funcionalidade manualmente. Isso permite que você suporte simultaneamente as versões antigas do CMake e, em alguns lugares, aproveite os novos recursos.
No início de qualquer CMakeLists.txt
deve especificar as características do projeto com a equipe do projeto para melhor design com ambientes integrados e outras ferramentas de desenvolvimento.
É importante notar que, se a palavra-chave LANGUAGES
for omitida, os idiomas padrão serão C CXX
. Você também pode desativar a indicação de qualquer idioma, escrevendo a palavra-chave NONE
como uma lista de idiomas ou apenas deixando uma lista vazia.
Executando arquivos de script
O comando include
substitui a linha de sua chamada pelo código do arquivo especificado, agindo de forma semelhante ao comando include
pré include
processador C / C ++. Este exemplo executa o arquivo de script MyCMakeScript.cmake
comando descrito:
message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]")
Neste exemplo, a primeira mensagem notificará que a variável TEST_VARIABLE
não foi definida; no entanto, se o script MyCMakeScript.cmake
essa variável, a segunda mensagem já informará sobre o novo valor da variável de teste. Portanto, o arquivo de script incluído pelo comando include
não cria seu próprio escopo, mencionado nos comentários do artigo anterior .
Compilação de arquivos executáveis
O comando add_executable
compila o arquivo executável com o nome fornecido na lista de fontes. É importante observar que o nome final do arquivo depende da plataforma de destino (por exemplo, <ExecutableName>.exe
ou apenas <ExecutableName>
). Um exemplo típico de chamar este comando:
Compilação da biblioteca
O comando add_library
compila a biblioteca com a exibição e o nome especificados da fonte. É importante observar que o nome final da biblioteca depende da plataforma de destino (por exemplo, lib<LibraryName>.a
ou <LibraryName>.lib
). Um exemplo típico de chamar este comando:
- Bibliotecas estáticas são definidas pela palavra-chave
STATIC
como o segundo argumento e são arquivos de arquivos de objetos associados a arquivos executáveis e outras bibliotecas em tempo de compilação; - Bibliotecas dinâmicas são especificadas pela palavra-chave
SHARED
como o segundo argumento e são bibliotecas binárias carregadas pelo sistema operacional durante a execução do programa; - Bibliotecas modulares são definidas pela palavra-chave
MODULE
como o segundo argumento e são bibliotecas binárias carregadas usando a técnica de execução pelo próprio executável; - Bibliotecas de objetos são definidas pela palavra-chave
OBJECT
como o segundo argumento e são um conjunto de arquivos de objetos associados a arquivos executáveis e outras bibliotecas em tempo de compilação.
Adicionando fonte à meta
Há casos que exigem várias adições de arquivos de origem ao destino. Para isso, é target_sources
o comando target_sources
, que pode adicionar fontes ao destino várias vezes.
O primeiro argumento para o comando target_sources
é o nome do destino especificado anteriormente usando os add_executable
ou add_executable
, e os argumentos a seguir são a lista de arquivos de origem a serem adicionados.
As chamadas repetidas ao target_sources
adicionam os arquivos de origem ao destino na ordem em que foram chamados, para que os dois blocos de código inferiores sejam funcionalmente equivalentes:
Arquivos gerados
O local dos arquivos de saída gerados pelos add_library
e add_library
é determinado apenas no estágio de geração; no entanto, essa regra pode ser alterada com várias variáveis que determinam o local final dos arquivos binários:
Arquivos executáveis sempre são considerados objetivos de execução, bibliotecas estáticas são consideradas objetivos de arquivamento e bibliotecas modulares são consideradas objetivos de biblioteca. Para plataformas "não DLL", as bibliotecas dinâmicas são consideradas destinos de biblioteca e, para "plataformas DLL", objetivos de execução. Essas variáveis não são fornecidas para bibliotecas de objetos, pois esse tipo de bibliotecas é gerado nos intestinos do diretório CMakeFiles
.
É importante observar que todas as plataformas baseadas no Windows, incluindo Cygwin, são consideradas "plataformas DLL".
Layout da biblioteca
O comando target_link_libraries
biblioteca ou executável com outras bibliotecas fornecidas. O primeiro argumento para esse comando é o nome do destino gerado pelos add_library
ou add_library
, e os argumentos subsequentes são os nomes dos destinos da biblioteca ou caminhos completos para as bibliotecas. Um exemplo:
Vale a pena notar que as bibliotecas modulares não podem ser vinculadas a arquivos executáveis ou outras bibliotecas, pois elas destinam-se apenas ao carregamento por técnicas de execução.
Trabalhar com objetivos
Como mencionado nos comentários, os alvos no CMake também estão sujeitos a manipulação manual, embora muito limitada.
É possível controlar as propriedades dos destinos projetados para definir o processo de montagem do projeto. O comando get_target_property
valor da propriedade de destino para a variável fornecida. Este exemplo exibe o valor da propriedade C_STANDARD
do destino C_STANDARD
na tela:
O comando set_target_properties
define as propriedades de destino especificadas para os valores especificados. Este comando aceita uma lista de objetivos para os quais os valores da propriedade serão definidos e, em seguida, a palavra-chave PROPERTIES
, seguida por uma lista do formulário < > < >
:
O exemplo acima define as propriedades dos destinos MyTarget
que afetam o processo de compilação, a saber: ao compilar o destino MyTarget
CMake MyTarget
compilador use o padrão C11. Todos os nomes conhecidos de propriedades de destino estão listados nesta página .
Também é possível verificar destinos definidos anteriormente usando a construção if(TARGET <TargetName>)
:
Adicionando subprojetos
O comando add_subdirectory
solicita que o CMake processe imediatamente o arquivo de subprojeto especificado. O exemplo abaixo demonstra a aplicação do mecanismo descrito:
Neste exemplo, o primeiro argumento para o comando add_subdirectory
é o subprojeto add_subdirectory
, e o segundo argumento é opcional e informa o CMake sobre a pasta destinada aos arquivos gerados do subprojeto incluído (por exemplo, CMakeCache.txt
e cmake_install.cmake
).
Vale ressaltar que todas as variáveis do escopo pai são herdadas pelo diretório adicionado e todas as variáveis definidas e redefinidas nesse diretório estarão visíveis apenas para ele (se a palavra-chave PARENT_SCOPE
não PARENT_SCOPE
especificada pelo argumento do comando set
). Esse recurso foi mencionado nos comentários do artigo anterior .
Pesquisa de Pacotes
O comando find_package
localiza e carrega as configurações de um projeto externo. Na maioria dos casos, é usado para links subsequentes de bibliotecas externas, como Boost e GSL . Este exemplo chama o comando descrito para procurar a biblioteca GSL e depois vincular:
No exemplo acima, o comando find_package
aceita o nome do pacote como find_package
primeiro argumento e, em seguida, a versão necessária. A opção REQUIRED
requer a impressão de um erro fatal e o encerramento do CMake se o pacote necessário não for encontrado. O oposto é a opção QUIET
, exigindo que o CMake continue seu trabalho, mesmo que o pacote não tenha sido encontrado.
Em seguida, o MyExecutable
vinculado à biblioteca GSL com o comando target_link_libraries
usando a variável GSL::gsl
, que encapsula o local da GSL já compilada.
No final, o comando target_include_directories
é target_include_directories
, informando o compilador sobre a localização dos arquivos de cabeçalho da biblioteca GSL. Observe que a variável GSL_INCLUDE_DIRS
é usada para GSL_INCLUDE_DIRS
local dos cabeçalhos que descrevi (este é um exemplo de configurações de pacotes importados).
Você provavelmente deseja verificar o resultado de uma pesquisa de pacote se especificou a opção QUIET
. Isso pode ser feito verificando a <PackageName>_FOUND
, que é determinada automaticamente após a find_package
comando find_package
. Por exemplo, se você importar com êxito as configurações GSL para o seu projeto, a variável GSL_FOUND
se tornará verdadeira.
Em geral, o comando find_package
possui dois tipos de inicialização: modular e configuração. O exemplo acima aplicou um formulário modular. Isso significa que, quando o comando é chamado, o CMake procura por um arquivo de script no formato Find<PackageName>.cmake
no diretório CMAKE_MODULE_PATH
e o inicia e importa todas as configurações necessárias (nesse caso, o CMake lançou o arquivo FindGSL.cmake
padrão).
Maneiras de incluir cabeçalhos
Você pode informar o compilador sobre a localização dos cabeçalhos incluídos usando dois comandos: include_directories
e target_include_directories
. Você decide qual usar, no entanto, vale a pena considerar algumas diferenças entre eles (a idéia é sugerida nos comentários ).
O comando include_directories
afeta o escopo do diretório. Isso significa que todos os diretórios de cabeçalho especificados por este comando serão usados para todos os propósitos do CMakeLists.txt
atual, bem como para subprojetos processados (consulte add_subdirectory
).
O comando target_include_directories
afeta target_include_directories
o destino especificado pelo primeiro argumento e não afeta outros destinos. O exemplo abaixo demonstra a diferença entre os dois comandos:
add_executable(RequestGenerator RequestGenerator.c) add_executable(ResponseGenerator ResponseGenerator.c)
Nos comentários, é mencionado que em projetos modernos o uso dos link_libraries
include_directories
e link_libraries
é indesejável. Uma alternativa são os target_link_libraries
e target_link_libraries
que atuam apenas em objetivos específicos, e não em todo o escopo atual.
Instalação do Projeto
O comando install
gera regras de instalação para o seu projeto. Este comando é capaz de trabalhar com objetivos, arquivos, pastas e muito mais. Primeiro, considere estabelecer metas.
Para definir metas, você deve passar a palavra-chave TARGETS
como o primeiro argumento da função descrita, seguida de uma lista das metas a serem definidas e, em seguida, a palavra-chave DESTINATION
com o local do diretório em que as metas especificadas serão definidas. Este exemplo demonstra uma configuração de meta típica:
O processo para descrever a instalação dos arquivos é semelhante, exceto que TARGETS
deve especificar FILES
vez da palavra-chave TARGETS
. Um exemplo demonstrando a instalação de arquivos:
O processo para descrever a instalação de pastas é semelhante, exceto que você deve especificar DIRECTORY
vez da palavra-chave FILES
. É importante observar que durante a instalação, todo o conteúdo da pasta será copiado, e não apenas o nome. Um exemplo de instalação de pastas é o seguinte:
Após concluir o processamento do CMake de todos os seus arquivos, você pode instalar todos os objetos descritos com o sudo checkinstall
(se o CMake gerar um Makefile
) ou executar esta ação no ambiente de desenvolvimento integrado que suporta o CMake.
Exemplo visual do projeto
Este guia não seria completo sem demonstrar um exemplo do mundo real do uso do sistema de criação do CMake. Considere um diagrama de projeto simples usando o CMake como o único sistema de compilação:
+ MyProject - CMakeLists.txt - Defines.h - StartProgram.c + core - CMakeLists.txt - Core.h - ProcessInvoker.c - SystemManager.c
O arquivo de montagem principal CMakeLists.txt
descreve a compilação de todo o programa: primeiro, o comando add_executable
é add_executable
que compila o arquivo executável, depois o comando add_subdirectory
é add_subdirectory
, o que estimula o processamento do subprojeto e, finalmente, o arquivo executável é vinculado à biblioteca compilada:
O arquivo core/CMakeLists.txt
é chamado pelo arquivo assembly principal e compila a biblioteca estática MyProgramCore
destinada à vinculação com o arquivo executável:
Após uma série de comandos cmake . && make && sudo checkinstall
cmake . && make && sudo checkinstall
sistema de compilação CMake é concluído com êxito. O primeiro comando começa a processar o arquivo CMakeLists.txt
no diretório raiz do projeto, o segundo comando finalmente compila os arquivos binários necessários e o terceiro comando instala o MyProgram
compilado no sistema.
Conclusão
Agora você pode escrever seus próprios e entender os arquivos do CMake de outras pessoas e pode ler em detalhes sobre outros mecanismos no site oficial .
O próximo artigo deste guia se concentrará em testar e criar pacotes usando o CMake e será lançado em uma semana. Até breve!