CMake i C++ — večna braća

CMake i C++ — večna braća

V procesu razvoja radije menjam kompajlere, režime gradnje, verzije zavisnosti, sprovodim statičku analizu, mjerim performanse, pravim pokrivenost, generišem dokumentaciju itd. I jako volim CMake, jer mi omogućava da radim sve ono što želim.

Mnogi kritikuju CMake, i često s pravom, ali ako se malo potrudimo, nije sve tako loše, a u poslednje vreme veoma je dobro, a pravac razvoja je sasvim pozitivan.

U ovoj beležnici želim da podelim kako se prilično lako može organizovati header biblioteka na C++ jeziku u CMake sistemu, kako bismo dobili sledeću funkcionalnost:

  1. Izgradnja;
  2. Automatsko pokretanje testova;
  3. Merenje pokrivenosti koda;
  4. Instalacija;
  5. Autodokumentovanje;
  6. Generisanje online peskarije;
  7. Statička analiza.

Ko već zna nešto o C++ i CMake-u može jednostavno preuzeti projektni šablon i početi da ga koristi.


Vsebina

  1. Projekt iznutra
    1. Struktura projekta
    2. Glavni CMake fajl (./CMakeLists.txt)
      1. Informacije o projektu
      2. Opcije projekta
      3. Opcije kompajliranja
      4. Glavni cilj
      5. Namestitev
      6. Testovi
      7. Dokumentacija
      8. Online peskarija
    3. Skript za testove (test/CMakeLists.txt)
      1. Testiranje
      2. Pokrivenost
    4. Skript za dokumentaciju (doc/CMakeLists.txt)
    5. Skript za online peskariju (online/CMakeLists.txt)
  2. Projekt spolja
    1. Izgradnja
      1. Generacija
      2. Izgradnja
    2. Opcije
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Ciljevi izgradnje
      1. Privzeto
      2. mylib-unit-tests
      3. check
      4. coverage
      5. doc
      6. wandbox
    4. Primeri
  3. Orodja
  4. Statička analiza
  5. Zadnja misel

Projekt iznutra

Struktura projekta

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

Najpre ćemo govoriti o tome kako organizovati CMake skripte, pa će one biti detaljno objašnjene. Ostale datoteke svako zainteresovan može pogledati direktno na stranici projektnog šablona.

Glavni CMake fajl (./CMakeLists.txt)

Informacije o projektu

U prvom redu, potrebno je zahtevati potrebnu verziju CMake sistema. CMake se razvija, menjaju se potpisi komandi, ponašanje u različitim uslovima. Da bi CMake odmah razumeo šta od njega tražimo, potrebno je odmah definisati naše zahteve.

cmake_minimum_required(VERSION 3.13)

Zatim ćemo odrediti naš projekat, njegovo ime, verziju, korišćene jezike i ostalo (vidi) komandu project).

U ovom slučaju navodimo jezik CXX (to znači C++), kako bi CMake bio opušten i ne tražio kompajler za jezik C (po defaultu CMake podržava dva jezika: C i C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Tukaj lahko takoj preverite, ali je naš projekt vključen v drug projekt kot podprojekt. To nam bo močno pomagalo v prihodnje.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Opcije projekta

Predvidimo dve možnosti.

Prva možnost — MYLIB_TESTING — za izklop modularnih testov. To je lahko potrebno, če smo prepričani, da testi delujejo pravilno, in želimo, na primer, samo namestiti ali zapakirati naš projekt. Ali pa je naš projekt vključen kot podprojekt — v tem primeru ni zanimivo za uporabnika našega projekta, da izvaja naše teste. Saj ne testirate odvisnosti, ki jih uporabljate?

option(MYLIB_TESTING "Omogoči modularno testiranje" ON)

Poleg tega bomo naredili ločeno možnost MYLIB_COVERAGE za merjenje pokritosti kode s testi, vendar bo to zahtevalo dodatna orodja, zato jo bo treba izrecno vključiti.

option(MYLIB_COVERAGE "Omogoči merjenje pokritosti kode s testi" OFF)

Opcije kompajliranja

Seveda smo kul programerji c++, zato želimo od prevajalnika maksimalno diagnostiko med prevajanjem. Nobena napaka ne bo ušla.

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
)

Tudi razširitve bomo onemogočili, da bi se popolnoma skladali s standardom jezika C++. Privzeto so v CMake vključene.

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

Glavni cilj

Naša knjižnica je sestavljena samo iz glavnih datotek, kar pomeni, da ne razpolagamo z izhodom v obliki statičnih ali dinamičnih knjižnic. Po drugi strani pa, da bi lahko uporabljali našo knjižnico zunaj, jo je treba namestiti, mora biti odkrite v sistemu in povezana s svojim projektom, pri čemer morajo biti skupaj s tem povezane tudi te glavne datoteke ter morda še kakšne dodatne lastnosti.

Za ta namen ustvarimo vmesno knjižnico.

add_library(mylib INTERFACE)

Povezujemo glavne datoteke z našo vmesno knjižnico.

Sodobna, modna, mladinska uporaba CMake pomeni, da se glavne datoteke, lastnosti itd. prenesejo prek ene same tarče. Tako je dovolj reči target_link_libraries(target PRIVATE dependency), in vse glavne datoteke, ki so povezane s tarčo dependency, bodo dostopne za izvorne datoteke, ki pripadajo tarči. target. In niso potrebnih nobenih [target_]include_directories. To bo prikazano spodaj pri razčlembi CMake skripte za modularne teste.

Prav tako je pomembno omeniti izraze-generatorje: $.

Ta ukaz povezuje potrebne glave z našo vmesniško knjižnico, pri čemer, če bo naša knjižnica povezana z nekim ciljem v eni hierarhiji CMake, bodo zanjo povezane glave iz imenika ${CMAKE_CURRENT_SOURCE_DIR}/include, in če je naša knjižnica nameščena v sistem in povezana v drugem projektu z ukazom find_package, bodo zanjo povezane glave iz imenika include v zvezi z imenikom namestitve.

target_include_directories(mylib INTERFACE
    $
    $
)

Nastavimo standard jezika. Seveda, najnovejši. Pri tem ne samo, da vključimo standard, ampak ga tudi razširimo na tiste, ki bodo uporabljali našo knjižnico. To se doseže s tem, da je nastavljena lastnost kategorizirana INTERFACE (glej ukaz target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Ustvarimo psevdonim za našo knjižnico. Pri tem bo za lepoto v posebnem 'imenskemu prostoru'. To bo koristno, ko se v naši knjižnici pojavijo različni moduli, in jih potrebujemo povezati neodvisno drug od drugega. Kot v Boostu, na primer.

add_library(Mylib::mylib ALIAS mylib)

Namestitev

Namestitev naših glav v sistem. Tukaj je vse preprosto. Povezujemo, da mora mapa z vsemi glavami priti v imenik include v zvezi z mestom namestitve.

install(DIRECTORY include/mylib DESTINATION include)

Nato obvestimo sistem gradnje, da želimo imeti možnost v zunanjih projektih poklicati ukaz find_package(Mylib) in prejeti cilj Mylib::mylib.

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

Naslednji ukaz je treba razumeti tako. Ko v zunanjih projektih pokličemo ukaz find_package(Mylib 1.2.3 REQUIRED), in je dejanska različica nameščene knjižnice nezdružljiva z različico 1.2.3, bo CMake samodejno ustvaril napako. To pomeni, da ni treba ročno spremljati različic.

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)

Testovi

Če so testi izključeni jasno z ustrezno možnostjo ali naš projekt je podprojekt, tj. povezan je s drugim CMake projektom pomoću naredbe add_subdirectory, ne prelazimo dalje po hijerarhiji, a skripta koja opisuje naredbe za generiranje i pokretanje testova jednostavno se ne pokreće.

if(NOT MYLIB_TESTING)
    message(STATUS "Testiranje projekta Mylib je isključeno")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib se ne testira u modu podmodula")
else()
    add_subdirectory(test)
endif()

Dokumentacija

Dokumentacija također neće biti generirana u slučaju podprojekta.

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

Online peskarija

Slično tome, online sandboxi podprojekta također neće biti.

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

Skript za testove (test/CMakeLists.txt)

Testiranje

Prvo nalazimo paket s potrebnim testnim okvirom (zamijenite ga svojim omiljenim).

find_package(doctest 2.3.3 REQUIRED)

Kreiramo naš izvršni datoteku s testovima. Obično direktno u izvršni binarni dodajem samo datoteku koja sadrži funkciju main.

add_executable(mylib-unit-tests test_main.cpp)

A datoteke u kojima su opisani sami testovi dodajem kasnije. No, to nije obavezno.

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

Povezujemo ovisnosti. Imajte na umu da smo našem binarnom dodali samo potrebne CMake ciljeve, i nismo pozvali naredbu target_include_directories. Naslovi iz testnog okvira i iz naše Mylib::mylib, kao i parametri gradnje (u našem slučaju to je standard jezika C++) prošireni su zajedno s tim ciljevima.

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

Konačno, kreiramo lažni cilj čija je "izgradnja" ekvivalentna pokretanju testova, i dodajemo taj cilj u zadanu izgradnju (za to je odgovoran atribut ALL). To znači da zadana izgradnja pokreće testove, tj. nikada nećemo zaboraviti na njihovo pokretanje.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pokrivenost

Nakon toga uključujemo mjerenje pokrivenosti koda, ako je navedena odgovarajuća opcija. Neću se previše detaljno upuštati, jer se više odnosi na alat za mjerenje pokrivenosti nego na CMake. Važno je samo napomenuti da će se prema rezultatima stvoriti cilj coverage, s kojim je jednostavno pokrenuti mjerenje pokrivenosti.

find_program(GCOVR_EXECUTABLE gcovr) if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE) message(STATUS "Merjenje pokritosti kode s testi je vklopljeno") 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 "Za merjenje pokritosti kode s testi je potrebna programa gcovr") endif()

Skript za dokumentaciju (doc/CMakeLists.txt)

Našli smo Doxygen.

find_package(Doxygen)

Nato preverimo, ali je uporabnik nastavil spremenljivko z jezikom. Če je, jo pustimo pri miru, če ni, vzamemo ruščino. Nato konfiguriramo datoteke sistema Doxygen. Vse potrebne spremenljivke, vključno z jezikom, gredo tja v procesu konfiguracije (glej komando configure_file).

Po tem ustvarimo cilj doc, ki bo sprožil generiranje dokumentacije. Ker generiranje dokumentacije ni nujno potrebno v procesu razvoja, cilj po privzetku ne bo vključen, zato ga bo treba izrecno zagnati.

if (Doxygen_FOUND) if (NOT MYLIB_DOXYGEN_LANGUAGE) set(MYLIB_DOXYGEN_LANGUAGE Russian) endif() message(STATUS "Doxygen dokumentacija bo generirana v ${MYLIB_DOXYGEN_LANGUAGE}") configure_file(Doxyfile.in Doxyfile) add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) endif ()

Skript za online peskariju (online/CMakeLists.txt)

Tu najdemo tretji Python in ustvarimo cilj wandbox, ki generira zahtevo, ustrezno API-ju storitve Wandbox, in jo pošlje. V odgovor pridemo do povezave do končne peskovnika.

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 "Za ustvarjanje spletne peskovnice je potreben interpreter jezika Python 3") endif()

Projekt spolja

Zdaj poglejmo, kako vse to uporabljati.

Izgradnja

Gradnja tega projekta, kot katerega koli drugega projekta v sistemu za gradnjo CMake, sestavlja iz dveh faz:

Generacija

cmake -S pot/k/izvornikom -B pot/k/gradbeni/direktoriji [opcje ...]

Če zgornja ukaz ne deluje zaradi stare različice CMake, poskusite opustiti -S:

cmake pot/k/izvornikom -B pot/k/gradbeni/direktoriji [opcje ...]

Več o opcijah.

Gradnja projekta

cmake --build pot/k/gradbeni/direktoriji [--target cilj]

Več o gradbenih ciljih.

Opcije

MYLIB_COVERAGE

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

Vključi cilj coverage, s katerim lahko začnete merjenje pokritosti kode s testi.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [druge možnosti ...]

Omogoča onemogočanje gradnje enotnih testov in cilja check. Kot posledica se onemogoči merjenje pokritosti kode s testi (glej. MYLIB_COVERAGE).

Prav tako se testiranje samodejno onemogoči, če se projekt vključi v drug projekt kot podprojekt z uporabo ukaza add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [druge možnosti ...]

Preklopi jezik dokumentacije, ki jo generira cilj doc , na določenega. Seznam razpoložljivih jezikov je na spletni strani sistema Doxygen.

Privzeto je vključena ruščina.

Ciljevi izgradnje

Privzeto

cmake --build pot/do/gradbene/direktorije
cmake --build pot/do/gradbene/direktorije --target all

Če cilj ni naveden (kar je enakovredno cilju all), se gradi vse, kar je mogoče, in se pokliče cilj check.

mylib-unit-tests

cmake --build pot/do/gradbene/direktorije --target mylib-unit-tests

Kompilira enotne teste. Privzeto vključeno.

check

cmake --build pot/k/gradbeni/direktoriji --target check

Zaganja zbrane (gradi, če še niso) enotne teste. Privzeto vključeno.

Glej tudi mylib-unit-tests.

coverage

cmake --build pot/k/gradbeni/direktoriji --target coverage

Analizira zagnane (zagnati, če še niso) enotne teste za pokritost kode s testi z uporabo programa gcovr.

Izhod pokritosti bo videti približno takole:

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

Cilj je na voljo samo, ko je možnost vključena MYLIB_COVERAGE.

Glej tudi check.

doc

cmake --build pot/k/gradbeni/direktoriji --target doc

Zaganja generacijo dokumentacije kode z uporabo sistema Doxygen.

wandbox

cmake --build pot/k/gradbeni/direktoriji --target wandbox

Odgovor storitve izgleda približno takole:

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

Za to se uporablja storitev Wandbox. Ne vem, kako elastični so njihovi strežniki, ampak mislim, da ne bi smeli zlorabljati te možnosti.

Primeri

Gradnja projekta v načinu za odkrivanje z merjenjem pokritosti

cmake -S pot/k/izvorom -B pot/k/gradbeni/direktoriji -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build pot/k/gradbeni/direktoriji --target coverage --parallel 16

Namestitev projekta brez predhodne gradnje in testiranja

cmake -S pot/k/izvorom -B pot/k/gradbeni/direktorij -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=pot/k/namestitveni/direktorij
cmake --build pot/k/gradbeni/direktorij --target install

Gradnja v prodajnem načinu z izbranim prevajalnikom

cmake -S pot/k/izvorom -B pot/k/gradbeni/direktorij -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=pot/k/direktoriji/kje/so/naložene/odvisnosti
cmake --build pot/k/gradbeni/direktorij --parallel 4

Generiranje dokumentacije v angleščini

cmake -S pot/k/izvorom -B pot/k/gradbeni/direktorij -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build pot/k/gradbeni/direktorij --target doc

Orodja

  1. CMake 3.13

    Dejansko je različica CMake 3.13 potrebna le za izvajanje nekaterih konzolnih ukazov, opisanih v tej dokumentaciji. Z vidika sintakse CMake skript je dovolj različica 3.8, če se generacija kliče na druge načine.

  2. Knjižnica za testiranje doctest

    Testiranje se lahko onemogoči (glej možnost MYLIB_TESTING).

  3. Doxygen

    Za preklop jezika, v katerem bo generirana dokumentacija, je na voljo možnost MYLIB_DOXYGEN_LANGUAGE.

  4. Interpreter programskega jezika Python 3

    Za samodejno generiranje spletne peskovnike.

Statička analiza

Z CMake in nekaj dobrimi orodji lahko zagotovite statično analizo z minimalnim trudom.

Cppcheck

V CMake je vgrajena podpora za orodje za statično analizo Cppcheck.

Za to je treba uporabiti možnost CMAKE_CXX_CPPCHECK:

cmake -S pot/k/izvorom -B pot/k/gradbeni/direktorij -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Ipot/k/izvorom/include"

Po tem se bo statična analiza samodejno izvajala vsakič med kompilacijo in ponovo kompilacijo izvorov. Nič dodatnega ni treba storiti.

Clang

S čudežnim orodjem scan-build lahko prav tako hitro zaženete statično analizo:

scan-build cmake -S pot/k/izvorom -B pot/k/gradbeni/direktorij -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build pot/k/gradbeni/direktorij

Tukaj, za razliko od primera s Cppcheckom, je potrebno vsakič zagnati gradnjo preko scan-build.

Zadnja misel

CMake je zelo močan in prilagodljiv sistem, ki omogoča uresničitev funkcionalnosti po vašem okusu. In čeprav je sintaksa včasih vprašljiva, še zdaleč ni tako strašna, kot jo opisujejo. Uporabite sistem gradnje CMake v korist družbe in za vaše zdravje.

→ Prenesite predlogo projekta

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster