CMake un C++ ir brāļi uz visiem laikiem

CMake un C++ ir brāļi uz visiem laikiem

Izstrādes laikā man patīk mainīt kompilatorus, veidot režīmus, atkarības versijas, veikt statisko analīzi, mērīt veiktspēju, vākt pārklājumu, ģenerēt dokumentāciju utt. Un es ļoti mīlu CMake, jo tas ļauj man darīt visu, ko es vēlos.

Daudzi cilvēki kritizē CMake, un bieži vien pelnīti, bet, ja paskatās, tad ne viss ir tik slikti, un nesen vispār nav slikti, un attīstības virziens ir diezgan pozitīvs.

Šajā piezīmē es vēlos jums pastāstīt, kā vienkārši organizēt galvenes bibliotēku programmā C++ sistēmā CMake, lai iegūtu šādu funkcionalitāti:

  1. Montāža;
  2. automātiskās palaišanas testi;
  3. Koda pārklājuma mērīšana;
  4. Uzstādīšana;
  5. automātiskā dokumentācija;
  6. Tiešsaistes smilškastes ģenerēšana;
  7. Statiskā analīze.

Ikviens, kurš jau saprot priekšrocības un C-make, var vienkārši lejupielādēt projekta veidni un sāciet to lietot.


saturs

  1. Projekts no iekšpuses
    1. Projekta struktūra
    2. Galvenais CMake fails (./CMakeLists.txt)
      1. Informācija par projektu
      2. Projekta iespējas
      3. Kompilācijas iespējas
      4. Galvenais mērķis
      5. Uzstādīšana
      6. Testi
      7. Документация
      8. Tiešsaistes smilšu kaste
    3. Testa skripts (test/CMakeLists.txt)
      1. Testēšana
      2. Pārklājums
    4. Skripts dokumentācijai (doc/CMakeLists.txt)
    5. Skripts tiešsaistes smilškastei (online/CMakeLists.txt)
  2. Projekts ārpusē
    1. Montāža
      1. Paaudze
      2. Montāža
    2. Iespējas
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montāžas mērķi
      1. Pēc noklusējuma
      2. mylib-unit-tests
      3. pārbaudīt
      4. pārklājums
      5. doc
      6. zižļu kaste
    4. piemēri
  3. Darbarīki
  4. Statiskā analīze
  5. Pēcvārds

Projekts no iekšpuses

Projekta struktūra

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

Mēs galvenokārt runāsim par to, kā organizēt CMake skriptus, tāpēc tie tiks apspriesti detalizēti. Ikviens var tieši skatīt pārējos failus veidnes projekta lapā.

Galvenais CMake fails (./CMakeLists.txt)

Informācija par projektu

Pirmkārt, jums ir jāpieprasa nepieciešamā CMake sistēmas versija. CMake attīstās, komandu paraksti un uzvedība dažādos apstākļos mainās. Lai CMake uzreiz saprastu, ko mēs no tā vēlamies, mums nekavējoties jāreģistrē savas prasības tai.

cmake_minimum_required(VERSION 3.13)

Pēc tam mēs norādīsim mūsu projektu, tā nosaukumu, versiju, izmantotās valodas utt. (sk. команду project).

Šajā gadījumā mēs norādām valodu CXX (un tas nozīmē C++), lai CMake nenoslogotos un nemeklētu C valodas kompilatoru (pēc noklusējuma CMake ietver divas valodas: C un C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Šeit jūs varat uzreiz pārbaudīt, vai mūsu projekts ir iekļauts kādā citā projektā kā apakšprojekts. Tas ļoti palīdzēs nākotnē.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Projekta iespējas

Mēs piedāvāsim divas iespējas.

Pirmais variants ir MYLIB_TESTING — lai atspējotu vienību testus. Tas var būt nepieciešams, ja esam pārliecināti, ka ar testiem viss ir kārtībā, bet mēs vēlamies tikai, piemēram, instalēt vai pakotēt mūsu projektu. Vai arī mūsu projekts ir iekļauts kā apakšprojekts - šajā gadījumā mūsu projekta lietotājs nav ieinteresēts veikt mūsu testus. Jūs taču nepārbaudāt izmantotās atkarības, vai ne?

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

Turklāt mēs izveidosim atsevišķu iespēju MYLIB_COVERAGE koda pārklājuma mērīšanai ar testiem, taču tam būs nepieciešami papildu rīki, tāpēc tas būs skaidri jāiespējo.

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

Kompilācijas iespējas

Protams, mēs esam forši plus programmētāji, tāpēc vēlamies no kompilatora nodrošināt maksimālu kompilēšanas laika diagnostiku. Neviena pele neizslīdēs cauri.

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
)

Mēs arī atspējosim paplašinājumus, lai pilnībā atbilstu C++ valodas standartam. Tie ir iespējoti pēc noklusējuma programmā CMake.

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

Galvenais mērķis

Mūsu bibliotēkā ir tikai galvenes faili, kas nozīmē, ka mums nav nekādu izplūdumu statisku vai dinamisku bibliotēku veidā. No otras puses, lai izmantotu mūsu bibliotēku ārēji, tai ir jābūt instalētai, tai jābūt nosakāmai sistēmā un jāsavieno ar jūsu projektu, un tajā pašā laikā šīm pašām galvenes, kā arī, iespējams, dažas papildu, tam ir pievienotas īpašības.

Šim nolūkam mēs izveidojam interfeisa bibliotēku.

add_library(mylib INTERFACE)

Mēs saistām galvenes ar mūsu interfeisa bibliotēku.

Mūsdienīgs, moderns, jauniešu lietojums CMake nozīmē, ka galvenes, rekvizīti utt. pārraida caur vienu mērķi. Tāpēc pietiks pateikt target_link_libraries(target PRIVATE dependency)un visas galvenes, kas ir saistītas ar mērķi dependency, būs pieejami avotiem, kas pieder mērķim target. Un jums tas nav vajadzīgs [target_]include_directories. Tas tiks parādīts zemāk analīzē CMake skripts vienību testiem.

Ir vērts pievērst uzmanību arī t.s. выражения-генераторы: $<...>.

Šī komanda saista mums nepieciešamās galvenes ar mūsu interfeisa bibliotēku, un, ja mūsu bibliotēka ir savienota ar jebkuru mērķi tajā pašā CMake hierarhijā, tad direktorija galvenes tiks saistītas ar to. ${CMAKE_CURRENT_SOURCE_DIR}/include, un ja mūsu bibliotēka ir instalēta sistēmā un savienota ar citu projektu, izmantojot komandu find_package, tad ar to tiks saistītas direktorija galvenes include attiecībā pret instalācijas direktoriju.

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

Noteiksim valodas standartu. Protams, pats pēdējais. Tajā pašā laikā mēs ne tikai iekļaujam standartu, bet arī attiecinām to uz tiem, kas izmantos mūsu bibliotēku. Tas tiek panākts, jo iestatītajam īpašumam ir kategorija INTERFACE (Sk. komandu target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Izveidosim aizstājvārdu mūsu bibliotēkai. Turklāt skaistuma labad tas atradīsies īpašā “vārdu telpā”. Tas būs noderīgi, ja mūsu bibliotēkā parādās dažādi moduļi un mēs tos savienosim neatkarīgi viens no otra. Kā, piemēram, Bustā.

add_library(Mylib::mylib ALIAS mylib)

Uzstādīšana

Mūsu galvenes instalēšana sistēmā. Šeit viss ir vienkārši. Mēs sakām, ka mapei ar visām galvenēm jāiet direktorijā include attiecībā pret uzstādīšanas vietu.

install(DIRECTORY include/mylib DESTINATION include)

Pēc tam mēs informējam būvēšanas sistēmu, ka vēlamies izsaukt komandu trešo pušu projektos find_package(Mylib) un gūt vārtus Mylib::mylib.

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

Nākamā burvestība ir jāsaprot šādi. Kad trešās puses projektā mēs izsaucam komandu find_package(Mylib 1.2.3 REQUIRED), un instalētās bibliotēkas reālā versija nebūs saderīga ar versiju 1.2.3CMake automātiski ģenerēs kļūdu. Tas ir, jums nebūs manuāli jāizseko versijas.

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)

Testi

Ja testi ir atspējoti, izmantojot atbilstošā opcija vai mūsu projekts ir apakšprojekts, tas ir, tas ir savienots ar citu CMake projektu, izmantojot komandu add_subdirectory, mēs nepārvietojamies tālāk pa hierarhiju, un skripts, kas apraksta komandas testu ģenerēšanai un izpildei, vienkārši nedarbojas.

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

Документация

Arī apakšprojekta gadījumā dokumentācija netiks ģenerēta.

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

Tiešsaistes smilšu kaste

Tāpat apakšprojektam nebūs arī tiešsaistes smilšu kastes.

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

Testa skripts (test/CMakeLists.txt)

Testēšana

Pirmkārt, mēs atrodam paketi ar nepieciešamo testa ietvaru (nomainiet ar savu iecienītāko).

find_package(doctest 2.3.3 REQUIRED)

Izveidosim izpildāmo failu ar testiem. Parasti es pievienoju tieši izpildāmajam binārajam failam tikai to failu, kurā būs šī funkcija main.

add_executable(mylib-unit-tests test_main.cpp)

Un es pievienoju failus, kuros paši testi ir aprakstīti vēlāk. Bet jums tas nav jādara.

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

Mēs savienojam atkarības. Lūdzu, ņemiet vērā, ka mēs saistījām tikai vajadzīgos CMake mērķus ar mūsu bināro failu un neizsaucām komandu target_include_directories. Virsraksti no testa ietvara un no mūsu Mylib::mylib, kā arī uzbūves parametri (mūsu gadījumā tas ir C++ valodas standarts) tika sasniegti kopā ar šiem mērķiem.

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

Visbeidzot mēs izveidojam fiktīvu mērķi, kura “būvējums” ir līdzvērtīgs testu izpildei, un pievienojam šo mērķi noklusējuma būvējumam (par to atbild atribūts ALL). Tas nozīmē, ka noklusējuma versija aktivizē testu izpildi, kas nozīmē, ka mēs nekad neaizmirsīsim tos palaist.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pārklājums

Pēc tam mēs iespējojam koda pārklājuma mērīšanu, ja ir norādīta atbilstošā opcija. Es neiedziļināšos detaļās, jo tie vairāk attiecas uz pārklājuma mērīšanas rīku, nevis ar CMake. Ir tikai svarīgi atzīmēt, ka, pamatojoties uz rezultātiem, tiks izveidots mērķis coverage, ar kuru ērti sākt pārklājuma mērīšanu.

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

Skripts dokumentācijai (doc/CMakeLists.txt)

Atrasts Doxygen.

find_package(Doxygen)

Tālāk mēs pārbaudām, vai lietotājs ir iestatījis valodas mainīgo. Ja jā, tad neaiztiekam, ja nē, tad ņemam krievu valodu. Pēc tam mēs konfigurējam Doxygen sistēmas failus. Visi nepieciešamie mainīgie, tostarp valoda, tiek parādīti konfigurācijas procesa laikā (sk. команду configure_file).

Tad mēs izveidojam mērķi doc, kas sāks ģenerēt dokumentāciju. Tā kā izstrādes procesā dokumentācijas ģenerēšana nav lielākā nepieciešamība, mērķis pēc noklusējuma netiks iespējots, tas būs skaidri jāpalaiž.

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

Skripts tiešsaistes smilškastei (online/CMakeLists.txt)

Šeit mēs atrodam trešo Python un izveidojam mērķi wandbox, kas ģenerē pieprasījumu, kas atbilst pakalpojuma API Wandbox, un aizsūta viņu prom. Atbildei ir pievienota saite uz gatavo smilšu kasti.

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

Projekts ārpusē

Tagad apskatīsim, kā to visu izmantot.

Montāža

Šī projekta, tāpat kā jebkura cita projekta CMake veidošanas sistēmā, izveide sastāv no diviem posmiem:

Paaudze

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

Ja iepriekš minētā komanda nedarbojās vecas CMake versijas dēļ, mēģiniet izlaist -S:

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

Vairāk par opcijām.

Projekta būvēšana

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

Vairāk par montāžas mērķiem.

Iespējas

MYLIB_COVERAGE

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

Ietver mērķi coverage, ar kuru jūs varat sākt koda pārklājuma mērīšanu ar testiem.

MYLIB_TESTING

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

Nodrošina iespēju atspējot vienības pārbaudes veidošanu un mērķauditorijas atlasi check. Rezultātā tiek izslēgta koda pārklājuma mērīšana ar testiem (sk. MYLIB_COVERAGE).

Testēšana tiek automātiski atspējota arī tad, ja projekts ir savienots ar citu projektu kā apakšprojektu, izmantojot komandu add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Pārslēdz mērķa ģenerētās dokumentācijas valodu doc uz doto. Pieejamo valodu sarakstu sk Doxygen sistēmas vietne.

Krievu valoda ir iespējota pēc noklusējuma.

Montāžas mērķi

Pēc noklusējuma

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

Ja mērķis nav norādīts (kas ir līdzvērtīgs mērķim all), savāc visu, ko var, un arī izsauc mērķi check.

mylib-unit-tests

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

Sastāda vienību testus. Iespējots pēc noklusējuma.

pārbaudīt

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

Palaiž apkopotos (apkopotos, ja vēl nav) vienību testus. Iespējots pēc noklusējuma.

См. также mylib-unit-tests.

pārklājums

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

Analizē palaistos (palaiž, ja vēl nav) vienību testus, lai noteiktu koda pārklājumu ar testiem, izmantojot programmu gcovr.

Pārklājuma izplūde izskatīsies apmēram šādi:

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

Mērķis ir pieejams tikai tad, ja opcija ir iespējota MYLIB_COVERAGE.

См. также check.

doc

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

Uzsāk koda dokumentācijas ģenerēšanu, izmantojot sistēmu Doksīds.

zižļu kaste

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

Atbilde no dienesta izskatās apmēram šādi:

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

Pakalpojums tiek izmantots šim nolūkam Wandbox. Es nezinu, cik elastīgi ir viņu serveri, bet es domāju, ka šo iespēju nevajadzētu izmantot ļaunprātīgi.

piemēri

Veidojiet projektu atkļūdošanas režīmā ar pārklājuma mērījumu

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

Projekta uzstādīšana bez iepriekšējas montāžas un testēšanas

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

Veidot izlaišanas režīmā ar noteiktu kompilatoru

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

Dokumentācijas ģenerēšana angļu valodā

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

Darbarīki

  1. CMake 3.13

    Faktiski CMake versija 3.13 ir nepieciešama tikai, lai palaistu dažas šajā palīdzībā aprakstītās konsoles komandas. No CMake skriptu sintakses viedokļa pietiek ar versiju 3.8, ja ģenerēšana tiek izsaukta citos veidos.

  2. Testēšanas bibliotēka doktests

    Testēšanu var atspējot (sk опцию MYLIB_TESTING).

  3. Doksīds

    Ir pieejama iespēja pārslēgt valodu, kurā tiks ģenerēta dokumentācija MYLIB_DOXYGEN_LANGUAGE.

  4. Valodas tulks Python 3

    Automātiskajai ģenerēšanai tiešsaistes smilšu kastes.

Statiskā analīze

Izmantojot CMake un dažus labus rīkus, varat nodrošināt statisku analīzi ar minimālu piepūli.

Cppcheck

CMake ir iebūvēts statiskās analīzes rīka atbalsts Cppcheck.

Lai to izdarītu, jums ir jāizmanto opcija CMAKE_CXX_CPPCHECK:

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

Pēc tam statiskā analīze tiks automātiski palaists katru reizi, kad avots tiks apkopots un pārkompilēts. Nekas papildus nav jādara.

Klans

Ar brīnišķīga instrumenta palīdzību scan-build Varat arī ātri palaist statisko analīzi:

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

Šeit atšķirībā no Cppcheck gadījuma jums ir jāpalaiž būvējums katru reizi scan-build.

Pēcvārds

CMake ir ļoti jaudīga un elastīga sistēma, kas ļauj īstenot funkcionalitāti katrai gaumei un krāsai. Un, lai gan sintakse dažkārt atstāj daudz ko vēlēties, velns joprojām nav tik briesmīgs, kā viņš ir gleznots. Izmantojiet CMake build sistēmu sabiedrības un veselības labā.

Lejupielādēt projekta veidni

Avots: www.habr.com

Pievieno komentāru