CMake og C++ – brþdre for livet

CMake og C++ – brþdre for livet

Under development, I love to change compilers, build modes, dependency versions, perform static analysis, measure performance, gather coverage, generate documentation, etc. I really love CMake because it allows me to do everything I want.

Many criticize CMake, often justifiably, but if you look closely, it's not that bad, and lately it's actually quite good, and the direction of development is quite positive.

In this note, I want to explain how relatively easily to organize a header library in C++ using CMake, to achieve the following functionality:

  1. Building;
  2. Auto-launching tests;
  3. Measuring code coverage;
  4. Installation;
  5. Auto-documentation;
  6. Generating an online sandbox;
  7. Static analysis.

Anyone already familiar with C++ and CMake can simply download the project template and start using it.


Indhold

  1. The project from the inside
    1. Project structure
    2. Main CMake file (./CMakeLists.txt)
      1. Information om projektet
      2. Project options
      3. Compilation options
      4. Main target
      5. Installation
      6. Tests
      7. Dokumentation
      8. Online sandbox
    3. Script for tests (test/CMakeLists.txt)
      1. Testning
      2. Coverage
    4. Script for documentation (doc/CMakeLists.txt)
    5. Script for online sandbox (online/CMakeLists.txt)
  2. The project from the outside
    1. Kompilering
      1. Generation
      2. Kompilering
    2. Muligheder
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Build targets
      1. By default
      2. mylib-unit-tests
      3. check
      4. coverage
      5. doc
      6. wandbox
    4. Eksempler
  3. VĂŠrktĂžjer
  4. Statisk analyse
  5. Efterord

The project from the inside

Project structure

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

The main focus will be on how to structure CMake scripts, so they will be examined in detail. Other files can be reviewed directly on the project template page..

Main CMake file (./CMakeLists.txt)

Information om projektet

First, you need to request the required version of CMake. CMake evolves, command signatures change, behavior under various conditions varies. To ensure CMake immediately understands our requirements, we need to specify them upfront.

cmake_minimum_required(VERSION 3.13)

Then we will define our project, its name, version, languages used, and others (see the project command).

In this case, we specify the language CXX (which means C++), so CMake doesn’t get confused and look for a C language compiler (by default, CMake includes two languages: C and C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Her kan du straks tjekke, om vores projekt er inkluderet som et underprojekt i et andet projekt. Det vil hjĂŠlpe betydeligt fremadrettet.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Project options

Lad os overveje to muligheder.

Den fĂžrste mulighed — MYLIB_TESTING — til at deaktivere modultests. Dette kan vĂŠre nĂždvendigt, hvis vi er sikre pĂ„, at alt er i orden med testene, og vi f.eks. kun Ăžnsker at installere eller pakke vores projekt. Eller hvis vores projekt er inkluderet som et underprojekt — i det tilfĂŠlde ville brugeren af vores projekt ikke vĂŠre interesseret i at kĂžre vores tests. Tester du ikke de afhĂŠngigheder, du bruger?

option(MYLIB_TESTING "Aktivér modulÊr testning" ON)

Derudover vil vi lave en separat mulighed MYLIB_COVERAGE for mÄling af testdÊkning, men den vil krÊve yderligere vÊrktÞjer, sÄ den skal aktiveres eksplicit.

option(MYLIB_COVERAGE "Aktivér mÄling af testdÊkning" OFF)

Compilation options

SelvfÞlgelig er vi dygtige C++ programmÞrer, sÄ vi Þnsker maksimum diagnostiske niveauer fra compileren. Ingen mus skal kunne slippe forbi.

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 deaktiverer ogsÄ udvidelserne for helt at overholde C++ sprogets standard. Som standard er de aktiveret i CMake.

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

Main target

Vores bibliotek bestÄr kun af header-filer, sÄ vi har ingen output i form af statiske eller dynamiske biblioteker. PÄ den anden side, for at bruge vores bibliotek udenfor, skal det installeres, sÄ det kan findes i systemet og links til dit projekt, og samtidig skal disse header-filer vÊre bundet til det, samt muligvis nogle yderligere egenskaber.

Til dette formÄl opretter vi et interface-bibliotek.

add_library(mylib INTERFACE)

Vi binder headers til vores interface-bibliotek.

Moderne, trendy, ungdommelig brug af CMake indebÊrer, at headers, egenskaber osv. overfÞres gennem et enkelt mÄl. SÄ det er nok at sige target_link_libraries(target PRIVATE dependency), og alle headers, der er associeret med mÄlet dependency, vil vÊre tilgÊngelige for kildene, der tilhÞrer mÄlet target. Og der krÊves ingen [target_]include_directoriesDette vil blive vist nedenfor i analysen CMake-scriptet til modulÊre tests.

Det er ogsÄ vÊrd at bemÊrke det sÄkaldte genereringsudtryk: $.

Denne kommando forbinder de nÞdvendige headers med vores interface-bibliotek, og hvis vores bibliotek bliver tilsluttet et mÄl i en CMake-hierarki, vil headers fra mappen ${CMAKE_CURRENT_SOURCE_DIR}/include, blive knyttet til det, og hvis vores bibliotek er installeret i systemet og tilsluttet i et andet projekt med kommandoen find_package, vil headers fra mappen include i forhold til installationsmappen.

target_include_directories(mylib INTERFACE
    $
    $
)

Lad os fastsÊtte sprogstandarden. SelvfÞlgelig den seneste. Ikke bare inkluderer vi standarden, men spreder den til dem, der vil bruge vores bibliotek. Dette opnÄs ved, at den installerede egenskab har kategorien INTERFACE (se kommandoen target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Lad os oprette et alias for vores bibliotek. For Êstetikens skyld vil det vÊre i et sÊrligt "navnerum". Dette vil vÊre nyttigt, nÄr vores bibliotek fÄr forskellige moduler, og vi vil tilslutte dem uafhÊngigt af hinanden. Som i Boost, for eksempel.

add_library(Mylib::mylib ALIAS mylib)

Installation

Installation af vores headers i systemet. Her er det simpelt. Vi siger, at mappen med alle headers skal placeres i mappen include i forhold til installationsstedet.

install(DIRECTORY include/mylib DESTINATION include)

DernÊst informerer vi bygningssystemet om, at vi Þnsker at kunne kalde kommandoen find_package(Mylib) og fÄ mÄlet Mylib::mylib.

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

Den nÊste kommando skal forstÄs sÄledes. NÄr vi i et eksternt projekt kalder kommandoen find_package(Mylib 1.2.3 REQUIRED), og den faktiske version af det installerede bibliotek er inkompatibel med versionen 1.2.3, vil CMake automatisk generere en fejl. Det vil sige, at vi ikke behÞver at holde Þje med versionerne 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 testene er blevet deaktiveret klart med den relevante indstilling eller vores projekt er et underprojekt, hvilket betyder, at det er tilsluttet et andet CMake-projekt via kommandoen add_subdirectory, vi gÄr ikke videre i hierarkiet, og scriptet, der beskriver kommandoerne til at generere og kÞre tests, bliver simpelthen ikke kÞrt.

if(NOT MYLIB_TESTING)
    message(STATUS "Testning af Mylib projekt er deaktiveret")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib testes ikke i undermodulet")
else()
    add_subdirectory(test)
endif()

Dokumentation

Dokumentationen vil heller ikke blive genereret i tilfĂŠlde af et underprojekt.

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

Online sandbox

Tilsvarende vil der heller ikke vĂŠre en online sandkasse for underprojektet.

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

Script for tests (test/CMakeLists.txt)

Testning

FĂžrst finder vi pakken med det nĂždvendige testframework (udskift med dit yndlings).

find_package(doctest 2.3.3 REQUIRED)

Vi opretter vores eksekverbare fil med tests. Normalt tilfĂžjer jeg kun filen, der indeholder funktionen, direkte i den eksekverbare binĂŠr. main.

add_executable(mylib-unit-tests test_main.cpp)

Men jeg tilfĂžjer testfilerne senere. Men dette er ikke nĂždvendigt.

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

Vi tilslutter afhÊngigheder. BemÊrk, at vi kun har knyttet de nÞdvendige CMake-mÄl til vores binÊr, og vi kaldte ikke kommandoen target_include_directories. Headerne fra testframeworket og fra vores Mylib::mylib, samt byggeparametre (i vores tilfÊlde er det standarden for C++) kom igennem med disse mÄl.

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

Endelig opretter vi et fiktivt mÄl, hvis "bygning" er lig med at kÞre tests, og tilfÞjer dette mÄl til standardbygningen (dette hÄndteres af attributten ALL). Dette betyder, at standardbygningen initierer testen, sÄ vi aldrig glemmer at kÞre dem.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Coverage

Derefter aktiverer vi kodeoverdÊkning, hvis den tilsvarende mulighed er angivet. Jeg vil ikke gÄ ind i detaljerne, da de mere relaterer til vÊrktÞjet til at mÄle dÊkning end til CMake. Det er kun vigtigt at bemÊrke, at der ud fra resultaterne vil blive oprettet et mÄl coverage, som praktisk kan bruges til at udfÞre dÊkning.

find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "MÄling af testdÊkning er aktiveret")

    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 "Programmet gcovr er nÞdvendigt for at mÄle testdÊkning")
endif()

Script for documentation (doc/CMakeLists.txt)

Vi har fundet Doxygen.

find_package(Doxygen)

DernĂŠst tjekker vi, om brugeren har angivet en sprogvariabel. Hvis ja, rĂžrer vi ikke ved den; hvis nej, tager vi russisk. Derefter konfigurerer vi Doxygen-systemfilerne. Alle nĂždvendige variabler, inklusive sproget, kommer ind i konfigurationen (se. configure_file-kommanden).

Derefter opretter vi et mÄl doc, der vil starte genereringen af dokumentationen. Da dokumentationsgenerering ikke er den stÞrste nÞdvendighed i udviklingsprocessen, vil mÄlet som standard ikke vÊre aktiveret, det skal startes eksplicit.

if (Doxygen_FOUND)
    if (NOT MYLIB_DOXYGEN_LANGUAGE)
        set(MYLIB_DOXYGEN_LANGUAGE Dansk)
    endif()
    message(STATUS "Doxygen-dokumentation vil blive genereret pÄ ${MYLIB_DOXYGEN_LANGUAGE}")
    configure_file(Doxyfile.in Doxyfile)
    add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}\/Doxyfile)
endif ()

Script for online sandbox (online/CMakeLists.txt)

Her finder vi den tredje Python og opretter et mÄl wandbox, der genererer en forespÞrgsel, der svarer til API'en for tjenesten Wandbox, og sender den. I svaret modtager vi 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 "En Python 3 fortolker er nĂždvendig for at oprette en online sandkasse.")
endif()

The project from the outside

Nu ser vi pÄ, hvordan man bruger det hele.

Kompilering

Bygningen af dette projekt, ligesom ethvert andet projekt i CMake-bygsystemet, bestÄr af to trin:

Generation

cmake -S sti\/til\/kilde -B sti\/til\/bygge\/mappe [muligheder ...]

Hvis ovenstÄende kommando ikke virker pÄ grund af en Êldre version af CMake, kan du prÞve at droppe -S:

cmake sti\/til\/kilde -B sti\/til\/bygge\/mappe [muligheder ...]

LĂŠs mere om mulighederne.

Byg projektet

cmake --build sti\/til\/bygge\/mappe [--target target]

LÊs mere om bygge-mÄl.

Muligheder

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [andre muligheder ...]

Aktiverer mÄlet coverage, ved hjÊlp af hvilken man kan starte en mÄling af kode dÊkning med tests.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [andre muligheder ...]

Giver mulighed for at deaktivere byggeriet af modul tests og mÄl check. FÞlgelig deaktiveres mÄling af kode dÊkning med tests (se. MYLIB_COVERAGE).

OgsÄ testning deaktiveres automatisk, hvis projektet inkluderes i et andet projekt som et underprojekt ved hjÊlp af kommandoen add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [andre muligheder ...]

Skifter sprog for dokumentationen, som mÄlet genererer doc til det angivne. Se listen over tilgÊngelige sprog pÄ Doxygen systemets hjemmeside.

Som standard er russisk aktiveret.

Build targets

By default

cmake --build sti/til/bygningsdirectory
cmake --build sti/til/bygningsdirectory --target all

Hvis mÄlet ikke er angivet (hvilket er Êkvivalent med mÄlet alle), bygger alt, hvad der kan, samt kalder mÄlet check.

mylib-unit-tests

cmake --build sti/til/bygningsdirectory --target mylib-unit-tests

Kompilerer modul tests. Aktiveret som standard.

check

cmake --build sti/til/bygningsdirectory --target check

KĂžrer de bygget (bygger, hvis ikke allerede) modul tests. Aktiveret som standard.

Se ogsÄ mylib-unit-tests.

coverage

cmake --build sti/til/bygningsdirectory --target coverage

Analyserer de kĂžrende (kĂžrer, hvis ikke allerede) modul tests for kode dĂŠkning ved hjĂŠlp af programmet gcovr.

Udgangen af dÊkningen vil se omtrent sÄdan ud:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /sti/til/cmakecpptemplate/include/
------------------------------------------------------------------------------
Filer                                       Linjer    Exec  DĂŠkning   Manglende
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

MÄlet er kun tilgÊngeligt, nÄr mulighederne er aktiveret MYLIB_COVERAGE.

Se ogsÄ check.

doc

cmake --build sti/til/bygningsdirectory --target doc

Starter genereringen af dokumentation til koden ved hjĂŠlp af systemet Doxygen.

wandbox

cmake --build sti/til/bygningsdirectory --target wandbox

Svaret fra tjenesten ser omtrent sÄdan ud:

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

Dette gĂžres ved hjĂŠlp af tjenesten Wandbox. Jeg ved ikke, hvor elastiske deres servere er, men jeg tror, at man ikke bĂžr misbruge denne mulighed.

Eksempler

Bygning af projektet i debug-tilstand med mÄling af dÊkning

cmake -S sti/til/kilde -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build sti/til/bygningsdirectory --target coverage --parallel 16

Installation af projektet uden forudgÄende opbygning og testning

cmake -S sti/til/kilde -B sti/til/bygningsmappe -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=sti/til/installationsmappe
cmake --build sti/til/bygningsmappe --target install

Bygning i udgivelsesmÄde med den angivne compiler

cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=sti/til/directory/hvor/afhĂŠngigheder/er/installeret
cmake --build sti/til/bygningsmappe --parallel 4

Generering af dokumentation pÄ engelsk

cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build sti/til/bygningsmappe --target doc

VĂŠrktĂžjer

  1. CMake 3.13

    Faktisk krÊves CMake version 3.13 kun for at kÞre visse konsolkommandoer, der er beskrevet i denne vejledning. Med hensyn til syntaksen i CMake-scripts er version 3.8 tilstrÊkkelig, hvis genereringen kaldes pÄ andre mÄder.

  2. Testbibliotek doctest

    Testning kan deaktiveres (se option MYLIB_TESTING).

  3. Doxygen

    For at skifte sprog, som dokumentationen genereres pÄ, er der en mulighed MYLIB_DOXYGEN_LANGUAGE.

  4. Tolk for programmeringssprog Python 3

    Til automatisk generering online-sandbox.

Statisk analyse

Med CMake og et par gode vĂŠrktĂžjer kan statisk analyse sikres med minimal indsats.

Cppcheck

CMake har indbygget support for vĂŠrktĂžjet til statisk analyse Cppcheck.

For dette skal du bruge indstillingen CMAKE_CXX_CPPCHECK:

cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Isti/til/kilde/include"

Efter dette vil den statiske analyse automatisk blive udfĂžrt hver gang der kompileres og rekonstrueres kilden. Der er ikke behov for at foretage yderligere handlinger.

Clang

Ved hjÊlp af det vidunderlige vÊrktÞj scan-build kan du ogsÄ kÞre statisk analyse pÄ ingen tid:

scan-build cmake -S sti/til/kilde -B sti/til/bygningsmappe -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build sti/til/bygningsmappe

Her, i modsĂŠtning til tilfĂŠlde med Cppcheck, krĂŠves det at bygge igen hver gang via scan-build.

Efterord

CMake er et meget kraftfuldt og fleksibelt system, der gÞr det muligt at implementere funktionalitet i enhver smag og farve. Og selvom syntaksen til tider kan vÊre mere Þnskelig, er det ikke sÄ skrÊmmende, som det lyder. Brug CMake-byggevÊrktÞjet til gavn for samfundet og til gavn for dit helbred.

→ Download projekt-skabelonen

Kilde: habr.com

KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster