
În procesul de dezvoltare, îmi place să schimb compilatoarele, modurile de construire, versiunile dependențelor, să fac analize statice, să măsor performanța, să colectez acoperirea, să generez documentație și altele. Și îmi place foarte mult CMake, pentru că îmi permite să fac tot ceea ce vreau.
Mulți critică CMake, și adesea pe bună dreptate, dar dacă ne aprofundăm, nu totul este atât de rău, iar în ultima vreme chiar stă bine., iar direcția de dezvoltare este destul de pozitivă.
În această notă, vreau să explic cum este relativ simplu să organizezi o bibliotecă de anteturi în C++ în sistemul CMake, pentru a obține următoarea funcționalitate:
- Construire;
- Autotestare;
- Măsurarea acoperirii codului;
- Instalare;
- Autodocumentare;
- Generarea unui sandbox online;
- Analiză statică.
Cine se pricepe deja la C++ și CMake poate pur și simplu și începe să-l folosească.
Cuprins
.
├── 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În principal, voi discuta despre organizarea scripturilor CMake, așa că acestea vor fi analizate detaliat. Ceilalți pot vizualiza fiecare fișier direct .
În primul rând, trebuie să solicităm versiunea necesară a sistemului CMake. CMake evoluează, se modifică semnăturile comenzilor, comportamentul în diferite condiții. Pentru ca CMake să înțeleagă imediat ce vrem de la el, trebuie să ne fixăm din start cerințele.
cmake_minimum_required(VERSION 3.13)Apoi, vom defini proiectul nostru, numele său, versiunea, limbajele utilizate și altele (vezi ).
În acest caz, indicăm limbajul CXX (iar asta înseamnă C++), pentru ca CMake să nu facă probleme și să nu caute compilatorul pentru limbajul 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 în alt proiect ca subproiect. Acest lucru va ajuta considerabil în continuare.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Să prevedem două opțiuni.
Prima opțiune — — pentru dezactivarea testelor modulare. Acest lucru poate fi necesar dacă suntem siguri că testele sunt în regulă și dorim, de exemplu, doar să instalăm sau să pachetam proiectul nostru. Sau proiectul nostru este inclus ca subproiect — în acest caz, utilizatorului proiectului nostru nu-i va păsa să ruleze testele noastre. Nu testați dependențele pe care le utilizați?
option(MYLIB_TESTING "Activați testarea modulară" ON)În plus, vom crea o opțiune separată pentru măsurarea acoperirii codului de teste, dar aceasta va necesita instrumente suplimentare, așa că va trebui activată în mod explicit.
option(MYLIB_COVERAGE "Activați măsurarea acoperirii codului de teste" OFF)
Desigur, suntem programatori C++ foarte pricepuți, așa că dorim un nivel maxim de diagnosticare în timpul compilării din partea compilatorului. Nicio greșeală nu va trece neobservată.
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 complet standardul limbajului C++. În mod implicit, acestea sunt activate în CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
Biblioteca noastră constă doar din fișiere de antet, ceea ce înseamnă că nu avem ieșiri sub formă de biblioteci statice sau dinamice. Pe de altă parte, pentru a utiliza biblioteca noastră din exterior, trebuie instalată, trebuie să fie detectabilă în sistem și să fie legată de proiectul tău, iar împreună cu ea, să fie legate aceste antete, precum și, poate, câteva proprietăți suplimentare.
Pentru acest scop, creăm o bibliotecă de interfață.
add_library(mylib INTERFACE)Legăm antetele la biblioteca noastră de interfață.
Utilizarea modernă, la modă, a CMake presupune că antetele, proprietățile etc. sunt transmise printr-o singură țintă. Astfel, este suficient să spunem , iar toate antetele asociate cu ținta dependency, vor fi disponibile pentru sursele care aparțin țintei destinația. Și nu este nevoie de niciun [target_]include_directoriesAcest lucru va fi demonstrat mai jos în analiza .
De asemenea, este important să acordăm atenție așa-numitelor .
Această comandă asociază capsele de care avem nevoie cu biblioteca noastră de interfață, iar în cazul în care biblioteca noastră este conectată la un anumit obiect în cadrul aceleași ierarhii CMake, vor fi asociate capsele din directorul ${CMAKE_CURRENT_SOURCE_DIR}/include, iar dacă biblioteca noastră este instalată în sistem și conectată într-un alt proiect folosind comanda , atunci vor fi asociate capsele din directorul include în raport cu directorul de instalare.
target_include_directories(mylib INTERFACE
$
$
)Setăm standardul limbajului. Desigur, cel mai recent. În acest fel, nu doar includem standardul, ci și îl extindem asupra celor care vor folosi biblioteca noastră. Acest lucru se realizează prin faptul că proprietatea stabilită are categoria INTERFACE (vezi ).
target_compile_features(mylib INTERFACE cxx_std_17)Creăm un alias pentru biblioteca noastră. De altfel, pentru frumusețe, va fi într-un „spațiu de nume” special. Acest lucru va fi util atunci când biblioteca noastră va avea diferite module, și le vom conecta independent unii de alții. .
add_library(Mylib::mylib ALIAS mylib)
Instalarea capselor noastre în sistem. Aici totul este simplu. Spunem că folderul cu toate capsele trebuie să ajungă în directorul include în raport cu locul de instalare.
install(DIRECTORY include/mylib DESTINATION include)Apoi informăm sistemul de construire că dorim să avem posibilitatea de a apela comanda find_package(Mylib) și să obținem obiectul Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Următoarea comandă trebuie înțeleasă astfel. Când în proiecte externe vom apela comanda find_package(Mylib 1.2.3 REQUIRED), și în același timp versiunea reală a bibliotecii instalate se dovedește a fi incompatibilă cu versiunea 1.2.3, CMake va genera automat o eroare. Adică nu va fi nevoie să urmărim versiunile manual.
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 prin sau proiectul nostru este un subproiect, adică este conectat la un alt proiect CMake prin comanda , nu trecem mai departe în ierarhie, iar scriptul în care sunt descrise comenzile pentru generarea și rularea testelor pur și simplu nu se execută.
if(NOT MYLIB_TESTING)
message(STATUS "Testarea proiectului Mylib este dezactivată")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib nu este testat în modul submodul")
else()
add_subdirectory(test)
endif()
Documentația nu va fi generată în cazul unui subproiect.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
În mod similar, nu va exista un sand box online pentru subproiect.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
În primul rând, găsim pachetul cu cadrul de testare dorit (înlocuiți cu preferatul vostru).
find_package(doctest 2.3.3 REQUIRED)Creăm fișierul executabil cu teste. De obicei, adaug doar fișierul care va conține funcția main.
add_executable(mylib-unit-tests test_main.cpp)Dar fișierele în care sunt descrise testele în sine le adaug mai târziu. Dar nu este obligatoriu să procedați astfel.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Conectăm dependențele. Rețineți că la binarul nostru am legat doar obiectivele CMake necesare, și nu am apelat comanda target_include_directories. Anteturile din cadrul de testare și din Mylib::mylib, precum și parametrii de compilare (în cazul nostru este standardul limbajului C++) au trecut împreună cu aceste obiective.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)În cele din urmă, creăm un obiectiv fals, "compilarea" căruia este echivalentă cu rularea testelor, și adăugăm acest obiectiv la compilarea implicită (acest lucru este gestionat de atributul , sau). Aceasta înseamnă că compilarea implicită inițiază rularea testelor, deci nu le vom uita niciodată.
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, deoarece acestea se referă mai mult la instrumentul de măsurare a acoperirii decât la CMake. Este important doar să notăm că, în urma rezultatelor, va fi creat un obiectiv , cu ajutorul căruia este convenabil să rulați măsurarea acoperirii.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Măsurarea acoperirii codului prin teste este activată")
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 "Pentru măsurarea acoperirii codului prin teste este necesar programul gcovr")
endif()
.
find_package(Doxygen)Apoi verificăm dacă utilizatorul a setat variabila cu limba. Dacă da, nu o modificăm, dacă nu, atunci o setăm pe română. Apoi configurăm fișierele sistemului Doxygen. Toate variabilele necesare, inclusiv limba, ajung acolo în timpul configurării (vezi. ).
După aceea, creăm o țintă , care va lansa generarea documentației. Deoarece generarea documentației nu este o necesitate stringentă în procesul de dezvoltare, ținta nu va fi inclusă în mod implicit, aceasta va trebui să fie lansată explicit.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Romanian)
endif()
message(STATUS "Documentația Doxygen va fi generată în ${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, conform API-ului serviciului , și o trimite. Ca răspuns, primim un link către sandboxul generat.
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 "Pentru crearea unui sandbox online este necesar un interpretator Python versiunea 3")
endif()
Acum să vedem cum putem folosi toate acestea.
Compilarea acestui proiect, la fel ca orice alt proiect pe sistemul de compilare CMake, constă în două etape:
cmake -S cale/catre/surse -B cale/catre/directoria/de/compilare [opțiuni ...]Dacă comanda de mai sus nu a funcționat din cauza unei versiuni vechi de CMake, încercați să omiteți
-S:cmake cale/catre/surse -B cale/catre/directoria/de/compilare [opțiuni ...]
.
cmake --build cale/catre/directoria/de/compilare [--target țintă].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [alte opțiuni ...]Activează ținta , cu ajutorul căruia se poate lansa măsurarea acoperirii codului cu teste.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [alte opțiuni ...]Oferă posibilitatea de a dezactiva compilarea testelor modulare și a obiectivului . În consecință, se dezactivează măsurarea acoperirii codului cu teste (vezi ).
De asemenea, testarea este dezactivată automat în cazul în care proiectul este adăugat la alt proiect ca subproiect prin comanda .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [alte opțiuni ...]Schimbă limba documentației generate de obiectiv la cea specificată. Lista limbilor disponibile poate fi găsită pe .
Implicit este activă limba rusă.
cmake --build calea/spre/directorul/de/compilare
cmake --build calea/spre/directorul/de/compilare --target allDacă obiectivul nu este specificat (ceea ce este echivalent cu obiectivul all), compilează tot ce este posibil și, de asemenea, cheamă obiectivul .
cmake --build calea/spre/directorul/de/compilare --target mylib-unit-testsCompilarea testelor modulare. Activat în mod implicit.
cmake --build calea/spre/directorul/de/compilare --target checkRulează testele modulare compilate (le compilează dacă nu sunt compilate). Activat în mod implicit.
Vezi de asemenea .
cmake --build calea/spre/directorul/de/compilare --target coverageAnalizează testele modulare rulante (le rulează dacă nu sunt rulante) pentru acoperirea codului cu teste folosind programul .
Rezultatul acoperirii va arăta aproximativ astfel:
------------------------------------------------------------------------------
Raport de Acoperire a Codului GCC
Director: /calea/catre/cmakecpptemplate/include/
------------------------------------------------------------------------------
Fișier Linii Exec Acoperire Lipsă
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------Obiectivul este disponibil doar atunci când opțiunea este activată .
Vezi de asemenea .
cmake --build calea/spre/directorul/de/compilare --target docÎncepe generarea documentației codului folosind sistemul .
cmake --build calea/spre/directorul/de/compilare --target wandboxRăspunsul de la serviciu arată aproximativ astfel:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Pentru aceasta se folosește serviciul . Nu știu cât de rezistente sunt serverele lor, dar cred că nu ar trebui să abuzezi de această oportunitate.
Compilarea proiectului în modul de depanare cu măsurarea acoperirii
cmake -S calea/spre/surse -B calea/spre/directorul/de/compilare -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build calea/spre/directorul/de/compilare --target coverage --parallel 16Instalarea proiectului fără o construcție și testare prealabilă
cmake -S calea/spre/surse -B calea/spre/directoria/constructiei -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=calea/spre/directoria/de/installare
cmake --build calea/spre/directoria/constructiei --target installConstruirea în modul de eliberare cu compilatorul specificat
cmake -S calea/spre/surse -B calea/spre/directoria/constructiei -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=calea/spre/directoria/unde/sunt/instalate/dependințele
cmake --build calea/spre/directoria/constructiei --parallel 4Generarea documentației în limba engleză
cmake -S calea/spre/surse -B calea/spre/directoria/constructiei -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build calea/spre/directoria/constructiei --target doc
3.13
De fapt, versiunea CMake 3.13 este necesară doar pentru a rula anumite comenzi de consolă, descrise în această documentație. Din punct de vedere al sintaxei scripturilor CMake, versiunea 3.8 este suficientă dacă generarea este apelată prin alte metode.
Biblioteca de testare
Testarea poate fi dezactivată (vedeți ).
Pentru a schimba limba în care va fi generată documentația, există opțiunea .
Interpretul limbajului de programare
Pentru generarea automată .
Cu ajutorul CMake și a câtorva instrumente bune, se poate asigura analiza statică cu un minim de eforturi.
Cppcheck
CMake include suport pentru un instrument de analiză statică .
Pentru aceasta, trebuie utilizată opțiunea :
cmake -S calea/spre/surse -B calea/spre/directoria/constructiei -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Icalea/spre/surse/include"După aceasta, analiza statică va fi rulată automat de fiecare dată în timpul compilării și recompilării surselor. Nu este nevoie să faceți nimic în plus.
Clang
Cu ajutorul minunatului instrument se poate de asemenea să se ruleze analiza statică rapid:
scan-build cmake -S calea/spre/surse -B calea/spre/directoria/constructiei -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build calea/spre/directoria/constructieiAici, spre deosebire de cazul cu Cppcheck, este necesar să se ruleze construcția de fiecare dată prin scan-build.
CMake este un sistem foarte puternic și flexibil, care permite implementarea funcționalității în orice stil și culoare. Și, deși sintaxa uneori lasă de dorit, nu este atât de înfricoșătoare pe cât pare. Folosiți sistemul de construire CMake pentru binele societății și în beneficiul sănătății.
→
Sursa: habr.com
