CMake i C++ su braća zauvijek

CMake i C++ su braća zauvijek

Tijekom razvoja volim mijenjati prevoditelje, načine izrade, verzije ovisnosti, izvoditi statičku analizu, mjeriti performanse, prikupljati pokrivenost, generirati dokumentaciju itd. I stvarno volim CMake jer mi omogućuje da radim sve što želim.

Mnogi ljudi kritiziraju CMake, i to često zasluženo, ali ako pogledate, nije sve tako loše, a nedavno uopće nije loše, a smjer razvoja je dosta pozitivan.

U ovoj bilješci želim vam reći kako jednostavno organizirati biblioteku zaglavlja u C++ u sustavu CMake da dobijete sljedeću funkcionalnost:

  1. Skupština;
  2. testovi automatskog pokretanja;
  3. Mjerenje pokrivenosti koda;
  4. Montaža;
  5. Auto-dokumentacija;
  6. Online sandbox generacija;
  7. Statička analiza.

Svatko tko već razumije prednosti i C-make može jednostavno preuzimanje predloška projekta i počnite ga koristiti.


sadržaj

  1. Projekt iznutra
    1. Struktura projekta
    2. Glavna CMake datoteka (./CMakeLists.txt)
      1. Informacije o projektu
      2. Mogućnosti projekta
      3. Mogućnosti kompilacije
      4. Glavni cilj
      5. Instalacija
      6. testovi
      7. Документация
      8. Online sandbox
    3. Testna skripta (test/CMakeLists.txt)
      1. Testiranje
      2. Pokrivenost
    4. Skripta za dokumentaciju (doc/CMakeLists.txt)
    5. Skripta za online sandbox (online/CMakeLists.txt)
  2. Projekt izvana
    1. zbor
      1. generacija
      2. zbor
    2. Opcije
      1. MYLIB_POKRIVANJE
      2. MYLIB_TESTIRANJE
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montažne mete
      1. Po defaultu
      2. mylib-jedinični testovi
      3. provjeriti
      4. pokrivenost
      5. doc
      6. kutija za štapiće
    4. Primjeri
  3. Alat
  4. Statička analiza
  5. pogovor

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

Uglavnom ćemo govoriti o tome kako organizirati CMake skripte, pa će se o njima detaljnije raspravljati. Svatko može izravno vidjeti ostale datoteke na stranici predloška projekta.

Glavna CMake datoteka (./CMakeLists.txt)

Informacije o projektu

Prije svega, potrebno je zatražiti potrebnu verziju CMake sustava. CMake se razvija, mijenjaju se potpisi naredbi i ponašanje u različitim uvjetima. Kako bi CMake odmah shvatio što želimo od njega, moramo odmah zabilježiti svoje zahtjeve za njim.

cmake_minimum_required(VERSION 3.13)

Zatim ćemo označiti naš projekt, njegov naziv, verziju, korištene jezike itd. (vidi. команду project).

U ovom slučaju označavamo jezik CXX (a to znači C++) tako da se CMake ne napreže i traži kompilator za C jezik (prema zadanim postavkama CMake uključuje dva jezika: C i C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Ovdje možete odmah provjeriti je li naš projekt uključen u neki drugi projekt kao potprojekt. Ovo će vam puno pomoći u budućnosti.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Mogućnosti projekta

Pružit ćemo dvije mogućnosti.

Prva opcija je MYLIB_TESTING — za onemogućavanje jediničnih testova. To može biti potrebno ako smo sigurni da je sve u redu s testovima, ali želimo samo npr. instalirati ili pakirati naš projekt. Ili je naš projekt uključen kao podprojekt - u ovom slučaju korisnik našeg projekta nije zainteresiran za izvođenje naših testova. Ne testirate ovisnosti koje koristite, zar ne?

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

Osim toga, napravit ćemo zasebnu opciju MYLIB_COVERAGE za mjerenje pokrivenosti koda testovima, ali će zahtijevati dodatne alate, pa će se morati izričito omogućiti.

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

Mogućnosti kompilacije

Naravno, mi smo cool plus programeri, pa želimo maksimalnu razinu dijagnostike tijekom kompajliranja od kompajlera. Niti jedan miš se neće provući.

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
)

Također ćemo onemogućiti proširenja kako bismo bili u potpunosti usklađeni sa standardom jezika C++. Oni su prema zadanim postavkama omogućeni u CMakeu.

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

Glavni cilj

Naša biblioteka sastoji se samo od datoteka zaglavlja, što znači da nemamo ispuha u obliku statičkih ili dinamičkih biblioteka. S druge strane, da bi eksterno koristili našu biblioteku, ona mora biti instalirana, mora biti detektabilna u sustavu i povezana s vašim projektom, a istovremeno ta ista zaglavlja, kao i eventualno neka dodatna, pridaju mu svojstva.

U tu svrhu stvaramo biblioteku sučelja.

add_library(mylib INTERFACE)

Zaglavlja povezujemo s bibliotekom sučelja.

Moderna, moderna, mladalačka upotreba CMakea podrazumijeva da zaglavlja, svojstva itd. prenosi kroz jednu metu. Dakle, dovoljno je reći target_link_libraries(target PRIVATE dependency)i sva zaglavlja koja su povezana s ciljem dependency, bit će dostupni za izvore koji pripadaju cilju target. I ne treba ti ništa [target_]include_directories. To će biti prikazano u nastavku analize CMake skripta za jedinične testove.

Također vrijedi obratiti pažnju na tzv. выражения-генераторы: $<...>.

Ova naredba pridružuje zaglavlja koja su nam potrebna s našom bibliotekom sučelja, a ako je naša biblioteka povezana s bilo kojim ciljem unutar iste CMake hijerarhije, tada će zaglavlja iz direktorija biti povezana s njom ${CMAKE_CURRENT_SOURCE_DIR}/include, a ako je naša knjižnica instalirana na sustav i povezana s drugim projektom pomoću naredbe find_package, tada će zaglavlja iz imenika biti povezana s njim include u odnosu na instalacijski direktorij.

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

Postavimo jezični standard. Naravno, posljednji. U isto vrijeme, ne samo da uključujemo standard, već ga i proširujemo na one koji će koristiti našu knjižnicu. To se postiže činjenicom da svojstvo skupa ima kategoriju INTERFACE (Vidi. naredba target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Stvorimo alias za našu knjižnicu. Štoviše, zbog ljepote, bit će u posebnom "prostoru imena". Ovo će biti korisno kada se u našoj biblioteci pojave različiti moduli, a mi ih povezujemo neovisno jedan o drugom. Kao na primjer u Busti.

add_library(Mylib::mylib ALIAS mylib)

Instalacija

Instaliranje naših zaglavlja u sustav. Ovdje je sve jednostavno. Kažemo da mapa sa svim zaglavljima treba ići u direktorij include u odnosu na mjesto instalacije.

install(DIRECTORY include/mylib DESTINATION include)

Zatim obavještavamo sustav za izgradnju da želimo imati mogućnost pozivanja naredbe u projektima trećih strana find_package(Mylib) i postići gol Mylib::mylib.

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

Sljedeću čaroliju treba shvatiti na ovaj način. Kada u projektu treće strane pozivamo naredbu find_package(Mylib 1.2.3 REQUIRED), a prava verzija instalirane biblioteke bit će nekompatibilna s verzijom 1.2.3CMake će automatski generirati pogrešku. To jest, nećete morati ručno pratiti verzije.

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

Ako su testovi izričito onemogućeni korištenjem odgovarajuća opcija ili je naš projekt podprojekt, odnosno naredbom je povezan s drugim CMake projektom add_subdirectory, ne idemo dalje po hijerarhiji, a skripta koja opisuje naredbe za generiranje i izvođenje testova jednostavno se ne pokreće.

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

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

Dokumentacija se također neće generirati u slučaju podprojekta.

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

Online sandbox

Isto tako, potprojekt neće imati ni online sandbox.

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

Testna skripta (test/CMakeLists.txt)

Testiranje

Prije svega, nalazimo paket sa potrebnim okvirom za testiranje (zamijenite onim koji vam je najdraži).

find_package(doctest 2.3.3 REQUIRED)

Kreirajmo našu izvršnu datoteku s testovima. Obično dodam izravno u izvršnu binarnu datoteku samo datoteku koja će sadržavati funkciju main.

add_executable(mylib-unit-tests test_main.cpp)

I dodajem datoteke u kojima su kasnije opisani sami testovi. Ali ne morate to učiniti.

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

Povezujemo ovisnosti. Imajte na umu da smo s našim binarnim datotekama povezali samo CMake ciljeve koji su nam bili potrebni i nismo pozvali naredbu target_include_directories. Naslovi iz okvira testa i iz našeg Mylib::mylib, kao i parametri izgradnje (u našem slučaju, ovo je standard jezika C++) došli su zajedno s ovim ciljevima.

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

Konačno, stvaramo lažni cilj, čija je "izgradnja" ekvivalentna izvođenju testova, i dodajemo ovaj cilj u zadanu verziju (atribut je odgovoran za ovo ALL). To znači da zadana verzija pokreće testove, što znači da ih nikada nećemo zaboraviti pokrenuti.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pokrivenost

Zatim omogućujemo mjerenje pokrivenosti koda ako je navedena odgovarajuća opcija. Neću ulaziti u detalje jer se oni više odnose na alat za mjerenje pokrivenosti nego na CMake. Važno je samo napomenuti da će se na temelju rezultata kreirati gol coverage, s kojim je zgodno započeti mjerenje pokrivenosti.

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

Skripta za dokumentaciju (doc/CMakeLists.txt)

Pronađen Doxygen.

find_package(Doxygen)

Zatim provjeravamo je li korisnik postavio jezičnu varijablu. Ako da, onda ga ne diramo, ako ne, onda uzimamo ruski. Zatim konfiguriramo datoteke sustava Doxygen. Sve potrebne varijable, uključujući jezik, idu tamo tijekom procesa konfiguracije (pogledajte. команду configure_file).

Zatim stvaramo cilj doc, čime će započeti generiranje dokumentacije. Budući da generiranje dokumentacije nije najveća potreba u procesu razvoja, cilj neće biti omogućen prema zadanim postavkama; morat će se eksplicitno pokrenuti.

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

Skripta za online sandbox (online/CMakeLists.txt)

Ovdje nalazimo treći Python i stvaramo cilj wandbox, koji generira zahtjev koji odgovara API-ju usluge Kutija za štapiće, i šalje ga. Odgovor dolazi s vezom na gotov 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 "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()

Projekt izvana

Sada pogledajmo kako sve to iskoristiti.

zbor

Izgradnja ovog projekta, kao i bilo kojeg drugog projekta na CMake sustavu za izgradnju, sastoji se od dvije faze:

generacija

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

Ako gornja naredba nije radila zbog stare verzije CMakea, pokušajte je izostaviti -S:

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

Više o opcijama.

Izgradnja projekta

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

Više o montažnim ciljevima.

Opcije

MYLIB_POKRIVANJE

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

Uključuje cilj coverage, s kojim možete početi mjeriti pokrivenost koda testovima.

MYLIB_TESTIRANJE

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

Pruža mogućnost onemogućavanja izrade i cilja jediničnog testa check. Kao rezultat toga, mjerenje pokrivenosti koda testovima je isključeno (vidi. MYLIB_COVERAGE).

Testiranje je također automatski onemogućeno ako je projekt pomoću naredbe povezan s drugim projektom kao podprojekt add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Mijenja jezik dokumentacije koju cilj generira doc danom. Za popis dostupnih jezika pogledajte Web stranica sustava Doxygen.

Ruski je omogućen prema zadanim postavkama.

Montažne mete

Po defaultu

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

Ako cilj nije naveden (što je ekvivalentno cilju all), skuplja sve što može, a također poziva metu check.

mylib-jedinični testovi

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

Sastavlja jedinične testove. Omogućeno prema zadanim postavkama.

provjeriti

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

Pokreće prikupljene (prikupljene, ako već nisu) jedinične testove. Omogućeno prema zadanim postavkama.

Vidi također mylib-unit-tests.

pokrivenost

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

Analizira pokrenute (pokreće, ako već nije) jedinične testove za pokrivenost koda testovima pomoću programa gcovr.

Ispuh premaza izgledat će otprilike ovako:

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

Cilj je dostupan samo kada je opcija uključena MYLIB_COVERAGE.

Vidi također check.

doc

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

Počinje generiranje kodne dokumentacije korištenjem sustava Doxygen.

kutija za štapiće

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

Odgovor servisa izgleda otprilike ovako:

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

Za to se koristi usluga Kutija za štapiće. Ne znam koliko su njihovi serveri fleksibilni, ali mislim da se ova prilika ne smije zlorabiti.

Primjeri

Izgradite projekt u načinu otklanjanja pogrešaka s mjerenjem pokrivenosti

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

Instalacija projekta bez prethodne montaže i testiranja

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

Izgradite u načinu izdanja s danim kompajlerom

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

Generiranje dokumentacije na engleskom jeziku

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

Alat

  1. CMake 3.13

    Zapravo, CMake verzija 3.13 potrebna je samo za pokretanje nekih konzolnih naredbi opisanih u ovoj pomoći. Sa stajališta sintakse CMake skripti, verzija 3.8 je dovoljna ako se generacija poziva na druge načine.

  2. Knjižnica za testiranje doctest

    Testiranje se može onemogućiti (vidi опцию MYLIB_TESTING).

  3. Doxygen

    Za promjenu jezika na kojem će se generirati dokumentacija postoji opcija MYLIB_DOXYGEN_LANGUAGE.

  4. Tumač jezika Python 3

    Za automatsko generiranje online pješčanici.

Statička analiza

Uz CMake i nekoliko dobrih alata, možete pružiti statičku analizu uz minimalan napor.

Cppcheck

CMake ima ugrađenu podršku za alat za statičku analizu Cppcheck.

Da biste to učinili, morate koristiti opciju CMAKE_CXX_CPPCHECK:

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

Nakon toga, statička analiza će se automatski pokrenuti svaki put kada se izvor kompajlira i ponovno kompajlira. Nema potrebe učiniti ništa dodatno.

Zveckanje

Uz pomoć prekrasnog alata scan-build Također možete pokrenuti statičku analizu u tren oka:

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

Ovdje, za razliku od slučaja s Cppcheckom, morate svaki put pokrenuti izgradnju scan-build.

pogovor

CMake je vrlo moćan i fleksibilan sustav koji vam omogućuje implementaciju funkcionalnosti za svaki ukus i boju. I, iako sintaksa ponekad ostavlja mnogo da se poželi, vrag ipak nije tako strašan kao što je naslikan. Koristite CMake sustav izgradnje za dobrobit društva i zdravlja.

Preuzmite predložak projekta

Izvor: www.habr.com

Dodajte komentar