
Bei der Entwicklung wechsle ich gern Compiler, Build-Modi, AbhĂ€ngigkeitsversionen, fĂŒhre statische Analysen durch, messe die Leistung, erstelle Coverage-Reports, generiere Dokumentationen usw. Und ich liebe CMake, weil es mir erlaubt, alles zu tun, was ich möchte.
Viele kritisieren CMake, oft zu Recht, aber wenn man genauer hinsieht, ist nicht alles so schlecht, und in letzter Zeit lÀuft es sogar ziemlich gut, und die Entwicklung ist durchaus positiv.
In diesem Beitrag möchte ich erklÀren, wie man eine Header-Bibliothek in C++ mit CMake relativ einfach organisieren kann, um die folgende FunktionalitÀt zu erhalten:
- Build;
- Automatischer Teststart;
- Code Coverage-Messung;
- Installation;
- Automatisierte Dokumentation;
- Generierung einer Online-Sandbox;
- Statische Analyse.
Wer sich bereits mit C++ und CMake auskennt, kann einfach und damit beginnen.
Inhalt
.
âââ 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.cppIm Folgenden wird erlĂ€utert, wie CMake-Skripte organisiert werden, weshalb sie im Detail behandelt werden. Die ĂŒbrigen Dateien kann jeder Interessierte direkt .
ZunĂ€chst muss die benötigte Version von CMake angefordert werden. CMake entwickelt sich weiter, die Kommandos Ă€ndern sich, das Verhalten unter verschiedenen Bedingungen. Damit CMake sofort versteht, was wir von ihm wollen, mĂŒssen wir unsere Anforderungen gleich festlegen.
cmake_minimum_required(VERSION 3.13)Dann geben wir unser Projekt, seinen Namen, die Version, die verwendeten Sprachen usw. an (siehe ).
In diesem Fall geben wir die Sprache an, CXX (das bedeutet C++), damit CMake nicht verwirrt wird und nicht nach einem C-Compiler sucht (standardmĂ€Ăig sind in CMake zwei Sprachen aktiviert: C und C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Hier können wir auch sofort ĂŒberprĂŒfen, ob unser Projekt in ein anderes Projekt als Unterprojekt integriert ist. Das wird in Zukunft sehr hilfreich sein.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Lassen Sie uns zwei Optionen vorsehen.
Die erste Option â â zur Deaktivierung von Modultests. Dies kann nĂŒtzlich sein, wenn wir sicher sind, dass mit den Tests alles in Ordnung ist und wir beispielsweise nur unser Projekt installieren oder paketieren möchten. Oder wenn unser Projekt als Unterprojekt integriert ist â in diesem Fall ist es fĂŒr die Benutzer unseres Projekts nicht interessant, unsere Tests auszufĂŒhren. Testen Sie nicht die AbhĂ€ngigkeiten, die Sie verwenden?
option(MYLIB_TESTING "Modultests aktivieren" ON)DarĂŒber hinaus werden wir eine separate Option schaffen zur Messung der Testabdeckungsquote, die jedoch zusĂ€tzliche Werkzeuge erfordert, daher muss sie ausdrĂŒcklich aktiviert werden.
option(MYLIB_COVERAGE "Messe die Testabdeckungsquote aktivieren" OFF)
NatĂŒrlich sind wir grandiose C++-Programmierer und möchten vom Compiler das Maximum an diagnostischen Informationen zur Compile-Zeit. Keine Maus wird unentdeckt bleiben.
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
)Auch Erweiterungen deaktivieren wir, um vollstĂ€ndig dem C++-Standard zu entsprechen. StandardmĂ€Ăig sind sie in CMake aktiviert.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Unsere Bibliothek besteht nur aus Header-Dateien, weswegen wir keine Ausgaben in Form von statischen oder dynamischen Bibliotheken haben. Andererseits, um unsere Bibliothek extern zu verwenden, muss sie installiert werden, damit sie im System erkannt und in Ihr Projekt eingebunden werden kann, wobei zusammen mit ihr auch diese Header-Anweisungen gebunden werden mĂŒssen, sowie möglicherweise einige zusĂ€tzliche Eigenschaften.
Zu diesem Zweck erstellen wir eine Schnittstellenbibliothek.
add_library(mylib INTERFACE)Binden Sie die Header an unsere Schnittstellenbibliothek.
Die moderne, trendige Nutzung von CMake impliziert, dass die Header, Eigenschaften usw. ĂŒber ein einziges Ziel ĂŒbergeben werden. Es genĂŒgt daher zu sagen , und alle Header, die mit dem Ziel assoziiert sind dependency, stehen den Quellcodes zur VerfĂŒgung, die zum Ziel gehören target. Und es sind keine [target_]include_directories. Dies wird unten bei der Analyse .
Es ist auch erwÀhnenswert, die sogenannten .
Dieser Befehl verknĂŒpft die benötigten Header mit unserer Schnittstellenbibliothek. Falls unsere Bibliothek zu einem Ziel innerhalb einer CMake-Hierarchie hinzugefĂŒgt wird, werden die Header aus dem Verzeichnis ${CMAKE_CURRENT_SOURCE_DIR}/include, und wenn unsere Bibliothek im System installiert und in ein anderes Projekt mittels des Befehls , dann werden die Header aus dem Verzeichnis verknĂŒpft include relativ zum Installationsverzeichnis.
target_include_directories(mylib INTERFACE
$
$
)Wir setzen den Sprachstandard fest. NatĂŒrlich den neuesten. Dabei aktivieren wir nicht nur den Standard, sondern machen ihn auch fĂŒr diejenigen verfĂŒgbar, die unsere Bibliothek nutzen werden. Dies wird dadurch erreicht, dass die eingestellte Eigenschaft die Kategorie INTERFACE (siehe ).
target_compile_features(mylib INTERFACE cxx_std_17)Wir erstellen einen Alias fĂŒr unsere Bibliothek. Schönheitshalber wird er in einem speziellen âNamensraumâ angelegt. Das ist nĂŒtzlich, wenn in unserer Bibliothek verschiedene Module erscheinen und wir diese unabhĂ€ngig einfĂŒgen wollen. .
add_library(Mylib::mylib ALIAS mylib)
Die Installation unserer Header im System. Hier ist alles einfach. Wir sagen, dass der Ordner mit all den Headern in das Verzeichnis include relativ zum Installationsort soll.
install(DIRECTORY include/mylib DESTINATION include)AnschlieĂend informieren wir das Build-System darĂŒber, dass wir wollen, dass in externen Projekten der Befehl find_package(Mylib) aufgerufen wird und das Ziel Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Der nĂ€chste Zauberspruch sollte so verstanden werden. Wenn wir in einem externen Projekt den Befehl find_package(Mylib 1.2.3 REQUIRED), und die tatsĂ€chliche Version der installierten Bibliothek inkompatibel mit der Version ist, 1.2.3, wird CMake automatisch einen Fehler generieren. Das heiĂt, man muss die Versionen nicht manuell ĂŒberwachen.
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)
Wenn Tests explizit mit oder unser Projekt ist ein Teilprojekt, das heiĂt, es wird in ein anderes CMake-Projekt mit dem Befehl verbunden , wir gehen nicht weiter in der Hierarchie und das Skript, das die Befehle zur Generierung und AusfĂŒhrung der Tests beschreibt, wird einfach nicht gestartet.
if(NOT MYLIB_TESTING)
message(STATUS "Das Testen des Mylib-Projekts ist deaktiviert")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib wird im Submodul-Modus nicht getestet")
else()
add_subdirectory(test)
endif()
Die Dokumentation wird ebenfalls nicht generiert, wenn es sich um ein Teilprojekt handelt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Ăhnlich wird es auch keine Online-Sandbox fĂŒr das Teilprojekt geben.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Zuerst finden wir das Paket mit dem benötigten Test-Framework (ersetzen Sie es durch Ihr Lieblingsframework).
find_package(doctest 2.3.3 REQUIRED)Wir erstellen unsere ausfĂŒhrbare Datei mit den Tests. In der Regel fĂŒge ich nur die Datei hinzu, die die Funktion enthĂ€lt, main.
add_executable(mylib-unit-tests test_main.cpp)Die Dateien, in denen die eigentlichen Tests beschrieben sind, fĂŒge ich spĂ€ter hinzu. Aber es ist nicht unbedingt erforderlich, das so zu machen.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Wir fĂŒgen AbhĂ€ngigkeiten hinzu. Beachten Sie, dass wir nur die benötigten CMake-Ziele an unsere BinĂ€rdatei gebunden haben und den Befehl nicht aufgerufen haben target_include_directories. Die Header aus dem Test-Framework und aus unserem Mylib::mylib, sowie die Build-Parameter (in unserem Fall der Sprachstandard C++) wurden zusammen mit diesen Zielen ĂŒbernommen.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)SchlieĂlich erstellen wir ein Dummy-Ziel, dessen "Build" dem AusfĂŒhren der Tests entspricht, und fĂŒgen dieses Ziel zum Standard-Build hinzu (das wird durch das Attribut ALL). Das bedeutet, dass der Standard-Build den Testlauf initiiert, das heiĂt, wir vergessen niemals, sie auszufĂŒhren.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Dann aktivieren wir die Codeabdeckungsmessung, wenn die entsprechende Option angegeben ist. Ich werde nicht ins Detail gehen, da diese mehr mit dem Werkzeug zur Messung der Abdeckung zu tun hat als mit CMake. Es ist nur wichtig zu beachten, dass auf Grundlage der Ergebnisse ein Ziel erstellt wird , mit dem es einfach ist, die Abdeckung zu messen.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Die Messung der Codeabdeckung durch Tests ist aktiviert")
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 die Messung der Codeabdeckung durch Tests wird das Programm gcovr benötigt")
endif()
.
find_package(Doxygen)Als NĂ€chstes ĂŒberprĂŒfen wir, ob der Benutzer die Sprache eingestellt hat. Wenn ja, Ă€ndern wir nichts, wenn nicht, nehmen wir Russisch. Dann konfigurieren wir die Dateien des Doxygen-Systems. Alle notwendigen Variablen, einschlieĂlich der Sprache, gelangen wĂ€hrend des Konfigurationsprozesses dorthin (siehe ).
Danach erstellen wir ein Ziel , das die Generierung der Dokumentation ausfĂŒhrt. Da die Generierung der Dokumentation im Entwicklungsprozess nicht die gröĂte Notwendigkeit ist, wird das Ziel standardmĂ€Ăig nicht aktiviert und muss explizit gestartet werden.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Russisch)
endif()
message(STATUS "Doxygen-Dokumentation wird in ${MYLIB_DOXYGEN_LANGUAGE} generiert")
configure_file(Doxyfile.in Doxyfile)
add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()
Hier finden wir den dritten Python und erstellen ein Ziel , das eine Anfrage generiert, die der API des Dienstes entspricht , und sendet sie. Als Antwort erhalten wir einen Link zur fertigen Sandbox.
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 die Erstellung eines Online-Sandbox wird ein Interpreter fĂŒr die Programmiersprache Python der 3. Version benötigt")
endif()
Jetzt schauen wir uns an, wie man all dies benutzt.
Der Build dieses Projekts, wie auch jedes andere Projekt im CMake-Bausystem, besteht aus zwei Phasen:
cmake -S pfad/zur/quelle -B pfad/zum/build/verzeichnis [optionen ...]Wenn der obige Befehl wegen einer alten Version von CMake nicht funktioniert hat, versuchen Sie es mit
-S:cmake pfad/zur/quelle -B pfad/zum/build/verzeichnis [optionen ...]
.
cmake --build pfad/zum/build/verzeichnis [--target ziel].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [weitere optionen ...]Aktiviert das Ziel , mit dessen Hilfe man die Codeabdeckung durch Tests ĂŒberprĂŒfen kann.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [weitere Optionen ...]Bietet die Möglichkeit, den Bau von Modultests und Ziel auszuschalten . Infolgedessen wird die Codeabdeckung durch Tests abgeschaltet (siehe ).
Das Testen wird auch automatisch deaktiviert, wenn das Projekt als Unterprojekt durch den Befehl angeschlossen wird .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [weitere Optionen ...]Wechselt die Sprache der Dokumentation, die durch das Ziel generiert wird auf die angegebene. Eine Liste verfĂŒgbarer Sprachen finden Sie auf .
StandardmĂ€Ăig ist Russisch aktiviert.
cmake --build pfad/zum/bauverzeichnis
cmake --build pfad/zum/bauverzeichnis --target allWenn das Ziel nicht angegeben wird (was dem Ziel entspricht all), wird alles gesammelt, was möglich ist, und das Ziel wird aufgerufen .
cmake --build pfad/zum/bauverzeichnis --target mylib-unit-testsKompiliert die Modultests. StandardmĂ€Ăig aktiviert.
cmake --build pfad/zur/bauverzeichnis --target checkFĂŒhrt die gesammelten (fĂŒhrt aus, wenn noch nicht geschehen) Modultests aus. StandardmĂ€Ăig aktiviert.
Siehe auch .
cmake --build pfad/zur/bauverzeichnis --target coverageAnalysiert die ausgefĂŒhrten (fĂŒhrt aus, wenn noch nicht geschehen) Modultests auf Codeabdeckung mit dem Programm .
Die Ausgabe der Abdeckung wird etwa so aussehen:
------------------------------------------------------------------------------
GCC Code Coverage Report
Verzeichnis: /pfad/zum/cmakecpptemplate/include/
------------------------------------------------------------------------------
Datei Zeilen AusfĂŒhrung Abdeckung Fehlend
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
GESAMT 2 2 100%
------------------------------------------------------------------------------Das Ziel ist nur verfĂŒgbar, wenn die Option aktiviert ist .
Siehe auch .
cmake --build pfad/zur/bauverzeichnis --target docStartet die Generierung von Dokumentationen zum Code mit dem System .
cmake --build pfad/zur/bauverzeichnis --target wandboxDie Antwort vom Dienst sieht etwa so aus:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}DafĂŒr wird ein Dienst verwendet . Ich weiĂ nicht, wie stabil ihre Server sind, aber ich denke, dass man diese Möglichkeit nicht ĂŒberstrapazieren sollte.
Projektbau im Debug-Modus mit Abdeckungserfassung
cmake -S pfad/zum/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build pfad/zum/bauverzeichnis --target coverage --parallel 16Installation des Projekts ohne vorherige Kompilierung und Tests
cmake -S pfad/zur/quellcode -B pfad/zum/bauverzeichnis -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=pfad/zur/installationsverzeichnis
cmake --build pfad/zum/bauverzeichnis --target installBau im Release-Modus mit dem angegebenen Compiler
cmake -S pfad/zur/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=pfad/zur/directory/wo/abhÀngigkeiten/installiert/sind
cmake --build pfad/zum/bauverzeichnis --parallel 4Generierung der Dokumentation in Englisch
cmake -S pfad/zur/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build pfad/zum/bauverzeichnis --target doc
3.13
TatsĂ€chlich wird CMake-Version 3.13 nur fĂŒr das AusfĂŒhren einiger Konsolenbefehle benötigt, die in dieser Dokumentation beschrieben sind. Was die Syntax der CMake-Skripte betrifft, reicht Version 3.8 aus, wenn die Generierung auf andere Weise aufgerufen wird.
Testbibliothek
Tests können deaktiviert werden (siehe ).
Um die Sprache zu wechseln, in der die Dokumentation generiert wird, gibt es eine Option .
Interpreter fĂŒr Programmiersprachen
FĂŒr die automatische Generierung .
Mit CMake und ein paar guten Werkzeugen kann eine statische Analyse mit minimalem Aufwand sichergestellt werden.
Cppcheck
In CMake ist die UnterstĂŒtzung fĂŒr ein statisches Analysetool integriert .
DafĂŒr muss die Option verwendet werden :
cmake -S pfad/zur/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Ipfad/zur/quellcode/include"Danach wird die statische Analyse automatisch bei jeder Kompilierung und Neukompilierung des Quellcodes gestartet. Es sind keine weiteren MaĂnahmen erforderlich.
Microsoft Visual C++
Mit dem wundervollen Werkzeug kann auch die statische Analyse im Handumdrehen gestartet werden:
scan-build cmake -S pfad/zur/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build pfad/zum/bauverzeichnisHier muss im Gegensatz zum Fall mit Cppcheck jedes Mal der Bau ĂŒber scan-build.
CMake ist ein sehr leistungsfĂ€higes und flexibles System, das es ermöglicht, FunktionalitĂ€ten ganz nach Wunsch zu realisieren. Und obwohl die Syntax manchmal zu wĂŒnschen ĂŒbrig lĂ€sst, ist der Teufel nicht so schlimm, wie er dargestellt wird. Nutzen Sie das CMake-Bausystem zum Wohle der Gesellschaft und zur Gesundheit.
â
Quelle: habr.com
