CMake ja C++ – vennad igavesti

CMake ja C++ – vennad igavesti

Arendusprotsessis meeldib mulle vahetada kompilaatorite, kogumise režiimide, sõltuvuste versioonide vahel, teha staatilist analüüsi, mõõta jõudlust, koguda katvust, genereerida dokumentatsiooni jne. Ja mulle meeldib väga CMake, sest see võimaldab mul teha kõike, mida ma tahan.

Paljud inimesed kritiseerivad CMake'i ning tihti on see õigustatud, kuid kui asja lähemalt uurida, pole asi üldse nii halb, ja viimase ajal on see isegi päris hästi edenenud, ning areng suund on täiesti positiivne.

Selles märkuses soovin rääkida sellest, kuidas on üsna lihtne organiseerida C++ peaaguplokki CMake süsteemis, et saavutada järgmine funktsionaalsus:

  1. Kogumine;
  2. Automaatne testide käivitamine;
  3. Koodikatvuse mõõtmine;
  4. Paigaldamine;
  5. Automaatne dokumenteerimine;
  6. Online-keskkonna genereerimine;
  7. Staatiline analüüs.

Kes juba teab Plusidest ja CMake'ist, võib lihtsalt alla laadida projekti mall ja hakkata seda kasutama.


Sisukord

  1. Projekt seestpoolt
    1. Projekti struktuur
    2. Peamine CMake-fail (./CMakeLists.txt)
      1. Teave projekti kohta
      2. Projekti valikud
      3. Kompileerimise valikud
      4. Peamine eesmärk
      5. Paigaldamine
      6. Testid
      7. Dokumentatsioon
      8. Online-keskkond
    3. Testide skript (test/CMakeLists.txt)
      1. Testimine
      2. Katvus
    4. Dokumentatsiooni skript (doc/CMakeLists.txt)
    5. Online-keskkonna skript (online/CMakeLists.txt)
  2. Projekt väljaspool
    1. Kogumine
      1. Genereerimine
      2. Kogumine
    2. Valikud
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Kogumise eesmärgid
      1. Vaikimisi
      2. mylib-unit-tests
      3. check
      4. coverage
      5. doc
      6. wandbox
    4. Näited
  3. Tööriistad
  4. Staatiline analüüs
  5. Eessõna

Projekt seestpoolt

Projekti struktuur

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

Põhiliselt räägime sellest, kuidas organiseerida CMake skripte, seetõttu käsitletakse neid põhjalikult. Ülejäänud faile saab iga soovija vaadata otse projekti malli lehelt.

Peamine CMake-fail (./CMakeLists.txt)

Teave projekti kohta

Esiteks tuleb küsida vajalikku versiooni CMake süsteemist. CMake areneb, käskude allkirjad ja käitumine erinevates tingimustes muutuvad. Et CMake kohe mõistaks, mida me temalt ootame, tuleb kohe fikseerida meie ehk nõudmised.

cmake_minimum_required(VERSION 3.13)

Seejärel tähistame oma projekti, selle nime, versiooni, kasutatavad keeled ja muud (vt. käsku project).

Sel juhul määrame keele CXX (see tähendab C++), et CMake ei pingutaks ja ei otsiks C keele kompilaatorit (CMake'is on vaikimisi sisse lülitatud kaks keelt: C ja C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Siin on võimalik kohe kontrollida, kas meie projekt on teises projektis alaprojektina kaasatud. See aitab meid tulevikus oluliselt.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Projekti valikud

Kaalume kahte valikut.

Esimene valik — MYLIB_TESTING — modulaarsete testide väljalülitamine. See võib olla vajalik, kui oleme kindlad, et testidega on kõik korras, kuid soovime näiteks oma projekti ainult installida või pakkida. Või on meie projekt kaasatud alaprojektina — sellisel juhul pole meie projekti kasutajale huvi meie teste käivitada. Kas te testite sõltuvusi, mida kasutate?

option(MYLIB_TESTING "Luba modulaarne testimine" ON)

Lisaks teeme eraldi valiku MYLIB_COVERAGE kattuvuse mõõtmiseks testidega, kuid see vajab täiendavaid tööriistu, seega tuleb see selgelt lubada.

option(MYLIB_COVERAGE "Luba testidega kattuvuse mõõtmine" OFF)

Kompileerimise valikud

Muidugi oleme me ägedad C++ programmeerijad ja seetõttu tahame kompilaatori poolt maksimaalset kompileerimise aja diagnostikat. Ükski hiir ei lipsa läbi.

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
)

Samuti keelame laiendused, et täielikult vastata C++ keele standardile. CMake'is on need vaikimisi lubatud.

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

Peamine eesmärk

Meie teek koosneb ainult pealkirjafailidest, mistõttu meil ei ole mingit väljundit staatiliste või dünaamiliste teekide näol. Teisest küljest, et meie teeki väljastpoolt kasutada, tuleb see installida, et seda saaks süsteemis avastada ja oma projekti lisada, ning sellega peavad olema koos need sama pealkirjad, samuti võimalikud täiendavad omadused.

Selle eesmärgi saavutamiseks loome liidese teegi.

add_library(mylib INTERFACE)

Seome pealkirjad meie liidese teegiga.

Kaasaegne, trendikas ja nooruslik CMake'i kasutamine eeldab, et pealkirjad, omadused jne edastatakse läbi üheainsa sihtmärk. target_link_libraries(target PRIVATE dependency), ja kõik pealkirjad, mis on seotud sihtmärgiga dependency, on saadaval sihtmärgi kuuluvatele lähtekoodidele target. Ja ei ole vaja mingit [target_]include_directories. Это будет продемонстрировано ниже при разборе CMake-скрипта для модульных тестов.

Также стоит обратить внимание на т.н. выражения-генераторы: $<...>.

Данная команда ассоциирует нужные нам заголовки с нашей интерфейсной библиотекой, причём, в случае, если наша библиотека будет подключена к какой-либо цели в рамках одной иерархии CMake, то с ней будут ассоциированы заголовки из директории ${CMAKE_CURRENT_SOURCE_DIR}/include, а если наша библиотека установлена в систему и подключена в другой проект с помощью команды find_package, то с ней будут ассоциированы заголовки из директории include относительно директории установки.

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

Установим стандарт языка. Разумеется, самый последний. При этом не просто включаем стандарт, но и распространяем его на тех, кто будет использовать нашу библиотеку. Это достигается за счёт того, что установленное свойство имеет категорию INTERFACE (vt. команду target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Заводим псевдоним для нашей библиотеки. Причём для красоты он будет в специальном «пространстве имён». Это будет полезно, когда в нашей библиотеке появятся разные модули, и мы заходим подключать их независимо друг от друга. Как в Бусте, например.

add_library(Mylib::mylib ALIAS mylib)

Paigaldamine

Установка наших заголовков в систему. Тут всё просто. Говорим, что папка со всеми заголовками должна попасть в директорию include относительно места установки.

install(DIRECTORY include/mylib DESTINATION include)

Далее сообщаем системе сборки о том, что мы хотим иметь возможность в сторонних проектах звать команду find_package(Mylib) и получать цель Mylib::mylib.

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

Следующее заклинание нужно понимать так. Когда в стороннем проекте мы вызовем команду find_package(Mylib 1.2.3 REQUIRED), и при этом реальная версия установленной библиотеки окажется несовместимой с версией 1.2.3, CMake автоматически сгенерирует ошибку. То есть не нужно будет следить за версиями вручную.

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)

Testid

Если тесты выключены явно с помощью соответствующей опции või meie projekt on alamprojekt, see tähendab, et see on ühendatud teise CMake projekti kaudu käsu add_subdirectory, me ei liigu hierarhias edasi ja skript, milles on kirjeldatud käsud testide genereerimiseks ja käivitamiseks, lihtsalt ei käivitu.

if(NOT MYLIB_TESTING)
    message(STATUS "Mylibi projekti testimine on keelatud")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylibi ei testita alammodulina")
else()
    add_subdirectory(test)
endif()

Dokumentatsioon

Dokumentatsiooni ei genereerita ka alamprojekti puhul.

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

Online-keskkond

Sarnaselt ei ole alamprojekti juhul ka veebiliivakaste.

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

Testide skript (test/CMakeLists.txt)

Testimine

Esiteks otsime paketti vajaliku testimisraamistiku jaoks (asendage oma lemmikuga).

find_package(doctest 2.3.3 REQUIRED)

Loome oma käivitatava faili testide jaoks. Tavaline on, et käivitatavasse binaari lisan ainult faili, kus on funktsioon main.

add_executable(mylib-unit-tests test_main.cpp)

Ja failid, kus on kirja pandud testid, lisan hiljem. Kuid nii teha ei ole vajalik.

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

Seome sõltuvused. Pange tähele, et meie binaariga seostasime ainult vajalikud CMake eesmärgid ega kutsunud üles käsku target_include_directories. Testimisraamistiku pealkirjad ja meie Mylib::mylib, samuti kogumise parameetrid (meie juhul on see C++ keele standard) läksid koos nende eesmärkidega läbi.

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

Lõpuks loome vale eesmärgi, mille "kogumine" on võrdne testide käitamisega, ja lisame selle doelame standardkogumisse (seda juhib atribuut , või). See tähendab, et vaikimisi kogumine käivitab testide käitamist, seega me kunagi ei unusta neid käivitada.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Katvus

Seejärel aktiveerime koodikatvuse mõõtmise, kui vastav valik on määratud. Sügavale detailidesse ei lähe, kuna need kuuluvad rohkem katvuse mõõtamisseadmesse kui CMake'ile. Oluline on märkida ainult, et tulemuste põhjal luuakse eesmärk coverage, millega on mugav käivitada katvuse mõõtmine.

find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "Koodikatse katvuse mõõtmine on sisse lülitatud")

    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 "Koodikatse katvuse mõõtmiseks on vajalik programm gcovr")
endif()

Dokumentatsiooni skript (doc/CMakeLists.txt)

Leitud Doxygen.

find_package(Doxygen)

Edasi kontrollime, kas kasutaja on määranud keelemuutuja. Kui jah, siis ei puutu sellesse, kui ei, siis võtame vene. Seejärel konfigureerime Doxygen süsteemi failid. Kõik vajalikud muutujad, sealhulgas keel, saadakse konfiguratsiooni käigus (vt. komando configure_file).

Peale seda loome sihiks doc, mis käivitab dokumentatsiooni genereerimise. Kuna dokumentatsiooni genereerimine ei ole arenduse käigus kõige tähtsam vajadus, siis ei ole siht vaikimisi sisse lülitatud, see tuleb käivitada selgelt.

if (Doxygen_FOUND)
    if (NOT MYLIB_DOXYGEN_LANGUAGE)
        set(MYLIB_DOXYGEN_LANGUAGE Vene)
    endif()
    message(STATUS "Doxygeni dokumentatsioon genereeritakse keeles ${MYLIB_DOXYGEN_LANGUAGE}")
    configure_file(Doxyfile.in Doxyfile)
    add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}\/Doxyfile)
endif ()

Online-keskkonna skript (online/CMakeLists.txt)

Siin leiame kolmanda Python'i ja loome sihiks wandbox, mis genereerib päringu, mis vastab teenuse API-le Wandbox, ja saadab selle. Vastuseks tulevad valmis liivakasti link.

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 "Veebiliivakasti loomiseks on vajalik Python 3 interpreteur")
endif()

Projekt väljaspool

Nüüd vaatame, kuidas seda kõike kasutada.

Kogumine

Selle projekti koostamine, nagu ka iga teise CMake süsteemi projekti puhul, koosneb kahest etapist:

Genereerimine

cmake -S tee\/kallid -B tee\/kogumiskohta [valikud ...]

Kui ülemine käsk ei toiminud vanema CMake versiooni tõttu, proovige jätta välja -S:

cmake tee\/kallid -B tee\/kogumiskohta [valikud ...]

Rohkem valikute kohta.

Projekti koostamine

cmake --build tee\/kogumiskohta [--target siht]

Rohkem koostamis eesmärkide kohta.

Valikud

MYLIB_COVERAGE

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

Lülitab sihi sisse coverage, mille on võimalik käivitada koodikatvuse mõõtmine testide kaudu.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [muud seaded ...]

Pakub võimalusi moodulite testimise ja eesmärgi väljalülitamiseks. check. Selle tulemusena lülitatakse koodikatvuse mõõtmine testide kaudu välja (vt. MYLIB_COVERAGE).

Samuti lülitatakse testimine automaatselt välja, kui projekt lisatakse teise projekti alaprojektina käsu abil add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [muud seaded ...]

Vahetab dokumentatsiooni keele, mille genereerib eesmärk doc mugandatuks. Saadaval olevate keelte loetelu leiate Doxygen süsteemi veebisaidilt..

Vaikimisi on keel vene.

Kogumise eesmärgid

Vaikimisi

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

Kui eesmärki ei ole määratud (mis on samaväärne eesmärgiga all), kogub kõik, mis on võimalik, ning kutsub üles eesmärki. check.

mylib-unit-tests

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

Kompileerib moodulitestide. Vaikimisi on see sisse lülitatud.

check

cmake --build tee/ehituskausta --target check

Käivitab koostatud (kogub, kui veel mitte) moodulitestid. Vaikimisi on see sisse lülitatud.

Vaata ka mylib-unit-tests.

coverage

cmake --build tee/ehituskausta --target coverage

Analüüsib käivitatud (käivitab, kui veel mitte) moodulitestid koodikatvuse osas testimise ajal programmi gcovr.

Katvuse väljund näeb välja selline:

------------------------------------------------------------------------------
                           GCC Koodikatvuse Aruanne
Kaust: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
Fail                                       Read       Täitmine  Katvus   Puuduvad
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

Eesmärk on saadaval ainult, kui valik on sisse lülitatud. MYLIB_COVERAGE.

Vaata ka check.

doc

cmake --build tee/ehituskausta --target doc

Käivitab dokumentatsiooni genereerimise koodi jaoks Doxygen süsteemi abil. Doxygen.

wandbox

cmake --build tee/ehituskausta --target wandbox

Teenuse vastus näeb välja selline:

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

Selleks kasutatakse teenust Wandbox. Ma ei tea, kui paindlikud nende serverid on, kuid ma arvan, et seda võimalust ei tasu kuritarvitada.

Näited

Projekti ülesehitus tõrkeotsingu režiimis koodikatvuse mõõtmisega.

cmake -S tee/komplekt/dokumendid -B tee/ehituskausta -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build tee/ehituskausta --target coverage --parallel 16

Projekti paearendust ja testimist

cmake -S tee/koodifailid -B tee/ehituskaust -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=tee/paigalduskaust
cmake --build tee/ehituskaust --target install

Ehitus vabastamisrežiimis määratud kompilaatoriga

cmake -S tee/koodifailid -B tee/ehituskaust -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=tee/kaust/kuhu/paigaldatud/sõltuvused
cmake --build tee/ehituskaust --parallel 4

Dokumentatsiooni genereerimine inglise keeles

cmake -S tee/koodifailid -B tee/ehituskaust -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build tee/ehituskaust --target doc

Tööriistad

  1. CMake 3.13

    Tegelikult vajatakse CMake versiooni 3.13 ainult teatud konsolikomandide käivitamiseks, mis on kirjeldatud antud juhendis. CMake-skriptide süntaksiks piisab versioonist 3.8, kui genereerimist kutsuda välja muude viiside kaudu.

  2. Testimise raamatukogu doctest

    Testimist saab keelduda (vt. valikut MYLIB_TESTING).

  3. Doxygen

    Dokumentatsioonikeele vahetamiseks on olemas valik MYLIB_DOXYGEN_LANGUAGE.

  4. Tõlkija YP Python 3

    Automaatseks genereerimiseks veebipesakond.

Staatiline analüüs

CMake ja paar head tööriista võivad hõlpsasti tagada staatilise analüüsi minimaalse vaevaga.

Cppcheck

CMake'is on sisseehitatud tugi staatilise analüüsi tööriistale Cppcheck.

Selleks tuleb kasutada valikut CMAKE_CXX_CPPCHECK:

cmake -S tee/koodifailid -B tee/ehituskaust -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Itee/koodifailid/include"

Pärast seda käivitatakse staatiline analüüs automaatselt iga kord, kui lähtefaile kompileeritakse ja uuesti kompileeritakse. Ei ole vaja midagi lisada.

Clang

Imekauni tööriista abil scan-build saab samuti staatilist analüüsi kiiresti käivitada:

scan-build cmake -S tee/koodifailid -B tee/ehituskaust -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build tee/ehituskaust

Siin on vastupidiselt Cppcheck'ile vajalik igakordselt käivitada ehitus scan-build.

Eessõna

CMake on väga võimas ja paindlik süsteem, mis võimaldab realiseerida igasugust funktsionaalsust. Ja kuigi süntaks jätab vahel soovida, ei ole see nii hirmus, kui teda maalitakse. Kasutage CMake ehitussüsteemi ühiskonna hüvanguks ja enda terviseks.

Laadi alla projekti mall

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster