CMake und C ++ - BrĂĽder fĂĽr immer

Freundschaft fĂĽr immer


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:


  1. Versammlung;
  2. Autostart-Tests;
  3. Messung der Codeabdeckung;
  4. Installation;
  5. Automatische Dokumentation
  6. Online-Sandbox-Generierung;
  7. Statische Analyse

Wer die Profis und S-Marken bereits versteht, kann einfach die Projektvorlage herunterladen und verwenden.


Inhalt


  1. Projekt von innen nach auĂźen
    1. Projektstruktur
    2. Haupt-CMake-Datei (./CMakeLists.txt)
      1. Projektinformationen
      2. Projektoptionen
      3. Kompilierungsoptionen
      4. Hauptziel
      5. Installation
      6. Tests
      7. Die Dokumentation
      8. Online Sandbox
    3. Skript fĂĽr Tests (test / CMakeLists.txt)
      1. Testen
      2. Abdeckung
    4. Skript zur Dokumentation (doc / CMakeLists.txt)
    5. Skript fĂĽr eine Online-Sandbox (online / CMakeLists.txt)
  2. Projekt drauĂźen
    1. Montage
      1. Generation
      2. Montage
    2. Optionen
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montageziele
      1. Default
      2. Mylib-Unit-Tests
      3. ĂĽberprĂĽfen
      4. Abdeckung
      5. doc
      6. Zauberstabbox
    4. Beispiele
  3. Die Werkzeuge
  4. Statische Analyse
  5. Nachwort


Projekt von innen nach auĂźen



Projektstruktur


. ├── 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 .



Haupt-CMake-Datei (./CMakeLists.txt)



Projektinformationen


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) 


Projektoptionen


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) 


Kompilierungsoptionen


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() 


Hauptziel


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


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) 


Tests


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() 


Die Dokumentation


Auch bei einem Teilprojekt wird keine Dokumentation erstellt.


 if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif() 


Online Sandbox


In ähnlicher Weise verfügt das Teilprojekt auch nicht über Online-Sandboxen.


 if(NOT IS_SUBPROJECT) add_subdirectory(online) endif() 


Skript fĂĽr Tests (test / CMakeLists.txt)



Testen


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) 


Abdeckung


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() 


Skript zur Dokumentation (doc / CMakeLists.txt)


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 () 


Skript fĂĽr eine Online-Sandbox (online / CMakeLists.txt)


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() 


Projekt drauĂźen


Ăśberlegen Sie nun, wie Sie alles verwenden sollen.



Montage


Die Montage dieses Projekts besteht wie jedes andere Projekt auf dem CMake-Montagesystem aus zwei Schritten:



Generation


 cmake -S // -B /// [ ...] 

Wenn der obige Befehl aufgrund der alten Version von CMake nicht funktioniert hat, lassen Sie -S :
 cmake // -B /// [ ...] 

Mehr zu Optionen .



Projektmontage


 cmake --build /// [--target target] 

Lesen Sie mehr ĂĽber Montageziele .



Optionen



MYLIB_COVERAGE


 cmake -S ... -B ... -DMYLIB_COVERAGE=ON [  ...] 

Enthält ein coverage , mit dem Sie die Codeabdeckung mit Tests messen können.



MYLIB_TESTING


 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.



MYLIB_DOXYGEN_LANGUAGE


 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.



Montageziele



Default


 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.



Mylib-Unit-Tests


 cmake --build path/to/build/directory --target mylib-unit-tests 

Kompiliert Unit-Tests. Standardmäßig aktiviert.



ĂĽberprĂĽfen


 cmake --build /// --target check 

Läuft gesammelte (sammelt, wenn noch nicht) Unit-Tests. Standardmäßig aktiviert.


Siehe auch mylib-unit-tests .



Abdeckung


 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 .



doc


 cmake --build /// --target doc 

Startet die Generierung der Codedokumentation mit dem Doxygen- System.



Zauberstabbox


 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.



Beispiele


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 


Die Werkzeuge


  1. 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.


  2. Doctest-Testbibliothek


    Das Testen kann deaktiviert werden (siehe MYLIB_TESTING ).


  3. Sauerstoff


    Um die Sprache zu wechseln, in der die Dokumentation generiert wird, wird die Option MYLIB_DOXYGEN_LANGUAGE .


  4. Python 3 Interpreter


    Automatische Generierung von Online-Sandboxen .




Statische Analyse


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.



Nachwort


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

Source: https://habr.com/ru/post/de461817/


All Articles