
Under udvikling kan jeg godt lide at ændre compilere, bygge modes, afhængighedsversioner, udføre statisk analyse, måle ydeevne, indsamle dækning, generere dokumentation og så videre. Og jeg elsker virkelig CMake, fordi det giver mig mulighed for at gøre, hvad jeg vil.
Mange skælder ud på CMake, og ofte fortjent, men hvis man ser efter, er det ikke så slemt, men på det seneste slet ikke dårligt, og udviklingsretningen er ret positiv.
I denne note vil jeg fortælle dig, hvor nemt det er at organisere et C++ header-bibliotek i et CMake-system for at få følgende funktionalitet:
- montage;
- Autorun tests;
- Kodedækningsmåling;
- installation;
- Autodokumentation;
- Online sandkassegenerering;
- Statisk analyse.
Hvem allerede forstår fordelene og si-make kan bare og begynde at bruge det.
Indhold
.
├── 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.cppVi vil hovedsageligt tale om, hvordan man organiserer CMake-scripts, så de vil blive analyseret i detaljer. Resten af filerne kan ses direkte af alle. .
Først og fremmest skal du anmode om den ønskede version af CMake-systemet. CMake udvikler sig, kommandosignaturer ændres, adfærd under forskellige forhold. For at CMake umiddelbart kan forstå, hvad vi vil have ud af det, skal vi straks rette vores krav til det.
cmake_minimum_required(VERSION 3.13)Derefter angiver vi vores projekt, dets navn, version, anvendte sprog osv. ).
I dette tilfælde skal du angive sproget CXX (hvilket betyder C++), så CMake ikke gider lede efter en C-compiler (som standard er to sprog inkluderet i CMake: C og C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Her kan du med det samme tjekke, om vores projekt indgår i et andet projekt som delprojekt. Dette vil hjælpe meget i fremtiden.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Lad os overveje to muligheder.
Den første mulighed er - at deaktivere enhedstest. Det kan være nødvendigt, hvis vi er sikre på, at alt er i orden med testene, og vi for eksempel kun vil installere eller pakke vores projekt. Eller vores projekt indgår som et delprojekt - i dette tilfælde er brugeren af vores projekt ikke interesseret i at køre vores tests. Du tester ikke de afhængigheder, du bruger, vel?
option(MYLIB_TESTING "Включить модульное тестирование" ON)Derudover vil vi lave en separat mulighed til måling af kodedækning med test, men det vil kræve yderligere værktøjer, så du bliver nødt til at aktivere det eksplicit.
option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)
Selvfølgelig er vi seje programmører-positive, så vi vil have det maksimale niveau af kompileringstidsdiagnostik fra compileren. Ikke en eneste mus kommer igennem.
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
)Vi vil også deaktivere udvidelser for fuldt ud at overholde sprogstandarden C++. De er som standard aktiveret i CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Vores bibliotek består kun af header-filer, hvilket betyder, at vi ikke har noget output i form af statiske eller dynamiske biblioteker. På den anden side, for at bruge vores bibliotek udefra, skal du installere det, du skal være i stand til at finde det i systemet og forbinde det til dit projekt, og samtidig også disse headere som muligvis nogle yderligere egenskaber.
Til dette formål opretter vi et grænsefladebibliotek.
add_library(mylib INTERFACE)Vi binder overskrifterne til vores grænsefladebibliotek.
Moderne, trendy, ungdommelig brug af CMake betyder, at headere, egenskaber mv. transmitteret gennem et enkelt mål. Så det er nok at sige , og alle overskrifter, der er knyttet til målet dependency, vil være tilgængelig for kilder, der tilhører målet target. Og du behøver ikke nogen [target_]include_directories. Dette vil blive demonstreret nedenfor under parsing .
Det er også værd at være opmærksom på den såkaldte. .
Denne kommando forbinder de overskrifter, vi har brug for, med vores grænsefladebibliotek, og hvis vores bibliotek er forbundet til ethvert mål inden for det samme CMake-hierarki, vil overskrifterne fra biblioteket blive knyttet til det ${CMAKE_CURRENT_SOURCE_DIR}/include, og hvis vores bibliotek er installeret på systemet og forbundet til et andet projekt ved hjælp af kommandoen , så vil overskrifterne fra mappen blive knyttet til den include i forhold til installationsmappen.
target_include_directories(mylib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)Indstil sprogstandarden. Selvfølgelig den seneste. Samtidig medtager vi ikke kun standarden, men distribuerer den også til dem, der skal bruge vores bibliotek. Dette opnås ved, at den indstillede egenskab har en kategori INTERFACE (See. ).
target_compile_features(mylib INTERFACE cxx_std_17)Vi får et alias til vores bibliotek. Og for skønheden vil det være i et særligt "navneområde". Dette vil være nyttigt, når forskellige moduler vises i vores bibliotek, og vi går for at forbinde dem uafhængigt af hinanden. .
add_library(Mylib::mylib ALIAS mylib)
Installation af vores headere i systemet. Alt er enkelt her. Vi siger, at mappen med alle overskrifterne skal falde ind i mappen include vedrørende installationsstedet.
install(DIRECTORY include/mylib DESTINATION include)Dernæst fortæller vi byggesystemet, at vi ønsker at kunne kalde kommandoen i tredjepartsprojekter find_package(Mylib) og få et mål Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Følgende besværgelse skal forstås således. Når vi er i et sideprojekt, kalder vi kommandoen find_package(Mylib 1.2.3 REQUIRED), og samtidig vil den rigtige version af det installerede bibliotek være inkompatibel med versionen 1.2.3, vil CMake automatisk generere en fejl. Det vil sige, at du ikke behøver at holde styr på versioner manuelt.
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)
Hvis tests eksplicit er deaktiveret med eller vores projekt er et underprojekt, det vil sige, det er forbundet med et andet CMake-projekt ved hjælp af kommandoen , vi går ikke længere ned i hierarkiet, og scriptet, som beskriver kommandoerne til at generere og køre test, starter simpelthen ikke.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Der vil heller ikke blive genereret dokumentation, hvis der er tale om et delprojekt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Tilsvarende vil delprojektet heller ikke have en online sandkasse.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Først og fremmest finder vi en pakke med den ønskede testramme (erstat den med din favorit).
find_package(doctest 2.3.3 REQUIRED)Vi opretter vores eksekverbare fil med tests. Normalt tilføjer jeg direkte til den eksekverbare binære fil kun den fil, hvori funktionen vil være main.
add_executable(mylib-unit-tests test_main.cpp)Og de filer, hvori selve testene er beskrevet, tilføjer jeg senere. Men det er ikke nødvendigt at gøre det.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Vi forbinder afhængigheder. Bemærk venligst, at vi kun vedhæftede de CMake-mål, vi havde brug for, til vores binære, og kaldte ikke kommandoen target_include_directories. Overskrifter fra testrammen og fra vores Mylib::mylib, samt byggemuligheder (i vores tilfælde C++ sprogstandarden) kravlede sammen med disse mål.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Til sidst opretter vi et dummy-mål, hvis "build" svarer til at køre test, og tilføjer dette mål til standardbuilden (dette er attributtens ansvar ALL). Det betyder, at standardbuilden vil udløse testene til at køre, hvilket betyder, at vi aldrig glemmer at køre dem.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Slå derefter kodedækningsmåling til, hvis den tilsvarende indstilling er indstillet. Jeg vil ikke gå ind i detaljerne, for de er mere relateret til dækningsmålingsværktøjet end til CMake. Det er kun vigtigt at bemærke, at resultaterne vil skabe et mål , hvormed det er praktisk at begynde at måle dækning.
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)Dernæst tjekker vi om variablen med sproget er indstillet af brugeren. Hvis ja, så rører vi det ikke, hvis ikke, så tager vi russisk. Derefter konfigurerer vi Doxygen-systemfilerne. Alle de nødvendige variabler, inklusive sproget, kommer dertil under konfigurationsprocessen (se. ).
Så laver vi et mål , som vil begynde at generere dokumentation. Da generering af dokumentation ikke er det største behov i udviklingsprocessen, vil målet ikke være inkluderet som standard, det skal køres eksplicit.
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 ()
Her finder vi den tredje Python og laver et mål , som genererer en anmodning svarende til tjenestens API , og sender den. Som svar kommer et link til den færdige sandkasse.
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()
Lad os nu se på, hvordan man bruger det hele.
Opbygningen af dette projekt, som ethvert andet projekt på CMake-byggesystemet, består af to faser:
cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]Hvis kommandoen ovenfor ikke virkede på grund af en ældre version af CMake, så prøv at udelade
-S:cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]
.
cmake --build путь/к/сборочной/директории [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]Inkluderer mål , hvormed du kan begynde at måle kodedækning med test.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]Giver mulighed for at slå enhedstestbygning og mål fra . Som et resultat er målingen af kodedækning ved test slået fra (se ).
Test er også automatisk deaktiveret, hvis projektet er forbundet til et andet projekt som et underprojekt ved hjælp af kommandoen .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]Skifter sproget for den dokumentation, som målet genererer til den givne. For en liste over tilgængelige sprog, se .
Russisk er aktiveret som standard.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allHvis målet ikke er angivet (hvilket svarer til målet all), samler alt, hvad der er muligt, og kalder også målet .
cmake --build path/to/build/directory --target mylib-unit-testsKompilerer enhedstests. Aktiveret som standard.
cmake --build путь/к/сборочной/директории --target checkKører bygget (bygger, hvis ikke allerede) enhedstests. Aktiveret som standard.
Se også .
cmake --build путь/к/сборочной/директории --target coverageAnalyserer kørende (kører, hvis ikke endnu) enhedstests for kodedækning ved test ved hjælp af programmet .
Dækningsoutputtet vil se nogenlunde således ud:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------Målet er kun tilgængeligt, når indstillingen er aktiveret. .
Se også .
cmake --build путь/к/сборочной/директории --target docStarter generering af dokumentation for koden ved hjælp af systemet .
cmake --build путь/к/сборочной/директории --target wandboxSvaret fra tjenesten ser sådan ud:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Tjenesten bruges til dette. . Jeg ved ikke, hvordan gummiservere de har, men jeg synes, at du ikke skal misbruge denne funktion.
Byg projekt i fejlretningstilstand med dækningsmåling
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16Installation af et projekt uden forudbygning og test
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target installByg i frigivelsestilstand af den angivne compiler
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4Generering af dokumentation på engelsk
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc
3.13
Faktisk kræves CMake version 3.13 kun for at køre nogle af de konsolkommandoer, der er beskrevet i denne hjælp. Ud fra syntaksen af CMake-scripts er version 3.8 tilstrækkelig, hvis genereringen kaldes på andre måder.
Test bibliotek
Testning kan deaktiveres (se ).
For at skifte det sprog, som dokumentationen vil blive genereret på, er der en mulighed .
PL tolk
Til automatisk generering .
Ved hjælp af CMake og et par gode værktøjer kan du levere statisk analyse med minimal fifleri.
Cppcheck
Understøttelse af statisk analyseværktøj indbygget i CMake .
For at gøre dette skal du bruge indstillingen :
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"Derefter vil statisk analyse automatisk blive lanceret hver gang under kompilering og genkompilering af kilder. Der skal ikke gøres noget ekstra.
Dunk
Med et fantastisk værktøj Du kan også køre statisk analyse på ingen tid:
scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директорииHer er det, i modsætning til tilfældet med Cppcheck, påkrævet at køre build hver gang igennem scan-build.
CMake er et meget kraftfuldt og fleksibelt system, der giver dig mulighed for at implementere funktionalitet for enhver smag og farve. Og selvom syntaksen nogle gange lader meget tilbage at ønske, er djævelen stadig ikke så forfærdelig, som han er malet. Brug CMake build-systemet til gavn for samfundet og sundheden.
→
Kilde: www.habr.com
