
În timpul dezvoltării, îmi place să schimb compilatoarele, să construiesc moduri, versiunile de dependență, să efectuez analize statice, să măsor performanța, să colectez acoperire, să generez documentație etc. Și îmi place foarte mult CMake pentru că îmi permite să fac tot ce îmi doresc.
Mulți oameni critică CMake și de multe ori pe merit, dar dacă te uiți la el, nu totul este atât de rău și recent nu-i rău deloc, iar direcția de dezvoltare este destul de pozitivă.
În această notă, vreau să vă spun cum să organizați pur și simplu o bibliotecă de antet în C++ în sistemul CMake pentru a obține următoarea funcționalitate:
- Asamblare;
- teste de rulare automată;
- Măsurarea acoperirii codului;
- Instalare;
- Auto-documentare;
- Generare online sandbox;
- Analiza statica.
Oricine înțelege deja avantajele și C-make poate pur și simplu și începe să-l folosești.
Conținut
.
├── 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.cppVom vorbi în principal despre cum să organizați scripturile CMake, așa că vor fi discutate în detaliu. Oricine poate vizualiza restul fișierelor direct .
În primul rând, trebuie să solicitați versiunea necesară a sistemului CMake. CMake evoluează, semnăturile de comandă și comportamentul în diferite condiții se schimbă. Pentru ca CMake să înțeleagă imediat ce dorim de la el, trebuie să ne înregistrăm imediat cerințele pentru acesta.
cmake_minimum_required(VERSION 3.13)Apoi vom desemna proiectul nostru, numele, versiunea, limbile folosite etc. (vezi. ).
În acest caz indicăm limba CXX (și asta înseamnă C++), astfel încât CMake să nu se încordeze și să caute un compilator de limbaj C (în mod implicit, CMake include două limbaje: C și C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Aici puteți verifica imediat dacă proiectul nostru este inclus într-un alt proiect ca subproiect. Acest lucru va ajuta foarte mult în viitor.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Vă vom oferi două opțiuni.
Prima opțiune este — pentru a dezactiva testele unitare. Acest lucru poate fi necesar dacă suntem siguri că totul este în ordine cu testele, dar vrem doar, de exemplu, să ne instalăm sau să împachetăm proiectul. Sau proiectul nostru este inclus ca subproiect - în acest caz, utilizatorul proiectului nostru nu este interesat să ruleze testele noastre. Nu testezi dependențele pe care le folosești, nu-i așa?
option(MYLIB_TESTING "Включить модульное тестирование" ON)În plus, vom face o opțiune separată pentru măsurarea acoperirii codului prin teste, dar va necesita instrumente suplimentare, deci va trebui să fie activat în mod explicit.
option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)
Desigur, suntem programatori cool plus, așa că dorim nivelul maxim de diagnosticare în timp de compilare de la compilator. Nici un singur mouse nu va strecura.
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
)De asemenea, vom dezactiva extensiile pentru a respecta pe deplin standardul limbajului C++. Sunt activate implicit în CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Biblioteca noastră constă numai din fișiere antet, ceea ce înseamnă că nu avem nicio epuizare sub formă de biblioteci statice sau dinamice. Pe de altă parte, pentru a putea folosi biblioteca noastră extern, aceasta trebuie să fie instalată, să fie detectabilă în sistem și conectată la proiectul dvs. și, în același timp, aceleași anteturi, precum și eventual unele suplimentare, sunt atașate proprietăților acestuia.
În acest scop, creăm o bibliotecă de interfață.
add_library(mylib INTERFACE)Legăm antetele la biblioteca noastră de interfețe.
Utilizarea modernă, la modă, pentru tineri a CMake implică faptul că anteturile, proprietățile etc. transmise printr-o singură țintă. Așa că este suficient să spun , și toate anteturile care sunt asociate cu ținta dependency, va fi disponibil pentru sursele aparținând țintei target. Și nu aveți nevoie de niciunul [target_]include_directories. Acest lucru va fi demonstrat mai jos în analiză .
De asemenea, merită să acordați atenție așa-numitelor. .
Această comandă asociază anteturile de care avem nevoie cu biblioteca noastră de interfață, iar dacă biblioteca noastră este conectată la orice țintă din aceeași ierarhie CMake, atunci anteturile din director vor fi asociate cu aceasta. ${CMAKE_CURRENT_SOURCE_DIR}/include, și dacă biblioteca noastră este instalată pe sistem și conectată la alt proiect folosind comanda , atunci anteturile din director vor fi asociate cu acesta include raportat la directorul de instalare.
target_include_directories(mylib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)Să stabilim un standard de limbă. Desigur, chiar ultimul. În același timp, nu doar includem standardul, ci îl extindem și la cei care vor folosi biblioteca noastră. Acest lucru se realizează datorită faptului că proprietatea set are o categorie INTERFACE (A se vedea. ).
target_compile_features(mylib INTERFACE cxx_std_17)Să creăm un alias pentru biblioteca noastră. Mai mult, pentru frumusețe, va fi într-un „namespace” special. Acest lucru va fi util atunci când în biblioteca noastră apar diferite module și mergem să le conectăm independent unul de celălalt. .
add_library(Mylib::mylib ALIAS mylib)
Instalarea anteturilor noastre în sistem. Totul este simplu aici. Spunem că folderul cu toate anteturile ar trebui să intre în director include raportat la locul de instalare.
install(DIRECTORY include/mylib DESTINATION include)În continuare, informăm sistemul de construcție că dorim să putem apela comanda în proiecte terțe find_package(Mylib) și obține un gol Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Următoarea vrajă ar trebui înțeleasă astfel. Când într-un proiect terță parte, apelăm comanda find_package(Mylib 1.2.3 REQUIRED), iar versiunea reală a bibliotecii instalate va fi incompatibilă cu versiunea 1.2.3CMake va genera automat o eroare. Adică, nu va trebui să urmăriți manual versiunile.
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)
Dacă testele sunt dezactivate explicit folosind sau proiectul nostru este un subproiect, adică este conectat la un alt proiect CMake folosind comanda , nu ne deplasăm mai departe de-a lungul ierarhiei, iar scriptul, care descrie comenzile pentru generarea și rularea testelor, pur și simplu nu rulează.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Nici documentația nu va fi generată în cazul unui subproiect.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
La fel, subproiectul nu va avea nici un sandbox online.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
În primul rând, găsim un pachet cu cadrul de testare necesar (înlocuiește-l cu cel preferat).
find_package(doctest 2.3.3 REQUIRED)Să creăm fișierul nostru executabil cu teste. De obicei adaug direct la binarul executabil doar fișierul care va conține funcția main.
add_executable(mylib-unit-tests test_main.cpp)Și adaug fișiere în care testele în sine sunt descrise mai târziu. Dar nu trebuie să faci asta.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Conectăm dependențe. Vă rugăm să rețineți că am legat doar țintele CMake de care aveam nevoie la binarul nostru și nu am apelat comanda target_include_directories. Titluri din cadrul de testare și din al nostru Mylib::mylib, precum și parametrii de construcție (în cazul nostru, acesta este standardul limbajului C++) au venit împreună cu aceste obiective.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)În cele din urmă, creăm o țintă inactivă, a cărei „build” este echivalentă cu rularea testelor și adăugăm această țintă la versiunea implicită (atributul este responsabil pentru acest lucru ALL). Aceasta înseamnă că versiunea implicită declanșează rularea testelor, ceea ce înseamnă că nu vom uita niciodată să le rulăm.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Apoi, activăm măsurarea acoperirii codului dacă este specificată opțiunea corespunzătoare. Nu voi intra în detalii, pentru că se referă mai mult la un instrument de măsurare a acoperirii decât la CMake. Este important doar să rețineți că pe baza rezultatelor se va crea un obiectiv , cu care este convenabil să începeți măsurarea acoperirii.
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()
.
find_package(Doxygen)Apoi, verificăm dacă utilizatorul a setat variabila de limbă. Dacă da, atunci nu o atingem, dacă nu, atunci luăm limba rusă. Apoi configurăm fișierele de sistem Doxygen. Toate variabilele necesare, inclusiv limba, merg acolo în timpul procesului de configurare (vezi. ).
Apoi ne creăm un obiectiv , care va începe să genereze documentația. Deoarece generarea documentației nu este cea mai mare nevoie în procesul de dezvoltare, ținta nu va fi activată implicit; va trebui să fie lansată în mod explicit.
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 ()
Aici găsim al treilea Python și creăm o țintă , care generează o solicitare corespunzătoare API-ului serviciului , și îl trimite departe. Răspunsul vine cu un link către cutia de nisip finalizată.
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()
Acum să vedem cum să folosim toate acestea.
Construirea acestui proiect, ca orice alt proiect pe sistemul de construcție CMake, constă în două etape:
cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]Dacă comanda de mai sus nu a funcționat din cauza unei versiuni vechi de CMake, încercați să omiteți
-S:cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]
.
cmake --build путь/к/сборочной/директории [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]Include ținta , cu care puteți începe măsurarea acoperirii codului prin teste.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]Oferă posibilitatea de a dezactiva construirea și țintirea testului unitar . Ca urmare, măsurarea acoperirii codului prin teste este dezactivată (vezi. ).
Testarea este, de asemenea, dezactivată automat dacă proiectul este conectat la un alt proiect ca subproiect folosind comanda .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]Schimbă limba documentației pe care o generează ținta la cel dat. Pentru o listă a limbilor disponibile, consultați .
Rusă este activată implicit.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allDacă ținta nu este specificată (care este echivalentă cu ținta all), colectează tot ce poate și, de asemenea, cheamă ținta .
cmake --build path/to/build/directory --target mylib-unit-testsCompilează teste unitare. Activat implicit.
cmake --build путь/к/сборочной/директории --target checkRulează testele unitare colectate (colectate, dacă nu deja). Activat implicit.
Vezi de asemenea .
cmake --build путь/к/сборочной/директории --target coverageAnalizează testele unitare care rulează (rulează, dacă nu deja) pentru acoperirea codului prin teste folosind programul .
Evacuarea acoperirii va arăta cam așa:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------Ținta este disponibilă numai când opțiunea este activată .
Vezi de asemenea .
cmake --build путь/к/сборочной/директории --target docÎncepe generarea documentației de cod folosind sistemul .
cmake --build путь/к/сборочной/директории --target wandboxRăspunsul serviciului arată cam așa:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Serviciul este folosit pentru aceasta . Nu știu cât de flexibile sunt serverele lor, dar cred că această oportunitate nu trebuie abuzată.
Construiți proiectul în modul de depanare cu măsurarea acoperirii
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16Instalarea unui proiect fără asamblare și testare preliminară
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target installConstruiți în modul de lansare cu un compilator dat
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4Generarea documentatiei in limba engleza
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc
3.13
De fapt, versiunea 3.13 de CMake este necesară doar pentru a rula unele dintre comenzile consolei descrise în acest ajutor. Din punctul de vedere al sintaxei scripturilor CMake, versiunea 3.8 este suficientă dacă generarea este numită în alte moduri.
Biblioteca de testare
Testarea poate fi dezactivată (vezi ).
Pentru a schimba limba în care va fi generată documentația, este oferită o opțiune .
Interpret de limbă
Pentru generare automată .
Cu CMake și câteva instrumente bune, puteți oferi analiză statică cu un efort minim.
Cppcheck
CMake are suport încorporat pentru un instrument de analiză statică .
Pentru a face acest lucru, trebuie să utilizați opțiunea :
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"După aceasta, analiza statică va fi lansată automat de fiecare dată când sursa este compilată și recompilată. Nu este nevoie să faceți nimic suplimentar.
Zăngăni
Cu ajutorul unui instrument minunat De asemenea, puteți rula analize statice în cel mai scurt timp:
scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директорииAici, spre deosebire de cazul Cppcheck, trebuie să rulați construcția de fiecare dată scan-build.
CMake este un sistem foarte puternic și flexibil care vă permite să implementați funcționalități pentru fiecare gust și culoare. Și, deși sintaxa lasă uneori mult de dorit, diavolul încă nu este atât de groaznic pe cât este pictat. Utilizați sistemul de construcție CMake în beneficiul societății și al sănătății.
→
Sursa: www.habr.com
