CMake a C++ sú bratia navždy

CMake a C++ sú bratia navždy

Počas vývoja rád mením kompilátory, zostavujem režimy, verzie závislostí, vykonávam statickú analýzu, meriam výkon, zbieram pokrytie, generujem dokumentáciu atď. A naozaj milujem CMake, pretože mi umožňuje robiť všetko, čo chcem.

Mnoho ľudí kritizuje CMake a často zaslúžene, ale ak sa na to pozriete, nie je všetko také zlé a nedávno Vôbec nie zléa smer vývoja je celkom pozitívny.

V tejto poznámke vám chcem povedať, ako jednoducho zorganizovať knižnicu hlavičiek v C++ v systéme CMake, aby ste získali nasledujúce funkcie:

  1. zhromaždenie;
  2. Testy automatického spustenia;
  3. meranie pokrytia kódom;
  4. Inštalácia;
  5. Automatická dokumentácia;
  6. Generovanie online karantény;
  7. Statická analýza.

Každý, kto už chápe výhody a C-make, môže jednoducho stiahnuť šablónu projektu a začnite ho používať.


Obsah

  1. Projekt zvnútra
    1. Štruktúra projektu
    2. Hlavný súbor CMake (./CMakeLists.txt)
      1. Informácie o projekte
      2. Možnosti projektu
      3. Možnosti kompilácie
      4. Hlavným cieľom
      5. Inštalácia
      6. skúšky
      7. Záznamy
      8. Online pieskovisko
    3. Testovací skript (test/CMakeLists.txt)
      1. Testovanie
      2. Pokrytie
    4. Skript pre dokumentáciu (doc/CMakeLists.txt)
    5. Skript pre online karanténu (online/CMakeLists.txt)
  2. Projekt vonku
    1. zhromaždenia
      1. generácie
      2. zhromaždenia
    2. Možnosti
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montážne terče
      1. V predvolenom nastavení
      2. mylib-unit-tests
      3. skontrolovať
      4. krytie
      5. doc
      6. prútikový box
    4. príklady
  3. Nástroje
  4. Statická analýza
  5. Doslov

Projekt zvnútra

Štruktúra projektu

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

Budeme sa baviť hlavne o tom, ako organizovať skripty CMake, takže sa o nich budeme podrobne rozprávať. Ktokoľvek si môže pozrieť zvyšok súborov priamo na stránke projektu šablóny.

Hlavný súbor CMake (./CMakeLists.txt)

Informácie o projekte

V prvom rade si treba vyžiadať požadovanú verziu systému CMake. CMake sa vyvíja, podpisy príkazov a správanie v rôznych podmienkach sa menia. Aby CMake okamžite pochopil, čo od neho chceme, musíme naň okamžite zaznamenať naše požiadavky.

cmake_minimum_required(VERSION 3.13)

Potom označíme náš projekt, jeho názov, verziu, používané jazyky atď. (viď. команду project).

V tomto prípade uvádzame jazyk CXX (a to znamená C++), aby sa CMake nenamáhal a nehľadal kompilátor jazyka C (štandardne CMake obsahuje dva jazyky: C a C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Tu si môžete ihneď skontrolovať, či je náš projekt zaradený do iného projektu ako podprojekt. V budúcnosti to veľmi pomôže.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Možnosti projektu

Ponúkame dve možnosti.

Prvá možnosť je MYLIB_TESTING — zakázať testy jednotiek. To môže byť potrebné, ak sme si istí, že je všetko v poriadku s testami, ale chceme napríklad iba nainštalovať alebo zabaliť náš projekt. Alebo je náš projekt zahrnutý ako podprojekt – v tomto prípade používateľ nášho projektu nemá záujem spustiť naše testy. Netestuješ závislosti, ktoré používaš, však?

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

Okrem toho urobíme samostatnú možnosť MYLIB_COVERAGE na meranie pokrytia kódu testami, ale bude to vyžadovať ďalšie nástroje, takže bude potrebné explicitne povoliť.

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

Možnosti kompilácie

Samozrejme, sme cool plus programátori, takže od kompilátora chceme maximálnu úroveň diagnostiky počas kompilácie. Neprekĺzne ani jedna myš.

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
)

Zakážeme tiež rozšírenia, aby boli plne v súlade s jazykovým štandardom C++. V CMake sú štandardne povolené.

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

Hlavným cieľom

Naša knižnica pozostáva iba z hlavičkových súborov, čo znamená, že nemáme žiadne výfuky vo forme statických alebo dynamických knižníc. Na druhej strane, aby sme mohli našu knižnicu používať externe, musí byť nainštalovaná, musí byť detekovateľná v systéme a prepojená s vaším projektom a zároveň tieto isté hlavičky, ako aj prípadne nejaké doplnkové, sú k nemu pripojené vlastnosti.

Na tento účel vytvoríme knižnicu rozhrania.

add_library(mylib INTERFACE)

Hlavičky viažeme na našu knižnicu rozhrania.

Moderné, módne používanie CMake pre mládež znamená, že hlavičky, vlastnosti atď. prenášané cez jeden jediný cieľ. Takže stačí povedať target_link_libraries(target PRIVATE dependency)a všetky hlavičky, ktoré sú priradené k cieľu dependency, bude k dispozícii pre zdroje patriace do cieľa target. A nepotrebuješ žiadne [target_]include_directories. To bude demonštrované nižšie v analýze CMake skript pre unit testy.

Za pozornosť stojí aj tzv. выражения-генераторы: $<...>.

Tento príkaz spája hlavičky, ktoré potrebujeme, s našou knižnicou rozhrania a ak je naša knižnica pripojená k akémukoľvek cieľu v rámci rovnakej hierarchie CMake, budú k nej priradené hlavičky z adresára. ${CMAKE_CURRENT_SOURCE_DIR}/includea ak je naša knižnica nainštalovaná v systéme a pripojená k inému projektu pomocou príkazu find_package, potom k nemu budú priradené hlavičky z adresára include vzhľadom na inštalačný adresár.

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

Stanovme si jazykový štandard. Samozrejme, že úplne posledný. Zároveň štandard nielen zaraďujeme, ale rozširujeme aj na tých, ktorí budú našu knižnicu využívať. To sa dosiahne vďaka skutočnosti, že nastavená vlastnosť má kategóriu INTERFACE (Viď. target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Vytvorme alias pre našu knižnicu. Navyše pre krásu bude v špeciálnom „namespace“. Bude to užitočné, keď sa v našej knižnici objavia rôzne moduly a prepojíme ich nezávisle od seba. Ako napríklad v Buste.

add_library(Mylib::mylib ALIAS mylib)

Inštalácia

Inštalácia našich hlavičiek do systému. Všetko je tu jednoduché. Hovoríme, že priečinok so všetkými hlavičkami by mal ísť do adresára include vzhľadom na miesto inštalácie.

install(DIRECTORY include/mylib DESTINATION include)

Ďalej informujeme zostavovací systém, že chceme mať možnosť volať príkaz v projektoch tretích strán find_package(Mylib) a dostať gól Mylib::mylib.

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

Nasledujúce kúzlo by sa malo chápať takto. V projekte tretej strany voláme príkaz find_package(Mylib 1.2.3 REQUIRED)a skutočná verzia nainštalovanej knižnice bude nekompatibilná s verziou 1.2.3CMake automaticky vygeneruje chybu. To znamená, že nebudete musieť sledovať verzie manuálne.

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)

skúšky

Ak sú testy zakázané explicitne pomocou zodpovedajúca možnosť alebo náš projekt je podprojekt, to znamená, že je pripojený k inému projektu CMake pomocou príkazu add_subdirectory, ďalej sa v hierarchii neposúvame a skript, ktorý popisuje príkazy na generovanie a spúšťanie testov, sa jednoducho nespustí.

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

Záznamy

Dokumentácia sa tiež nebude generovať v prípade podprojektu.

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

Online pieskovisko

Rovnako ani podprojekt nebude mať online pieskovisko.

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

Testovací skript (test/CMakeLists.txt)

Testovanie

V prvom rade nájdeme balík s požadovaným testovacím rámcom (nahraďte ho obľúbeným).

find_package(doctest 2.3.3 REQUIRED)

Poďme vytvoriť náš spustiteľný súbor s testami. Zvyčajne pridávam priamo do spustiteľného binárneho súboru iba súbor, ktorý bude obsahovať funkciu main.

add_executable(mylib-unit-tests test_main.cpp)

A pridávam súbory, v ktorých sú neskôr popísané samotné testy. Ale nemusíte to robiť.

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

Spájame závislosti. Upozorňujeme, že sme prepojili iba ciele CMake, ktoré sme potrebovali, s naším binárnym súborom a nezavolali sme príkaz target_include_directories. Nadpisy z testovacieho rámca a z nášho Mylib::mylib, ako aj parametre zostavy (v našom prípade ide o jazykový štandard C++) spolu s týmito cieľmi dosiahli.

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

Nakoniec vytvoríme fiktívny cieľ, ktorého „zostavenie“ je ekvivalentné spusteniu testov a pridáme tento cieľ do predvoleného zostavenia (za to zodpovedá atribút ALL). To znamená, že predvolená zostava spúšťa testy, čo znamená, že ich nikdy nezabudneme spustiť.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pokrytie

Ďalej povolíme meranie pokrytia kódom, ak je zadaná vhodná možnosť. Nebudem zachádzať do podrobností, pretože sa týkajú skôr nástroja na meranie pokrytia ako CMake. Dôležité je len poznamenať, že na základe výsledkov sa vytvorí cieľ coverage, s ktorým je vhodné začať merať pokrytie.

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

Skript pre dokumentáciu (doc/CMakeLists.txt)

Našiel sa Doxygen.

find_package(Doxygen)

Ďalej skontrolujeme, či používateľ nastavil premennú jazyka. Ak áno, nedotkneme sa ho, ak nie, berieme ruštinu. Potom nakonfigurujeme systémové súbory Doxygen. Všetky potrebné premenné vrátane jazyka sa tam nachádzajú počas procesu konfigurácie (pozri. команду configure_file).

Potom vytvoríme cieľ doc, ktorý začne generovať dokumentáciu. Keďže generovanie dokumentácie nie je tou najväčšou potrebou v procese vývoja, cieľ nebude štandardne povolený, bude musieť byť spustený explicitne.

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

Skript pre online karanténu (online/CMakeLists.txt)

Tu nájdeme tretí Python a vytvoríme cieľ wandbox, ktorý vygeneruje požiadavku zodpovedajúcu servisnému API Box na prútika pošle ho preč. Odpoveď prichádza s odkazom na hotový 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 vonku

Teraz sa pozrime na to, ako toto všetko využiť.

zhromaždenia

Budovanie tohto projektu, rovnako ako akýkoľvek iný projekt na zostavovacom systéme CMake, pozostáva z dvoch fáz:

generácie

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

Ak príkaz uvedený vyššie nefungoval kvôli starej verzii CMake, skúste ho vynechať -S:

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

Viac o možnostiach.

Budovanie projektu

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

Viac o montážnych cieľoch.

Možnosti

MYLIB_COVERAGE

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

Zahŕňa cieľ coverage, pomocou ktorého môžete začať merať pokrytie kódu testami.

MYLIB_TESTING

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

Poskytuje možnosť zakázať zostavovanie a zacielenie testovania jednotiek check. V dôsledku toho sa vypne meranie pokrytia kódu testami (pozri. MYLIB_COVERAGE).

Testovanie je tiež automaticky vypnuté, ak je projekt pripojený k inému projektu ako podprojekt pomocou príkazu add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Prepne jazyk dokumentácie, ktorú generuje cieľ doc k danému. Zoznam dostupných jazykov nájdete v časti Webová stránka systému Doxygen.

Ruština je predvolene povolená.

Montážne terče

V predvolenom nastavení

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

Ak nie je zadaný cieľ (čo je ekvivalentné s cieľom all), zbiera všetko, čo môže, a tiež zavolá cieľ check.

mylib-unit-tests

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

Kompiluje unit testy. Predvolene povolené.

skontrolovať

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

Spustí zhromaždené (zhromaždené, ak ešte nie) testy jednotiek. Predvolene povolené.

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

krytie

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

Analyzuje spustené (ak ešte nie je spustené) jednotkové testy na pokrytie kódu testami pomocou programu gcovr.

Výfukový plyn bude vyzerať asi takto:

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

Cieľ je dostupný len vtedy, keď je táto možnosť povolená MYLIB_COVERAGE.

См. также check.

doc

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

Spustí generovanie dokumentácie kódu pomocou systému doxygen.

prútikový box

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

Odpoveď zo služby vyzerá asi takto:

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

Na to slúži služba Box na prútik. Neviem, aké flexibilné sú ich servery, ale myslím si, že táto príležitosť by sa nemala zneužívať.

príklady

Zostavte projekt v režime ladenia s meraním pokrytia

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

Inštalácia projektu bez predbežnej montáže a testovania

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

Zostavte v režime vydania s daným kompilátorom

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

Generovanie dokumentácie v angličtine

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

Nástroje

  1. CMake 3.13

    V skutočnosti je CMake verzia 3.13 potrebná len na spustenie niektorých príkazov konzoly opísaných v tomto pomocníkovi. Z pohľadu syntaxe skriptov CMake je verzia 3.8 dostatočná, ak sa generovanie volá inými spôsobmi.

  2. Testovacia knižnica doctest

    Testovanie je možné deaktivovať (pozri опцию MYLIB_TESTING).

  3. doxygen

    Na prepnutie jazyka, v ktorom sa bude dokumentácia generovať, je k dispozícii možnosť MYLIB_DOXYGEN_LANGUAGE.

  4. Jazykový tlmočník Python 3

    Pre automatické generovanie online pieskoviská.

Statická analýza

S CMake a niekoľkými dobrými nástrojmi môžete poskytnúť statickú analýzu s minimálnym úsilím.

Cppcheck

CMake má vstavanú podporu pre nástroj statickej analýzy Cppcheck.

Ak to chcete urobiť, musíte použiť možnosť CMAKE_CXX_CPPCHECK:

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

Potom sa pri každej kompilácii a prekompilovaní zdroja automaticky spustí statická analýza. Nie je potrebné robiť nič navyše.

rinčanie

S pomocou úžasného nástroja scan-build Okamžite môžete spustiť aj statickú analýzu:

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

Tu, na rozdiel od prípadu s Cppcheck, musíte zostavu spustiť zakaždým scan-build.

Doslov

CMake je veľmi výkonný a flexibilný systém, ktorý vám umožňuje implementovať funkčnosť pre každý vkus a farbu. A hoci syntax niekedy ponecháva veľa na želanie, diabol stále nie je taký strašný, ako je namaľovaný. Využite zostavovací systém CMake v prospech spoločnosti a zdravia.

Stiahnite si šablónu projektu

Zdroj: hab.com

Pridať komentár