CMake in C++ sta brata za vedno

CMake in C++ sta brata za vedno

Med razvojem rad spreminjam prevajalnike, gradim načine, različice odvisnosti, izvajam statično analizo, merim zmogljivost, zbiram pokritost, ustvarjam dokumentacijo itd. In res obožujem CMake, ker mi omogoča, da naredim vse, kar želim.

Mnogi ljudje kritizirajo CMake in pogosto zasluženo, a če pogledate, ni vse tako slabo in pred kratkim sploh ni slabo, smer razvoja pa je precej pozitivna.

V tej opombi vam želim povedati, kako preprosto organizirati knjižnico glav v C++ v sistemu CMake, da dobite naslednjo funkcionalnost:

  1. Montaža;
  2. Preizkusi samodejnega zagona;
  3. Merjenje pokritosti kode;
  4. namestitev;
  5. Avtodokumentacija;
  6. Generiranje spletnega peskovnika;
  7. Statična analiza.

Kdor že razume prednosti in C-make, lahko preprosto prenesite predlogo projekta in ga začni uporabljati.


Vsebina

  1. Projekt od znotraj
    1. Struktura projekta
    2. Glavna datoteka CMake (./CMakeLists.txt)
      1. Informacije o projektu
      2. Možnosti projekta
      3. Možnosti kompilacije
      4. Glavni cilj
      5. Namestitev
      6. Testi
      7. Dokumentacija
      8. Spletni peskovnik
    3. Testni skript (test/CMakeLists.txt)
      1. Testiranje
      2. Zajetje
    4. Skript za dokumentacijo (doc/CMakeLists.txt)
    5. Skript za spletni peskovnik (online/CMakeLists.txt)
  2. Projekt zunaj
    1. Skupščina
      1. Generacija
      2. Skupščina
    2. Možnosti
      1. MYLIB_COVERAGE
      2. MYLIB_TESTIRANJE
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montažne tarče
      1. Privzeto
      2. mylib-enotni testi
      3. preveriti
      4. pokritost
      5. doc
      6. škatla za palčke
    4. Primeri
  3. Orodja
  4. Statična analiza
  5. spremna beseda

Projekt od znotraj

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

Govorili bomo predvsem o tem, kako organizirati skripte CMake, zato jih bomo podrobneje obravnavali. Preostale datoteke si lahko vsakdo ogleda neposredno na strani s predlogo projekta.

Glavna datoteka CMake (./CMakeLists.txt)

Informacije o projektu

Najprej morate zahtevati zahtevano različico sistema CMake. CMake se razvija, podpisi ukazov in obnašanje v različnih pogojih se spreminjajo. Da bi CMake takoj razumel, kaj želimo od njega, moramo takoj zabeležiti naše zahteve zanj.

cmake_minimum_required(VERSION 3.13)

Nato bomo določili naš projekt, njegovo ime, različico, uporabljene jezike itd. (glej. команду project).

V tem primeru navedemo jezik CXX (in to pomeni C++), tako da se CMake ne napreza in išče prevajalnik jezika C (privzeto CMake vključuje dva jezika: C in C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Tukaj lahko takoj preverite, ali je naš projekt vključen v drug projekt kot podprojekt. To bo v prihodnosti zelo pomagalo.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Možnosti projekta

Ponudili bomo dve možnosti.

Prva možnost je MYLIB_TESTING — da onemogočite teste enot. To je morda potrebno, če smo prepričani, da je s testi vse v redu, želimo pa na primer samo namestiti ali zapakirati naš projekt. Ali pa je naš projekt vključen kot podprojekt - v tem primeru uporabnik našega projekta ni zainteresiran za izvajanje naših testov. Ne testirate odvisnosti, ki jih uporabljate, kajne?

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

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

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

Možnosti kompilacije

Seveda smo kul programerji, zato želimo od prevajalnika najvišjo raven diagnostike med prevajanjem. Niti ena miška se ne bo izmuznila.

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
)

Prav tako bomo onemogočili razširitve, da bomo popolnoma skladni s standardom jezika C++. V CMake so privzeto omogočeni.

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

Glavni cilj

Naša knjižnica je sestavljena samo iz datotek glave, kar pomeni, da nimamo nobenega izpuha v obliki statičnih ali dinamičnih knjižnic. Po drugi strani pa mora biti naša knjižnica za zunanjo uporabo nameščena, zaznavna v sistemu in povezana z vašim projektom, hkrati pa te iste glave in morda še kakšne dodatne, so nanjo povezane lastnosti.

V ta namen izdelamo vmesniško knjižnico.

add_library(mylib INTERFACE)

Glave povežemo z našo knjižnico vmesnika.

Sodobna, modna, mladinska uporaba CMake pomeni, da glave, lastnosti itd. prenašajo skozi eno samo tarčo. Torej dovolj je reči target_link_libraries(target PRIVATE dependency)in vse glave, ki so povezane s ciljem dependency, bo na voljo za vire, ki pripadajo cilju target. In ne potrebuješ nobenega [target_]include_directories. To bo prikazano v nadaljevanju analize Skript CMake za teste enot.

Prav tako je vredno biti pozoren na t.i. выражения-генераторы: $<...>.

Ta ukaz poveže glave, ki jih potrebujemo, z našo vmesniško knjižnico, in če je naša knjižnica povezana s katerim koli ciljem znotraj iste hierarhije CMake, bodo glave iz imenika povezane z njo ${CMAKE_CURRENT_SOURCE_DIR}/include, in če je naša knjižnica nameščena v sistemu in povezana z drugim projektom z ukazom find_package, bodo z njim povezane glave iz imenika include glede na namestitveni imenik.

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

Postavimo jezikovni standard. Seveda čisto zadnjega. Hkrati pa standard ne samo vključimo, temveč ga razširimo na tiste, ki bodo uporabljali našo knjižnico. To je doseženo zaradi dejstva, da ima nastavljena lastnost kategorijo INTERFACE (glej ukaz target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Ustvarimo vzdevek za našo knjižnico. Še več, zaradi lepote bo v posebnem "imenskem prostoru". To bo uporabno, ko se v naši knjižnici pojavijo različni moduli in jih povezujemo neodvisno drug od drugega. Kot na primer v Busti.

add_library(Mylib::mylib ALIAS mylib)

Namestitev

Namestitev naših glav v sistem. Tukaj je vse preprosto. Pravimo, da mora mapa z vsemi glavami iti v imenik include glede na lokacijo namestitve.

install(DIRECTORY include/mylib DESTINATION include)

Nato obvestimo sistem gradnje, da želimo imeti možnost klica ukaza v projektih tretjih oseb find_package(Mylib) in zadeti gol Mylib::mylib.

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

Naslednji urok je treba razumeti tako. Ko v projektu tretje osebe pokličemo ukaz find_package(Mylib 1.2.3 REQUIRED), in prava različica nameščene knjižnice ne bo združljiva z različico 1.2.3CMake bo samodejno ustvaril napako. To pomeni, da vam ne bo treba ročno slediti različicam.

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

Če so testi izrecno onemogočeni z uporabo ustrezno možnost ali pa je naš projekt podprojekt, to pomeni, da je z ukazom povezan z drugim projektom CMake add_subdirectory, se ne premaknemo naprej po hierarhiji in skripta, ki opisuje ukaze za generiranje in izvajanje testov, preprosto ne teče.

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

Dokumentacija

V primeru podprojekta tudi dokumentacija ne bo izdelana.

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

Spletni peskovnik

Prav tako podprojekt ne bo imel spletnega peskovnika.

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

Testni skript (test/CMakeLists.txt)

Testiranje

Najprej najdemo paket z zahtevanim testnim ogrodjem (zamenjajte ga z vašim najljubšim).

find_package(doctest 2.3.3 REQUIRED)

Ustvarimo našo izvršljivo datoteko s testi. Običajno neposredno v izvršljivo binarno datoteko dodam samo datoteko, ki bo vsebovala funkcijo main.

add_executable(mylib-unit-tests test_main.cpp)

In dodam datoteke, v katerih so kasneje opisani sami testi. Ampak tega vam ni treba storiti.

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

Povezujemo odvisnosti. Upoštevajte, da smo z binarno datoteko povezali samo cilje CMake, ki smo jih potrebovali, in nismo poklicali ukaza target_include_directories. Naslovi iz testnega okvira in iz našega Mylib::mylib, kot tudi parametri gradnje (v našem primeru je to jezikovni standard C++) so prišli skupaj s temi cilji.

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

Nazadnje ustvarimo navidezno tarčo, katere »zgradba« je enakovredna izvajanju testov, in dodamo ta tarča k privzeti gradnji (za to je odgovoren atribut ALL). To pomeni, da privzeta zgradba sproži izvajanje testov, kar pomeni, da jih ne bomo nikoli pozabili izvesti.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Zajetje

Nato omogočimo merjenje pokritosti kode, če je navedena ustrezna možnost. V podrobnosti se ne bom spuščal, ker se nanašajo bolj na orodje za merjenje pokritosti kot na CMake. Pomembno je le omeniti, da bo na podlagi rezultatov ustvarjen cilj coverage, s katerim je priročno začeti meriti pokritost.

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 za dokumentacijo (doc/CMakeLists.txt)

Našel Doxygen.

find_package(Doxygen)

Nato preverimo, ali je uporabnik nastavil jezikovno spremenljivko. Če da, potem se ga ne dotikamo, če ne, potem vzamemo ruščino. Nato konfiguriramo sistemske datoteke Doxygen. Vse potrebne spremenljivke, vključno z jezikom, gredo tja med postopkom konfiguracije (glejte. команду configure_file).

Nato ustvarimo cilj doc, ki bo začel z izdelavo dokumentacije. Ker ustvarjanje dokumentacije ni največja potreba v procesu razvoja, cilj ne bo privzeto omogočen; treba ga bo izrecno zagnati.

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 za spletni peskovnik (online/CMakeLists.txt)

Tukaj najdemo tretji Python in ustvarimo cilj wandbox, ki ustvari zahtevo, ki ustreza API-ju storitve Wandbox, in ga pošlje stran. Odgovor vsebuje povezavo do dokončanega 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 "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()

Projekt zunaj

Zdaj pa poglejmo, kako vse to uporabiti.

Skupščina

Gradnja tega projekta, kot katerega koli drugega projekta v sistemu gradnje CMake, je sestavljena iz dveh stopenj:

Generacija

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

Če zgornji ukaz ni deloval zaradi stare različice CMake, ga poskusite izpustiti -S:

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

Več o možnostih.

Gradnja projekta

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

Več o montažnih ciljih.

Možnosti

MYLIB_COVERAGE

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

Vključuje cilj coverage, s katerim lahko začnete meriti pokritost kode s testi.

MYLIB_TESTIRANJE

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

Zagotavlja možnost onemogočanja gradnje in ciljanja testa enote check. Zaradi tega je merjenje pokritosti kode s testi izklopljeno (glej. MYLIB_COVERAGE).

Testiranje je tudi samodejno onemogočeno, če je projekt z ukazom povezan z drugim projektom kot podprojekt add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Preklopi jezik dokumentacije, ki jo ustvari cilj doc danemu. Za seznam razpoložljivih jezikov glejte Spletno mesto sistema Doxygen.

Ruščina je privzeto omogočena.

Montažne tarče

Privzeto

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

Če cilj ni naveden (kar je enakovredno cilju all), pobere vse, kar lahko, in pokliče tudi tarčo check.

mylib-enotni testi

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

Sestavlja teste enot. Privzeto omogočeno.

preveriti

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

Izvaja zbrane (zbrane, če še niso) teste enot. Privzeto omogočeno.

Glej tudi mylib-unit-tests.

pokritost

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

Analizira izvajanje (izvaja, če še ni) enotnih testov za pokritost kode s testi, ki uporabljajo program gcovr.

Izpuh prevleke bo videti nekako takole:

------------------------------------------------------------------------------
                           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 na voljo samo, ko je možnost omogočena MYLIB_COVERAGE.

Glej tudi check.

doc

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

Zažene generiranje kodne dokumentacije s pomočjo sistema Doksigen.

škatla za palčke

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

Odgovor storitve je videti nekako takole:

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

Za to se uporablja storitev Wandbox. Ne vem, kako prilagodljivi so njihovi strežniki, vendar mislim, da te priložnosti ne bi smeli zlorabiti.

Primeri

Zgradite projekt v načinu za odpravljanje napak z merjenjem pokritosti

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

Namestitev projekta brez predhodne montaže in testiranja

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

Zgradite v načinu izdaje z danim prevajalnikom

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

Izdelava dokumentacije v angleškem jeziku

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

Orodja

  1. CMake 3.13

    Pravzaprav je CMake različice 3.13 potreben samo za izvajanje nekaterih ukazov konzole, opisanih v tej pomoči. Z vidika sintakse skriptov CMake je različica 3.8 zadostna, če se generiranje kliče na druge načine.

  2. Knjižnica za testiranje doctest

    Testiranje je mogoče onemogočiti (glejte опцию MYLIB_TESTING).

  3. Doksigen

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

  4. Jezikovni tolmač Python 3

    Za samodejno generiranje spletni peskovniki.

Statična analiza

S CMake in nekaj dobrimi orodji lahko zagotovite statično analizo z minimalnim naporom.

Cppcheck

CMake ima vgrajeno podporo za orodje za statično analizo Cppcheck.

Če želite to narediti, morate uporabiti možnost CMAKE_CXX_CPPCHECK:

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

Po tem se bo statična analiza samodejno zagnala vsakič, ko bo vir preveden in ponovno preveden. Ničesar dodatnega ni treba narediti.

Zvok

S pomočjo čudovitega orodja scan-build V hipu lahko zaženete tudi statično analizo:

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

Tu morate za razliko od primera s Cppcheckom vsakokrat zagnati gradnjo scan-build.

spremna beseda

CMake je zelo zmogljiv in prilagodljiv sistem, ki vam omogoča implementacijo funkcionalnosti za vsak okus in barvo. In čeprav sintaksa včasih pušča veliko želenega, hudič še vedno ni tako grozen, kot je naslikan. Uporabite gradbeni sistem CMake v dobro družbe in zdravja.

Prenesite predlogo projekta

Vir: www.habr.com

Dodaj komentar