CMake a C++ – bratři navždy

CMake a C++ – bratři navždy

Během vývoje rád měním kompilátory, režimy sestavení, verze závislostí, provádím statickou analýzu, měřím výkon, shromažďuji pokrytí, generuji dokumentaci atd. A CMake mám velmi rád, protože mi umožňuje dělat všechno, co chci.

Mnozí CMake kritizují, a často oprávněně, ale pokud se na to podíváte zblízka, není to tak špatné, a v poslední době je to dokonce velmi dobré, a směr rozvoje je poměrně pozitivní.

V této poznámce bych chtěl říci, jak docela jednoduše uspořádat hlavičkovou knihovnu v jazyce C++ v systému CMake, aby bylo možné získat následující funkčnost:

  1. Sestavení;
  2. Automatické spuštění testů;
  3. Měření pokrytí kódu;
  4. Instalaci;
  5. Automatické dokumentování;
  6. Generaci online písečně;
  7. Statická analýza.

Kdo se v C++ a CMake již vyzná, může jednoduše stáhnout šablonu projektu a začít ji používat.


Obsah

  1. Projekt zevnitř
    1. Struktura projektu
    2. Hlavní CMake soubor (./CMakeLists.txt)
      1. Informace o projektu
      2. Možnosti projektu
      3. Možnosti kompilace
      4. Hlavní cíl
      5. Instalace
      6. Testy
      7. Dokumentace
      8. Online písečná
    3. Skript pro testy (test/CMakeLists.txt)
      1. Testování
      2. Pokrytí
    4. Skript pro dokumentaci (doc/CMakeLists.txt)
    5. Skript pro online písečnou (online/CMakeLists.txt)
  2. Projekt zvenčí
    1. Sestavení
      1. Generace
      2. Sestavení
    2. Možnosti
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Sestavovací cíle
      1. Ve výchozím nastavení
      2. mylib-unit-tests
      3. check
      4. coverage
      5. doc
      6. wandbox
    4. Příklady
  3. Nástroje
  4. Statická analýza
  5. Poznámka na závěr

Projekt zevnitř

Struktura 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

Hlavně se budu zabývat tím, jak organizovat CMake skripty, proto budou podrobně probrány. Ostatní soubory si může každý zájemce prohlédnout přímo na stránce šablony projektu..

Hlavní CMake soubor (./CMakeLists.txt)

Informace o projektu

Nejdříve je třeba požádat o potřebnou verzi systému CMake. CMake se vyvíjí, mění se signatury příkazů, chování v různých podmínkách. Aby CMake ihned pochopil, co od něj chceme, je nutné naše požadavky hned stanovit.

cmake_minimum_required(VERSION 3.13)

Poté definujeme náš projekt, jeho název, verzi, používané jazyky a další (viz. příkaz project).

V tomto případě uvádíme jazyk CXX (což znamená C++), aby se CMake nezatěžoval a nehledal kompilátor jazyka C (ve výchozím nastavení CMake zahrnuje dva jazyky: C a C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Zde si můžete okamžitě zkontrolovat, zda je náš projekt zahrnut v jiném projektu jako podprojekt. To nám výrazně pomůže dále.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Možnosti projektu

Předpokládejme dvě možnosti.

První možnost — MYLIB_TESTING — pro deaktivaci modulových testů. To může být potřebné, pokud si jsme jisti, že s testy je vše v pořádku, a chceme například pouze nainstalovat nebo zabalit náš projekt. Nebo je náš projekt zahrnut jako podprojekt — v tomto případě není pro uživatele našeho projektu zajímavé spouštět naše testy. Testujete snad závislosti, které používáte?

option(MYLIB_TESTING "Povolit modulové testování" ON)

Kromě toho vytvoříme samostatnou možnost MYLIB_COVERAGE pro měření pokrytí kódu testy, ale ta bude vyžadovat další nástroje, a proto ji bude nutné explicitně povolit.

option(MYLIB_COVERAGE "Povolit měření pokrytí kódu testy" OFF)

Možnosti kompilace

Samozřejmě, jsme skvělí programátoři v C++, takže chceme od kompilátoru maximální úroveň diagnostiky při kompilaci. Žádná myš nezůstane neodhalena.

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
)

Rozšíření také vypneme, abychom plně vyhověli standardu jazyka C++. Ve výchozím nastavení jsou v CMake zapnuta.

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

Hlavní cíl

Naše knihovna se skládá pouze z hlavičkových souborů, což znamená, že nemáme žádný výstup ve formě statických nebo dynamických knihoven. Na druhou stranu, abychom mohli naši knihovnu použít externě, je nutné ji nainstalovat, aby byla v systému detekována a připojena k našemu projektu, přičemž s ní musí být spojeny tyto hlavičky a také případně nějaké další vlastnosti.

Za tímto účelem vytváříme rozhraní knihovnu.

add_library(mylib INTERFACE)

Připojujeme hlavičky k naší rozhraní knihovně.

Moderní, populární a současné použití CMake předpokládá, že hlavičky, vlastnosti atd. jsou předávány prostřednictvím jediné cíle. Takže stačí říct target_link_libraries(target PRIVATE dependency), a všechny hlavičky, které jsou spojeny s cílem dependency, budou dostupné pro zdrojové kódy patřící k cíli. target. A žádné [target_]include_directories. To bude demonstrováno níže při analýze CMake skriptu pro modulové testy.

Také je dobré věnovat pozornost tzv. výrazům-generátorům: $.

Tento příkaz asociuje potřebné hlavičky s naší rozhraní knihovnou a pokud bude naše knihovna propojena s nějakým cílem v rámci jedné hierarchie CMake, budou s ní asociovány hlavičky z adresáře ${CMAKE_CURRENT_SOURCE_DIR}/include, a pokud je naše knihovna nainstalována do systému a propojena v jiném projektu pomocí příkazu find_package, budou s ní asociovány hlavičky z adresáře include ve vztahu k adresáři instalace.

target_include_directories(mylib INTERFACE
    $
    $
)

Nastavíme standard jazyka. Samozřejmě, ten nejnovější. Přičemž nezahrnujeme pouze standard, ale rozšiřujeme ho na ty, kdo budou používat naši knihovnu. Toho dosáhneme tím, že nastavená vlastnost má kategorii INTERFACE (viz příkaz target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Tvoříme alias pro naši knihovnu. A pro krásu bude v speciálním „jmenném prostoru“. To bude užitečné, když se v naší knihovně objeví různé moduly a chceme je připojit nezávisle jeden na druhém. Jako v Boostu, například.

add_library(Mylib::mylib ALIAS mylib)

Instalace

Instalace našich hlaviček do systému. Tady je to jednoduché. Říkáme, že složka se všemi hlavičkami by měla být umístěna do adresáře include ve vztahu k místu instalace.

install(DIRECTORY include/mylib DESTINATION include)

Dále informujeme systém sestavení o tom, že chceme mít možnost v cizích projektech používat příkaz find_package(Mylib) a obdržet cíl Mylib::mylib.

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

Následující kouzlo je třeba chápat takto. Když v cizím projektu zavoláme příkaz find_package(Mylib 1.2.3 REQUIRED), a přitom skutečná verze nainstalované knihovny bude nekompatibilní s verzí 1.2.3, CMake automaticky vygeneruje chybu. To znamená, že není nutné sledovat verze ručně.

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)

Testy

Pokud jsou testy explicitně vypnuty pomocí příslušné volby nebo náš projekt je podpроект, tedy je připojen do jiného CMake projektu pomocí příkazu add_subdirectory, nepokračujeme dále na úrovni hierarchie a skript, ve kterém jsou popsány příkazy pro generování a spuštění testů, se jednoduše nespustí.

if(NOT MYLIB_TESTING)
    message(STATUS "Testování projektu Mylib je vypnuto")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib se netestuje v režimu podmodulu")
else()
    add_subdirectory(test)
endif()

Dokumentace

Dokumentace také nebude generována v případě podp projektu.

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

Online písečná

Podobně, online písečné boxy pro podp projekt také nebudou.

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

Skript pro testy (test/CMakeLists.txt)

Testování

Nejprve najdeme balíček s potřebným testovacím frameworkem (nahraďte svým oblíbeným).

find_package(doctest 2.3.3 REQUIRED)

Vytváříme náš spustitelný soubor s testy. Obvykle přidávám pouze soubor, ve kterém bude funkce main.

add_executable(mylib-unit-tests test_main.cpp)

A soubory, ve kterých jsou popsány samotné testy, přidám později. Ale není to nutné.

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

Připojujeme závislosti. Všimněte si, že k našemu binárnímu jsme přiřadili pouze potřebné CMake cíle, a nevolali jsme příkaz target_include_directories. Hlavičky z testovacího frameworku a z našeho Mylib::mylib, a také parametry sestavení (v našem případě to je standard jazyka C++) prošly spolu s těmito cíli.

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

Nakonec vytváříme fiktivní cíl, "jehož stavba" je ekvivalentní spuštění testů, a přidáváme tento cíl do sestavy podle výchozího nastavení (to zajišťuje atribut ALL). To znamená, že výchozí sestavení iniciuje spuštění testů, tedy nikdy na jejich spuštění nezapomeneme.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pokrytí

Dále zahrnujeme měření pokrytí kódu, pokud je zadána odpovídající volba. Nechci se pouštět do podrobností, protože se týkají spíše nástroje pro měření pokrytí než CMake. Důležité je pouze poznamenat, že na základě výsledků bude vytvořen cíl coverage, pomocí kterého je pohodlné spouštět měření pokrytí.

find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "Měření pokrytí kódu testy bylo povoleno")

    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 "Program gcovr je vyžadován pro měření pokrytí kódu testy")
endif()

Skript pro dokumentaci (doc/CMakeLists.txt)

Našli jsme Doxygen.

find_package(Doxygen)

Dále ověřujeme, zda uživatel nastavil proměnnou s jazykem. Pokud ano, nezasahujeme do ní, pokud ne, vezmeme ruštinu. Poté konfigrujeme soubory systému Doxygen. Všechny potřebné proměnné, včetně jazyka, se do nich dostanou během konfiguračního procesu (viz příkaz configure_file).

Poté vytvoříme cíl doc, který spustí generování dokumentace. Protože generování dokumentace není nejvyšší prioritou během vývoje, bude cíl ve výchozím nastavení vypnutý a bude ho potřeba spustit explicitně.

if (Doxygen_FOUND)
    if (NOT MYLIB_DOXYGEN_LANGUAGE)
        set(MYLIB_DOXYGEN_LANGUAGE Czech)
    endif()
    message(STATUS "Dokumentace Doxygen bude generována v ${MYLIB_DOXYGEN_LANGUAGE}")
    configure_file(Doxyfile.in Doxyfile)
    add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()

Skript pro online písečnou (online/CMakeLists.txt)

Zde najdeme třetí Python a vytvoříme cíl wandbox, který generuje žádost odpovídající API služby Wandbox, a odešle ji. V odpovědi přijde odkaz na hotový pískoviště.

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 "Pro vytvoření online pískoviště je vyžadován interpretr programovacího jazyka Python verze 3")
endif()

Projekt zvenčí

Nyní se podívejme, jak s tím vším pracovat.

Sestavení

Sestavení tohoto projektu, stejně jako jakéhokoli jiného projektu na systému sestavení CMake, se skládá ze dvou etap:

Generace

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/directory [možnosti ...]

Pokud výše uvedený příkaz nefungoval kvůli staré verzi CMake, zkuste opustit -S:

cmake cesta/k/zdrojům -B cesta/k/sestavovací/directory [možnosti ...]

Více o možnostech.

Sestavení projektu

cmake --build cesta/k/sestavovací/directory [--target cíl]

Více o cílech sestavení.

Možnosti

MYLIB_COVERAGE

cmake -S ... -B ... -DMYLIB_COVERAGE=ON [další možnosti ...]

Povoluje cíl coverage, pomocí které je možné spustit měření pokrytí kódu testy.

MYLIB_TESTING

cmake -S ... -B ... -DMYLIB_TESTING=OFF [další možnosti ...]

Umožňuje vypnout sestavení modulových testů a cíle check. V důsledku toho se vypne měření pokrytí kódu testy (viz. MYLIB_COVERAGE).

Testování se také automaticky vypne, pokud je projekt připojen do jiného projektu jako podprojekt pomocí příkazu add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [další možnosti ...]

Přepíná jazyk dokumentace, kterou generuje cíl doc na zadaný. Seznam dostupných jazyků je uveden na stránkách systému Doxygen.

Ve výchozím nastavení je povolena ruština.

Sestavovací cíle

Ve výchozím nastavení

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

Pokud cíl není uveden (což je ekvivalentní cíli all), sestavuje vše, co je možné, a také volá cíl check.

mylib-unit-tests

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

Komplikuje modulové testy. Vydáno ve výchozím nastavení.

check

cmake --build cesta/k/sestavovací/directory --target check

Spouští sestavené (sestavuje, pokud ještě ne) modulové testy. Vydáno ve výchozím nastavení.

Viz také mylib-unit-tests.

coverage

cmake --build cesta/k/sestavovací/directory --target coverage

Analyzuje spuštěné (spouští, pokud ještě ne) modulové testy z hlediska pokrytí kódu testy pomocí programu gcovr.

Výstup pokrytí bude vypadat přibližně 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%
------------------------------------------------------------------------------

Cíl je k dispozici pouze při povolené možnosti MYLIB_COVERAGE.

Viz také check.

doc

cmake --build cesta/k/sestavovací/directory --target doc

Spouští generaci dokumentace kódu pomocí systému Doxygen.

wandbox

cmake --build cesta/k/sestavovací/directory --target wandbox

Odpověď od služby vypadá přibližně takto:

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

K tomu se používá služba Wandbox. Nevím, jak moc mají pružné servery, ale myslím, že zneužívat tuto možnost by se nemělo.

Příklady

Sestavení projektu v ladicím režimu s měřením pokrytí

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/directory -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build cesta/k/sestavovací/directory --target coverage --parallel 16

Instalace projektu bez předchozího sestavení a testování

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/direktorii -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=cesta/k/instalační/direktorii
cmake --build cesta/k/sestavovací/direktorii --target install

Sestavení v režimu vydání zvoleným kompilátorem

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/direktorii -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=cesta/k/direktorii/kde/sou/závislosti
cmake --build cesta/k/sestavovací/direktorii --parallel 4

Generování dokumentace v angličtině

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/direktorii -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build cesta/k/sestavovací/direktorii --target doc

Nástroje

  1. CMake 3.13

    Ve skutečnosti je k běhu některých konzolových příkazů uvedených v této příručce vyžadována verze CMake 3.13. Z pohledu syntaxe CMake skriptů je dostačující verze 3.8, pokud je generace volána jinými způsoby.

  2. Testovací knihovna doctest

    Testování lze vypnout (viz volba MYLIB_TESTING).

  3. Doxygen

    Pro přepnutí jazyka, ve kterém bude dokumentace generována, je k dispozici volba MYLIB_DOXYGEN_LANGUAGE.

  4. Interpret JAZYKA Python 3

    Pro automatickou generaci online pískoviště.

Statická analýza

S pomocí CMake a pár dobrých nástrojů lze zajistit statickou analýzu s minimálním úsilím.

Cppcheck

CMake má vestavěnou podporu pro nástroj pro statickou analýzu Cppcheck.

K tomu je třeba využít volbu CMAKE_CXX_CPPCHECK:

cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/direktorii -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I cesta/k/zdrojům/include"

Po tomto bude statická analýza automaticky spouštěna každýkrát během kompilace a rekonstrukce zdrojů. Není třeba dělat nic dalšího.

Clang

Pomocí úžasného nástroje scan-build lze také spustit statickou analýzu velmi rychle:

scan-build cmake -S cesta/k/zdrojům -B cesta/k/sestavovací/direktorii -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build cesta/k/sestavovací/direktorii

Zde, na rozdíl od případu s Cppcheck, je třeba každé sestavení spouštět pomocí scan-build.

Poznámka na závěr

CMake je velmi mocný a flexibilní systém, který umožňuje implementovat funkčnost podle libosti. Ačkoli syntaxe někdy zanechává co chtít, není to tak strašné, jak se zdá. Používejte systém sestavení CMake k prospěchu společnosti a ve prospěch zdraví.

→ Stáhněte si šablonu projektu

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster