CMake og C++ — brþdre for alltid

CMake og C++ — brþdre for alltid

I enjoy changing compilers, build modes, dependency versions, performing static analysis, measuring performance, collecting coverage, generating documentation, etc. during the development process. I really love CMake because it allows me to do everything I want.

Many criticize CMake, and often justifiably, but if you delve into it, it's not so bad, and recently it's actually quite good, and the development direction is quite positive.

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

  1. Build;
  2. Automatic test launch;
  3. Code coverage measurement;
  4. Installation;
  5. Auto-documentation;
  6. Online sandbox generation;
  7. Static analysis.

Those who already understand C++ and CMake can simply download the project template and start using it.


Innhold

  1. Inside the project
    1. Project structure
    2. Main CMake file (./CMakeLists.txt)
      1. Informasjon om prosjektet
      2. Project options
      3. Compilation options
      4. Main target
      5. Installasjon
      6. Tester
      7. Dokumentasjon
      8. Online sandbox
    3. Test script (test/CMakeLists.txt)
      1. Testing
      2. Coverage
    4. Documentation script (doc/CMakeLists.txt)
    5. Online sandbox script (online/CMakeLists.txt)
  2. Outside the project
    1. Montering
      1. Generation
      2. Montering
    2. Alternativer
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Build targets
      1. Som standard
      2. mylib-unit-tests
      3. check
      4. dekning
      5. doc
      6. wandbox
    4. Eksempler
  3. Tools
  4. Static analysis
  5. Etterord

Inside the project

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 organizing CMake scripts, so they will be examined in detail. Anyone interested can directly view the other files on the template project page.

Main CMake file (./CMakeLists.txt)

Informasjon om prosjektet

First, you need to request the required version of CMake. CMake evolves, command signatures change, behavior varies under different conditions. To ensure CMake understands what we want from it right away, we need to define our requirements to it from the start.

cmake_minimum_required(VERSION 3.13)

Then we will specify our project, its name, version, used languages, etc. (see the project command).

In this case, we specify the language CXX (which means C++), so CMake doesn't have to worry about searching for a C language compiler (by default, CMake includes two languages: C and C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Her kan du ogsÄ sjekke om prosjektet vÄrt er inkludert i et annet prosjekt som et underprosjekt. Dette vil vÊre til stor hjelp fremover.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Project options

Vi vil forutse to alternativer.

Det fĂžrste alternativet — MYLIB_TESTING — for Ă„ deaktivere modulĂŠre tester. Dette kan vĂŠre nĂždvendig hvis vi er sikre pĂ„ at testene er i orden, og vi Ăžnsker for eksempel bare Ă„ installere eller pakke prosjektet vĂ„rt. Eller hvis prosjektet vĂ„rt er inkludert som et underprosjekt — i sĂ„ fall vil brukeren av prosjektet vĂ„rt ikke vĂŠre interessert i Ă„ kjĂžre testene vĂ„re. Tester du ikke avhengighetene du bruker?

option(MYLIB_TESTING "Aktiver modulĂŠrtesting" ON)

I tillegg vil vi lage et eget alternativ MYLIB_COVERAGE for Ä mÄle testdekning, men dette vil kreve ekstra verktÞy, sÄ det mÄ aktiveres eksplisitt.

option(MYLIB_COVERAGE "Aktiver mÄling av testdekning" OFF)

Compilation options

SelvfÞlgelig er vi dyktige C++-programmerere, sÄ vi Þnsker Ä fÄ maksimal diagnoseverdi fra kompilatoren pÄ kompileringstid. Ingen feil skal glippe.

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 utvidelser for Ä vÊre i full overensstemmelse med C++-standarden. Som standard er de aktivert i CMake.

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

Main target

Biblioteket vÄrt bestÄr kun av header-filer, noe som betyr at vi ikke har noen utganger i form av statiske eller dynamiske biblioteker. PÄ den andre siden, for Ä bruke biblioteket vÄrt eksternt, mÄ det installeres, det mÄ kunne oppdages i systemet og kobles til prosjektet ditt, og samtidig mÄ de tilknyttede headerne vÊre tilgjengelige, samt kanskje noen ekstra egenskaper.

For dette formÄlet lager vi et grensesnittbibliotek.

add_library(mylib INTERFACE)

Vi binder headerne til grensesnittbiblioteket vÄrt.

Moderne, trendy, ungdommelig bruk av CMake betyr at headerne, egenskapene osv. overfÞres gjennom ett enkelt mÄl. Dermed er det nok Ä si target_link_libraries(target PRIVATE dependency), og alle headerne som er assosiert med mÄlet dependency, vil vÊre tilgjengelige for kildekoden tilknyttet mÄlet mÄl. Og det er ikke nÞdvendig med noen [target_]include_directories. Dette vil bli demonstrert nedenfor ved Ä analysere CMake-skript for modulÊre tester.

Det er ogsÄ verdt Ä merke seg den sÄkalte genererende uttrykk: $.

Denne kommandoen assosierer nÞdvendige overskrifter med vÄrt grensesnittsbibliotek, og dersom biblioteket vÄrt blir koblet til et mÄl innenfor en hierarki av CMake, vil overskriftene fra mappen ${CMAKE_CURRENT_SOURCE_DIR}/include, bli assosiert med det, men hvis biblioteket vÄrt er installert i systemet og koblet til et annet prosjekt ved hjelp av kommandoen find_package, vil overskriftene bli assosiert fra mappen include relativt til installasjonsmappen.

target_include_directories(mylib INTERFACE
    $
    $
)

Vi setter sprÄknormen. SelvfÞlgelig den nyeste. Dette inkluderer ikke bare Ä aktivere normen, men ogsÄ Ä utvide den til dem som bruker biblioteket vÄrt. Dette oppnÄs ved at egenskapen som er satt har kategorien INTERFACE (se. kommanden target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Vi lager et alias for biblioteket vÄrt. Og for Ä gjÞre det penere vil det vÊre i et spesial 'navnerom'. Dette vil vÊre nyttig nÄr vÄrt bibliotek fÄr forskjellige moduler, og vi Þnsker Ä koble dem uavhengig av hverandre. Som med Boost, for eksempel.

add_library(Mylib::mylib ALIAS mylib)

Installasjon

Installasjon av vÄre overskrifter i systemet. Her er det enkelt. Vi sier at mappen med alle overskriftene skal plasseres i mappen include relativt til installasjonsstedet.

install(DIRECTORY include/mylib DESTINATION include)

Videre informerer vi bygesystemet om at vi Þnsker Ä kunne kalle 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 neste kommandoen mÄ forstÄs slik. NÄr vi i et eksternt prosjekt kaller kommandoen find_package(Mylib 1.2.3 REQUIRED), og den faktiske versjonen av det installerte biblioteket viser seg Ä vÊre inkompatibel med versjonen 1.2.3, vil CMake automatisk generere en feil. Det betyr at man ikke trenger Ä fÞlge med pÄ versjonene 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)

Tester

Hvis testene er deaktivert eksplisitt ved hjelp av den tilsvarende opsjonen eller prosjektet vÄrt er et delprosjekt, det vil si koblet til et annet CMake-prosjekt ved hjelp av kommandoen add_subdirectory, vi gÄr ikke videre i hierarkiet, og skriptet som beskriver kommandoene for Ä generere og kjÞre tester, blir ganske enkelt ikke kjÞrt.

if(NOT MYLIB_TESTING)
    message(STATUS "Testing av Mylib-prosjektet er slÄtt av")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib blir ikke testet i undermodulmodus")
else()
    add_subdirectory(test)
endif()

Dokumentasjon

Dokumentasjonen vil heller ikke bli generert i tilfelle av et delprosjekt.

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

Online sandbox

Tilsvarende vil det heller ikke vĂŠre noen online sandkasser for delprosjektet.

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

Test script (test/CMakeLists.txt)

Testing

FĂžrst finner vi pakken med det nĂždvendige testrammeverket (erstatt med din favoritt).

find_package(doctest 2.3.3 REQUIRED)

Vi oppretter vÄr kjÞrbare fil med tester. Vanligvis legger jeg bare til filen som inneholder funksjonen direkte i den kjÞrbare binÊren main.

add_executable(mylib-unit-tests test_main.cpp)

Men filene som beskriver testene selv, legger jeg til senere. Men det er ikke nĂždvendig Ă„ gjĂžre det slik.

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

Vi kobler til avhengigheter. VÊr oppmerksom pÄ at vi har knyttet bare de nÞdvendige CMake-mÄlene til vÄr binÊrfil og ikke kalt kommandoen target_include_directories. Overskriftene fra testrammeverket og fra vÄr Mylib::mylib, samt byggparametrene (i vÄrt tilfelle er dette sprÄkstandarden C++) kom med disse mÄlene.

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

Til slutt oppretter vi et falskt mÄl, der byggingen er ekvivalent med Ä kjÞre testene, og legger dette mÄlet inn i standardbyggingen (dette styres av attributtet ALL). Dette betyr at standardbyggingen initierer testkjÞringen, slik at vi aldri glemmer Ä kjÞre dem.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Coverage

Deretter aktiverer vi kodedekningstesting hvis det er angitt en tilsvarende opsjon. Jeg vil ikke gÄ inn i detaljene, fordi de mer handler om instrumentet for dekningstesting enn om CMake. Det er bare viktig Ä merke seg at det vil bli opprettet et mÄl dekning, som gjÞr det enkelt Ä kjÞre dekningstesting.

find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "Testing code coverage measurement is enabled")

    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 "The gcovr program is required for code coverage measurements")
endif()

Documentation script (doc/CMakeLists.txt)

Found Doxygen.

find_package(Doxygen)

Next, we check if the user has set a language variable. If yes, we leave it as is; if no, we take Russian. Then we configure the Doxygen system files. All necessary variables, including the language, get included during the configuration process (see the configure_file command).

After that, we create a target doc, which will run the documentation generation. Since generating documentation is not the highest priority in the development process, this target will not be included by default and must be run explicitly.

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

Online sandbox script (online/CMakeLists.txt)

Here we find the third Python and create a target wandbox, which generates a request corresponding to the API of the service Wandbox, and sends it. In response, a link to the ready sandbox is provided.

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 "An interpreter for the 3rd version of the Python language is required to create an online sandbox")
endif()

Outside the project

Now let's look at how to use all of this.

Montering

Building this project, like any other project on the CMake build system, consists of two stages:

Generation

cmake -S path/to/source -B path/to/build/directory [options ...]

If the above command did not work due to an older version of CMake, try omitting -S:

cmake path/to/source -B path/to/build/directory [options ...]

More about the options.

Building the project

cmake --build path/to/build/directory [--target target]

More about build targets.

Alternativer

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [other options ...]

Includes the target dekning, som lar deg starte koding dekning mÄlinger med tester.

MYLIB_TESTING

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

Gir mulighet til Ä deaktivere byggingen av enhetstester og mÄl check. Som et resultat deaktiveres kode dekning mÄlinger (se MYLIB_COVERAGE).

Testing deaktiveres ogsÄ automatisk hvis prosjektet kobles til et annet prosjekt som et subprosjekt med kommandoen add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Bytter sprÄket til dokumentasjonen som genereres av mÄlet doc til det angitte. Liste over tilgjengelige sprÄk se pÄ Doxygen-systemets nettsted.

Som standard er russisk aktivert.

Build targets

Som standard

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

Hvis ikke noe mÄl er spesifisert (som er ekvivalent med mÄlet all), bygger alt som er mulig, og kaller ogsÄ mÄlet check.

mylib-unit-tests

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

Kompilerer enhetstestene. Inkludert som standard.

check

cmake --build path/til/samlingsmappe --target check

KjĂžrer de kompilerte (bygger hvis ikke allerede) enhetstestene. Inkludert som standard.

Se ogsÄ mylib-unit-tests.

dekning

cmake --build path/til/samlingsmappe --target coverage

Analyserer kjĂžrte (kjĂžrer hvis ikke allerede) enhetstester for kode dekning ved hjelp av programmet gcovr.

Kodedekningsutdataene vil se om lag slik ut:

------------------------------------------------------------------------------
                           GCC Kode Dekningsrapport
Katalog: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
Fil                                       Linjer    Exec  Dekning   Manglende
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

MÄlene er bare tilgjengelig nÄr alternativet er aktivert MYLIB_COVERAGE.

Se ogsÄ check.

doc

cmake --build path/til/samlingsmappe --target doc

Starter genereringen av dokumentasjon for koden ved hjelp av systemet Doxygen.

wandbox

cmake --build path/til/samlingsmappe --target wandbox

Svaret fra tjenesten ser omtrent slik ut:

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

Dette gjĂžres ved hjelp av tjenesten Wandbox. Jeg vet ikke hvor robuste serverne deres er, men jeg tror ikke det er lurt Ă„ misbruke denne muligheten.

Eksempler

Bygger prosjektet i feilfinningstilstand med dekning mÄling

cmake -S path/til/kildekode -B path/til/samlingsmappe -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build path/til/samlingsmappe --target coverage --parallel 16

Installasjon av prosjektet uten forhÄndskomplisering og testing

cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=sti/til/installeringsdirectory
cmake --build sti/til/bygningsdirectory --target install

Bygging i utgave-modus med angitt kompilator

cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=sti/til/directory/der/avhengigheter/er/installert
cmake --build sti/til/bygningsdirectory --parallel 4

Generering av dokumentasjon pÄ engelsk

cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build sti/til/bygningsdirectory --target doc

Tools

  1. CMake 3.13

    Faktisk er CMake versjon 3.13 kun nÞdvendig for Ä kjÞre noen konsollkommandoer beskrevet i denne hjelpen. NÄr det gjelder syntaksen for CMake-skript, er versjon 3.8 tilstrekkelig hvis genereringen skjer pÄ andre mÄter.

  2. Testingsbibliotek doctest

    Testing kan deaktiveres (se alternativet MYLIB_TESTING).

  3. Doxygen

    For Ä bytte sprÄk som dokumentasjonen skal genereres pÄ, er det et alternativ MYLIB_DOXYGEN_LANGUAGE.

  4. Tolk for programmeringssprÄk Python 3

    For automatisk generering nettbaserte sandkasser.

Static analysis

Med CMake og et par gode verktÞy kan man pÄfÞre statisk analyse med minimal innsats.

Cppcheck

CMake har innebygd stĂžtte for verktĂžy for statisk analyse Cppcheck.

For dette mÄ man bruke alternativet CMAKE_CXX_CPPCHECK:

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

Etter dette vil den statiske analysen automatisk bli kjĂžrt hver gang koden kompilert og recompilert. Man trenger ikke Ă„ gjĂžre noe ekstra.

Clang

Ved hjelp av det fantastiske verktÞyet scan-build kan man ogsÄ kjÞre statisk analyse pÄ et blunk:

scan-build cmake -S sti/til/kildekode -B sti/til/bygningsdirectory -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build sti/til/bygningsdirectory

Her, i motsetning til tilfelle med Cppcheck, er det nĂždvendig Ă„ kjĂžre byggingen hver gang via scan-build.

Etterord

CMake er et svÊrt kraftig og fleksibelt system som lar deg implementere funksjonalitet etter enhver smak. Selv om syntaksen av og til kunne vÊrt bedre, er det ikke sÄ skummelt som det ser ut. Bruk CMake-bygget til samfunnets beste og til egen nytte.

→ Last ned prosjektmal

Kilde: habr.com

KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster