Durante o desenvolvemento, gústame cambiar compiladores, modos de compilación, versións de dependencia, realizar análises estáticas, medir o rendemento, recoller cobertura, xerar documentación, etc. E realmente encántame CMake porque me permite facer todo o que quero.
Moita xente critica CMake, e moitas veces merecidamente, pero se o miras, non todo está tan mal, e recentemente non está nada mal, e a dirección do desenvolvemento é bastante positiva.
Nesta nota, quero dicirche como simplemente organizar unha biblioteca de cabeceiras en C++ no sistema CMake para obter a seguinte funcionalidade:
Asemblea;
Probas de execución automática;
Medición de cobertura de código;
Instalación;
Documentación automática;
Xeración de sandbox en liña;
Análise estática.
Calquera persoa que xa entenda as vantaxes e C-make pode simplemente Descargar modelo de proxecto e comeza a usalo.
Falaremos principalmente sobre como organizar os scripts de CMake, polo que se comentarán en detalle. Calquera pode ver o resto dos ficheiros directamente na páxina do proxecto modelo.
Primeiro de todo, cómpre solicitar a versión requirida do sistema CMake. CMake está evolucionando, as sinaturas de comandos e o comportamento en diferentes condicións están cambiando. Para que CMake entenda inmediatamente o que queremos del, necesitamos rexistrar inmediatamente os nosos requisitos para iso.
cmake_minimum_required(VERSION 3.13)
Despois designaremos o noso proxecto, o seu nome, versión, idiomas empregados, etc. (ver. команду project).
Neste caso indicamos a lingua CXX (e isto significa C++) para que CMake non se esforce e busque un compilador de linguaxe C (por defecto, CMake inclúe dúas linguaxes: C e C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Aquí pode comprobar inmediatamente se o noso proxecto está incluído noutro proxecto como subproxecto. Isto axudará moito no futuro.
A primeira opción é MYLIB_TESTING — para desactivar as probas unitarias. Isto pode ser necesario se estamos seguros de que todo está en orde coas probas, pero só queremos, por exemplo, instalar ou empaquetar o noso proxecto. Ou o noso proxecto inclúese como un subproxecto; neste caso, o usuario do noso proxecto non está interesado en executar as nosas probas. Non probas as dependencias que usas, non si?
Ademais, faremos unha opción separada MYLIB_COVERAGE para medir a cobertura do código mediante probas, pero requirirá ferramentas adicionais, polo que terá que estar activado de forma explícita.
Por suposto, somos programadores xeniais, polo que queremos o máximo nivel de diagnóstico en tempo de compilación do compilador. Nin un só rato se deslizará.
A nosa biblioteca está formada só por ficheiros de cabeceira, o que significa que non temos ningún escape en forma de bibliotecas estáticas ou dinámicas. Por outra banda, para poder utilizar externamente a nosa biblioteca é preciso que estea instalada, que sexa detectable no sistema e conectada ao seu proxecto, e ao mesmo tempo estas mesmas cabeceiras, así como posiblemente algunhas adicionais, están unidas a ela propiedades.
Para este fin, creamos unha biblioteca de interfaces.
add_library(mylib INTERFACE)
Enlazamos cabeceiras á nosa biblioteca de interfaces.
O uso moderno, de moda e xuvenil de CMake implica que cabeceiras, propiedades, etc. transmitido a través dun único obxectivo. Así que abonda con dicir target_link_libraries(target PRIVATE dependency), e todas as cabeceiras que están asociadas co destino dependency, estará dispoñible para fontes pertencentes ao destino target. E non precisa ningún [target_]include_directories. Isto demostrarase a continuación na análise Script CMake para probas unitarias.
Este comando asocia as cabeceiras que necesitamos coa nosa biblioteca de interfaces, e se a nosa biblioteca está conectada a algún destino dentro da mesma xerarquía de CMake, as cabeceiras do directorio asociaranse a el. ${CMAKE_CURRENT_SOURCE_DIR}/include, e se a nosa biblioteca está instalada no sistema e conectada a outro proxecto mediante o comando find_package, entón as cabeceiras do directorio asociaranse a el include relativo ao directorio de instalación.
Establecemos un estándar lingüístico. Por suposto, o último. Ao mesmo tempo, non só incluímos o estándar, senón que tamén o estendimos a aqueles que utilicen a nosa biblioteca. Isto conséguese debido ao feito de que a propiedade do conxunto ten unha categoría INTERFACE (Ver. comando target_compile_features).
Imos crear un alias para a nosa biblioteca. Ademais, para a beleza, estará nun "espazo de nomes" especial. Isto será útil cando aparezan diferentes módulos na nosa biblioteca, e imos conectalos de forma independente uns dos outros. Como en Busta, por exemplo.
Instalando as nosas cabeceiras no sistema. Aquí todo é sinxelo. Dicimos que o cartafol con todas as cabeceiras debe ir ao directorio include relativo ao lugar de instalación.
A continuación, informamos ao sistema de compilación de que queremos poder chamar ao comando en proxectos de terceiros find_package(Mylib) e conseguir un gol Mylib::mylib.
O seguinte feitizo debe entenderse deste xeito. Cando nun proxecto de terceiros chamamos ao comando find_package(Mylib 1.2.3 REQUIRED), e a versión real da biblioteca instalada será incompatible coa versión 1.2.3CMake xerará automaticamente un erro. É dicir, non necesitará rastrexar versións manualmente.
Se as probas están desactivadas explícitamente usando opción correspondente ou o noso proxecto é un subproxecto, é dicir, está conectado a outro proxecto CMake mediante o comando add_subdirectory, non avanzamos máis na xerarquía e o script, que describe os comandos para xerar e executar probas, simplemente non se executa.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Conectamos dependencias. Teña en conta que só ligamos os obxectivos CMake que necesitabamos ao noso binario e non chamamos ao comando target_include_directories. Títulos do marco de proba e do noso Mylib::mylib, así como os parámetros de compilación (no noso caso, este é o estándar da linguaxe C++) chegou xunto con estes obxectivos.
Finalmente, creamos un obxectivo ficticio, cuxa "construción" é equivalente a realizar probas, e engadimos este destino á compilación predeterminada (o atributo é responsable deste ALL). Isto significa que a compilación predeterminada desencadea a execución das probas, o que significa que nunca esqueceremos executalas.
add_custom_target(check ALL COMMAND mylib-unit-tests)
A continuación, activamos a medición da cobertura do código se se especifica a opción adecuada. Non entrarei en detalles, porque se relacionan máis cunha ferramenta para medir a cobertura que con CMake. Só é importante ter en conta que en función dos resultados crearase un obxectivo coverage, co que é conveniente comezar 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 continuación, comprobamos se o usuario estableceu a variable de idioma. Se si, entón non o tocamos, se non, entón tomamos ruso. Despois configuramos os ficheiros do sistema Doxygen. Todas as variables necesarias, incluído o idioma, van alí durante o proceso de configuración (ver. команду configure_file).
Despois creamos un obxectivo doc, que comezará a xerar documentación. Dado que a xeración de documentación non é a maior necesidade no proceso de desenvolvemento, o destino non estará activado por defecto; terá que lanzarse de forma explícita.
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 ()
Aquí atopamos o terceiro Python e creamos un destino wandbox, que xera unha solicitude correspondente á API do servizo Caixa de variñas, e despídeo. A resposta vén cunha ligazón ao sandbox rematado.
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()
Ofrece a posibilidade de desactivar a compilación e o destino da proba unitaria check. Como resultado, a medición da cobertura de código mediante probas está desactivada (ver. MYLIB_COVERAGE).
As probas tamén se desactivan automaticamente se o proxecto está conectado a outro proxecto como subproxecto mediante o comando add_subdirectory.
De feito, a versión 3.13 de CMake só é necesaria para executar algúns dos comandos da consola descritos nesta axuda. Desde o punto de vista da sintaxe dos scripts CMake, a versión 3.8 é suficiente se a xeración se chama doutro xeito.
CMake é un sistema moi potente e flexible que che permite implementar funcionalidades para todos os gustos e cor. E, aínda que a sintaxe ás veces deixa moito que desexar, o demo aínda non é tan terrible como está pintado. Use o sistema de construción CMake para o beneficio da sociedade e da saúde.