
Durante o processo de desenvolvimento, gosto de alterar compiladores, modos de compilação, versões de dependência, executar análises estáticas, medir desempenho, coletar cobertura, gerar documentação etc. E eu realmente amo o CMake, porque me permite fazer tudo o que quero.
Muitos repreendem o CMake, e geralmente com justiça, mas se você olhar, nem tudo é tão ruim, e ultimamente é muito bom , e a direção do desenvolvimento é bastante positiva.
Neste artigo, quero dizer como é simples organizar uma biblioteca de cabeçalho C ++ no sistema CMake para obter a seguinte funcionalidade:
- Montagem;
- Testes de inicialização automática;
- Medição da cobertura do código;
- Instalação;
- Documentação automática
- Geração de sandbox online;
- Análise estática
Quem já entende os prós e os fabricantes pode fazer o download do modelo do projeto e começar a usá-lo.
Conteúdo
- Projeto de dentro para fora
- Estrutura do projeto
- Arquivo principal do CMake (./CMakeLists.txt)
- Informações do Projeto
- Opções do Projeto
- Opções de compilação
- Objetivo principal
- Instalação
- Testes
- A documentação
- Sandbox online
- Script para testes (test / CMakeLists.txt)
- Teste
- Cobertura
- Script para documentação (doc / CMakeLists.txt)
- Script para uma sandbox online (online / CMakeLists.txt)
- Projeto fora
- Assembléia
- Geração
- Assembléia
- Opções
- MYLIB_COVERAGE
- MYLIB_TESTING
- MYLIB_DOXYGEN_LANGUAGE
- Objetivos da assembléia
- Por padrão
- mylib-unit-tests
- verificar
- cobertura
- doc
- caixa de varinha
- Exemplos
- As ferramentas
- Análise estática
- Posfácio
. ├── CMakeLists.txt ├── README.en.md ├── README.md ├── doc │ ├── CMakeLists.txt │ └── Doxyfile.in ├── include │ └── mylib │ └── myfeature.hpp ├── online │ ├── CMakeLists.txt │ ├── mylib-example.cpp │ └── wandbox.py └── test ├── CMakeLists.txt ├── mylib │ └── myfeature.cpp └── test_main.cpp
Falaremos principalmente sobre como organizar os scripts do CMake, para que sejam analisados em detalhes. Todos podem ver o restante dos arquivos diretamente na página do modelo do projeto .
Primeiro de tudo, você precisa solicitar a versão correta do sistema CMake. O CMake está evoluindo, as assinaturas da equipe, o comportamento em diferentes condições estão mudando. Para que o CMake entenda imediatamente o que queremos dele, precisamos corrigir imediatamente nossos requisitos para ele.
cmake_minimum_required(VERSION 3.13)
Em seguida, designamos nosso projeto, seu nome, versão, idiomas usados etc. (consulte project
).
Nesse caso, especificamos a linguagem CXX
(que significa C ++) para que o CMake não force e não procure o compilador da linguagem C (por padrão, dois idiomas estão incluídos no CMake: 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.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Nós fornecemos duas opções.
A primeira opção - MYLIB_TESTING
- para desativar os testes de unidade. Isso pode ser necessário se tivermos certeza de que tudo está em ordem com os testes e queremos, por exemplo, apenas instalar ou empacotar nosso projeto. Ou nosso projeto é incluído como um subprojeto - nesse caso, o usuário do nosso projeto não está interessado em executar nossos testes. Você não testa as dependências que você usa?
option(MYLIB_TESTING " " ON)
Além disso, faremos uma opção separada MYLIB_COVERAGE
para medir a cobertura do código com testes, mas serão necessárias ferramentas adicionais, portanto você precisará ativá-lo explicitamente.
option(MYLIB_COVERAGE " " OFF)
É claro que somos programadores legais e esperamos o nível máximo de diagnóstico de tempo de compilação do compilador. Nem um único mouse passará.
add_compile_options( -Werror -Wall -Wextra -Wpedantic -Wcast-align -Wcast-qual -Wconversion -Wctor-dtor-privacy -Wenum-compare -Wfloat-equal -Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wsign-conversion -Wsign-promo )
Desabilitaremos também as extensões para cumprir totalmente com o padrão da linguagem C ++. Por padrão, eles estão incluídos no CMake.
if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif()
Nossa biblioteca consiste apenas em arquivos de cabeçalho, o que significa que não temos exaustão na forma de bibliotecas estáticas ou dinâmicas. Por outro lado, para usar nossa biblioteca de fora, é necessário instalá-la, ser capaz de encontrá-la no sistema e conectá-la ao seu projeto e, ao mesmo tempo, esses mesmos cabeçalhos, bem como, possivelmente, alguns adicionais propriedades.
Para esse fim, criamos uma biblioteca de interfaces.
add_library(mylib INTERFACE)
Vincule os cabeçalhos à nossa biblioteca front-end.
O uso moderno, elegante e jovem do CMake implica que cabeçalhos, propriedades etc. transmitido através de uma única finalidade. Portanto, basta dizer target_link_libraries(target PRIVATE dependency)
e todos os cabeçalhos associados ao destino de dependency
estarão disponíveis para fontes pertencentes ao target
. E nenhum [target_]include_directories
é necessário. Isso será demonstrado abaixo ao analisar o script CMake para testes de unidade .
Também vale a pena prestar atenção aos chamados -: $<...>
.
Este comando associa os cabeçalhos necessários à nossa biblioteca front-end e, se a nossa biblioteca estiver conectada a um destino na mesma hierarquia do CMake, os cabeçalhos do diretório ${CMAKE_CURRENT_SOURCE_DIR}/include
serão associados a ele e, se o nosso Como a biblioteca está instalada no sistema e conectada a outro projeto usando o find_package
, os cabeçalhos do diretório de include
relativos ao diretório de instalação serão associados a ele.
target_include_directories(mylib INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
Defina o padrão do idioma. Claro, o último. Ao mesmo tempo, não apenas incluímos o padrão, mas também o distribuímos para aqueles que usarão nossa biblioteca. Isso é alcançado devido ao fato de a propriedade set ter a categoria INTERFACE
(consulte o comando target_compile_features ).
target_compile_features(mylib INTERFACE cxx_std_17)
Criamos um alias para nossa biblioteca. Além disso, para a beleza, ele estará em um "espaço para nome" especial. Isso será útil quando diferentes módulos aparecerem em nossa biblioteca e vamos conectá-los independentemente um do outro. Como no Boost, por exemplo .
add_library(Mylib::mylib ALIAS mylib)
Instalando nossos cabeçalhos no sistema. Tudo é simples aqui. Dizemos que a pasta com todos os cabeçalhos deve estar no diretório de include
relativo ao local da instalação.
install(DIRECTORY include/mylib DESTINATION include)
Em seguida, informamos o sistema de compilação que queremos chamar find_package(Mylib)
em projetos de terceiros e obtemos o Mylib::mylib
destino.
install(TARGETS mylib EXPORT MylibConfig) install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)
O próximo feitiço deve ser entendido da seguinte forma. Quando chamamos find_package(Mylib 1.2.3 REQUIRED)
em um projeto de terceiros e, nesse caso, a versão real da biblioteca instalada será incompatível com a versão 1.2.3
, o CMake gerará automaticamente um erro. Ou seja, você não precisará seguir as versões manualmente.
include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)
Se os testes forem desativados explicitamente usando a opção apropriada ou se o nosso projeto for um subprojeto, ou seja, conectado a outro projeto do CMake usando o add_subdirectory
, não iremos mais além na hierarquia, e o script que descreve os comandos para gerar e executar os testes simplesmente não será iniciado .
if(NOT MYLIB_TESTING) message(STATUS " Mylib ") elseif(IS_SUBPROJECT) message(STATUS "Mylib ") else() add_subdirectory(test) endif()
A documentação também não será gerada no caso de um subprojeto.
if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif()
Da mesma forma, o subprojeto também não terá caixas de proteção online.
if(NOT IS_SUBPROJECT) add_subdirectory(online) endif()
Primeiro de tudo, encontramos o pacote com a estrutura de teste desejada (substitua-a pela sua favorita).
find_package(doctest 2.3.3 REQUIRED)
Criamos nosso arquivo executável com testes. Normalmente, adiciono apenas o arquivo no qual a função main
estará diretamente no binário executável.
add_executable(mylib-unit-tests test_main.cpp)
E os arquivos que descrevem os próprios testes são adicionados mais tarde. Mas isso não é necessário.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)
Conectamos dependências. Observe que target_include_directories
apenas target_include_directories
destinos target_include_directories
CMake que precisávamos ao nosso binário e não chamamos o comando target_include_directories
. Os cabeçalhos da estrutura de teste e do nosso Mylib::mylib
, bem como os parâmetros de construção (no nosso caso, esse é o padrão da linguagem C ++) rastrearam junto com esses objetivos.
target_link_libraries(mylib-unit-tests PRIVATE Mylib::mylib doctest::doctest )
Por fim, crie um destino fictício, cujo "assembly" seja equivalente à execução de testes e inclua esse destino no assembly padrão (o atributo ALL
é responsável por isso). Isso significa que a montagem, por padrão, inicia o lançamento dos testes, ou seja, nunca esqueceremos de executá-los.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Em seguida, habilitamos a medição da cobertura do código, se a opção correspondente for especificada. Não vou entrar em detalhes, porque eles se relacionam mais com a ferramenta para medir a cobertura do que com o CMake. É importante observar que, com base nos resultados, será criada uma meta de coverage
, com a 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()
Doxigênio encontrado .
find_package(Doxygen)
Em seguida, verificamos se o usuário definiu a variável de idioma. Se sim, então não toque, se não, então pegue russo. Em seguida, configure os arquivos do sistema Doxygen. Todas as variáveis necessárias, incluindo o idioma, chegam lá durante o processo de configuração (consulte o configure_file
).
Em seguida, criamos o destino do doc
, que iniciará a geração da documentação. Como a geração de documentação não é a maior necessidade no processo de desenvolvimento, por padrão o objetivo não será habilitado, ele deverá ser iniciado 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 destino wandbox
que gera uma solicitação que corresponde à API do serviço Wandbox e a envia. Em resposta, é fornecido um link para a sandbox concluída.
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()
Agora considere como usar tudo.
A montagem deste projeto, como qualquer outro projeto no sistema de montagem do CMake, consiste em dois estágios:
cmake -S // -B /// [ ...]
Se o comando acima não funcionou devido à versão antiga do CMake, tente omitir -S
:
cmake // -B /// [ ...]
Mais sobre opções .
cmake --build /// [--target target]
Leia mais sobre metas de montagem .
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [ ...]
Inclui uma meta de coverage
, com a qual você pode começar a medir a cobertura do código com testes.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [ ...]
Fornece a capacidade de desligar o conjunto de teste da unidade e o alvo de check
. Como resultado, a medição da cobertura do código por testes é desativada (consulte MYLIB_COVERAGE
).
Além disso, o teste é desativado automaticamente se o projeto estiver conectado a outro projeto como um subprojeto usando o add_subdirectory
.
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [ ...]
Alterna o idioma da documentação que o destino do doc
gera para o especificado. Para obter uma lista dos idiomas disponíveis, consulte o site da Doxygen .
Por padrão, o russo está ativado.
cmake --build path/to/build/directory cmake --build path/to/build/directory --target all
Se o alvo não for especificado (o que equivale ao objetivo de all
), coleciona tudo o que é possível e também chama o alvo de check
.
cmake --build path/to/build/directory --target mylib-unit-tests
Compila testes de unidade. Ativado por padrão.
cmake --build /// --target check
Executa testes de unidade coletados (coleta, se ainda não). Ativado por padrão.
Veja também mylib-unit-tests
.
cmake --build /// --target coverage
Analisa a execução (executa, se ainda não) de testes de unidade para cobrir o código com testes usando o programa gcovr .
A exaustão do revestimento será mais ou menos assim:
------------------------------------------------------------------------------ GCC Code Coverage Report Directory: /path/to/cmakecpptemplate/include/ ------------------------------------------------------------------------------ File Lines Exec Cover Missing ------------------------------------------------------------------------------ mylib/myfeature.hpp 2 2 100% ------------------------------------------------------------------------------ TOTAL 2 2 100% ------------------------------------------------------------------------------
O destino está disponível apenas quando MYLIB_COVERAGE
.
Veja também check
.
cmake --build /// --target doc
Inicia a geração da documentação do código usando o sistema Doxygen .
cmake --build /// --target wandbox
A resposta do serviço é mais ou menos assim:
{ "permlink" : "QElvxuMzHgL9fqci", "status" : "0", "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci" }
Para fazer isso, use o serviço Wandbox . Não sei como eles têm servidores de borracha, mas acho que essa oportunidade não deve ser abusada.
Montagem do projeto no modo de depuração com medição de cobertura
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON cmake --build /// --target coverage --parallel 16
Instalação do projeto sem montagem e teste preliminares
cmake -S // -B /// -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/// cmake --build /// --target install
Construir no modo de liberação pelo compilador especificado
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=///// cmake --build /// --parallel 4
Geração de documentação em inglês
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English cmake --build /// --target doc
CMake 3.13
De fato, o CMake 3.13 é necessário apenas para executar alguns dos comandos do console descritos nesta ajuda. Do ponto de vista da sintaxe dos scripts do CMake, a versão 3.8 é suficiente se a geração for chamada de outras maneiras.
Biblioteca de testes Doctest
O teste pode ser desativado (consulte a MYLIB_TESTING
).
Doxygen
Para alternar o idioma em que a documentação será gerada, a opção MYLIB_DOXYGEN_LANGUAGE
é MYLIB_DOXYGEN_LANGUAGE
.
Intérprete Python 3
Para gerar automaticamente caixas de proteção online .
Com o CMake e algumas boas ferramentas, você pode fornecer análises estáticas com movimentos mínimos do corpo.
Cppcheck
O CMake possui suporte interno para a ferramenta de análise estática Cppcheck .
Para fazer isso, use a opção CMAKE_CXX_CPPCHECK
:
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I///include"
Depois disso, a análise estática será iniciada automaticamente todas as vezes durante a compilação e recompilação das fontes. Você não precisa fazer nada extra.
Clang
Usando a maravilhosa ferramenta de scan-build
, você também pode executar a análise estática em duas contagens:
scan-build cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug scan-build cmake --build ///
Aqui, diferentemente do caso do Cppcheck, você precisa executar a montagem todas as vezes através do scan-build
.
O 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 criação do CMake para o benefício da sociedade e da saúde.
→ Baixar modelo de projeto