
In der Entwicklungsphase wechsle ich gerne Compiler, Build-Modi, AbhĂ€ngigkeitsversionen, fĂŒhre statische Analysen durch, messe die Performance, erstelle Deckungsberichte, generiere Dokumentationen usw. Besonders begeistere ich mich fĂŒr CMake, da es mir ermöglicht, all dies nach meinen Vorstellungen zu tun.
Viele kritisieren CMake, und oft zu Recht. Doch wenn man genau hin schaut, ist nicht alles so schlecht, und in letzter Zeit ist es sogar recht gut, und die Entwicklungsrichtung macht einen durchaus positiven Eindruck.
In diesem Artikel möchte ich erlÀutern, wie man recht einfach eine Header-Bibliothek in C++ mit CMake organisiert, um folgende FunktionalitÀten zu erhalten:
- Build-Prozess;
- Automatisches Testen;
- Code Coverage-Messung;
- Installation;
- Automatische Dokumentation;
- Erzeugung einer Online-Sandbox;
- Statische Analyse.
Wer sich bereits mit C++ und CMake auskennt, kann einfach und damit arbeiten.
Inhalt
.
âââ CMakeLists.txt
âââ README.de.md
âââ README.md
âââ doc
â âââ CMakeLists.txt
â âââ Doxyfile.in
âââ include
â âââ mylib
â âââ myfeature.hpp
âââ online
â âââ CMakeLists.txt
â âââ mylib-beispiel.cpp
â âââ wandbox.py
âââ test
âââ CMakeLists.txt
âââ mylib
â âââ myfeature.cpp
âââ test_main.cppIm Mittelpunkt steht, wie man CMake-Skripte organisiert, weshalb diese ausfĂŒhrlich behandelt werden. Andere Dateien können von jedem Interessierten direkt angesehen werden .
ZunĂ€chst mĂŒssen wir die benötigte Version von CMake anfordern. CMake entwickelt sich weiter, die Befehls-Signaturen und das Verhalten unter verschiedenen Bedingungen Ă€ndern sich. Damit CMake sofort versteht, was wir von ihm wollen, sollten wir gleich zu Beginn unsere Anforderungen festlegen.
cmake_minimum_required(VERSION 3.13)Dann spezifizieren wir unser Projekt, seinen Namen, die Version, die verwendeten Sprachen und weitere Details (siehe ).
In diesem Fall geben wir die Sprache an CXX (das bedeutet C++), damit CMake nicht nach dem C-Compiler sucht (standardmĂ€Ăig sind in CMake zwei Sprachen enthalten: C und C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Hier können Sie auch sofort ĂŒberprĂŒfen, ob unser Projekt als Unterprojekt in ein anderes Projekt integriert ist. Das wird uns in Zukunft erheblich helfen.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Wir sehen zwei Optionen vor.
Die erste Option â â zum Deaktivieren der Modultests. Dies könnte erforderlich sein, wenn wir sicher sind, dass die Tests in Ordnung sind und wir beispielsweise nur unser Projekt installieren oder paketieren möchten. Oder unser Projekt ist als Unterprojekt integriert â in diesem Fall ist es fĂŒr den Benutzer unseres Projekts uninteressant, unsere Tests auszufĂŒhren. Testen Sie schlieĂlich nicht die AbhĂ€ngigkeiten, die Sie verwenden?
option(MYLIB_TESTING "Modultests aktivieren" ON)DarĂŒber hinaus erstellen wir eine separate Option zur Messung der Testabdeckung, die jedoch zusĂ€tzliche Werkzeuge erfordert, weshalb sie ausdrĂŒcklich aktiviert werden muss.
option(MYLIB_COVERAGE "Messung der Testabdeckung aktivieren" OFF)
NatĂŒrlich sind wir groĂartige C++-Programmierer, und deshalb möchten wir vom Compiler den maximalen Diagnosestandard zur Compilezeit. Keine Maus wird hier durchrutschen.
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
)Wir werden auch die Erweiterungen deaktivieren, um den C++-Standards vollstĂ€ndig zu entsprechen. StandardmĂ€Ăig sind sie in CMake aktiviert.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Unsere Bibliothek besteht ausschlieĂlich aus Header-Dateien, daher verfĂŒgen wir nicht ĂŒber statische oder dynamische Bibliotheken. Um unsere Bibliothek jedoch extern nutzen zu können, muss sie installiert werden, damit sie im System gefunden und in Ihr Projekt integriert werden kann, wobei ebenfalls die Header-Dateien und möglicherweise weitere Eigenschaften gebunden sind.
Zu diesem Zweck erstellen wir eine Schnittstellenbibliothek.
add_library(mylib INTERFACE)Wir binden die Header an unsere Schnittstellenbibliothek.
Die moderne, trendige Nutzung von CMake impliziert, dass Header, Eigenschaften usw. durch ein einziges Ziel ĂŒbergeben werden. Daher reicht es aus zu sagen , und alle Ăberschriften, die mit dem Ziel assoziiert sind dependency, werden fĂŒr die ZieleigentĂŒmer verfĂŒgbar sein target. Und es sind keine [target_]include_directories. Dies wird unten bei der Analyse des .
Es ist auch wichtig, auf die sogenannten .
Dieser Befehl verknĂŒpft die benötigten Header mit unserer Schnittstellenbibliothek. Wenn unsere Bibliothek in einer CMake-Hierarchie an ein Ziel angeschlossen wird, werden ihr die Header aus dem Verzeichnis ${CMAKE_CURRENT_SOURCE_DIR}/include, und wenn unsere Bibliothek im System installiert und von einem anderen Projekt ĂŒber den Befehl , verwendet wird, werden ihr die Header aus dem Verzeichnis include bezĂŒglich des Installationsverzeichnisses.
target_include_directories(mylib INTERFACE
$
$
)Legen wir den Sprachstandard fest. NatĂŒrlich die neueste Version. Dabei aktivieren wir nicht nur den Standard, sondern sorgen auch dafĂŒr, dass er auf diejenigen ausgeweitet wird, die unsere Bibliothek verwenden werden. Dies wird erreicht, indem die gesetzte Eigenschaft die Kategorie INTERFACE erstellen (siehe ).
target_compile_features(mylib INTERFACE cxx_std_17)Wir erstellen ein Alias fĂŒr unsere Bibliothek. Zu dekorativen Zwecken wird dies in einem speziellen âNamensraumâ geschehen. Das ist hilfreich, wenn in unserer Bibliothek verschiedene Module erscheinen und wir diese unabhĂ€ngig voneinander einbinden möchten. .
add_library(Mylib::mylib ALIAS mylib)
Installation unserer Header in das System. Hier ist alles einfach. Wir geben an, dass der Ordner mit allen Headers in das Verzeichnis include relativ zum Installationsort.
install(DIRECTORY include/mylib DESTINATION include)Als NĂ€chstes informieren wir das Build-System darĂŒber, dass wir die Möglichkeit haben möchten, in externen Projekten den Befehl find_package(Mylib) aufrufen zu können und das Ziel Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Der nĂ€chste Befehl sollte folgendermaĂen 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 1.2.3Version ist, wird CMake automatisch einen Fehler generieren. Das bedeutet, dass man die Versionen nicht manuell ĂŒberwachen muss.
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 die Tests explizit mit deaktiviert sind oder unser Projekt ein Unterprojekt ist, das heiĂt, es ist in ein anderes CMake-Projekt ĂŒber die Anweisung , fahren wir in der Hierarchie nicht weiter fort, und das Skript, das die Anweisungen fĂŒr die Generierung und AusfĂŒhrung der Tests beschreibt, wird einfach nicht ausgefĂŒhrt.
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 Unterprojekt handelt.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Ăhnlich wird es auch keine Online-Sandbox fĂŒr das Unterprojekt geben.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Zuerst finden wir das Paket mit dem gewĂŒnschten Test-Framework (ersetzen Sie es durch Ihr Lieblingsframework).
find_package(doctest 2.3.3 REQUIRED)Wir erstellen unsere ausfĂŒhrbare Datei mit den Tests. Normalerweise fĂŒge ich in die ausfĂŒhrbare BinĂ€rdatei nur die Datei hinzu, die die Funktion enthĂ€lt main.
add_executable(mylib-unit-tests test_main.cpp)Die Dateien, in denen die Tests selbst beschrieben werden, fĂŒge ich spĂ€ter hinzu. Aber das ist nicht unbedingt notwendig.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Wir binden die AbhÀngigkeiten ein. Beachten Sie, dass wir an unser BinÀrprogramm nur die benötigten CMake-Ziele gebunden haben und den Befehl nicht aufgerufen haben. target_include_directories. Die Header-Dateien aus dem Testframework und aus unserem Mylib::mylib, sowie die Build-Parameter (in unserem Fall ist das der C++-Standard) wurden zusammen mit diesen Zielen hineingebracht.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)SchlieĂlich erstellen wir ein fiktives Ziel, dessen "Build" dem AusfĂŒhren der Tests entspricht, und fĂŒgen dieses Ziel zur Standard-Build-Konfiguration hinzu (dafĂŒr ist das Attribut verantwortlich ALL). Das bedeutet, dass der Standard-Build den Testlauf initiiert, sodass wir niemals vergessen, sie auszufĂŒhren.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Dann aktivieren wir die Messung der Codeabdeckung, wenn die entsprechende Option gesetzt ist. Ich werde nicht ins Detail gehen, da diese mehr zum Tool zur Messung der Abdeckung gehören als zu CMake. Es ist nur wichtig zu beachten, dass auf Grundlage der Ergebnisse ein Ziel erstellt wird, , mit dem die Codeabdeckung bequem gestartet werden kann.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "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 "Das Programm gcovr ist erforderlich, um die Codeabdeckung zu messen.")
endif()
.
find_package(Doxygen)Nun ĂŒberprĂŒfen wir, ob der Benutzer eine Sprachvariable gesetzt hat. Wenn ja, Ă€ndern wir sie nicht, andernfalls verwenden wir Russisch. AnschlieĂend konfigurieren wir die Doxygen-Systemdateien. Alle benötigten Variablen, einschlieĂlich der Sprache, werden wĂ€hrend der Konfiguration ĂŒbernommen (siehe ).
Daraufhin erstellen wir ein Ziel , das die Dokumentationserstellung auslösen wird. Da die Dokumentationserstellung wĂ€hrend des Entwicklungsprozesses nicht die höchste PrioritĂ€t hat, wird das Ziel standardmĂ€Ăig nicht aktiviert sein und muss manuell gestartet werden.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Deutsch)
endif()
message(STATUS "Doxygen-Dokumentation wird in ${MYLIB_DOXYGEN_LANGUAGE} erstellt")
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 erstellt, die dem API-Dienst entspricht , und sendet sie ab. 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 einer Online-Sandbox wird ein Interpreter fĂŒr die Programmiersprache Python Version 3 benötigt")
endif()
Nun schauen wir uns an, wie man das alles benutzt.
Der Aufbau dieses Projekts, wie auch bei jedem anderen Projekt im CMake-Bausystem, besteht aus zwei Phasen:
cmake -S pfad/zum/quellcode -B pfad/zum/bauverzeichnis [optionen ...]Falls der obige Befehl aufgrund einer alten CMake-Version nicht funktioniert hat, versuchen Sie, -S wegzulassen
-S:cmake pfad/zum/quellcode -B pfad/zum/bauverzeichnis [optionen ...]
.
cmake --build pfad/zur/bau/directory [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [weitere Optionen ...]Aktiviert das Ziel , mit dem die Codeabdeckung durch Tests gemessen werden kann.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [weitere Optionen ...]Bietet die Möglichkeit, den Bau von Modultests und das Ziel auszuschalten . Dadurch wird die Codeabdeckung durch Tests ausgeschaltet (siehe ).
Tests werden auch automatisch deaktiviert, wenn das Projekt als Unterprojekt in ein anderes Projekt integriert wird ĂŒber den Befehl .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [weitere Optionen ...]Wechselt die Sprache der Dokumentation, die durch das Ziel generiert wird in die angegebene. Eine Liste der verfĂŒgbaren Sprachen finden Sie auf .
StandardmĂ€Ăig ist Russisch aktiviert.
cmake --build pfad/zur/bau/directory
cmake --build pfad/zur/bau/directory --target allWenn kein Ziel angegeben ist (was dem Ziel entspricht all), wird alles, was möglich ist, gebaut, und es wird das Ziel aufgerufen .
cmake --build pfad/zur/bau/directory --target mylib-unit-testsKompiliert die Modultests. StandardmĂ€Ăig aktiviert.
cmake --build pfad/zur/bau/directory --target checkFĂŒhrt die gebauten (baut sie, wenn noch nicht) Modultests aus. StandardmĂ€Ăig aktiviert.
Siehe auch .
cmake --build pfad/zur/bauverzeichnis --target coverageAnalysiert die laufenden (startet, falls noch nicht geschehen) Modultests auf Codeabdeckung mithilfe des Programms .
Der Abdeckungsbericht wird ungefÀhr so aussehen:
------------------------------------------------------------------------------
GCC Code Coverage Report
Verzeichnis: /pfad/zu/cmakecpptemplate/include/
------------------------------------------------------------------------------
Datei Zeilen AusfĂŒhrung Abdeckung Fehlende
------------------------------------------------------------------------------
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 Dokumentationserzeugung fĂŒr den Code mit dem System .
cmake --build pfad/zur/bauverzeichnis --target wandboxDie Antwort vom Dienst sieht ungefÀhr so aus:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}HierfĂŒr wird der Dienst genutzt . Ich weiĂ nicht, wie leistungsfĂ€hig ihre Server sind, aber ich denke, man sollte diese Möglichkeit nicht ĂŒberstrapazieren.
Baut das Projekt im Debug-Modus mit Abdeckungsmessung
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 Erstellung und Testung
cmake -S pfad/zum/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/zum/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/zum/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build pfad/zum/bauverzeichnis --target doc
3.13
TatsĂ€chlich ist CMake Version 3.13 nur erforderlich, um einige der in diesem Handbuch beschriebenen Konsolenbefehle auszufĂŒhren. In Bezug auf die Syntax der CMake-Skripte reicht Version 3.8 aus, wenn die Generierung auf andere Weise aufgerufen wird.
Testbibliothek
Das Testen kann deaktiviert werden (siehe ).
FĂŒr die Auswahl der Sprache, in der die Dokumentation generiert wird, steht eine Option zur VerfĂŒgung .
Interpreter der Programmiersprache
FĂŒr die automatische Generierung .
Mit CMake und einigen guten Tools kann die statische Analyse mit minimalem Aufwand realisiert werden.
Cppcheck
CMake bietet integrierte UnterstĂŒtzung fĂŒr das Tool zur statischen Analyse .
Zu diesem Zweck muss die Option :
cmake -S pfad/zum/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Ipfad/zum/quellcode/include"Danach wird die statische Analyse automatisch bei jedem Kompilierungs- und Wiederherstellungsvorgang der Quellcodes gestartet. Es sind keine zusÀtzlichen Schritte erforderlich.
Clang
Mit dem wunderbaren Tool lĂ€sst sich ebenfalls eine statische Analyse im Handumdrehen durchfĂŒhren:
scan-build cmake -S pfad/zum/quellcode -B pfad/zum/bauverzeichnis -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build pfad/zum/bauverzeichnisHier, im Gegensatz zu Cppcheck, muss der Build jedes Mal ĂŒber scan-build.
CMake ist ein Ă€uĂerst leistungsfĂ€higes und flexibles System, das es ermöglicht, FunktionalitĂ€ten nach Belieben zu implementieren. Und obwohl die Syntax manchmal zu wĂŒnschen ĂŒbrig lĂ€sst, ist der Teufel nicht so heiĂ, wie er gekocht wird. Nutzen Sie das CMake-Bausystem zum Wohl der Gesellschaft und zu Ihrem eigenen Nutzen.
â
Quelle: habr.com
