CMake og C++ er brødre for evigt

CMake og C++ er brødre for evigt

Under udvikling kan jeg godt lide at ændre compilere, bygge modes, afhængighedsversioner, udføre statisk analyse, måle ydeevne, indsamle dækning, generere dokumentation og så videre. Og jeg elsker virkelig CMake, fordi det giver mig mulighed for at gøre, hvad jeg vil.

Mange skælder ud på CMake, og ofte fortjent, men hvis man ser efter, er det ikke så slemt, men på det seneste slet ikke dårligt, og udviklingsretningen er ret positiv.

I denne note vil jeg fortælle dig, hvor nemt det er at organisere et C++ header-bibliotek i et CMake-system for at få følgende funktionalitet:

  1. montage;
  2. Autorun tests;
  3. Kodedækningsmåling;
  4. installation;
  5. Autodokumentation;
  6. Online sandkassegenerering;
  7. Statisk analyse.

Hvem allerede forstår fordelene og si-make kan bare download projekt skabelon og begynde at bruge det.


Indhold

  1. Projekt indefra
    1. Projektets struktur
    2. Hoved CMake-fil (./CMakeLists.txt)
      1. Projektinformation
      2. Projektmuligheder
      3. Kompileringsmuligheder
      4. primære mål
      5. Installation
      6. Tests
      7. Records
      8. Online sandkasse
    3. Script til test (test/CMakeLists.txt)
      1. Test
      2. Dækning
    4. Dokumentationsscript (doc/CMakeLists.txt)
    5. Script til online sandbox (online/CMakeLists.txt)
  2. projekt udenfor
    1. samling
      1. Generation
      2. samling
    2. Optioner
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. samlingsmål
      1. Som standard
      2. mylib-unit-tests
      3. kontrollere
      4. dækning
      5. dock
      6. tryllestavskasse
    4. Примеры
  3. Værktøj
  4. Statisk Analyse
  5. efterskrift

Projekt indefra

Projektets struktur

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

Vi vil hovedsageligt tale om, hvordan man organiserer CMake-scripts, så de vil blive analyseret i detaljer. Resten af ​​filerne kan ses direkte af alle. på skabelonprojektsiden.

Hoved CMake-fil (./CMakeLists.txt)

Projektinformation

Først og fremmest skal du anmode om den ønskede version af CMake-systemet. CMake udvikler sig, kommandosignaturer ændres, adfærd under forskellige forhold. For at CMake umiddelbart kan forstå, hvad vi vil have ud af det, skal vi straks rette vores krav til det.

cmake_minimum_required(VERSION 3.13)

Derefter angiver vi vores projekt, dets navn, version, anvendte sprog osv. команду project).

I dette tilfælde skal du angive sproget CXX (hvilket betyder C++), så CMake ikke gider lede efter en C-compiler (som standard er to sprog inkluderet i CMake: C og C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Her kan du med det samme tjekke, om vores projekt indgår i et andet projekt som delprojekt. Dette vil hjælpe meget i fremtiden.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Projektmuligheder

Lad os overveje to muligheder.

Den første mulighed er MYLIB_TESTING - at deaktivere enhedstest. Det kan være nødvendigt, hvis vi er sikre på, at alt er i orden med testene, og vi for eksempel kun vil installere eller pakke vores projekt. Eller vores projekt indgår som et delprojekt - i dette tilfælde er brugeren af ​​vores projekt ikke interesseret i at køre vores tests. Du tester ikke de afhængigheder, du bruger, vel?

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

Derudover vil vi lave en separat mulighed MYLIB_COVERAGE til måling af kodedækning med test, men det vil kræve yderligere værktøjer, så du bliver nødt til at aktivere det eksplicit.

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

Kompileringsmuligheder

Selvfølgelig er vi seje programmører-positive, så vi vil have det maksimale niveau af kompileringstidsdiagnostik fra compileren. Ikke en eneste mus kommer igennem.

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
)

Vi vil også deaktivere udvidelser for fuldt ud at overholde sprogstandarden C++. De er som standard aktiveret i CMake.

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

primære mål

Vores bibliotek består kun af header-filer, hvilket betyder, at vi ikke har noget output i form af statiske eller dynamiske biblioteker. På den anden side, for at bruge vores bibliotek udefra, skal du installere det, du skal være i stand til at finde det i systemet og forbinde det til dit projekt, og samtidig også disse headere som muligvis nogle yderligere egenskaber.

Til dette formål opretter vi et grænsefladebibliotek.

add_library(mylib INTERFACE)

Vi binder overskrifterne til vores grænsefladebibliotek.

Moderne, trendy, ungdommelig brug af CMake betyder, at headere, egenskaber mv. transmitteret gennem et enkelt mål. Så det er nok at sige target_link_libraries(target PRIVATE dependency), og alle overskrifter, der er knyttet til målet dependency, vil være tilgængelig for kilder, der tilhører målet target. Og du behøver ikke nogen [target_]include_directories. Dette vil blive demonstreret nedenfor under parsing CLave script til enhedstests.

Det er også værd at være opmærksom på den såkaldte. выражения-генераторы: $<...>.

Denne kommando forbinder de overskrifter, vi har brug for, med vores grænsefladebibliotek, og hvis vores bibliotek er forbundet til ethvert mål inden for det samme CMake-hierarki, vil overskrifterne fra biblioteket blive knyttet til det ${CMAKE_CURRENT_SOURCE_DIR}/include, og hvis vores bibliotek er installeret på systemet og forbundet til et andet projekt ved hjælp af kommandoen find_package, så vil overskrifterne fra mappen blive knyttet til den include i forhold til installationsmappen.

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

Indstil sprogstandarden. Selvfølgelig den seneste. Samtidig medtager vi ikke kun standarden, men distribuerer den også til dem, der skal bruge vores bibliotek. Dette opnås ved, at den indstillede egenskab har en kategori INTERFACE (See. target_compile_features kommando).

target_compile_features(mylib INTERFACE cxx_std_17)

Vi får et alias til vores bibliotek. Og for skønheden vil det være i et særligt "navneområde". Dette vil være nyttigt, når forskellige moduler vises i vores bibliotek, og vi går for at forbinde dem uafhængigt af hinanden. Som i Busta f.eks.

add_library(Mylib::mylib ALIAS mylib)

Installation

Installation af vores headere i systemet. Alt er enkelt her. Vi siger, at mappen med alle overskrifterne skal falde ind i mappen include vedrørende installationsstedet.

install(DIRECTORY include/mylib DESTINATION include)

Dernæst fortæller vi byggesystemet, at vi ønsker at kunne kalde kommandoen i tredjepartsprojekter find_package(Mylib) og få et mål Mylib::mylib.

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

Følgende besværgelse skal forstås således. Når vi er i et sideprojekt, kalder vi kommandoen find_package(Mylib 1.2.3 REQUIRED), og samtidig vil den rigtige version af det installerede bibliotek være inkompatibel med versionen 1.2.3, vil CMake automatisk generere en fejl. Det vil sige, at du ikke behøver at holde styr på versioner manuelt.

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)

Tests

Hvis tests eksplicit er deaktiveret med tilsvarende mulighed eller vores projekt er et underprojekt, det vil sige, det er forbundet med et andet CMake-projekt ved hjælp af kommandoen add_subdirectory, vi går ikke længere ned i hierarkiet, og scriptet, som beskriver kommandoerne til at generere og køre test, starter simpelthen ikke.

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

Records

Der vil heller ikke blive genereret dokumentation, hvis der er tale om et delprojekt.

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

Online sandkasse

Tilsvarende vil delprojektet heller ikke have en online sandkasse.

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

Script til test (test/CMakeLists.txt)

Test

Først og fremmest finder vi en pakke med den ønskede testramme (erstat den med din favorit).

find_package(doctest 2.3.3 REQUIRED)

Vi opretter vores eksekverbare fil med tests. Normalt tilføjer jeg direkte til den eksekverbare binære fil kun den fil, hvori funktionen vil være main.

add_executable(mylib-unit-tests test_main.cpp)

Og de filer, hvori selve testene er beskrevet, tilføjer jeg senere. Men det er ikke nødvendigt at gøre det.

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

Vi forbinder afhængigheder. Bemærk venligst, at vi kun vedhæftede de CMake-mål, vi havde brug for, til vores binære, og kaldte ikke kommandoen target_include_directories. Overskrifter fra testrammen og fra vores Mylib::mylib, samt byggemuligheder (i vores tilfælde C++ sprogstandarden) kravlede sammen med disse mål.

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

Til sidst opretter vi et dummy-mål, hvis "build" svarer til at køre test, og tilføjer dette mål til standardbuilden (dette er attributtens ansvar ALL). Det betyder, at standardbuilden vil udløse testene til at køre, hvilket betyder, at vi aldrig glemmer at køre dem.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Dækning

Slå derefter kodedækningsmåling til, hvis den tilsvarende indstilling er indstillet. Jeg vil ikke gå ind i detaljerne, for de er mere relateret til dækningsmålingsværktøjet end til CMake. Det er kun vigtigt at bemærke, at resultaterne vil skabe et mål coverage, hvormed det er praktisk at begynde at måle dækning.

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

Dokumentationsscript (doc/CMakeLists.txt)

Fandt Doxygen.

find_package(Doxygen)

Dernæst tjekker vi om variablen med sproget er indstillet af brugeren. Hvis ja, så rører vi det ikke, hvis ikke, så tager vi russisk. Derefter konfigurerer vi Doxygen-systemfilerne. Alle de nødvendige variabler, inklusive sproget, kommer dertil under konfigurationsprocessen (se. команду configure_file).

Så laver vi et mål doc, som vil begynde at generere dokumentation. Da generering af dokumentation ikke er det største behov i udviklingsprocessen, vil målet ikke være inkluderet som standard, det skal køres eksplicit.

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

Script til online sandbox (online/CMakeLists.txt)

Her finder vi den tredje Python og laver et mål wandbox, som genererer en anmodning svarende til tjenestens API tryllestavskasse, og sender den. Som svar kommer et link til den færdige sandkasse.

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 udenfor

Lad os nu se på, hvordan man bruger det hele.

samling

Opbygningen af ​​dette projekt, som ethvert andet projekt på CMake-byggesystemet, består af to faser:

Generation

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

Hvis kommandoen ovenfor ikke virkede på grund af en ældre version af CMake, så prøv at udelade -S:

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

Mere om muligheder.

Projekt opbygning

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

Mere om samlingsmål.

Optioner

MYLIB_COVERAGE

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

Inkluderer mål coverage, hvormed du kan begynde at måle kodedækning med test.

MYLIB_TESTING

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

Giver mulighed for at slå enhedstestbygning og mål fra check. Som et resultat er målingen af ​​kodedækning ved test slået fra (se MYLIB_COVERAGE).

Test er også automatisk deaktiveret, hvis projektet er forbundet til et andet projekt som et underprojekt ved hjælp af kommandoen add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Skifter sproget for den dokumentation, som målet genererer doc til den givne. For en liste over tilgængelige sprog, se Doxygen hjemmeside.

Russisk er aktiveret som standard.

samlingsmål

Som standard

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

Hvis målet ikke er angivet (hvilket svarer til målet all), samler alt, hvad der er muligt, og kalder også målet check.

mylib-unit-tests

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

Kompilerer enhedstests. Aktiveret som standard.

kontrollere

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

Kører bygget (bygger, hvis ikke allerede) enhedstests. Aktiveret som standard.

Se også mylib-unit-tests.

dækning

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

Analyserer kørende (kører, hvis ikke endnu) enhedstests for kodedækning ved test ved hjælp af programmet gcovr.

Dækningsoutputtet vil se nogenlunde således ud:

------------------------------------------------------------------------------
                           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ålet er kun tilgængeligt, når indstillingen er aktiveret. MYLIB_COVERAGE.

Se også check.

dock

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

Starter generering af dokumentation for koden ved hjælp af systemet doxygen.

tryllestavskasse

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

Svaret fra tjenesten ser sådan ud:

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

Tjenesten bruges til dette. tryllestavskasse. Jeg ved ikke, hvordan gummiservere de har, men jeg synes, at du ikke skal misbruge denne funktion.

Примеры

Byg projekt i fejlretningstilstand med dækningsmåling

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

Installation af et projekt uden forudbygning og test

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

Byg i frigivelsestilstand af den angivne compiler

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

Generering af dokumentation på engelsk

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

Værktøj

  1. CMake 3.13

    Faktisk kræves CMake version 3.13 kun for at køre nogle af de konsolkommandoer, der er beskrevet i denne hjælp. Ud fra syntaksen af ​​CMake-scripts er version 3.8 tilstrækkelig, hvis genereringen kaldes på andre måder.

  2. Test bibliotek doktest

    Testning kan deaktiveres (se опцию MYLIB_TESTING).

  3. doxygen

    For at skifte det sprog, som dokumentationen vil blive genereret på, er der en mulighed MYLIB_DOXYGEN_LANGUAGE.

  4. PL tolk Python 3

    Til automatisk generering online sandkasser.

Statisk Analyse

Ved hjælp af CMake og et par gode værktøjer kan du levere statisk analyse med minimal fifleri.

Cppcheck

Understøttelse af statisk analyseværktøj indbygget i CMake Cppcheck.

For at gøre dette skal du bruge indstillingen CMAKE_CXX_CPPCHECK:

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

Derefter vil statisk analyse automatisk blive lanceret hver gang under kompilering og genkompilering af kilder. Der skal ikke gøres noget ekstra.

Dunk

Med et fantastisk værktøj scan-build Du kan også køre statisk analyse på ingen tid:

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

Her er det, i modsætning til tilfældet med Cppcheck, påkrævet at køre build hver gang igennem scan-build.

efterskrift

CMake er et meget kraftfuldt og fleksibelt system, der giver dig mulighed for at implementere funktionalitet for enhver smag og farve. Og selvom syntaksen nogle gange lader meget tilbage at ønske, er djævelen stadig ikke så forfærdelig, som han er malet. Brug CMake build-systemet til gavn for samfundet og sundheden.

Hent projekt skabelon

Kilde: www.habr.com

Tilføj en kommentar