
Während der Entwicklung ändere ich gerne Compiler, Build-Modi und Abhängigkeitsversionen, führe statische Analysen durch, messe die Leistung, sammle Abdeckung, erstelle Dokumentation und so weiter. Und ich liebe CMake wirklich, weil ich damit tun kann, was ich will.
Viele schimpfen über CMake, und das oft zu Recht, aber wenn man hinschaut, ist es nicht so schlimm, aber in letzter Zeit gar nicht so schlecht, und die Richtung der Entwicklung ist durchaus positiv.
In diesem Hinweis möchte ich Ihnen erklären, wie einfach es ist, eine C++-Header-Bibliothek in einem CMake-System zu organisieren, um die folgende Funktionalität zu erhalten:
- Montage;
- Autorun-Tests;
- Messung der Codeabdeckung;
- Installation;
- Autodokumentation;
- Online-Sandbox-Generierung;
- Statische Analyse.
Wer die Vor- und Nachteile bereits versteht, kann es einfach tun und fangen Sie an, es zu verwenden.
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.cppDabei geht es vor allem um die Organisation von CMake-Skripten, daher werden diese im Detail analysiert. Die restlichen Dateien können von jedem direkt eingesehen werden. .
Zunächst müssen Sie die gewünschte Version des CMake-Systems anfordern. CMake entwickelt sich, Befehlssignaturen ändern sich, Verhalten unter verschiedenen Bedingungen. Damit CMake sofort versteht, was wir davon wollen, müssen wir unsere Anforderungen sofort festlegen.
cmake_minimum_required(VERSION 3.13)Dann bezeichnen wir unser Projekt, seinen Namen, seine Version, die verwendeten Sprachen usw. ).
Geben Sie in diesem Fall die Sprache an CXX (was C++ bedeutet), damit sich CMake nicht die Mühe macht, nach einem C-Compiler zu suchen (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 ein anderes Projekt eingebunden ist. Das wird in Zukunft sehr helfen.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Betrachten wir zwei Optionen.
Die erste Option ist - um Unit-Tests zu deaktivieren. Dies kann erforderlich sein, wenn wir sicher sind, dass bei den Tests alles in Ordnung ist, und wir beispielsweise unser Projekt nur installieren oder verpacken möchten. Oder unser Projekt wird als Unterprojekt eingebunden – in diesem Fall ist der Benutzer unseres Projekts nicht daran interessiert, unsere Tests durchzuführen. Sie testen die Abhängigkeiten, die Sie verwenden, nicht, oder?
option(MYLIB_TESTING "Включить модульное тестирование" ON)Darüber hinaus werden wir eine separate Option erstellen zum Messen der Codeabdeckung mit Tests, es sind jedoch zusätzliche Tools erforderlich, sodass Sie es explizit aktivieren müssen.
option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)
Natürlich sind wir coole Programmierer, also wollen wir vom Compiler ein Höchstmaß an Diagnose zur Kompilierungszeit. Keine einzige Maus kommt durch.
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 dem C++-Sprachstandard vollständig zu entsprechen. Sie sind in CMake standardmäßig aktiviert.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Unsere Bibliothek besteht nur aus Header-Dateien, das heißt, wir haben keine Ausgabe in Form von statischen oder dynamischen Bibliotheken. Um unsere Bibliothek von außen nutzen zu können, müssen Sie sie hingegen installieren, im System finden und mit Ihrem Projekt verbinden können, und gleichzeitig auch diese Header sowie möglicherweise einige zusätzliche Eigenschaften.
Zu diesem Zweck erstellen wir eine Schnittstellenbibliothek.
add_library(mylib INTERFACE)Wir binden die Header an unsere Schnittstellenbibliothek.
Der moderne, trendige und jugendliche Einsatz von CMake bedeutet, dass Header, Eigenschaften usw. über ein einzelnes Ziel übertragen. Es reicht also zu sagen und alle Header, die dem Ziel zugeordnet sind dependency, wird für Quellen verfügbar sein, die zum Ziel gehören target. Und du brauchst keine [target_]include_directories. Dies wird im Folgenden beim Parsen demonstriert .
Es lohnt sich auch, auf das sogenannte zu achten. .
Dieser Befehl verknüpft die von uns benötigten Header mit unserer Schnittstellenbibliothek. Wenn unsere Bibliothek mit einem beliebigen Ziel innerhalb derselben CMake-Hierarchie verbunden ist, werden ihr die Header aus dem Verzeichnis zugeordnet ${CMAKE_CURRENT_SOURCE_DIR}/include, und wenn unsere Bibliothek auf dem System installiert und über den Befehl mit einem anderen Projekt verbunden ist , dann werden ihm die Header aus dem Verzeichnis zugeordnet include relativ zum Installationsverzeichnis.
target_include_directories(mylib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)Legen Sie den Sprachstandard fest. Natürlich das Neueste. 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 festgelegte Eigenschaft eine Kategorie hat INTERFACE (Siehe. ).
target_compile_features(mylib INTERFACE cxx_std_17)Wir bekommen einen Alias für unsere Bibliothek. Und der Schönheit halber wird es in einem speziellen „Namensraum“ sein. Dies ist nützlich, wenn in unserer Bibliothek verschiedene Module erscheinen und wir sie unabhängig voneinander verbinden. .
add_library(Mylib::mylib ALIAS mylib)
Installieren unserer Header im System. Hier ist alles einfach. Wir sagen, dass der Ordner mit allen Headern in das Verzeichnis fallen sollte include bezüglich des Installationsortes.
install(DIRECTORY include/mylib DESTINATION include)Als nächstes teilen wir dem Build-System mit, dass wir den Befehl in Drittprojekten aufrufen können möchten find_package(Mylib) und ein Ziel finden Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)So ist die folgende Beschwörung zu verstehen. In einem Nebenprojekt rufen wir den Befehl auf find_package(Mylib 1.2.3 REQUIRED), und gleichzeitig ist die echte Version der installierten Bibliothek mit der Version nicht kompatibel 1.2.3, CMake generiert automatisch einen Fehler. Das heißt, Sie müssen die Versionen nicht manuell nachverfolgen.
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 deaktiviert werden oder unser Projekt ist ein Unterprojekt, das heißt, es ist über den Befehl mit einem anderen CMake-Projekt verbunden , gehen wir in der Hierarchie nicht weiter nach unten und das Skript, das die Befehle zum Generieren und Ausführen von Tests beschreibt, startet einfach nicht.
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()
Ebenso wird das Teilprojekt keine Online-Sandbox haben.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Zunächst finden wir ein Paket mit dem gewünschten Test-Framework (ersetzen Sie es durch Ihr Lieblings-Framework).
find_package(doctest 2.3.3 REQUIRED)Wir erstellen unsere ausführbare Datei mit Tests. Normalerweise füge ich direkt zur ausführbaren Binärdatei nur die Datei hinzu, in der sich die Funktion befindet main.
add_executable(mylib-unit-tests test_main.cpp)Und die Dateien, in denen die Tests selbst beschrieben sind, füge ich später hinzu. Dies ist jedoch nicht erforderlich.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Wir verbinden Abhängigkeiten. Bitte beachten Sie, dass wir nur die benötigten CMake-Ziele an unsere Binärdatei angehängt und den Befehl nicht aufgerufen haben target_include_directories. Header aus dem Testframework und von unserem Mylib::mylibsowie Build-Optionen (in unserem Fall der C++-Sprachstandard) wurden zusammen mit diesen Zielen gecrawlt.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Schließlich erstellen wir ein Dummy-Ziel, dessen „Build“ dem Ausführen von Tests entspricht, und fügen dieses Ziel dem Standard-Build (dem Attribut) hinzu ALL). Das bedeutet, dass der Standard-Build die Ausführung der Tests auslöst, sodass wir nie vergessen werden, sie auszuführen.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Aktivieren Sie als Nächstes die Messung der Codeabdeckung, sofern die entsprechende Option aktiviert ist. Ich werde nicht auf die Details eingehen, da sie eher mit dem Coverage-Messtool als mit CMake zusammenhängen. Wichtig ist nur zu beachten, dass aus den Ergebnissen ein Ziel entsteht , mit dem Sie bequem mit der Messung der Abdeckung beginnen können.
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()
.
find_package(Doxygen)Als nächstes prüfen wir, ob die Variable mit der Sprache vom Benutzer festgelegt wurde. Wenn ja, dann rühren wir es nicht an, wenn nicht, dann nehmen wir Russisch. Anschließend konfigurieren wir die Doxygen-Systemdateien. Alle notwendigen Variablen, einschließlich der Sprache, gelangen während des Konfigurationsprozesses dorthin (siehe. ).
Dann schaffen wir ein Ziel , wodurch mit der Erstellung der Dokumentation begonnen wird. Da die Erstellung der Dokumentation nicht den größten Bedarf im Entwicklungsprozess darstellt, wird das Ziel nicht standardmäßig einbezogen, sondern muss explizit ausgeführt 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 Ziel , wodurch eine der Dienst-API entsprechende Anfrage generiert wird , und sendet es. 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()
Schauen wir uns nun an, wie man alles nutzt.
Die Erstellung dieses Projekts besteht wie jedes andere Projekt auf dem CMake-Build-System aus zwei Phasen:
cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]Wenn der obige Befehl aufgrund einer älteren Version von CMake nicht funktioniert hat, versuchen Sie es wegzulassen
-S:cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]
.
cmake --build путь/к/сборочной/директории [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]Inklusive Ziel , mit dem Sie mit der Messung der Codeabdeckung durch Tests beginnen können.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]Bietet die Option, Unit-Test-Build und -Ziel zu deaktivieren . Dadurch wird die Messung der Codeabdeckung durch Tests ausgeschaltet (siehe ).
Außerdem wird das Testen automatisch deaktiviert, wenn das Projekt über den Befehl als Unterprojekt mit einem anderen Projekt verbunden wird .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]Schaltet die Sprache der Dokumentation um, die das Ziel generiert zum Gegebenen. Eine Liste der verfügbaren Sprachen finden Sie unter .
Russisch ist standardmäßig aktiviert.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allWenn das Ziel nicht angegeben ist (was dem Ziel entspricht all), sammelt alles, was möglich ist, und ruft auch das Ziel auf .
cmake --build path/to/build/directory --target mylib-unit-testsKompiliert Unit-Tests. Standardmäßig aktiviert.
cmake --build путь/к/сборочной/директории --target checkFührt erstellte (Builds, falls noch nicht geschehen) Unit-Tests durch. Standardmäßig aktiviert.
См. также .
cmake --build путь/к/сборочной/директории --target coverageAnalysiert laufende (falls noch nicht ausgeführte) Unit-Tests auf Codeabdeckung durch Tests, die das Programm verwenden .
Die Coverage-Ausgabe sieht etwa 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 die Option aktiviert ist. .
См. также .
cmake --build путь/к/сборочной/директории --target docStartet die Generierung der Dokumentation für den Code mithilfe des Systems .
cmake --build путь/к/сборочной/директории --target wandboxDie Antwort des Dienstes sieht in etwa so aus:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Hierzu wird der Dienst genutzt. . Ich weiß nicht, welche Rubber-Server sie haben, aber ich denke, dass man diese Funktion nicht missbrauchen sollte.
Erstellen Sie ein Projekt im Debug-Modus mit Abdeckungsmessung
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16Installieren eines Projekts ohne Voraufbau und Tests
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target installErstellen Sie im Release-Modus durch den angegebenen Compiler
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4Erstellung der Dokumentation in englischer Sprache
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc
3.13
Tatsächlich ist CMake Version 3.13 nur erforderlich, um einige der in dieser Hilfe beschriebenen Konsolenbefehle auszuführen. Aus Sicht der Syntax von CMake-Skripten ist Version 3.8 ausreichend, wenn die Generierung auf andere Weise aufgerufen wird.
Testbibliothek
Das Testen kann deaktiviert werden (siehe ).
Um die Sprache zu ändern, in der die Dokumentation generiert wird, besteht die Möglichkeit .
PL-Dolmetscher
Zur automatischen Generierung .
Mithilfe von CMake und einigen guten Tools können Sie statische Analysen mit minimalem Aufwand durchführen.
Cppcheck
Unterstützung für das in CMake integrierte statische Analysetool .
Nutzen Sie dazu die Option :
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"Danach wird die statische Analyse jedes Mal automatisch gestartet, wenn die Quellen kompiliert und neu kompiliert werden. Es muss nichts extra getan werden.
Clang
Mit einem wunderbaren Werkzeug Sie können auch im Handumdrehen eine statische Analyse durchführen:
scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директорииAnders als bei Cppcheck ist es hier erforderlich, den Build jedes Mal erneut auszuführen scan-build.
CMake ist ein sehr leistungsstarkes 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 dargestellt wird. Nutzen Sie das CMake-Build-System zum Wohle der Gesellschaft und Gesundheit.
→
Source: habr.com
