
Under development, I like to change compilers, build modes, dependency versions, perform static analysis, measure performance, collect coverage, generate documentation, etc. And 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 all that bad, and recently it's actually quite good, and the development direction is quite positive.
In this note, I want to explain how easy it is to organize a header library in C++ in CMake to achieve the following functionality:
- Building;
- Automatic test startup;
- Measuring code coverage;
- Installation;
- Auto-documentation;
- Generating an online sandbox;
- Static analysis.
Anyone who understands C++ and CMake can simply and start using it.
InnehÄll
.
âââ 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 organize CMake scripts, so they will be examined in detail. The other files can be viewed directly by anyone interested .
First, we need to request the required version of the CMake system. CMake evolves, command signatures change, and behavior varies under different conditions. To ensure CMake understands our intentions immediately, we need to specify our requirements upfront.
cmake_minimum_required(VERSION 3.13)Next, we define our project, its name, version, languages used, and other properties (see ).
In this case, we specify the language CXX (which means C++), so that 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)HÀr kan vi ocksÄ omedelbart kontrollera om vÄrt projekt ingÄr som ett delprojekt i ett annat projekt. Detta kommer att vara till stor hjÀlp framöver.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Vi kommer att övervÀga tvÄ alternativ.
Det första alternativet â â för att stĂ€nga av modultester. Detta kan vara nödvĂ€ndigt om vi Ă€r sĂ€kra pĂ„ att testerna fungerar bra, men vi vill till exempel bara installera eller paketera vĂ„rt projekt. Eller om vĂ„rt projekt ingĂ„r som ett delprojekt â i sĂ„ fall Ă€r det kanske inte relevant för anvĂ€ndaren att köra vĂ„ra tester. Du testar vĂ€l inte de beroenden du anvĂ€nder?
option(MYLIB_TESTING "Aktivera modultestning" ON)Dessutom kommer vi att skapa ett separat alternativ för att mÀta testtÀckning, men det kommer att krÀva ytterligare verktyg, sÄ det mÄste aktiveras tydligt.
option(MYLIB_COVERAGE "Aktivera mÀtning av testtÀckning" OFF)
SjÀlvklart Àr vi fantastiska C++-programmerare, sÄ vi vill ha maximal kompileringsdiagnosnivÄ frÄn kompilatorn. Ingen mus ska smita förbi.
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 kommer ocksÄ att stÀnga av tillÀgg för att fullt ut följa C++-standarden. Som standard Àr de aktiverade i CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
VÄrt bibliotek bestÄr endast av header-filer, vilket innebÀr att vi inte har nÄgot utslÀpp i form av statiska eller dynamiska bibliotek. à andra sidan, för att anvÀnda vÄrt bibliotek utanför, mÄste det installeras, det mÄste kunna upptÀckas i systemet och kopplas till sitt projekt, och samtidigt inkludera dessa header-filer samt kanske nÄgra ytterligare egenskaper.
För detta ÀndamÄl skapar vi ett grÀnssnittsbibliotek.
add_library(mylib INTERFACE)Ansluter header-filerna till vÄrt grÀnssnittsbibliotek.
Modern, trendig, ungdomlig anvÀndning av CMake innebÀr att header-filer, egenskaper osv. överförs genom ett enda mÄl. SÄ det rÀcker att sÀga , och alla header-filer som Àr kopplade till mÄlet syntax, kommer att vara tillgÀngliga för kÀllkoden som tillhör mÄlet target. Det krÀvs inga [target_]include_directories. Det kommer att demonstreras nedan vid analysen .
Det Àr ocksÄ vÀrt att uppmÀrksamma de sÄ kallade .
Detta kommando kopplar de nödvÀndiga rubrikerna med vÄrt grÀnsbibliotek, och om vÄrt bibliotek ansluts till nÄgot mÄl inom en och samma CMake-hierarki, kommer rubrikerna frÄn mappen ${CMAKE_CURRENT_SOURCE_DIR}/include, och om vÄrt bibliotek installeras i systemet och ansluts i ett annat projekt med kommandot , kommer rubrikerna att kopplas frÄn mappen include relativt installationsmappen.
target_include_directories(mylib INTERFACE
$
$
)Vi stÀller in sprÄkstandarden. SjÀlvklart, den senaste. Dessutom inkluderar vi inte bara standarden, utan sprider den till dem som kommer att anvÀnda vÄrt bibliotek. Detta uppnÄs genom att den instÀllda egenskapen har kategorin INTERFACE (se ).
target_compile_features(mylib INTERFACE cxx_std_17)Vi skapar ett alias för vÄrt bibliotek. Dessutom kommer det att vara i ett speciellt "namnrum" för att göra det snyggt. Detta kommer att vara anvÀndbart nÀr vÄrt bibliotek fÄr olika moduler och vi vill ansluta dem oberoende av varandra. .
add_library(Mylib::mylib ALIAS mylib)
Installation av vÄra rubriker i systemet. HÀr Àr det enkelt. Vi sÀger att mappen som innehÄller alla rubriker ska hamna i katalogen include relativt installationsplatsen.
install(DIRECTORY include/mylib DESTINATION include)DÀrefter informerar vi byggsystemet om att vi vill kunna kalla kommandot i externa projekt find_package(Mylib) och fÄ mÄlet Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Den följande kommandon ska förstÄs sÄ hÀr. NÀr vi i ett externt projekt anropar kommandot find_package(Mylib 1.2.3 REQUIRED), och om den faktiska versionen av det installerade biblioteket Àr inkompatibel med versionen 1.2.3, kommer CMake automatiskt att generera ett fel. Det betyder att vi inte behöver hÄlla reda pÄ versionerna manuellt.
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)
Om testerna Àr avstÀngda uttryckligen med hjÀlp av eller om vÄrt projekt Àr ett delprojekt, det vill sÀga om det Àr kopplat till ett annat CMake-projekt med kommandot , vi gÄr inte lÀngre ner i hierarkin, och skriptet som beskriver kommandona för att generera och köra testerna kommer helt enkelt inte att köras.
if(NOT MYLIB_TESTING)
message(STATUS "Testning av projektet Mylib avstÀngd")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib testas inte i delmoduls lÀge")
else()
add_subdirectory(test)
endif()
Dokumentationen kommer inte heller att genereras i fallet med ett delprojekt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
PÄ samma sÀtt kommer det inte att finnas nÄgra online-sandboxar för delprojektet.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Först letar vi efter paketet med det nödvÀndiga test-ramverket (byt ut mot ditt favoritrÀmsystem).
find_package(doctest 2.3.3 REQUIRED)Vi skapar vÄr körbara fil med tester. Vanligtvis lÀgger jag direkt i den körbara binÀren bara filen dÀr funktionen kommer att finnas main.
add_executable(mylib-unit-tests test_main.cpp)Men filer som beskriver sjÀlva testerna lÀgger jag till senare. Men det Àr inte nödvÀndigt att göra sÄ.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Vi kopplar in beroenden. Observera att vi kopplat endast de nödvÀndiga CMake-mÄlen till vÄr binÀrfil och inte anropat kommandot target_include_directories. Rubriker frÄn test-ramverket och frÄn vÄr Mylib::mylib, sÄvÀl som byggparams (i vÄrt fall Àr detta standardsprÄket C++) har gÄtt ihop med dessa mÄl.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Till slut skapar vi ett falskt mÄl, "bygget" av vilket Àr ekvivalent med att köra tester, och lÀgger till detta mÄl i standardbygget (detta ansvaras av attributet ALL). Detta innebÀr att standardbygget initierar körningen av testerna, det vill sÀga att vi aldrig glömmer att köra dem.
add_custom_target(check ALL COMMAND mylib-unit-tests)
DÀrefter aktiverar vi kodtÀckning mÀtning, om en motsvarande option har angetts. Jag gÄr inte in pÄ detaljerna eftersom de mer handlar om verktyget för tÀckningsmÀtning Àn CMake. Det Àr viktigt att notera att en mÄlinriktning kommer att skapas utifrÄn resultaten , som gör det bekvÀmt att köra kodtÀckning.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "MÀtning av kodtÀckning med tester Àr aktiverad")
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 "För att mÀta kodtÀckning med tester behövs programmet gcovr")
endif()
.
find_package(Doxygen)DÀrefter kontrollerar vi om anvÀndaren har stÀllt in en sprÄkvariabel. Om ja, lÄt den vara, om inte, tar vi ryska. Sedan konfigurerar vi Doxygen-systemfilerna. Alla nödvÀndiga variabler, inklusive sprÄk, hamnar dÀr under konfigurationen (se ).
DÀrefter skapar vi ett mÄl , som kommer att köra genereringen av dokumentationen. Eftersom genereringen av dokumentation inte Àr den största nödvÀndigheten i utvecklingsprocessen, kommer mÄlet som standard inte att vara aktiverat, utan mÄste startas explicit.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Russian)
endif()
message(STATUS "Doxygen-dokumentation kommer att genereras pÄ ${MYLIB_DOXYGEN_LANGUAGE}")
configure_file(Doxyfile.in Doxyfile)
add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()
HÀr hittar vi den tredje Python och skapar ett mÄl , som genererar en begÀran som överensstÀmmer med API:t för tjÀnsten , och skickar den. Svar kommer med en lÀnk till den fÀrdiga sandlÄdan.
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 "För att skapa en online-sandlÄda krÀvs en tolk för sprÄk Python version 3")
endif()
Nu ska vi se hur man anvÀnder allt detta.
Bygget av detta projekt, precis som vilket annat projekt pÄ CMake-byggsystemet, bestÄr av tvÄ steg:
cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggochkatalog [alternativ ...]Om kommandot ovan inte fungerar pÄ grund av en gammal version av CMake, försök att ta bort
-S:cmake sökvÀg/till/kÀllor -B sökvÀg/till/byggochkatalog [alternativ ...]
.
cmake --build sökvÀg/till/byggochkatalog [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [övriga alternativ ...]Aktivera mÄlet , med vilket man kan starta mÀtning av kodtÀckning med tester.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [övriga alternativ ...]Möjliggör att stÀnga av byggandet av modulÀra tester och mÄlet . Som en följd stÀngs mÀtningen av kodtÀckning med tester av (se. ).
Testning stÀngs ocksÄ automatiskt av om projektet inkluderas i ett annat projekt som en submodul med hjÀlp av kommandot .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [övriga alternativ ...]VÀxlar sprÄket för dokumentationen som mÄlet genererar till det angivna. Lista över tillgÀngliga sprÄk finns pÄ .
Som standard Àr ryska aktiverat.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allOm mÄlet inte anges (vilket Àr ekvivalent med mÄlet all), bygger allt som gÄr att bygga och anropar Àven mÄlet .
cmake --build path/to/build/directory --target mylib-unit-testsKommer att kompilera modulÀra tester. Detta Àr aktiverat som standard.
cmake --build path/k/samlings/directory --target checkKör de samlade (eller bygger om de inte redan Àr det) modulÀra testerna. Aktiverat som standard.
See also .
cmake --build path/k/samlings/directory --target coverageAnalyserar de körda (kör om de inte redan Àr det) modulÀra testerna för kodtÀckning med hjÀlp av programmet .
Utdata av tÀckningen kommer att se ut ungefÀr sÄ hÀr:
------------------------------------------------------------------------------
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 Àr endast tillgÀngligt nÀr alternativet Àr aktiverat .
See also .
cmake --build path/k/samlings/directory --target docStartar genereringen av dokumentation för koden med hjÀlp av systemet .
cmake --build path/k/samlings/directory --target wandboxSvaret frÄn tjÀnsten ser ungefÀr ut sÄ hÀr:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}För detta anvÀnds tjÀnsten . Jag vet inte hur hÄllbara deras servrar Àr, men jag tror inte att man bör missbruka denna möjlighet.
Byggandet av projektet i felsökningslÀge med mÀtning av tÀckning
cmake -S vÀg/till/kÀllkod -B vÀg/till/samlings/directory -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build vÀg/till/samlings/directory --target coverage --parallel 16Installation av projektet utan föregÄende bygg- och testning
cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggkatalog -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=sökvÀg/till/installationskatalog
cmake --build sökvÀg/till/byggkatalog --target installBygg i produktionslÀge med angiven kompilator
cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggkatalog -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=sökvÀg/till/katalog/dÀr/beroenden/Àr/installernade
cmake --build sökvÀg/till/byggkatalog --parallel 4Generering av dokumentation pÄ engelska
cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggkatalog -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build sökvÀg/till/byggkatalog --target doc
3.13
Faktiskt krÀvs CMake-version 3.13 bara för att köra vissa konsolkommandon som beskrivs i denna dokumentation. NÀr det gÀller syntax av CMake-skript Àr version 3.8 tillrÀcklig om generationen anropas pÄ andra sÀtt.
Testbibliotek
Testning kan stÀngas av (se ).
För att vÀxla sprÄk som dokumentationen ska genereras pÄ, finns det ett alternativ .
Tolk för programmeringssprÄk
För automatisk generation .
Med CMake och ett par bra verktyg kan man sÀkerstÀlla statisk analys med minimala insatser.
Cppcheck
CMake har inbyggt stöd för verktyget för statisk analys .
För detta behövs alternativet :
cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggkatalog -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I sökvÀg/till/kÀllor/include"Efter detta kommer den statiska analysen automatiskt att startas varje gÄng kÀllkoden kompileras och omkompileras. Inga ytterligare ÄtgÀrder behövs.
Clang
Med det fantastiska verktyget kan man ocksÄ köra statisk analys snabbt:
scan-build cmake -S sökvÀg/till/kÀllor -B sökvÀg/till/byggkatalog -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build sökvÀg/till/byggkatalogHÀr, till skillnad frÄn med Cppcheck, mÄste man alltid köra byggprocessen via scan-build.
CMake Àr ett mycket kraftfullt och flexibelt system som möjliggör implementation av funktionalitet pÄ alla sÀtt och vis. Och Àven om syntaktiska aspekter ibland lÀmnar mycket att önska, Àr det inte sÄ illa som det sÀgs. AnvÀnd CMake-byggsystemet till nytta för samhÀllet och för er hÀlsa.
â
KĂ€lla: habr.com
