
I enjoy changing compilers, build modes, dependency versions, performing static analysis, measuring performance, collecting coverage, generating documentation, etc. during the development process. I really love CMake because it allows me to do everything I want.
Many criticize CMake, and often justifiably, but if you delve into it, it's not so bad, and recently it's actually quite good, and the development direction is quite positive.
In this note, I want to explain how to easily organize a header library in C++ using CMake to achieve the following functionality:
- Build;
- Automatic test launch;
- Code coverage measurement;
- Installation;
- Auto-documentation;
- Online sandbox generation;
- Static analysis.
Those who already understand C++ and CMake can simply and start using it.
Innhold
.
âââ 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 organizing CMake scripts, so they will be examined in detail. Anyone interested can directly view the other files .
First, you need to request the required version of CMake. CMake evolves, command signatures change, behavior varies under different conditions. To ensure CMake understands what we want from it right away, we need to define our requirements to it from the start.
cmake_minimum_required(VERSION 3.13)Then we will specify our project, its name, version, used languages, etc. (see ).
In this case, we specify the language CXX (which means C++), so CMake doesn't have to worry about searching for a C language compiler (by default, CMake includes two languages: C and C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Her kan du ogsÄ sjekke om prosjektet vÄrt er inkludert i et annet prosjekt som et underprosjekt. Dette vil vÊre til stor hjelp fremover.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Vi vil forutse to alternativer.
Det fĂžrste alternativet â â for Ă„ deaktivere modulĂŠre tester. Dette kan vĂŠre nĂždvendig hvis vi er sikre pĂ„ at testene er i orden, og vi Ăžnsker for eksempel bare Ă„ installere eller pakke prosjektet vĂ„rt. Eller hvis prosjektet vĂ„rt er inkludert som et underprosjekt â i sĂ„ fall vil brukeren av prosjektet vĂ„rt ikke vĂŠre interessert i Ă„ kjĂžre testene vĂ„re. Tester du ikke avhengighetene du bruker?
option(MYLIB_TESTING "Aktiver modulÊrtesting" ON)I tillegg vil vi lage et eget alternativ for Ä mÄle testdekning, men dette vil kreve ekstra verktÞy, sÄ det mÄ aktiveres eksplisitt.
option(MYLIB_COVERAGE "Aktiver mÄling av testdekning" OFF)
SelvfÞlgelig er vi dyktige C++-programmerere, sÄ vi Þnsker Ä fÄ maksimal diagnoseverdi fra kompilatoren pÄ kompileringstid. Ingen feil skal glippe.
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 utvidelser for Ä vÊre i full overensstemmelse med C++-standarden. Som standard er de aktivert i CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Biblioteket vÄrt bestÄr kun av header-filer, noe som betyr at vi ikke har noen utganger i form av statiske eller dynamiske biblioteker. PÄ den andre siden, for Ä bruke biblioteket vÄrt eksternt, mÄ det installeres, det mÄ kunne oppdages i systemet og kobles til prosjektet ditt, og samtidig mÄ de tilknyttede headerne vÊre tilgjengelige, samt kanskje noen ekstra egenskaper.
For dette formÄlet lager vi et grensesnittbibliotek.
add_library(mylib INTERFACE)Vi binder headerne til grensesnittbiblioteket vÄrt.
Moderne, trendy, ungdommelig bruk av CMake betyr at headerne, egenskapene osv. overfÞres gjennom ett enkelt mÄl. Dermed er det nok Ä si , og alle headerne som er assosiert med mÄlet dependency, vil vÊre tilgjengelige for kildekoden tilknyttet mÄlet mÄl. Og det er ikke nÞdvendig med noen [target_]include_directories. Dette vil bli demonstrert nedenfor ved Ä analysere .
Det er ogsÄ verdt Ä merke seg den sÄkalte .
Denne kommandoen assosierer nÞdvendige overskrifter med vÄrt grensesnittsbibliotek, og dersom biblioteket vÄrt blir koblet til et mÄl innenfor en hierarki av CMake, vil overskriftene fra mappen ${CMAKE_CURRENT_SOURCE_DIR}/include, bli assosiert med det, men hvis biblioteket vÄrt er installert i systemet og koblet til et annet prosjekt ved hjelp av kommandoen , vil overskriftene bli assosiert fra mappen include relativt til installasjonsmappen.
target_include_directories(mylib INTERFACE
$
$
)Vi setter sprÄknormen. SelvfÞlgelig den nyeste. Dette inkluderer ikke bare Ä aktivere normen, men ogsÄ Ä utvide den til dem som bruker biblioteket vÄrt. Dette oppnÄs ved at egenskapen som er satt har kategorien INTERFACE (se. ).
target_compile_features(mylib INTERFACE cxx_std_17)Vi lager et alias for biblioteket vÄrt. Og for Ä gjÞre det penere vil det vÊre i et spesial 'navnerom'. Dette vil vÊre nyttig nÄr vÄrt bibliotek fÄr forskjellige moduler, og vi Þnsker Ä koble dem uavhengig av hverandre. .
add_library(Mylib::mylib ALIAS mylib)
Installasjon av vÄre overskrifter i systemet. Her er det enkelt. Vi sier at mappen med alle overskriftene skal plasseres i mappen include relativt til installasjonsstedet.
install(DIRECTORY include/mylib DESTINATION include)Videre informerer vi bygesystemet om at vi Þnsker Ä kunne kalle 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 neste kommandoen mÄ forstÄs slik. NÄr vi i et eksternt prosjekt kaller kommandoen find_package(Mylib 1.2.3 REQUIRED), og den faktiske versjonen av det installerte biblioteket viser seg Ä vÊre inkompatibel med versjonen 1.2.3, vil CMake automatisk generere en feil. Det betyr at man ikke trenger Ä fÞlge med pÄ versjonene 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 deaktivert eksplisitt ved hjelp av eller prosjektet vÄrt er et delprosjekt, det vil si koblet til et annet CMake-prosjekt ved hjelp av kommandoen , vi gÄr ikke videre i hierarkiet, og skriptet som beskriver kommandoene for Ä generere og kjÞre tester, blir ganske enkelt ikke kjÞrt.
if(NOT MYLIB_TESTING)
message(STATUS "Testing av Mylib-prosjektet er slÄtt av")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib blir ikke testet i undermodulmodus")
else()
add_subdirectory(test)
endif()
Dokumentasjonen vil heller ikke bli generert i tilfelle av et delprosjekt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Tilsvarende vil det heller ikke vĂŠre noen online sandkasser for delprosjektet.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
FĂžrst finner vi pakken med det nĂždvendige testrammeverket (erstatt med din favoritt).
find_package(doctest 2.3.3 REQUIRED)Vi oppretter vÄr kjÞrbare fil med tester. Vanligvis legger jeg bare til filen som inneholder funksjonen direkte i den kjÞrbare binÊren main.
add_executable(mylib-unit-tests test_main.cpp)Men filene som beskriver testene selv, legger jeg til senere. Men det er ikke nĂždvendig Ă„ gjĂžre det slik.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Vi kobler til avhengigheter. VÊr oppmerksom pÄ at vi har knyttet bare de nÞdvendige CMake-mÄlene til vÄr binÊrfil og ikke kalt kommandoen target_include_directories. Overskriftene fra testrammeverket og fra vÄr Mylib::mylib, samt byggparametrene (i vÄrt tilfelle er dette sprÄkstandarden C++) kom med disse mÄlene.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Til slutt oppretter vi et falskt mÄl, der byggingen er ekvivalent med Ä kjÞre testene, og legger dette mÄlet inn i standardbyggingen (dette styres av attributtet ALL). Dette betyr at standardbyggingen initierer testkjÞringen, slik at vi aldri glemmer Ä kjÞre dem.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Deretter aktiverer vi kodedekningstesting hvis det er angitt en tilsvarende opsjon. Jeg vil ikke gÄ inn i detaljene, fordi de mer handler om instrumentet for dekningstesting enn om CMake. Det er bare viktig Ä merke seg at det vil bli opprettet et mÄl , som gjÞr det enkelt Ä kjÞre dekningstesting.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Testing code coverage measurement is enabled")
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 "The gcovr program is required for code coverage measurements")
endif()
.
find_package(Doxygen)Next, we check if the user has set a language variable. If yes, we leave it as is; if no, we take Russian. Then we configure the Doxygen system files. All necessary variables, including the language, get included during the configuration process (see ).
After that, we create a target , which will run the documentation generation. Since generating documentation is not the highest priority in the development process, this target will not be included by default and must be run explicitly.
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 ()
Here we find the third Python and create a target , which generates a request corresponding to the API of the service , and sends it. In response, a link to the ready sandbox is provided.
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 "An interpreter for the 3rd version of the Python language is required to create an online sandbox")
endif()
Now let's look at how to use all of this.
Building this project, like any other project on the CMake build system, consists of two stages:
cmake -S path/to/source -B path/to/build/directory [options ...]If the above command did not work due to an older version of CMake, try omitting
-S:cmake path/to/source -B path/to/build/directory [options ...]
.
cmake --build path/to/build/directory [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [other options ...]Includes the target , som lar deg starte koding dekning mÄlinger med tester.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [andre opsjoner ...]Gir mulighet til Ä deaktivere byggingen av enhetstester og mÄl . Som et resultat deaktiveres kode dekning mÄlinger (se ).
Testing deaktiveres ogsÄ automatisk hvis prosjektet kobles til et annet prosjekt som et subprosjekt med kommandoen .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [andre opsjoner ...]Bytter sprÄket til dokumentasjonen som genereres av mÄlet til det angitte. Liste over tilgjengelige sprÄk se pÄ .
Som standard er russisk aktivert.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allHvis ikke noe mÄl er spesifisert (som er ekvivalent med mÄlet all), bygger alt som er mulig, og kaller ogsÄ mÄlet .
cmake --build path/to/build/directory --target mylib-unit-testsKompilerer enhetstestene. Inkludert som standard.
cmake --build path/til/samlingsmappe --target checkKjĂžrer de kompilerte (bygger hvis ikke allerede) enhetstestene. Inkludert som standard.
Se ogsÄ .
cmake --build path/til/samlingsmappe --target coverageAnalyserer kjĂžrte (kjĂžrer hvis ikke allerede) enhetstester for kode dekning ved hjelp av programmet .
Kodedekningsutdataene vil se om lag slik ut:
------------------------------------------------------------------------------
GCC Kode Dekningsrapport
Katalog: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
Fil Linjer Exec Dekning Manglende
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------MÄlene er bare tilgjengelig nÄr alternativet er aktivert .
Se ogsÄ .
cmake --build path/til/samlingsmappe --target docStarter genereringen av dokumentasjon for koden ved hjelp av systemet .
cmake --build path/til/samlingsmappe --target wandboxSvaret fra tjenesten ser omtrent slik ut:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Dette gjĂžres ved hjelp av tjenesten . Jeg vet ikke hvor robuste serverne deres er, men jeg tror ikke det er lurt Ă„ misbruke denne muligheten.
Bygger prosjektet i feilfinningstilstand med dekning mÄling
cmake -S path/til/kildekode -B path/til/samlingsmappe -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build path/til/samlingsmappe --target coverage --parallel 16Installasjon av prosjektet uten forhÄndskomplisering og testing
cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=sti/til/installeringsdirectory
cmake --build sti/til/bygningsdirectory --target installBygging i utgave-modus med angitt kompilator
cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=sti/til/directory/der/avhengigheter/er/installert
cmake --build sti/til/bygningsdirectory --parallel 4Generering av dokumentasjon pÄ engelsk
cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build sti/til/bygningsdirectory --target doc
3.13
Faktisk er CMake versjon 3.13 kun nÞdvendig for Ä kjÞre noen konsollkommandoer beskrevet i denne hjelpen. NÄr det gjelder syntaksen for CMake-skript, er versjon 3.8 tilstrekkelig hvis genereringen skjer pÄ andre mÄter.
Testingsbibliotek
Testing kan deaktiveres (se ).
For Ä bytte sprÄk som dokumentasjonen skal genereres pÄ, er det et alternativ .
Tolk for programmeringssprÄk
For automatisk generering .
Med CMake og et par gode verktÞy kan man pÄfÞre statisk analyse med minimal innsats.
Cppcheck
CMake har innebygd stĂžtte for verktĂžy for statisk analyse .
For dette mÄ man bruke alternativet :
cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Isti/til/kildekode/include"Etter dette vil den statiske analysen automatisk bli kjĂžrt hver gang koden kompilert og recompilert. Man trenger ikke Ă„ gjĂžre noe ekstra.
Clang
Ved hjelp av det fantastiske verktÞyet kan man ogsÄ kjÞre statisk analyse pÄ et blunk:
scan-build cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build sti/til/bygningsdirectoryHer, i motsetning til tilfelle med Cppcheck, er det nĂždvendig Ă„ kjĂžre byggingen hver gang via scan-build.
CMake er et svÊrt kraftig og fleksibelt system som lar deg implementere funksjonalitet etter enhver smak. Selv om syntaksen av og til kunne vÊrt bedre, er det ikke sÄ skummelt som det ser ut. Bruk CMake-bygget til samfunnets beste og til egen nytte.
â
Kilde: habr.com
