Durante o desenvolvimento, gosto de alterar compiladores, construir modos, versões de dependências, realizar análises estáticas, medir desempenho, coletar cobertura, gerar documentação, etc. E eu adoro o CMake porque me permite fazer tudo o que quero.
Muitas pessoas criticam o CMake, e muitas vezes com razão, mas se você olhar bem, nem tudo está tão ruim, e recentemente nada mal, e a direção do desenvolvimento é bastante positiva.
Nesta nota, quero dizer como simplesmente organizar uma biblioteca de cabeçalho em C++ no sistema CMake para obter a seguinte funcionalidade:
Conjunto;
Testes de execução automática;
Medição de cobertura de código;
Instalação;
Autodocumentação;
Geração de sandbox online;
Análise estática.
Quem já entende as vantagens e o C-make pode simplesmente baixar modelo de projeto e comece a usá-lo.
Falaremos principalmente sobre como organizar scripts CMake, portanto eles serão discutidos em detalhes. Qualquer pessoa pode visualizar o restante dos arquivos diretamente na página do projeto modelo.
Primeiro de tudo, você precisa solicitar a versão necessária do sistema CMake. O CMake está evoluindo, as assinaturas de comandos e o comportamento em diferentes condições estão mudando. Para que o CMake entenda imediatamente o que queremos dele, precisamos registrar imediatamente nossos requisitos.
cmake_minimum_required(VERSION 3.13)
A seguir designaremos o nosso projeto, seu nome, versão, idiomas utilizados, etc. команду project).
Neste caso indicamos o idioma CXX (e isso significa C++) para que o CMake não se esforce e procure um compilador de linguagem C (por padrão, o CMake inclui duas linguagens: C e C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Aqui você pode verificar imediatamente se nosso projeto está incluído em outro projeto como um subprojeto. Isso ajudará muito no futuro.
A primeira opção é MYLIB_TESTING — para desabilitar testes unitários. Isso pode ser necessário se tivermos certeza de que está tudo em ordem com os testes, mas quisermos apenas, por exemplo, instalar ou empacotar nosso projeto. Ou nosso projeto está incluído como um subprojeto - neste caso, o usuário do nosso projeto não tem interesse em executar nossos testes. Você não testa as dependências que usa, não é?
Além disso, faremos uma opção separada MYLIB_COVERAGE para medir a cobertura de código por meio de testes, mas exigirá ferramentas adicionais e, portanto, precisará ser habilitado explicitamente.
Nossa biblioteca consiste apenas em arquivos de cabeçalho, o que significa que não temos nenhum esgotamento na forma de bibliotecas estáticas ou dinâmicas. Por outro lado, para utilizar externamente nossa biblioteca, ela precisa estar instalada, deve ser detectável no sistema e conectada ao seu projeto, e ao mesmo tempo esses mesmos cabeçalhos, bem como possivelmente alguns adicionais, estão anexados a ele propriedades.
Para isso, criamos uma biblioteca de interfaces.
add_library(mylib INTERFACE)
Vinculamos cabeçalhos à nossa biblioteca de interface.
O uso moderno, moderno e jovem do CMake implica que cabeçalhos, propriedades, etc. transmitido através de um único alvo. Então basta dizer target_link_libraries(target PRIVATE dependency)e todos os cabeçalhos associados ao destino dependency, estará disponível para fontes pertencentes ao destino target. E você não precisa de nenhum [target_]include_directories. Isso será demonstrado abaixo na análise Script CMake para testes unitários.
Este comando associa os cabeçalhos que precisamos à nossa biblioteca de interface e, se nossa biblioteca estiver conectada a qualquer destino dentro da mesma hierarquia do CMake, os cabeçalhos do diretório serão associados a ela ${CMAKE_CURRENT_SOURCE_DIR}/include, e se nossa biblioteca estiver instalada no sistema e conectada a outro projeto usando o comando find_package, então os cabeçalhos do diretório serão associados a ele include relativo ao diretório de instalação.
Vamos definir um padrão de linguagem. Claro, o último. Ao mesmo tempo, não apenas incluímos o padrão, mas também o estendemos a quem utilizará nossa biblioteca. Isso é conseguido devido ao fato da propriedade definida possuir uma categoria INTERFACE (Veja. comando target_compile_features).
Vamos criar um alias para nossa biblioteca. Além disso, por beleza, estará em um “namespace” especial. Isso será útil quando diferentes módulos aparecerem em nossa biblioteca e formos conectá-los independentemente uns dos outros. Como em Busta, por exemplo.
Instalando nossos cabeçalhos no sistema. Tudo é simples aqui. Dizemos que a pasta com todos os cabeçalhos deve ir para o diretório include em relação ao local de instalação.
A seguir, informamos ao sistema de compilação que queremos poder chamar o comando em projetos de terceiros find_package(Mylib) e conseguir um gol Mylib::mylib.
O próximo feitiço deve ser entendido desta forma. Quando em um projeto de terceiros chamamos o comando find_package(Mylib 1.2.3 REQUIRED), e a versão real da biblioteca instalada será incompatível com a versão 1.2.3CMake irá gerar automaticamente um erro. Ou seja, você não precisará rastrear versões manualmente.
Se os testes forem desabilitados explicitamente usando opção correspondente ou nosso projeto é um subprojeto, ou seja, está conectado a outro projeto CMake através do comando add_subdirectory, não avançamos na hierarquia e o script que descreve os comandos para gerar e executar testes simplesmente não é executado.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Conectamos dependências. Observe que vinculamos apenas os alvos do CMake necessários ao nosso binário e não chamamos o comando target_include_directories. Títulos da estrutura de teste e da nossa Mylib::mylib, bem como parâmetros de construção (no nosso caso, este é o padrão da linguagem C++) vieram junto com esses objetivos.
Por fim, criamos um alvo fictício, cuja “construção” equivale à execução de testes, e adicionamos esse alvo ao build padrão (o atributo é responsável por isso ALL). Isso significa que a compilação padrão aciona a execução dos testes, o que significa que nunca esqueceremos de executá-los.
add_custom_target(check ALL COMMAND mylib-unit-tests)
A seguir, habilitamos a medição de cobertura de código se a opção apropriada for especificada. Não vou entrar em detalhes, pois se referem mais a uma ferramenta de medição de cobertura do que ao CMake. É importante apenas ressaltar que com base nos resultados será criada uma meta coverage, com o qual é conveniente começar a medir a cobertura.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Измерение покрытия кода тестами включено")
target_compile_options(mylib-unit-tests PRIVATE --coverage)
target_link_libraries(mylib-unit-tests PRIVATE gcov)
add_custom_target(coverage
COMMAND
${GCOVR_EXECUTABLE}
--root=${PROJECT_SOURCE_DIR}/include/
--object-directory=${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
check
)
elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE)
set(MYLIB_COVERAGE OFF)
message(WARNING "Для замеров покрытия кода тестами требуется программа gcovr")
endif()
A seguir, verificamos se o usuário configurou a variável de idioma. Se sim, então não tocamos nele; se não, então usamos o russo. Em seguida, configuramos os arquivos do sistema Doxygen. Todas as variáveis necessárias, incluindo o idioma, vão para lá durante o processo de configuração (ver. команду configure_file).
Então criamos uma meta doc, que começará a gerar documentação. Como a geração de documentação não é a maior necessidade no processo de desenvolvimento, o alvo não será habilitado por padrão; ele deverá ser lançado explicitamente.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Russian)
endif()
message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}")
configure_file(Doxyfile.in Doxyfile)
add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()
Aqui encontramos o terceiro Python e criamos um alvo wandbox, que gera uma solicitação correspondente à API do serviço caixa de varinha, e o manda embora. A resposta vem com um link para o sandbox finalizado.
find_program(PYTHON3_EXECUTABLE python3)
if(PYTHON3_EXECUTABLE)
set(WANDBOX_URL "https://wandbox.org/api/compile.json")
add_custom_target(wandbox
COMMAND
${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include |
curl -H "Content-type: application/json" -d @- ${WANDBOX_URL}
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS
mylib-unit-tests
)
else()
message(WARNING "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()
Fornece a capacidade de desativar a compilação e o destino do teste de unidade check. Como resultado, a medição da cobertura de código por meio de testes é desativada (consulte. MYLIB_COVERAGE).
O teste também será desabilitado automaticamente se o projeto estiver conectado a outro projeto como um subprojeto usando o comando add_subdirectory.
Na verdade, o CMake versão 3.13 só é necessário para executar alguns dos comandos do console descritos nesta ajuda. Do ponto de vista da sintaxe dos scripts CMake, a versão 3.8 é suficiente se a geração for chamada de outras formas.
Depois disso, a análise estática será iniciada automaticamente sempre que o código-fonte for compilado e recompilado. Não há necessidade de fazer nada adicional.
Clam
Com a ajuda de uma ferramenta maravilhosa scan-build Você também pode executar análises estáticas rapidamente:
CMake é um sistema muito poderoso e flexível que permite implementar funcionalidades para todos os gostos e cores. E, embora a sintaxe às vezes deixe muito a desejar, o diabo ainda não é tão terrível quanto é pintado. Use o sistema de construção CMake para o benefício da sociedade e da saúde.