
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:
- Sestavení;
- Automatické spuštění testů;
- Měření pokrytí kódu;
- Instalaci;
- Automatické dokumentování;
- Generaci online písečně;
- Statická analýza.
Kdo se v C++ a CMake již vyzná, může jednoduše a začít ji používat.
Obsah
.
├── 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.cppHlavně 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 .
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. ).
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)
Předpokládejme dvě možnosti.
První možnost — — 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 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)
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()
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 , 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 .
Také je dobré věnovat pozornost tzv. .
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 , 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 ).
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. .
add_library(Mylib::mylib ALIAS mylib)
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)
Pokud jsou testy explicitně vypnuty pomocí nebo náš projekt je podpроект, tedy je připojen do jiného CMake projektu pomocí příkazu , 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 také nebude generována v případě podp projektu.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Podobně, online písečné boxy pro podp projekt také nebudou.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
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)
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 , 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()
.
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 ).
Poté vytvoříme cíl , 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 ()
Zde najdeme třetí Python a vytvoříme cíl , který generuje žádost odpovídající API služby , 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()
Nyní se podívejme, jak s tím vším pracovat.
Sestavení tohoto projektu, stejně jako jakéhokoli jiného projektu na systému sestavení CMake, se skládá ze dvou etap:
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 ...]
.
cmake --build cesta/k/sestavovací/directory [--target cíl].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [další možnosti ...]Povoluje cíl , pomocí které je možné spustit měření pokrytí kódu testy.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [další možnosti ...]Umožňuje vypnout sestavení modulových testů a cíle . V důsledku toho se vypne měření pokrytí kódu testy (viz. ).
Testování se také automaticky vypne, pokud je projekt připojen do jiného projektu jako podprojekt pomocí příkazu .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [další možnosti ...]Přepíná jazyk dokumentace, kterou generuje cíl na zadaný. Seznam dostupných jazyků je uveden na .
Ve výchozím nastavení je povolena ruština.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allPokud cíl není uveden (což je ekvivalentní cíli all), sestavuje vše, co je možné, a také volá cíl .
cmake --build path/to/build/directory --target mylib-unit-testsKomplikuje modulové testy. Vydáno ve výchozím nastavení.
cmake --build cesta/k/sestavovací/directory --target checkSpouští sestavené (sestavuje, pokud ještě ne) modulové testy. Vydáno ve výchozím nastavení.
Viz také .
cmake --build cesta/k/sestavovací/directory --target coverageAnalyzuje spuštěné (spouští, pokud ještě ne) modulové testy z hlediska pokrytí kódu testy pomocí programu .
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 .
Viz také .
cmake --build cesta/k/sestavovací/directory --target docSpouští generaci dokumentace kódu pomocí systému .
cmake --build cesta/k/sestavovací/directory --target wandboxOdpověď 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 . Nevím, jak moc mají pružné servery, ale myslím, že zneužívat tuto možnost by se nemělo.
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 16Instalace 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 installSestavení 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 4Generová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
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.
Testovací knihovna
Testování lze vypnout (viz ).
Pro přepnutí jazyka, ve kterém bude dokumentace generována, je k dispozici volba .
Interpret JAZYKA
Pro automatickou generaci .
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 .
K tomu je třeba využít volbu :
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 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í/direktoriiZde, na rozdíl od případu s Cppcheck, je třeba každé sestavení spouštět pomocí scan-build.
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í.
→
Zdroj: habr.com
