
Under development, I love to change compilers, build modes, dependency versions, perform static analysis, measure performance, gather coverage, generate documentation, etc. I really love CMake because it allows me to do everything I want.
Many criticize CMake, often justifiably, but if you look closely, it's not that bad, and lately it's actually quite good, and the direction of development is quite positive.
In this note, I want to explain how relatively easily to organize a header library in C++ using CMake, to achieve the following functionality:
- Building;
- Auto-launching tests;
- Measuring code coverage;
- Installation;
- Auto-documentation;
- Generating an online sandbox;
- Static analysis.
Anyone already familiar with C++ and CMake can simply and start using it.
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.cppThe main focus will be on how to structure CMake scripts, so they will be examined in detail. Other files can be reviewed directly .
First, you need to request the required version of CMake. CMake evolves, command signatures change, behavior under various conditions varies. To ensure CMake immediately understands our requirements, we need to specify them upfront.
cmake_minimum_required(VERSION 3.13)Then we will define our project, its name, version, languages used, and others (see ).
In this case, we specify the language CXX (which means C++), so CMake doesnât get confused and look for a C language compiler (by default, CMake includes two languages: C and C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Her kan du straks tjekke, om vores projekt er inkluderet som et underprojekt i et andet projekt. Det vil hjĂŠlpe betydeligt fremadrettet.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Lad os overveje to muligheder.
Den fĂžrste mulighed â â til at deaktivere modultests. Dette kan vĂŠre nĂždvendigt, hvis vi er sikre pĂ„, at alt er i orden med testene, og vi f.eks. kun Ăžnsker at installere eller pakke vores projekt. Eller hvis vores projekt er inkluderet som et underprojekt â i det tilfĂŠlde ville brugeren af vores projekt ikke vĂŠre interesseret i at kĂžre vores tests. Tester du ikke de afhĂŠngigheder, du bruger?
option(MYLIB_TESTING "Aktivér modulÊr testning" ON)Derudover vil vi lave en separat mulighed for mÄling af testdÊkning, men den vil krÊve yderligere vÊrktÞjer, sÄ den skal aktiveres eksplicit.
option(MYLIB_COVERAGE "Aktivér mÄling af testdÊkning" OFF)
SelvfÞlgelig er vi dygtige C++ programmÞrer, sÄ vi Þnsker maksimum diagnostiske niveauer fra compileren. Ingen mus skal kunne slippe forbi.
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 deaktiverer ogsÄ udvidelserne for helt at overholde C++ sprogets standard. Som standard er de aktiveret i CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Vores bibliotek bestÄr kun af header-filer, sÄ vi har ingen output i form af statiske eller dynamiske biblioteker. PÄ den anden side, for at bruge vores bibliotek udenfor, skal det installeres, sÄ det kan findes i systemet og links til dit projekt, og samtidig skal disse header-filer vÊre bundet til det, samt muligvis nogle yderligere egenskaber.
Til dette formÄl opretter vi et interface-bibliotek.
add_library(mylib INTERFACE)Vi binder headers til vores interface-bibliotek.
Moderne, trendy, ungdommelig brug af CMake indebÊrer, at headers, egenskaber osv. overfÞres gennem et enkelt mÄl. SÄ det er nok at sige , og alle headers, der er associeret med mÄlet dependency, vil vÊre tilgÊngelige for kildene, der tilhÞrer mÄlet target. Og der krÊves ingen [target_]include_directoriesDette vil blive vist nedenfor i analysen .
Det er ogsÄ vÊrd at bemÊrke det sÄkaldte .
Denne kommando forbinder de nÞdvendige headers med vores interface-bibliotek, og hvis vores bibliotek bliver tilsluttet et mÄl i en CMake-hierarki, vil headers fra mappen ${CMAKE_CURRENT_SOURCE_DIR}/include, blive knyttet til det, og hvis vores bibliotek er installeret i systemet og tilsluttet i et andet projekt med kommandoen , vil headers fra mappen include i forhold til installationsmappen.
target_include_directories(mylib INTERFACE
$
$
)Lad os fastsÊtte sprogstandarden. SelvfÞlgelig den seneste. Ikke bare inkluderer vi standarden, men spreder den til dem, der vil bruge vores bibliotek. Dette opnÄs ved, at den installerede egenskab har kategorien INTERFACE (se ).
target_compile_features(mylib INTERFACE cxx_std_17)Lad os oprette et alias for vores bibliotek. For Êstetikens skyld vil det vÊre i et sÊrligt "navnerum". Dette vil vÊre nyttigt, nÄr vores bibliotek fÄr forskellige moduler, og vi vil tilslutte dem uafhÊngigt af hinanden. .
add_library(Mylib::mylib ALIAS mylib)
Installation af vores headers i systemet. Her er det simpelt. Vi siger, at mappen med alle headers skal placeres i mappen include i forhold til installationsstedet.
install(DIRECTORY include/mylib DESTINATION include)DernÊst informerer vi bygningssystemet om, at vi Þnsker at kunne kalde kommandoen find_package(Mylib) og fÄ mÄlet Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Den nÊste kommando skal forstÄs sÄledes. NÄr vi i et eksternt projekt kalder kommandoen find_package(Mylib 1.2.3 REQUIRED), og den faktiske version af det installerede bibliotek er inkompatibel med versionen 1.2.3, vil CMake automatisk generere en fejl. Det vil sige, at vi ikke behÞver at holde Þje med versionerne 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 testene er blevet deaktiveret klart med eller vores projekt er et underprojekt, hvilket betyder, at det er tilsluttet et andet CMake-projekt via kommandoen , vi gÄr ikke videre i hierarkiet, og scriptet, der beskriver kommandoerne til at generere og kÞre tests, bliver simpelthen ikke kÞrt.
if(NOT MYLIB_TESTING)
message(STATUS "Testning af Mylib projekt er deaktiveret")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib testes ikke i undermodulet")
else()
add_subdirectory(test)
endif()
Dokumentationen vil heller ikke blive genereret i tilfĂŠlde af et underprojekt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Tilsvarende vil der heller ikke vĂŠre en online sandkasse for underprojektet.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
FĂžrst finder vi pakken med det nĂždvendige testframework (udskift med dit yndlings).
find_package(doctest 2.3.3 REQUIRED)Vi opretter vores eksekverbare fil med tests. Normalt tilfĂžjer jeg kun filen, der indeholder funktionen, direkte i den eksekverbare binĂŠr. main.
add_executable(mylib-unit-tests test_main.cpp)Men jeg tilfĂžjer testfilerne senere. Men dette er ikke nĂždvendigt.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Vi tilslutter afhÊngigheder. BemÊrk, at vi kun har knyttet de nÞdvendige CMake-mÄl til vores binÊr, og vi kaldte ikke kommandoen target_include_directories. Headerne fra testframeworket og fra vores Mylib::mylib, samt byggeparametre (i vores tilfÊlde er det standarden for C++) kom igennem med disse mÄl.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Endelig opretter vi et fiktivt mÄl, hvis "bygning" er lig med at kÞre tests, og tilfÞjer dette mÄl til standardbygningen (dette hÄndteres af attributten ALL). Dette betyder, at standardbygningen initierer testen, sÄ vi aldrig glemmer at kÞre dem.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Derefter aktiverer vi kodeoverdÊkning, hvis den tilsvarende mulighed er angivet. Jeg vil ikke gÄ ind i detaljerne, da de mere relaterer til vÊrktÞjet til at mÄle dÊkning end til CMake. Det er kun vigtigt at bemÊrke, at der ud fra resultaterne vil blive oprettet et mÄl , som praktisk kan bruges til at udfÞre dÊkning.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "MÄling af testdÊkning er aktiveret")
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 "Programmet gcovr er nÞdvendigt for at mÄle testdÊkning")
endif()
.
find_package(Doxygen)DernĂŠst tjekker vi, om brugeren har angivet en sprogvariabel. Hvis ja, rĂžrer vi ikke ved den; hvis nej, tager vi russisk. Derefter konfigurerer vi Doxygen-systemfilerne. Alle nĂždvendige variabler, inklusive sproget, kommer ind i konfigurationen (se. ).
Derefter opretter vi et mÄl , der vil starte genereringen af dokumentationen. Da dokumentationsgenerering ikke er den stÞrste nÞdvendighed i udviklingsprocessen, vil mÄlet som standard ikke vÊre aktiveret, det skal startes eksplicit.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Dansk)
endif()
message(STATUS "Doxygen-dokumentation vil blive genereret pÄ ${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 opretter et mÄl , der genererer en forespÞrgsel, der svarer til API'en for tjenesten , og sender den. I svaret modtager vi 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 "En Python 3 fortolker er nĂždvendig for at oprette en online sandkasse.")
endif()
Nu ser vi pÄ, hvordan man bruger det hele.
Bygningen af dette projekt, ligesom ethvert andet projekt i CMake-bygsystemet, bestÄr af to trin:
cmake -S sti\/til\/kilde -B sti\/til\/bygge\/mappe [muligheder ...]Hvis ovenstÄende kommando ikke virker pÄ grund af en Êldre version af CMake, kan du prÞve at droppe
-S:cmake sti\/til\/kilde -B sti\/til\/bygge\/mappe [muligheder ...]
.
cmake --build sti\/til\/bygge\/mappe [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [andre muligheder ...]Aktiverer mÄlet , ved hjÊlp af hvilken man kan starte en mÄling af kode dÊkning med tests.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [andre muligheder ...]Giver mulighed for at deaktivere byggeriet af modul tests og mÄl . FÞlgelig deaktiveres mÄling af kode dÊkning med tests (se. ).
OgsÄ testning deaktiveres automatisk, hvis projektet inkluderes i et andet projekt som et underprojekt ved hjÊlp af kommandoen .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [andre muligheder ...]Skifter sprog for dokumentationen, som mÄlet genererer til det angivne. Se listen over tilgÊngelige sprog pÄ .
Som standard er russisk aktiveret.
cmake --build sti/til/bygningsdirectory
cmake --build sti/til/bygningsdirectory --target allHvis mÄlet ikke er angivet (hvilket er Êkvivalent med mÄlet alle), bygger alt, hvad der kan, samt kalder mÄlet .
cmake --build sti/til/bygningsdirectory --target mylib-unit-testsKompilerer modul tests. Aktiveret som standard.
cmake --build sti/til/bygningsdirectory --target checkKĂžrer de bygget (bygger, hvis ikke allerede) modul tests. Aktiveret som standard.
Se ogsÄ .
cmake --build sti/til/bygningsdirectory --target coverageAnalyserer de kĂžrende (kĂžrer, hvis ikke allerede) modul tests for kode dĂŠkning ved hjĂŠlp af programmet .
Udgangen af dÊkningen vil se omtrent sÄdan ud:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: /sti/til/cmakecpptemplate/include/
------------------------------------------------------------------------------
Filer Linjer Exec DĂŠkning Manglende
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------MÄlet er kun tilgÊngeligt, nÄr mulighederne er aktiveret .
Se ogsÄ .
cmake --build sti/til/bygningsdirectory --target docStarter genereringen af dokumentation til koden ved hjĂŠlp af systemet .
cmake --build sti/til/bygningsdirectory --target wandboxSvaret fra tjenesten ser omtrent sÄdan ud:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Dette gĂžres ved hjĂŠlp af tjenesten . Jeg ved ikke, hvor elastiske deres servere er, men jeg tror, at man ikke bĂžr misbruge denne mulighed.
Bygning af projektet i debug-tilstand med mÄling af dÊkning
cmake -S sti/til/kilde -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build sti/til/bygningsdirectory --target coverage --parallel 16Installation af projektet uden forudgÄende opbygning og testning
cmake -S sti/til/kilde -B sti/til/bygningsmappe -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=sti/til/installationsmappe
cmake --build sti/til/bygningsmappe --target installBygning i udgivelsesmÄde med den angivne compiler
cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=sti/til/directory/hvor/afhĂŠngigheder/er/installeret
cmake --build sti/til/bygningsmappe --parallel 4Generering af dokumentation pÄ engelsk
cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build sti/til/bygningsmappe --target doc
3.13
Faktisk krÊves CMake version 3.13 kun for at kÞre visse konsolkommandoer, der er beskrevet i denne vejledning. Med hensyn til syntaksen i CMake-scripts er version 3.8 tilstrÊkkelig, hvis genereringen kaldes pÄ andre mÄder.
Testbibliotek
Testning kan deaktiveres (se ).
For at skifte sprog, som dokumentationen genereres pÄ, er der en mulighed .
Tolk for programmeringssprog
Til automatisk generering .
Med CMake og et par gode vĂŠrktĂžjer kan statisk analyse sikres med minimal indsats.
Cppcheck
CMake har indbygget support for vĂŠrktĂžjet til statisk analyse .
For dette skal du bruge indstillingen :
cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Isti/til/kilde/include"Efter dette vil den statiske analyse automatisk blive udfĂžrt hver gang der kompileres og rekonstrueres kilden. Der er ikke behov for at foretage yderligere handlinger.
Clang
Ved hjÊlp af det vidunderlige vÊrktÞj kan du ogsÄ kÞre statisk analyse pÄ ingen tid:
scan-build cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build sti/til/bygningsmappeHer, i modsĂŠtning til tilfĂŠlde med Cppcheck, krĂŠves det at bygge igen hver gang via scan-build.
CMake er et meget kraftfuldt og fleksibelt system, der gÞr det muligt at implementere funktionalitet i enhver smag og farve. Og selvom syntaksen til tider kan vÊre mere Þnskelig, er det ikke sÄ skrÊmmende, som det lyder. Brug CMake-byggevÊrktÞjet til gavn for samfundet og til gavn for dit helbred.
â
Kilde: habr.com
