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

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

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

Mnozí nadávají CMake a často zaslouženě, ale když se podíváte, není to tak špatné, ale v poslední době to vůbec není špatnéa směr vývoje je vcelku pozitivní.

V této poznámce vám chci říci, jak snadné je uspořádat knihovnu hlaviček C++ v systému CMake, abyste získali následující funkce:

  1. shromáždění;
  2. Testy automatického spouštění;
  3. Měření pokrytí kódem;
  4. instalace;
  5. Autodokumentace;
  6. Generování online karantény;
  7. Statická analýza.

Kdo už chápe klady a si-make může jen stáhnout šablonu projektu a začněte jej používat.


Obsah

  1. Projekt zevnitř
    1. Struktura projektu
    2. Hlavní soubor CMake (./CMakeLists.txt)
      1. Informace o projektu
      2. Možnosti projektu
      3. Možnosti kompilace
      4. Hlavním cílem
      5. Instalace
      6. Testy
      7. Документация
      8. Online sandbox
    3. Skript pro testy (test/CMakeLists.txt)
      1. Testování
      2. Pokrytí
    4. Dokumentační skript (doc/CMakeLists.txt)
    5. Skript pro online karanténu (online/CMakeLists.txt)
  2. projekt venku
    1. shromáždění
      1. Generace
      2. shromáždění
    2. Možnosti
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. montážní cíle
      1. Ve výchozím nastavení
      2. mylib-unit-tests
      3. check
      4. krytí
      5. doc
      6. hůlka
    4. Příklady
  3. Nástroje
  4. Statická analýza
  5. Doslov

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

To se zaměří hlavně na to, jak organizovat skripty CMake, takže budou podrobně analyzovány. Zbytek souborů může kdokoli přímo zobrazit. na stránce projektu šablony.

Hlavní soubor CMake (./CMakeLists.txt)

Informace o projektu

Nejprve je potřeba požádat o požadovanou verzi systému CMake. CMake se vyvíjí, podpisy příkazů se mění, chování za různých podmínek. Aby CMake okamžitě pochopil, co od něj chceme, musíme na něj okamžitě opravit naše požadavky.

cmake_minimum_required(VERSION 3.13)

Poté označíme náš projekt, jeho název, verzi, používané jazyky atd. команду project).

V tomto případě zadejte jazyk CXX (což znamená C++), aby se CMake neobtěžovalo hledáním kompilátoru C (ve výchozím nastavení jsou v CMake zahrnuty dva jazyky: C a C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Zde si můžete ihned ověřit, zda je náš projekt zařazen do jiného projektu jako podprojekt. Do budoucna to hodně pomůže.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Možnosti projektu

Zvažme dvě možnosti.

První možností je MYLIB_TESTING - zakázat testy jednotek. To může být potřeba, pokud jsme si jisti, že je vše v pořádku s testy a chceme například pouze nainstalovat nebo zabalit náš projekt. Nebo je náš projekt zařazen jako podprojekt – v tomto případě uživatel našeho projektu nemá zájem spouštět naše testy. Netestuješ závislosti, které používáš, že ne?

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

Kromě toho vytvoříme samostatnou možnost MYLIB_COVERAGE pro měření pokrytí kódem pomocí testů, ale bude vyžadovat další nástroje, takže jej budete muset explicitně povolit.

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

Možnosti kompilace

Samozřejmě jsme cool programátoři-pozitivní, takže od kompilátoru chceme maximální úroveň diagnostiky v době kompilace. Ani jedna myš neprojde.

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
)

Zakážeme také rozšíření, aby plně vyhovovala jazykovému standardu C++. Ve výchozím nastavení jsou v CMake povoleny.

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

Hlavním cílem

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, abyste mohli naši knihovnu používat zvenčí, musíte si ji nainstalovat, musíte ji umět najít v systému a připojit k vašemu projektu a zároveň právě tyto hlavičky jako případně nějaké další vlastnosti.

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

add_library(mylib INTERFACE)

Svážeme hlavičky s naší knihovnou rozhraní.

Moderní, trendy a mladistvé použití CMake znamená, že záhlaví, vlastnosti atd. přenášeny přes jediný cíl. Takže stačí říct target_link_libraries(target PRIVATE dependency)a všechna záhlaví, která jsou přidružena k cíli dependency, bude k dispozici pro zdroje patřící do cíle target. A ty žádné nepotřebuješ [target_]include_directories. To bude ukázáno níže při analýze CMake skript pro testy jednotek.

Za pozornost stojí také tzv. выражения-генераторы: $<...>.

Tento příkaz spojuje hlavičky, které potřebujeme, s naší knihovnou rozhraní, a pokud je naše knihovna připojena k jakémukoli cíli ve stejné hierarchii CMake, pak k ní budou přiřazeny hlavičky z adresáře. ${CMAKE_CURRENT_SOURCE_DIR}/include, a pokud je naše knihovna nainstalována v systému a připojena k jinému projektu pomocí příkazu find_package, pak k němu budou přiřazeny hlavičky z adresáře include vzhledem k instalačnímu adresáři.

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

Nastavte jazykový standard. Samozřejmě nejnovější. Standard přitom nejen zařazujeme, ale také distribuujeme těm, kteří budou naši knihovnu využívat. Toho je dosaženo tím, že vlastnost set má kategorii INTERFACE (viz příkaz target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Získáme alias pro naši knihovnu. A pro krásu to bude ve speciálním „jmenném prostoru“. To bude užitečné, když se v naší knihovně objeví různé moduly a budeme je propojovat nezávisle na sobě. Jako například v Bustě.

add_library(Mylib::mylib ALIAS mylib)

Instalace

Instalace našich hlaviček do systému. Všechno je zde jednoduché. Říkáme, že složka se všemi záhlavími by měla spadat do adresáře include ohledně místa instalace.

install(DIRECTORY include/mylib DESTINATION include)

Dále říkáme systému sestavení, že chceme mít možnost volat příkaz v projektech třetích stran find_package(Mylib) a získat cíl Mylib::mylib.

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

Následující zaklínadlo je třeba chápat takto. Když jsme ve vedlejším projektu, voláme příkaz find_package(Mylib 1.2.3 REQUIRED), a zároveň skutečná verze nainstalované knihovny bude nekompatibilní s verzí 1.2.3, CMake automaticky vygeneruje chybu. To znamená, že nebudete muset 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 výslovně zakázány pomocí odpovídající možnost nebo náš projekt je podprojekt, to znamená, že je připojen k jinému projektu CMake pomocí příkazu add_subdirectory, nejdeme dále v hierarchii a skript, který popisuje příkazy pro generování a spouštění testů, se jednoduše nespustí.

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

Документация

Dokumentace nebude generována ani v případě dílčího projektu.

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

Online sandbox

Stejně tak podprojekt nebude mít online sandbox.

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

Skript pro testy (test/CMakeLists.txt)

Testování

Nejprve najdeme balíček s požadovaným testovacím rámcem (nahraďte jej svým oblíbeným).

find_package(doctest 2.3.3 REQUIRED)

Vytvoříme náš spustitelný soubor s testy. Obvykle přímo do spustitelného binárního souboru přidám pouze soubor, ve kterém funkce bude main.

add_executable(mylib-unit-tests test_main.cpp)

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

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

Propojujeme závislosti. Vezměte prosím na vědomí, že jsme k našemu binárnímu souboru připojili pouze cíle CMake, které jsme potřebovali, a nevolali příkaz target_include_directories. Záhlaví z testovacího rámce a z našeho Mylib::mylib, stejně jako možnosti sestavení (v našem případě jazykový standard C++) procházely společně s těmito cíli.

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

Nakonec vytvoříme fiktivní cíl, jehož „sestavení“ je ekvivalentní spouštění testů, a přidáme tento cíl do výchozího sestavení (toto je odpovědností atributu ALL). To znamená, že výchozí sestavení spustí testy, což znamená, že je nikdy nezapomeneme spustit.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Pokrytí

Dále zapněte měření pokrytí kódem, pokud je nastavena odpovídající možnost. Nebudu zabíhat do podrobností, protože se týkají spíše nástroje pro měření pokrytí než CMake. Důležité je pouze poznamenat, že výsledky vytvoří cíl coverage, se kterým je vhodné začít měřit pokrytí.

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

Dokumentační skript (doc/CMakeLists.txt)

Nalezen Doxygen.

find_package(Doxygen)

Dále zkontrolujeme, zda je proměnná s jazykem nastavena uživatelem. Pokud ano, pak se toho nedotkneme, pokud ne, vezmeme ruštinu. Poté nakonfigurujeme systémové soubory Doxygen. Všechny potřebné proměnné, včetně jazyka, se tam dostanou během procesu konfigurace (viz. команду configure_file).

Poté vytvoříme cíl doc, který zahájí generování dokumentace. Protože generování dokumentace není ve vývojovém procesu tou největší potřebou, cíl nebude ve výchozím nastavení zahrnut, bude muset být spuštěn explicitně.

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 pro online karanténu (online/CMakeLists.txt)

Zde najdeme třetí Python a vytvoříme cíl wandbox, který vygeneruje požadavek odpovídající servisnímu API Schránka na hůlkua odešle. Jako odpověď přichází odkaz na hotový sandbox.

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 venku

Nyní se podíváme na to, jak to vše využít.

shromáždění

Budování tohoto projektu, stejně jako jakýkoli jiný projekt na sestavení CMake, se skládá ze dvou fází:

Generace

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

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

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

Více o možnostech.

Sestavení projektu

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

Více o cílech montáže.

Možnosti

MYLIB_COVERAGE

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

Zahrnuje cíl coverage, pomocí kterého můžete začít měřit pokrytí kódem pomocí testů.

MYLIB_TESTING

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

Poskytuje možnost vypnout sestavení a cíl testu jednotky check. V důsledku toho se vypne měření pokrytí kódu testy (viz MYLIB_COVERAGE).

Testování je také automaticky zakázáno, pokud je projekt připojen k jinému projektu jako podprojekt pomocí příkazu add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Přepne jazyk dokumentace, kterou cíl generuje doc k danému. Seznam dostupných jazyků viz Web Doxygen.

Ruština je ve výchozím nastavení povolena.

montážní cíle

Ve výchozím nastavení

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

Pokud není zadán cíl (což je ekvivalentní cíli all), sbírá 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

Kompiluje unit testy. Ve výchozím nastavení povoleno.

check

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

Spustí vestavěné (sestaví, pokud již ne) testy jednotek. Ve výchozím nastavení povoleno.

См. также mylib-unit-tests.

krytí

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

Analyzuje spuštěné (pokud ještě ne) testy jednotek pro pokrytí kódu testy pomocí programu gcovr.

Výstup pokrytí bude vypadat nějak 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 dostupný pouze v případě, že je tato možnost povolena. MYLIB_COVERAGE.

См. также check.

doc

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

Spustí generování dokumentace pro kód pomocí systému Doxygen.

hůlka

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

Odpověď ze služby vypadá asi takto:

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

K tomu slouží služba. Schránka na hůlku. Nevím, jak mají gumové servery, ale myslím, že byste tuto funkci neměli zneužívat.

Příklady

Sestavte projekt v režimu ladění s měřením pokrytí

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

Instalace projektu bez předběžného sestavení a testování

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

Sestavte v režimu vydání daným kompilátorem

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

Vytváření dokumentace v angličtině

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

Nástroje

  1. CMake 3.13

    Ve skutečnosti je CMake verze 3.13 vyžadován pouze ke spuštění některých příkazů konzoly popsaných v této nápovědě. Z hlediska syntaxe skriptů CMake je verze 3.8 dostačující, pokud je generování voláno jinými způsoby.

  2. Testovací knihovna doctest

    Testování lze zakázat (viz опцию MYLIB_TESTING).

  3. Doxygen

    Pro přepnutí jazyka, ve kterém bude dokumentace generována, je zde možnost MYLIB_DOXYGEN_LANGUAGE.

  4. PL tlumočník Python 3

    Pro automatické generování online pískoviště.

Statická analýza

S pomocí CMake a několika dobrých nástrojů můžete poskytnout statickou analýzu s minimálními problémy.

Cppcheck

Podpora pro nástroj pro statickou analýzu zabudovaný do CMake Cppcheck.

Chcete-li to provést, použijte možnost CMAKE_CXX_CPPCHECK:

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

Poté bude statická analýza automaticky spuštěna pokaždé během kompilace a rekompilace zdrojů. Není třeba dělat nic navíc.

Zvonit

S úžasným nástrojem scan-build Můžete také okamžitě spustit statickou analýzu:

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

Zde, na rozdíl od případu s Cppcheck, je nutné spustit sestavení pokaždé scan-build.

Doslov

CMake je velmi výkonný a flexibilní systém, který vám umožní implementovat funkce pro každý vkus a barvu. A i když syntaxe někdy ponechává mnoho přání, ďábel stále není tak hrozný, jak je namalován. Používejte sestavovací systém CMake ve prospěch společnosti a zdraví.

Stáhněte si šablonu projektu

Zdroj: www.habr.com

Přidat komentář