A CMake és a C++ örökre testvérek

A CMake és a C++ örökre testvérek

Fejlesztés közben szeretek fordítókat cserélni, építeni módokat, függőségi verziókat, statikus elemzést végezni, teljesítményt mérni, lefedettséget gyűjteni, dokumentációt generálni stb. És nagyon szeretem a CMake-et, mert lehetővé teszi, hogy mindent megtegyek, amit akarok.

Sokan kritizálják a CMake-et, és gyakran megérdemelten, de ha megnézzük, nem minden olyan rossz, és mostanában egyáltalán nem rossz, és a fejlődés iránya meglehetősen pozitív.

Ebben a megjegyzésben azt szeretném elmondani, hogyan lehet egyszerűen megszervezni egy fejléckönyvtárat C++ nyelven a CMake rendszerben, hogy a következő funkciókat kapja meg:

  1. Összeszerelés;
  2. Automatikus futási tesztek;
  3. Kódlefedettség mérése;
  4. Telepítés;
  5. Auto-dokumentáció;
  6. Online sandbox generálás;
  7. Statikus elemzés.

Bárki, aki már érti az előnyöket és a C-make-et, egyszerűen megteheti projekt sablon letöltése és kezdje el használni.


Tartalom

  1. Projekt belülről
    1. Projekt szerkezete
    2. Fő CMake fájl (./CMakeLists.txt)
      1. Projekt információk
      2. Projektbeállítások
      3. Összeállítási lehetőségek
      4. A fő cél
      5. Telepítés
      6. Tesztek
      7. dokumentáció
      8. Online homokozó
    3. Tesztszkript (test/CMakeLists.txt)
      1. tesztelés
      2. Lefedettség
    4. Dokumentációs szkript (doc/CMakeLists.txt)
    5. Szkript az online sandboxhoz (online/CMakeLists.txt)
  2. Projekt kívül
    1. gyülekezés
      1. generáció
      2. gyülekezés
    2. Lehetőségek
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Szerelési célok
      1. Alapértelmezésben
      2. mylib-unit-tesztek
      3. ellenőrizze
      4. lefedettség
      5. doc
      6. pálcadoboz
    4. Примеры
  3. Tools
  4. Statikus elemzés
  5. utószó

Projekt belülről

Projekt szerkezete

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

Főleg a CMake szkriptek rendszerezéséről fogunk beszélni, így ezekről lesz szó részletesen. A többi fájlt bárki közvetlenül megtekintheti a projekt sablonoldalán.

Fő CMake fájl (./CMakeLists.txt)

Projekt információk

Először is le kell kérni a CMake rendszer szükséges verzióját. A CMake fejlődik, a parancs aláírások és a viselkedés különböző körülmények között változik. Ahhoz, hogy a CMake azonnal megértse, mit akarunk tőle, azonnal rögzítenünk kell a vele szemben támasztott követelményeinket.

cmake_minimum_required(VERSION 3.13)

Ezután kijelöljük a projektünket, annak nevét, verzióját, a használt nyelveket stb. (lásd. команду project).

Ebben az esetben a nyelvet jelöljük CXX (és ez C++-t jelent), hogy a CMake ne feszüljön, és ne keressen C nyelvű fordítót (alapértelmezés szerint a CMake két nyelvet tartalmaz: C és C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Itt azonnal ellenőrizheti, hogy projektünk alprojektként szerepel-e egy másik projektben. Ez sokat segít a jövőben.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Projektbeállítások

Két lehetőséget kínálunk.

Az első lehetőség az MYLIB_TESTING — az egységtesztek letiltásához. Erre akkor lehet szükség, ha biztosak vagyunk benne, hogy minden rendben van a tesztekkel, de csak például a projektünket szeretnénk telepíteni vagy csomagolni. Vagy a projektünk alprojektként szerepel – ebben az esetben a projektünk felhasználója nem érdekelt tesztjeink futtatásában. Ugye nem teszteled az általad használt függőségeket?

option(MYLIB_TESTING "Включить модульное тестирование" ON)

Ezen kívül külön opciót is készítünk MYLIB_COVERAGE kódlefedettség tesztekkel történő mérésére, de ehhez további eszközökre lesz szükség, ezért kifejezetten engedélyezni kell.

option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)

Összeállítási lehetőségek

Természetesen hűvös plusz programozók vagyunk, ezért szeretnénk a fordítóprogramtól a maximális fordítási idejű diagnosztikát. Egy egér sem csúszik át rajta.

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
)

A bővítményeket is letiltjuk, hogy teljes mértékben megfeleljünk a C++ nyelvi szabványnak. Alapértelmezés szerint engedélyezve vannak a CMake-ben.

if(NOT CMAKE_CXX_EXTENSIONS)
    set(CMAKE_CXX_EXTENSIONS OFF)
endif()

A fő cél

Könyvtárunk csak fejlécfájlokból áll, ami azt jelenti, hogy nincs kimerítésünk statikus vagy dinamikus könyvtárak formájában. Másrészt ahhoz, hogy a könyvtárunkat külsőleg használhassuk, telepíteni kell, a rendszerben észlelhetőnek kell lennie, és csatlakoznia kell a projekthez, és egyúttal ugyanazokat a fejléceket, esetleg néhány további fejlécet, tulajdonságok kapcsolódnak hozzá.

Ebből a célból létrehozunk egy interfész könyvtárat.

add_library(mylib INTERFACE)

A fejléceket az interfész könyvtárunkhoz kötjük.

A CMake modern, divatos, fiatalos használata azt jelenti, hogy a fejlécek, tulajdonságok stb. egyetlen célponton keresztül továbbítják. Szóval elég annyit mondani target_link_libraries(target PRIVATE dependency), és a célhoz társított összes fejléc dependency, elérhető lesz a célhoz tartozó források számára target. És nem kell semmi [target_]include_directories. Ezt az alábbiakban az elemzés bemutatja CMake szkript egységtesztekhez.

Érdemes még odafigyelni az ún. выражения-генераторы: $<...>.

Ez a parancs társítja a szükséges fejléceket az interfész könyvtárunkhoz, és ha a könyvtárunk csatlakozik bármely célhoz ugyanazon a CMake hierarchián belül, akkor a könyvtár fejlécei hozzá lesznek rendelve. ${CMAKE_CURRENT_SOURCE_DIR}/include, és ha a könyvtárunk telepítve van a rendszerre, és a paranccsal csatlakozik egy másik projekthez find_package, akkor a címtár fejlécei hozzá lesznek rendelve include a telepítési könyvtárhoz képest.

target_include_directories(mylib INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

Állítsunk be egy nyelvi szabványt. Természetesen a legutolsó. Ugyanakkor nemcsak a szabványt tartalmazzuk, hanem kiterjesztjük azokra is, akik használni fogják a könyvtárunkat. Ez annak köszönhető, hogy a beállított tulajdonságnak van kategóriája INTERFACE (Lásd. target_compile_features parancsot).

target_compile_features(mylib INTERFACE cxx_std_17)

Hozzon létre egy álnevet a könyvtárunknak. Sőt, a szépség kedvéért egy speciális „névtérben” lesz. Ez akkor lesz hasznos, ha különböző modulok jelennek meg a könyvtárunkban, és ezeket egymástól függetlenül kötjük össze. Mint például Bustában.

add_library(Mylib::mylib ALIAS mylib)

Telepítés

Fejléceink telepítése a rendszerbe. Itt minden egyszerű. Azt mondjuk, hogy az összes fejlécet tartalmazó mappának a könyvtárba kell kerülnie include a telepítés helyéhez képest.

install(DIRECTORY include/mylib DESTINATION include)

Ezután tájékoztatjuk az összeállítási rendszert, hogy meg akarjuk hívni a parancsot harmadik féltől származó projektekben find_package(Mylib) és kap egy gólt Mylib::mylib.

install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)

A következő varázslatot így kell érteni. Amikor egy harmadik féltől származó projektben meghívjuk a parancsot find_package(Mylib 1.2.3 REQUIRED), és a telepített könyvtár valós verziója nem lesz kompatibilis a verzióval 1.2.3A CMake automatikusan hibát generál. Vagyis nem kell manuálisan követnie a verziókat.

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)

Tesztek

Ha a tesztek le vannak tiltva kifejezetten a használatával megfelelő opciót vagy a projektünk egy alprojekt, vagyis egy másik CMake projekthez kapcsolódik a paranccsal add_subdirectory, nem haladunk tovább a hierarchiában, és a tesztek generálására és futtatására szolgáló parancsokat leíró szkript egyszerűen nem fut le.

if(NOT MYLIB_TESTING)
    message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
    add_subdirectory(test)
endif()

dokumentáció

Alprojekt esetében sem készül dokumentáció.

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

Online homokozó

Ugyanígy az alprojektnek sem lesz online homokozója.

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

Tesztszkript (test/CMakeLists.txt)

tesztelés

Először is találunk egy csomagot a szükséges teszt keretrendszerrel (cserélje ki kedvencére).

find_package(doctest 2.3.3 REQUIRED)

Készítsük el a futtatható fájlunkat tesztekkel. Általában csak azt a fájlt adom hozzá közvetlenül a futtatható binárishoz, amely a függvényt tartalmazza main.

add_executable(mylib-unit-tests test_main.cpp)

És hozzáadok fájlokat, amelyekben magukat a teszteket később ismertetjük. De nem kell ezt tenned.

target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)

Összekapcsoljuk a függőségeket. Kérjük, vegye figyelembe, hogy csak a szükséges CMake célokat kapcsoltuk össze a binárisunkkal, és nem hívtuk meg a parancsot target_include_directories. Címsorok a teszt keretrendszerből és a miénkből Mylib::mylib, valamint az összeállítási paraméterek (esetünkben ez a C++ nyelvi szabvány) ezekkel a célokkal együtt bejöttek.

target_link_libraries(mylib-unit-tests
    PRIVATE
        Mylib::mylib
        doctest::doctest
)

Végül létrehozunk egy dummy targetet, melynek „build”-je egyenértékű a tesztek futtatásával, és hozzáadjuk ezt a célt az alapértelmezett buildhez (az attribútum felelős ezért ALL). Ez azt jelenti, hogy az alapértelmezett build elindítja a tesztek futtatását, vagyis soha nem felejtjük el futtatni őket.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Lefedettség

Ezután engedélyezzük a kódlefedettség mérését, ha a megfelelő opció meg van adva. Nem megyek bele a részletekbe, mert ezek inkább egy lefedettségmérő eszközhöz kapcsolódnak, mint a CMake-hez. Csak azt fontos megjegyezni, hogy az eredmények alapján cél lesz coverage, amellyel kényelmesen el lehet kezdeni a lefedettség mérését.

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

Dokumentációs szkript (doc/CMakeLists.txt)

Megtalálta a Doxygent.

find_package(Doxygen)

Ezután ellenőrizzük, hogy a felhasználó beállította-e a nyelvi változót. Ha igen, akkor nem nyúlunk hozzá, ha nem, akkor vegyünk oroszt. Ezután konfiguráljuk a Doxygen rendszerfájlokat. Az összes szükséges változó, beleértve a nyelvet is, odakerül a konfigurációs folyamat során (lásd. команду configure_file).

Aztán létrehozunk egy célt doc, amely megkezdi a dokumentáció generálását. Mivel nem a dokumentáció generálása a legnagyobb igény a fejlesztési folyamatban, a cél alapértelmezés szerint nem lesz engedélyezve, azt kifejezetten el kell indítani.

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

Szkript az online sandboxhoz (online/CMakeLists.txt)

Itt megtaláljuk a harmadik Python-t, és létrehozunk egy célt wandbox, amely a szolgáltatás API-nak megfelelő kérést generál Pálcadoboz, és elküldi. A válasz egy linket tartalmaz a kész homokozóra.

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 kívül

Most pedig nézzük meg, hogyan használjuk mindezt.

gyülekezés

Ennek a projektnek a felépítése, mint bármely más projekt a CMake build rendszeren, két szakaszból áll:

generáció

cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Ha a fenti parancs a CMake egy régi verziója miatt nem működött, próbálja kihagyni -S:

cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Bővebben a lehetőségekről.

A projekt felépítése

cmake --build путь/к/сборочной/директории [--target target]

Bővebben az összeszerelési célokról.

Lehetőségek

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]

Tartalmazza a célt coverage, amellyel elkezdheti a kódlefedettség mérését tesztekkel.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]

Lehetővé teszi az egységteszt-felépítés és célzás letiltását check. Ennek eredményeként a kódlefedettség tesztekkel történő mérése kikapcsol (lásd. MYLIB_COVERAGE).

A tesztelés akkor is automatikusan letiltásra kerül, ha a projekt a paranccsal alprojektként kapcsolódik egy másik projekthez add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]

A cél által generált dokumentáció nyelvét váltja doc az adotthoz. Az elérhető nyelvek listáját lásd Doxygen rendszer honlapja.

Az orosz alapértelmezés szerint engedélyezve van.

Szerelési célok

Alapértelmezésben

cmake --build path/to/build/directory
cmake --build path/to/build/directory --target all

Ha a cél nincs megadva (ami egyenértékű a céllal all), mindent összegyűjt, amit csak lehet, és fel is hívja a célpontot check.

mylib-unit-tesztek

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

Egységteszteket állít össze. Alapértelmezés szerint engedélyezve.

ellenőrizze

cmake --build путь/к/сборочной/директории --target check

Futtatja az összegyűjtött (összegyűjtött, ha még nem) egységteszteket. Alapértelmezés szerint engedélyezve.

Lásd még mylib-unit-tests.

lefedettség

cmake --build путь/к/сборочной/директории --target coverage

Elemzi a futó (ha még nem) egységteszteket a kód lefedettségére a programot használó tesztekkel gcovr.

A bevonat kipufogója így fog kinézni:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

A cél csak akkor érhető el, ha az opció engedélyezve van MYLIB_COVERAGE.

Lásd még check.

doc

cmake --build путь/к/сборочной/директории --target doc

Elindítja a kóddokumentáció generálását a rendszer segítségével Doxigen.

pálcadoboz

cmake --build путь/к/сборочной/директории --target wandbox

A szolgálat válasza valahogy így néz ki:

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

A szolgáltatást erre használják Pálcadoboz. Nem tudom mennyire rugalmasak a szervereik, de úgy gondolom, hogy ezzel a lehetőséggel nem szabad visszaélni.

Примеры

Építse fel a projektet hibakeresési módban lefedettségméréssel

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16

Projekt telepítése előzetes összeszerelés és tesztelés nélkül

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target install

Kiadási módba építés egy adott fordítóval

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4

Angol nyelvű dokumentáció generálása

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc

Tools

  1. CMake 3.13

    Valójában a CMake 3.13-as verziója csak néhány, ebben a súgóban leírt konzolparancs futtatásához szükséges. A CMake szkriptek szintaxisa szempontjából a 3.8-as verzió elegendő, ha a generálást más módon hívják.

  2. Tesztkönyvtár doktest

    A tesztelés letiltható (lásd опцию MYLIB_TESTING).

  3. Doxigen

    Lehetőség van arra, hogy átváltsa a nyelvet, amelyen a dokumentáció készül MYLIB_DOXYGEN_LANGUAGE.

  4. Nyelvtolmács Python 3

    Automatikus generáláshoz online homokozók.

Statikus elemzés

A CMake és néhány jó eszköz segítségével minimális erőfeszítéssel statikus elemzést végezhet.

Cppcheck

A CMake beépített támogatással rendelkezik a statikus elemző eszközhöz Cppcheck.

Ehhez az opciót kell használnia CMAKE_CXX_CPPCHECK:

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"

Ezt követően a statikus elemzés automatikusan elindul minden alkalommal, amikor a forrás összeállításra és újrafordításra kerül. Nincs szükség további tennivalókra.

Csengés

Egy csodálatos eszköz segítségével scan-build A statikus elemzést is pillanatok alatt futtathatja:

scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директории

Itt, ellentétben a Cppcheck esetével, minden alkalommal le kell futtatnia a buildet scan-build.

utószó

A CMake egy nagyon erős és rugalmas rendszer, amely lehetővé teszi, hogy minden ízléshez és színhez funkcionalitást valósítson meg. És bár a szintaxis néha sok kívánnivalót hagy maga után, az ördög mégsem olyan szörnyű, mint ahogy lefestik. Használja a CMake build rendszert a társadalom és az egészség érdekében.

Töltse le a projektsablont

Forrás: will.com

Hozzászólás