
Durante el desarrollo, me gusta cambiar compiladores, modos de compilación, versiones de dependencia, realizar análisis estáticos, medir el rendimiento, recopilar cobertura, generar documentación, etc. Y realmente amo CMake porque me permite hacer lo que quiera.
Mucha gente regaña a CMake, y muchas veces con razón, pero si miras, no está tan mal, pero últimamente No está mal, y la dirección del desarrollo es bastante positiva.
En esta nota, quiero contarles lo fácil que es organizar una biblioteca de encabezados C++ en un sistema CMake para obtener la siguiente funcionalidad:
- asamblea;
- Pruebas de ejecución automática;
- Medición de cobertura de código;
- instalación;
- Autodocumentación;
- Generación de sandbox en línea;
- Análisis estático.
Quien ya entiende los pros y si-make puede simplemente y empieza a usarlo.
contenido
.
├── 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.cppEsto se centrará principalmente en cómo organizar los scripts de CMake, por lo que se analizarán en detalle. El resto de archivos puede ser visto directamente por cualquier persona. .
En primer lugar, debe solicitar la versión deseada del sistema CMake. CMake se desarrolla, las firmas de comandos cambian y el comportamiento bajo diferentes condiciones. Para que CMake comprenda de inmediato lo que queremos de él, debemos corregir de inmediato nuestros requisitos.
cmake_minimum_required(VERSION 3.13)Luego denotamos nuestro proyecto, su nombre, versión, idiomas utilizados, etc. ).
En este caso, especifique el idioma. CXX (que significa C++) para que CMake no se moleste en buscar un compilador de C (de forma predeterminada, se incluyen dos lenguajes en CMake: C y C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Aquí puede comprobar inmediatamente si nuestro proyecto está incluido en otro proyecto como subproyecto. Esto ayudará mucho en el futuro.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Consideremos dos opciones.
La primera opción es - para deshabilitar las pruebas unitarias. Esto puede ser necesario si estamos seguros de que todo está en orden con las pruebas y queremos, por ejemplo, solo instalar o empaquetar nuestro proyecto. O nuestro proyecto se incluye como un subproyecto; en este caso, el usuario de nuestro proyecto no está interesado en ejecutar nuestras pruebas. No pruebas las dependencias que usas, ¿verdad?
option(MYLIB_TESTING "Включить модульное тестирование" ON)Además, haremos una opción separada. para medir la cobertura del código con pruebas, pero requerirá herramientas adicionales, por lo que deberá habilitarlo explícitamente.
option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)
Por supuesto, somos buenos programadores, por lo que queremos el nivel máximo de diagnóstico en tiempo de compilación del compilador. Ni un solo ratón pasará.
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
)También desactivaremos las extensiones para cumplir plenamente con el estándar del lenguaje C++. Están habilitados de forma predeterminada en CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Nuestra biblioteca consta únicamente de archivos de encabezado, lo que significa que no tenemos ningún resultado en forma de bibliotecas estáticas o dinámicas. Por otro lado, para poder utilizar nuestra biblioteca desde el exterior, necesita instalarla, debe poder encontrarla en el sistema y conectarla a su proyecto, y al mismo tiempo estos mismos encabezados también como, posiblemente, algunas propiedades adicionales.
Para ello, creamos una biblioteca de interfaz.
add_library(mylib INTERFACE)Vinculamos los encabezados a nuestra biblioteca de interfaz.
El uso moderno, moderno y juvenil de CMake significa que los encabezados, propiedades, etc. transmitido a través de un único objetivo. Así que basta con decir y todos los encabezados asociados con el objetivo dependency, estará disponible para fuentes pertenecientes al objetivo target. Y no necesitas ninguno [target_]include_directories. Esto se demostrará a continuación al analizar .
También vale la pena prestar atención a los llamados. .
Este comando asocia los encabezados que necesitamos con nuestra biblioteca de interfaz, y si nuestra biblioteca está conectada a cualquier objetivo dentro de la misma jerarquía de CMake, entonces los encabezados del directorio se asociarán con él. ${CMAKE_CURRENT_SOURCE_DIR}/include, y si nuestra biblioteca está instalada en el sistema y conectada a otro proyecto usando el comando , entonces los encabezados del directorio se asociarán con él include relativo al directorio de instalación.
target_include_directories(mylib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)Establecer el estándar del idioma. Por supuesto, lo último. Al mismo tiempo, no solo incluimos el estándar, sino que también lo distribuimos a quienes utilizarán nuestra biblioteca. Esto se logra haciendo que la propiedad establecida tenga una categoría INTERFACE (Ver. ).
target_compile_features(mylib INTERFACE cxx_std_17)Obtenemos un alias para nuestra biblioteca. Y para la belleza, estará en un "espacio de nombres" especial. Esto nos será útil cuando en nuestra biblioteca aparezcan diferentes módulos y vayamos a conectarlos de forma independiente entre sí. .
add_library(Mylib::mylib ALIAS mylib)
Instalación de nuestros encabezados en el sistema. Aquí todo es sencillo. Decimos que la carpeta con todos los encabezados debe estar en el directorio include respecto al lugar de instalación.
install(DIRECTORY include/mylib DESTINATION include)A continuación, le decimos al sistema de compilación que queremos poder llamar el comando en proyectos de terceros. find_package(Mylib) y conseguir un objetivo Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)El siguiente encantamiento debe entenderse así. Cuando en un proyecto paralelo llamamos al comando find_package(Mylib 1.2.3 REQUIRED), y al mismo tiempo la versión real de la biblioteca instalada será incompatible con la versión 1.2.3, CMake generará automáticamente un error. Es decir, no necesitarás realizar un seguimiento de las versiones 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)
Si las pruebas están explícitamente deshabilitadas con o nuestro proyecto es un subproyecto, es decir, está conectado a otro proyecto de CMake usando el comando , no bajamos más abajo en la jerarquía y el script, que describe los comandos para generar y ejecutar pruebas, simplemente no se inicia.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Tampoco se generará documentación en el caso de un subproyecto.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
De manera similar, el subproyecto tampoco tendrá un sandbox en línea.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
En primer lugar, encontramos un paquete con el marco de prueba deseado (reemplácelo por su favorito).
find_package(doctest 2.3.3 REQUIRED)Creamos nuestro archivo ejecutable con pruebas. Generalmente, directamente al binario ejecutable, agrego solo el archivo en el que estará la función. main.
add_executable(mylib-unit-tests test_main.cpp)Y los archivos en los que se describen las pruebas en sí, los agrego más adelante. Pero no es necesario hacerlo.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Conectamos dependencias. Tenga en cuenta que adjuntamos solo los objetivos de CMake que necesitábamos a nuestro binario y no llamamos al comando target_include_directories. Encabezados del marco de prueba y de nuestro Mylib::mylib, así como las opciones de compilación (en nuestro caso, el estándar del lenguaje C++) rastreadas junto con estos objetivos.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Finalmente, creamos un objetivo ficticio cuya "compilación" es equivalente a ejecutar pruebas y agregamos este objetivo a la compilación predeterminada (esto es responsabilidad del atributo ALL). Esto significa que la compilación predeterminada activará la ejecución de las pruebas, lo que significa que nunca nos olvidaremos de ejecutarlas.
add_custom_target(check ALL COMMAND mylib-unit-tests)
A continuación, active la medición de cobertura de código, si está configurada la opción correspondiente. No entraré en detalles porque están más relacionados con la herramienta de medición de cobertura que con CMake. Sólo es importante tener en cuenta que los resultados crearán una meta. , con lo cual conviene empezar a medir la 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()
.
find_package(Doxygen)A continuación, comprobamos si la variable con el idioma la establece el usuario. Si es así, entonces no lo tocamos, si no, tomamos ruso. Luego configuramos los archivos del sistema Doxygen. Todas las variables necesarias, incluido el idioma, llegan allí durante el proceso de configuración (ver. ).
Luego creamos una meta. , que comenzará a generar documentación. Dado que generar documentación no es la mayor necesidad en el proceso de desarrollo, el objetivo no se incluirá de forma predeterminada, deberá ejecutarse explícitamente.
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í encontramos el tercer Python y creamos un objetivo. , que genera una solicitud correspondiente a la API del servicio y lo envía. En respuesta, aparece un enlace al sandbox terminado.
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()
Ahora veamos cómo usarlo todo.
La construcción de este proyecto, como cualquier otro proyecto en el sistema de construcción CMake, consta de dos etapas:
cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]Si el comando anterior no funcionó debido a una versión anterior de CMake, intente omitir
-S:cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]
.
cmake --build путь/к/сборочной/директории [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]Incluye objetivo , con el que podrás empezar a medir la cobertura del código con pruebas.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]Proporciona la opción de desactivar la compilación y el objetivo de la prueba unitaria. . Como resultado, la medición de la cobertura del código mediante pruebas se desactiva (consulte ).
Además, las pruebas se desactivan automáticamente si el proyecto está conectado a otro proyecto como un subproyecto mediante el comando .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]Cambia el idioma de la documentación que genera el destino. al dado. Para obtener una lista de idiomas disponibles, consulte .
El ruso está habilitado de forma predeterminada.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allSi no se especifica el objetivo (que es equivalente al objetivo all), recopila todo lo que es posible y también llama al objetivo .
cmake --build path/to/build/directory --target mylib-unit-testsCompila pruebas unitarias. Habilitado de forma predeterminada.
cmake --build путь/к/сборочной/директории --target checkEjecuta pruebas unitarias construidas (compiladas si no ya). Habilitado de forma predeterminada.
См. также .
cmake --build путь/к/сборочной/директории --target coverageAnaliza las pruebas unitarias en ejecución (ejecuta, si aún no) para la cobertura del código mediante pruebas que utilizan el programa. .
El resultado de la cobertura se verá así:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------El objetivo está disponible solo cuando la opción está habilitada. .
См. также .
cmake --build путь/к/сборочной/директории --target docInicia la generación de documentación para el código utilizando el sistema. .
cmake --build путь/к/сборочной/директории --target wandboxLa respuesta del servicio se parece a esto:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}El servicio se utiliza para esto. . No sé qué servidores de goma tienen, pero creo que no deberías abusar de esta característica.
Construir proyecto en modo de depuración con medición de cobertura
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16Instalar un proyecto sin preconstrucción ni pruebas
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target installConstruido en modo de lanzamiento por el compilador especificado
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4Generación de documentación en inglés.
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc
3.13
De hecho, la versión 3.13 de CMake solo es necesaria para ejecutar algunos de los comandos de la consola descritos en esta ayuda. Desde el punto de vista de la sintaxis de los scripts CMake, la versión 3.8 es suficiente si la generación se llama de otras formas.
Biblioteca de pruebas
Las pruebas se pueden desactivar (ver ).
Para cambiar el idioma en el que se generará la documentación, existe una opción .
Intérprete de PL
Para generación automática .
Con la ayuda de CMake y un par de buenas herramientas, puede proporcionar análisis estático con un mínimo de manipulación.
Comprobación de CPP
Soporte para herramienta de análisis estático integrada en CMake .
Para hacer esto, use la opción :
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"Después de eso, el análisis estático se iniciará automáticamente cada vez durante la compilación y recompilación de fuentes. No es necesario hacer nada adicional.
Sonido metálico
Con una herramienta maravillosa También puedes ejecutar análisis estático en poco tiempo:
scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директорииAquí, a diferencia del caso con Cppcheck, es necesario ejecutar la compilación cada vez scan-build.
CMake es un sistema muy potente y flexible que le permite implementar funciones para todos los gustos y colores. Y, aunque la sintaxis a veces deja mucho que desear, el diablo todavía no es tan terrible como lo pintan. Utilice el sistema de compilación CMake en beneficio de la sociedad y la salud.
→
Fuente: habr.com
