CMake und C++ — fĂŒr immer BrĂŒder

CMake und C++ — fĂŒr immer BrĂŒder

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:

  1. Build-Prozess;
  2. Automatisches Testen;
  3. Code Coverage-Messung;
  4. Installation;
  5. Automatische Dokumentation;
  6. Erzeugung einer Online-Sandbox;
  7. Statische Analyse.

Wer sich bereits mit C++ und CMake auskennt, kann einfach ein Projekt-Template herunterladen und damit arbeiten.


Inhalt

  1. Das Projekt aus der Innenperspektive
    1. Projektstruktur
    2. Die Haupt-CMake-Datei (./CMakeLists.txt)
      1. Projektdetails
      2. Projektoptionen
      3. Kompilierungsoptionen
      4. Hauptziel
      5. Installation von
      6. Tests
      7. Dokumentation
      8. Online-Sandbox
    3. Testskript (test/CMakeLists.txt)
      1. Testen
      2. Abdeckung
    4. Dokumentationsskript (doc/CMakeLists.txt)
    5. Online-Sandbox-Skript (online/CMakeLists.txt)
  2. Das Projekt von außen
    1. Zusammenbau
      1. Generierung
      2. Zusammenbau
    2. Optionen
      1. MYLIB_ABDECKUNG
      2. MYLIB_TEST
      3. MYLIB_DOXYGEN_SPRACHE
    3. Build-Ziele
      1. StandardmĂ€ĂŸig
      2. mylib-einheitstests
      3. ĂŒberprĂŒfen
      4. coverage
      5. doc
      6. wandbox
    4. Beispiele
  3. Werkzeuge
  4. Statische Analyse
  5. Nachwort

Das Projekt aus der Innenperspektive

Projektstruktur

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

Im Mittelpunkt steht, wie man CMake-Skripte organisiert, weshalb diese ausfĂŒhrlich behandelt werden. Andere Dateien können von jedem Interessierten direkt angesehen werden auf der Projektvorlagenseite.

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

Projektdetails

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 den Befehl project).

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)

Projektoptionen

Wir sehen zwei Optionen vor.

Die erste Option — MYLIB_TEST — 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 MYLIB_ABDECKUNG 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)

Kompilierungsoptionen

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

Hauptziel

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 target_link_libraries(target PRIVATE dependency), 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 CMake-Skripts fĂŒr Unit-Tests.

Es ist auch wichtig, auf die sogenannten Generator-AusdrĂŒcke: $.

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 find_package, 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 der Anweisung target_compile_features).

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. Wie zum Beispiel bei Boost.

add_library(Mylib::mylib ALIAS mylib)

Installation von

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)

Tests

Wenn die Tests explizit mit der entsprechenden Option deaktiviert sind oder unser Projekt ein Unterprojekt ist, das heißt, es ist in ein anderes CMake-Projekt ĂŒber die Anweisung add_subdirectory, 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()

Dokumentation

Die Dokumentation wird ebenfalls nicht generiert, wenn es sich um ein Unterprojekt handelt.

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

Online-Sandbox

Ähnlich wird es auch keine Online-Sandbox fĂŒr das Unterprojekt geben.

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

Testskript (test/CMakeLists.txt)

Testen

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)

Abdeckung

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

Dokumentationsskript (doc/CMakeLists.txt)

Doxygen gefunden.

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 den Befehl configure_file).

Daraufhin erstellen wir ein Ziel doc, 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 ()

Online-Sandbox-Skript (online/CMakeLists.txt)

Hier finden wir den dritten Python und erstellen ein Ziel wandbox, das eine Anfrage erstellt, die dem API-Dienst entspricht Wandbox, 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()

Das Projekt von außen

Nun schauen wir uns an, wie man das alles benutzt.

Zusammenbau

Der Aufbau dieses Projekts, wie auch bei jedem anderen Projekt im CMake-Bausystem, besteht aus zwei Phasen:

Generierung

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

Mehr zu den Optionen.

Projekt erstellen

cmake --build pfad/zur/bau/directory [--target target]

Erfahren Sie mehr ĂŒber Build-Ziele.

Optionen

MYLIB_ABDECKUNG

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

Aktiviert das Ziel coverage, mit dem die Codeabdeckung durch Tests gemessen werden kann.

MYLIB_TEST

cmake -S ... -B ... -DMYLIB_TESTING=OFF [weitere Optionen ...]

Bietet die Möglichkeit, den Bau von Modultests und das Ziel auszuschalten ĂŒberprĂŒfen. Dadurch wird die Codeabdeckung durch Tests ausgeschaltet (siehe MYLIB_ABDECKUNG).

Tests werden auch automatisch deaktiviert, wenn das Projekt als Unterprojekt in ein anderes Projekt integriert wird ĂŒber den Befehl add_subdirectory.

MYLIB_DOXYGEN_SPRACHE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [weitere Optionen ...]

Wechselt die Sprache der Dokumentation, die durch das Ziel generiert wird doc in die angegebene. Eine Liste der verfĂŒgbaren Sprachen finden Sie auf der Doxygen-Website.

StandardmĂ€ĂŸig ist Russisch aktiviert.

Build-Ziele

StandardmĂ€ĂŸig

cmake --build pfad/zur/bau/directory
cmake --build pfad/zur/bau/directory --target all

Wenn kein Ziel angegeben ist (was dem Ziel entspricht all), wird alles, was möglich ist, gebaut, und es wird das Ziel aufgerufen ĂŒberprĂŒfen.

mylib-einheitstests

cmake --build pfad/zur/bau/directory --target mylib-unit-tests

Kompiliert die Modultests. StandardmĂ€ĂŸig aktiviert.

ĂŒberprĂŒfen

cmake --build pfad/zur/bau/directory --target check

FĂŒhrt die gebauten (baut sie, wenn noch nicht) Modultests aus. StandardmĂ€ĂŸig aktiviert.

Siehe auch mylib-einheitstests.

coverage

cmake --build pfad/zur/bauverzeichnis --target coverage

Analysiert die laufenden (startet, falls noch nicht geschehen) Modultests auf Codeabdeckung mithilfe des Programms gcovr.

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

Siehe auch ĂŒberprĂŒfen.

doc

cmake --build pfad/zur/bauverzeichnis --target doc

Startet die Dokumentationserzeugung fĂŒr den Code mit dem System Doxygen.

wandbox

cmake --build pfad/zur/bauverzeichnis --target wandbox

Die Antwort vom Dienst sieht ungefÀhr so aus:

{
    "permlink" :    "QElvxuMzHgL9fqci",
    "status" :  "0",
    "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}

HierfĂŒr wird der Dienst genutzt Wandbox. Ich weiß nicht, wie leistungsfĂ€hig ihre Server sind, aber ich denke, man sollte diese Möglichkeit nicht ĂŒberstrapazieren.

Beispiele

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 16

Installation 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 install

Bau 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 4

Generierung 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

Werkzeuge

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

  2. Testbibliothek doctest

    Das Testen kann deaktiviert werden (siehe Option MYLIB_TESTING).

  3. Doxygen

    FĂŒr die Auswahl der Sprache, in der die Dokumentation generiert wird, steht eine Option zur VerfĂŒgung MYLIB_DOXYGEN_SPRACHE.

  4. Interpreter der Programmiersprache Python 3

    FĂŒr die automatische Generierung Online-Sandbox.

Statische Analyse

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

Zu diesem Zweck muss die Option CMAKE_CXX_CPPCHECK:

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 scan-build 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/bauverzeichnis

Hier, im Gegensatz zu Cppcheck, muss der Build jedes Mal ĂŒber scan-build.

Nachwort

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.

→ Projektvorlage herunterladen

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster