
Während des Entwicklungsprozesses ändere ich gerne Compiler, Build-Modi, Abhängigkeitsversionen, führe statische Analysen durch, messe die Leistung, sammle Deckung, generiere Dokumentation usw. Und ich liebe CMake wirklich, weil ich damit alles machen kann, was ich will.
Viele schimpfen mit CMake und oft zu Recht, aber wenn Sie schauen, ist nicht alles so schlecht, und in letzter Zeit ist es sehr gut und die Richtung der Entwicklung ist ziemlich positiv.
In diesem Artikel möchte ich erläutern, wie einfach es ist, eine C ++ - Headerbibliothek im CMake-System zu organisieren, um die folgenden Funktionen zu erhalten:
- Versammlung;
- Autostart-Tests;
- Messung der Codeabdeckung;
- Installation;
- Automatische Dokumentation
- Online-Sandbox-Generierung;
- Statische Analyse
Wer die Profis und S-Marken bereits versteht, kann einfach die Projektvorlage herunterladen und verwenden.
Inhalt
- Projekt von innen nach auĂźen
- Projektstruktur
- Haupt-CMake-Datei (./CMakeLists.txt)
- Projektinformationen
- Projektoptionen
- Kompilierungsoptionen
- Hauptziel
- Installation
- Tests
- Die Dokumentation
- Online Sandbox
- Skript fĂĽr Tests (test / CMakeLists.txt)
- Testen
- Abdeckung
- Skript zur Dokumentation (doc / CMakeLists.txt)
- Skript fĂĽr eine Online-Sandbox (online / CMakeLists.txt)
- Projekt drauĂźen
- Montage
- Generation
- Montage
- Optionen
- MYLIB_COVERAGE
- MYLIB_TESTING
- MYLIB_DOXYGEN_LANGUAGE
- Montageziele
- Default
- Mylib-Unit-Tests
- ĂĽberprĂĽfen
- Abdeckung
- doc
- Zauberstabbox
- Beispiele
- Die Werkzeuge
- Statische Analyse
- Nachwort
. ├── 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.cpp
Wir werden hauptsächlich darüber sprechen, wie CMake-Skripte organisiert werden, damit sie detailliert analysiert werden. Jeder kann den Rest der Dateien direkt auf der Projektvorlagenseite sehen .
Zunächst müssen Sie die richtige Version des CMake-Systems anfordern. CMake entwickelt sich weiter, Teamsignaturen, Verhalten unter verschiedenen Bedingungen ändern sich. Damit CMake sofort versteht, was wir von ihm wollen, müssen wir unsere Anforderungen an ihn sofort festlegen.
cmake_minimum_required(VERSION 3.13)
Dann bestimmen wir unser Projekt, seinen Namen, seine Version, die verwendeten Sprachen usw. (siehe project
).
In diesem Fall geben wir die CXX
Sprache ( CXX
C ++) an, damit CMake den C-Sprach-Compiler nicht belastet und nicht sucht (standardmäßig sind in CMake zwei Sprachen enthalten: C und C ++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Hier können Sie sofort prüfen, ob unser Projekt als Teilprojekt in einem anderen Projekt enthalten ist. Dies wird in Zukunft sehr hilfreich sein.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Wir bieten zwei Möglichkeiten.
Die erste Option - MYLIB_TESTING
- zum MYLIB_TESTING
. Dies kann erforderlich sein, wenn wir sicher sind, dass bei den Tests alles in Ordnung ist, und wir beispielsweise nur unser Projekt installieren oder verpacken möchten. Oder unser Projekt ist als Teilprojekt enthalten - in diesem Fall ist der Benutzer unseres Projekts nicht daran interessiert, unsere Tests durchzuführen. Sie testen die von Ihnen verwendeten Abhängigkeiten nicht?
option(MYLIB_TESTING " " ON)
DarĂĽber hinaus werden wir eine separate Option MYLIB_COVERAGE
zum Messen der Codeabdeckung mit Tests MYLIB_COVERAGE
sind jedoch zusätzliche Tools erforderlich, sodass Sie diese explizit aktivieren müssen.
option(MYLIB_COVERAGE " " OFF)
Natürlich sind wir coole Plus-Programmierer, daher möchten wir vom Compiler ein Höchstmaß an Diagnose der Kompilierungszeit. Keine einzige Maus wird 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 Erweiterungen deaktivieren, um den C ++ - Sprachstandard vollständig zu erfüllen. Standardmäßig sind sie in CMake enthalten.
if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif()
Unsere Bibliothek besteht nur aus Header-Dateien, was bedeutet, dass wir keinen Auspuff in Form von statischen oder dynamischen Bibliotheken haben. Um unsere Bibliothek im Freien nutzen zu können, müssen Sie sie installieren, im System finden und mit Ihrem Projekt verbinden können, und gleichzeitig dieselben Header sowie möglicherweise einige zusätzliche Eigenschaften.
Zu diesem Zweck erstellen wir eine Schnittstellenbibliothek.
add_library(mylib INTERFACE)
Binden Sie die Header an unsere Front-End-Bibliothek.
Die moderne, modische und jugendliche Verwendung von CMake impliziert, dass Header, Eigenschaften usw. durch einen einzigen Zweck ĂĽbertragen. Es reicht also aus, target_link_libraries(target PRIVATE dependency)
zu sagen, und alle Header, die dem dependency
sind, sind fĂĽr Quellen verfĂĽgbar, die zum target_link_libraries(target PRIVATE dependency)
gehören. Und es sind keine [target_]include_directories
erforderlich. Dies wird unten beim Parsen des CMake-Skripts fĂĽr Komponententests demonstriert.
Es lohnt sich auch, auf die sogenannten zu achten -: $<...>
.
Dieser Befehl ordnet die benötigten Header unserer Front-End-Bibliothek zu. Wenn unsere Bibliothek mit einem Ziel innerhalb derselben CMake-Hierarchie verbunden ist, werden Header aus dem Verzeichnis ${CMAKE_CURRENT_SOURCE_DIR}/include
damit verknĂĽpft, und wenn unsere Da die Bibliothek auf dem System installiert und mit dem find_package
mit einem anderen Projekt find_package
, werden Header aus dem include
Verzeichnis relativ zum Installationsverzeichnis zugeordnet.
target_include_directories(mylib INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
Stellen Sie den Sprachstandard ein. NatĂĽrlich das allerletzte. Gleichzeitig nehmen wir den Standard nicht nur auf, sondern verteilen ihn auch an diejenigen, die unsere Bibliothek nutzen werden. Dies wird dadurch erreicht, dass die set-Eigenschaft die Kategorie INTERFACE
(siehe Befehl target_compile_features ).
target_compile_features(mylib INTERFACE cxx_std_17)
Wir erstellen einen Alias ​​für unsere Bibliothek. Außerdem wird er sich für die Schönheit in einem speziellen "Namespace" befinden. Dies ist nützlich, wenn verschiedene Module in unserer Bibliothek angezeigt werden und wir sie unabhängig voneinander verbinden. Wie zum Beispiel in Boost .
add_library(Mylib::mylib ALIAS mylib)
Installation unserer Header im System. Hier ist alles einfach. Wir sagen, dass sich der Ordner mit allen Headern im include
Verzeichnis relativ zum Installationsort befinden sollte.
install(DIRECTORY include/mylib DESTINATION include)
Als Nächstes informieren wir das Build-System darüber, dass wir den find_package(Mylib)
in Projekten von Mylib::mylib
und das Ziel Mylib::mylib
.
install(TARGETS mylib EXPORT MylibConfig) install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)
Der nächste Zauber sollte wie folgt verstanden werden. Wenn wir find_package(Mylib 1.2.3 REQUIRED)
in einem Drittanbieterprojekt find_package(Mylib 1.2.3 REQUIRED)
und in diesem Fall die reale Version der installierten Bibliothek nicht mit Version 1.2.3
kompatibel ist, generiert CMake automatisch einen Fehler. Das heiĂźt, Sie mĂĽssen 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 die Tests mit der entsprechenden Option explizit add_subdirectory
oder wenn unser Projekt ein Teilprojekt ist, add_subdirectory
mit dem add_subdirectory
mit einem anderen CMake-Projekt add_subdirectory
, werden wir in der Hierarchie nicht weiter gehen, und das Skript, das die Befehle zum Generieren und AusfĂĽhren der Tests beschreibt, wird einfach nicht gestartet .
if(NOT MYLIB_TESTING) message(STATUS " Mylib ") elseif(IS_SUBPROJECT) message(STATUS "Mylib ") else() add_subdirectory(test) endif()
Auch bei einem Teilprojekt wird keine Dokumentation erstellt.
if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif()
In ähnlicher Weise verfügt das Teilprojekt auch nicht über Online-Sandboxen.
if(NOT IS_SUBPROJECT) add_subdirectory(online) endif()
Zunächst finden wir das Paket mit dem gewünschten Testframework (ersetzen Sie es durch Ihr Lieblingspaket).
find_package(doctest 2.3.3 REQUIRED)
Wir erstellen unsere ausfĂĽhrbare Datei mit Tests. Normalerweise fĂĽge ich nur die Datei hinzu, in der sich die main
direkt in der ausführbaren Binärdatei befindet.
add_executable(mylib-unit-tests test_main.cpp)
Und die Dateien, die die Tests selbst beschreiben, werden später hinzugefügt. Dies ist jedoch nicht erforderlich.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)
Wir verbinden Abhängigkeiten. Bitte beachten Sie, dass wir nur target_include_directories
benötigten CMake-Ziele mit unserer Binärdatei target_include_directories
und den Befehl target_include_directories
nicht aufgerufen target_include_directories
. Die Header aus dem Mylib::mylib
und aus unserer Mylib::mylib
sowie die Build-Parameter (in unserem Fall der C ++ - Sprachstandard) wurden zusammen mit diesen Zielen Mylib::mylib
.
target_link_libraries(mylib-unit-tests PRIVATE Mylib::mylib doctest::doctest )
Erstellen Sie schlieĂźlich ein fiktives Ziel, dessen "Assembly" dem AusfĂĽhren von Tests entspricht, und fĂĽgen Sie dieses Ziel der Standardassembly hinzu (das ALL
Attribut ist dafür verantwortlich). Dies bedeutet, dass die Assembly standardmäßig den Start von Tests initiiert, dh wir werden nie vergessen, sie auszuführen.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Als Nächstes aktivieren wir die Messung der Codeabdeckung, wenn die entsprechende Option angegeben ist. Ich werde nicht auf Details eingehen, da sie sich mehr auf das Tool zur Messung der Abdeckung als auf CMake beziehen. Es ist nur wichtig zu beachten, dass basierend auf den Ergebnissen ein coverage
erstellt wird, mit dem es bequem ist, mit der Messung der Abdeckung zu beginnen.
find_program(GCOVR_EXECUTABLE gcovr) if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE) message(STATUS " ") 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 " gcovr") endif()
Doxygen gefunden .
find_package(Doxygen)
Als nächstes prüfen wir, ob der Benutzer die Sprachvariable festgelegt hat. Wenn ja, dann nicht anfassen, wenn nicht, dann Russisch nehmen. Konfigurieren Sie dann die Doxygen-Systemdateien. Alle erforderlichen Variablen, einschließlich der Sprache, werden während des Konfigurationsprozesses abgerufen (siehe configure_file
).
AnschlieĂźend erstellen wir das doc
, mit dem die Erstellung der Dokumentation gestartet wird. Da die Erstellung von Dokumentationen nicht den größten Bedarf im Entwicklungsprozess darstellt, wird das Ziel standardmäßig nicht aktiviert und muss explizit gestartet werden.
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 ()
Hier finden wir den dritten Python und erstellen ein wandbox
Ziel, das eine Anforderung generiert, die mit der Wandbox- Service-API ĂĽbereinstimmt , und diese sendet. Als Antwort kommt ein 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 " - python 3- ") endif()
Ăśberlegen Sie nun, wie Sie alles verwenden sollen.
Die Montage dieses Projekts besteht wie jedes andere Projekt auf dem CMake-Montagesystem aus zwei Schritten:
cmake -S // -B /// [ ...]
Wenn der obige Befehl aufgrund der alten Version von CMake nicht funktioniert hat, lassen Sie -S
:
cmake // -B /// [ ...]
Mehr zu Optionen .
cmake --build /// [--target target]
Lesen Sie mehr ĂĽber Montageziele .
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [ ...]
Enthält ein coverage
, mit dem Sie die Codeabdeckung mit Tests messen können.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [ ...]
Bietet die Möglichkeit, die Einheitentestbaugruppe und das check
auszuschalten. Infolgedessen wird die Messung der Codeabdeckung durch Tests MYLIB_COVERAGE
(siehe MYLIB_COVERAGE
).
AuĂźerdem wird das Testen automatisch deaktiviert, wenn das Projekt mit dem add_subdirectory
als Unterprojekt mit einem anderen Projekt verbunden ist.
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [ ...]
Schaltet die vom Dokumentziel generierte Dokumentationssprache auf die angegebene Sprache um. Eine Liste der verfĂĽgbaren Sprachen finden Sie auf der Website des Doxygen-Systems .
Standardmäßig ist Russisch aktiviert.
cmake --build path/to/build/directory cmake --build path/to/build/directory --target all
Wenn das Ziel nicht angegeben ist (was dem Gesamtziel entspricht), sammelt es alles, was möglich ist, und ruft auch das check
auf.
cmake --build path/to/build/directory --target mylib-unit-tests
Kompiliert Unit-Tests. Standardmäßig aktiviert.
cmake --build /// --target check
Läuft gesammelte (sammelt, wenn noch nicht) Unit-Tests. Standardmäßig aktiviert.
Siehe auch mylib-unit-tests
.
cmake --build /// --target coverage
Analysiert laufende (wird ausgefĂĽhrt, falls noch nicht) Komponententests , um den Code mit Tests mit dem Programm gcovr abzudecken .
Der Auspuff der Beschichtung sieht ungefähr so ​​aus:
------------------------------------------------------------------------------ GCC Code Coverage Report Directory: /path/to/cmakecpptemplate/include/ ------------------------------------------------------------------------------ File Lines Exec Cover Missing ------------------------------------------------------------------------------ mylib/myfeature.hpp 2 2 100% ------------------------------------------------------------------------------ TOTAL 2 2 100% ------------------------------------------------------------------------------
Das Ziel ist nur verfĂĽgbar, wenn MYLIB_COVERAGE
.
Siehe auch check
.
cmake --build /// --target doc
Startet die Generierung der Codedokumentation mit dem Doxygen- System.
cmake --build /// --target wandbox
Die Antwort des Dienstes sieht ungefähr so ​​aus:
{ "permlink" : "QElvxuMzHgL9fqci", "status" : "0", "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci" }
Verwenden Sie dazu den Wandbox- Dienst. Ich weiĂź nicht, ĂĽber welche Gummiserver sie verfĂĽgen, aber ich denke, dass diese Gelegenheit nicht missbraucht werden sollte.
Projektassemblierung im Debug-Modus mit Abdeckungsmessung
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON cmake --build /// --target coverage --parallel 16
Projektinstallation ohne Vormontage und PrĂĽfung
cmake -S // -B /// -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/// cmake --build /// --target install
Vom angegebenen Compiler im Release-Modus erstellen
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=///// cmake --build /// --parallel 4
Englische Dokumentationserstellung
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English cmake --build /// --target doc
CMake 3.13
Tatsächlich ist CMake 3.13 nur erforderlich, um einige der in dieser Hilfe beschriebenen Konsolenbefehle auszuführen. Aus syntaktischer Sicht von CMake-Skripten ist Version 3.8 ausreichend, wenn die Generierung auf andere Weise aufgerufen wird.
Doctest-Testbibliothek
Das Testen kann deaktiviert werden (siehe MYLIB_TESTING
).
Sauerstoff
Um die Sprache zu wechseln, in der die Dokumentation generiert wird, wird die Option MYLIB_DOXYGEN_LANGUAGE
.
Python 3 Interpreter
Automatische Generierung von Online-Sandboxen .
Mit CMake und einigen guten Tools können Sie statische Analysen mit minimalen Körperbewegungen durchführen.
Cppcheck
CMake bietet integrierte UnterstĂĽtzung fĂĽr das statische Analysetool Cppcheck .
Verwenden Sie dazu die Option CMAKE_CXX_CPPCHECK
:
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I///include"
Danach wird die statische Analyse jedes Mal während der Kompilierung und Neukompilierung der Quellen automatisch gestartet. Sie müssen nichts extra tun.
Clang
Mit dem wunderbaren scan-build
Tool können Sie statische Analysen auch in zwei Schritten ausführen:
scan-build cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug scan-build cmake --build ///
Im Gegensatz zu Cppcheck mĂĽssen Sie hier die Assembly jedes Mal durch scan-build
ausfĂĽhren.
CMake ist ein sehr leistungsfähiges und flexibles System, mit dem Sie Funktionen für jeden Geschmack und jede Farbe implementieren können. Und obwohl die Syntax manchmal zu wünschen übrig lässt, ist der Teufel immer noch nicht so schrecklich, wie er gemalt wird. Verwenden Sie das CMake-Build-System zum Wohle der Gesellschaft und der Gesundheit.
→ Projektvorlage herunterladen