
Durante lo sviluppo, mi piace cambiare compilatori, modalità di compilazione, versioni delle dipendenze, eseguire analisi statiche, misurare le prestazioni, raccogliere copertura, generare documentazione, ecc. E adoro CMake perché mi permette di fare tutto ciò che voglio.
Molte persone criticano CMake, e spesso meritatamente, ma se lo guardi, non tutto è poi così male, e recentemente non è affatto male, e la direzione dello sviluppo è abbastanza positiva.
In questa nota voglio raccontarti come organizzare semplicemente una libreria di header in C++ nel sistema CMake per ottenere le seguenti funzionalità:
- Assemblea;
- Test di esecuzione automatica;
- Misurazione della copertura del codice;
- Installazione;
- Documentazione automatica;
- Generazione di sandbox online;
- Analisi statica.
Chi già comprende i vantaggi e C-make può farlo, semplicemente e iniziare a usarlo.
contenuto
.
├── 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.cppParleremo principalmente di come organizzare gli script CMake, quindi verranno discussi in dettaglio. Chiunque può visualizzare direttamente il resto dei file .
Prima di tutto, devi richiedere la versione richiesta del sistema CMake. CMake si sta evolvendo, le firme dei comandi e il comportamento in diverse condizioni stanno cambiando. Affinché CMake possa capire immediatamente cosa vogliamo da esso, dobbiamo registrare immediatamente i nostri requisiti.
cmake_minimum_required(VERSION 3.13)Quindi designeremo il nostro progetto, il suo nome, versione, lingue utilizzate, ecc. (vedi. ).
In questo caso indichiamo la lingua CXX (e questo significa C++) in modo che CMake non si sforzi di cercare un compilatore del linguaggio C (per impostazione predefinita, CMake include due linguaggi: C e C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)Qui puoi verificare immediatamente se il nostro progetto è incluso in un altro progetto come sottoprogetto. Questo aiuterà molto in futuro.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Forniremo due opzioni.
La prima opzione è - per disabilitare i test unitari. Ciò può essere necessario se siamo sicuri che tutto sia in ordine con i test, ma vogliamo solo, ad esempio, installare o pacchettizzare il nostro progetto. Oppure il nostro progetto è incluso come sottoprogetto: in questo caso l'utente del nostro progetto non è interessato a eseguire i nostri test. Non testerai le dipendenze che usi, vero?
option(MYLIB_TESTING "Включить модульное тестирование" ON)Inoltre, faremo un'opzione separata per misurare la copertura del codice tramite test, ma richiederà strumenti aggiuntivi, quindi dovrà essere abilitato esplicitamente.
option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)
Naturalmente, siamo dei bravi programmatori, quindi vogliamo il massimo livello di diagnostica in fase di compilazione dal compilatore. Non passerà nemmeno un topo.
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
)Disabiliteremo anche le estensioni per rispettare pienamente lo standard del linguaggio C++. Sono abilitati per impostazione predefinita in CMake.
if(NOT CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
La nostra libreria è composta solo da file header, il che significa che non abbiamo alcun scarico sotto forma di librerie statiche o dinamiche. D'altra parte, per poter utilizzare la nostra libreria esternamente, è necessario che sia installata, che sia rilevabile nel sistema e connessa al progetto, e allo stesso tempo queste stesse intestazioni, ed eventualmente alcune aggiuntive, sono collegati alle sue proprietà.
A questo scopo creiamo una libreria di interfacce.
add_library(mylib INTERFACE)Associamo le intestazioni alla nostra libreria di interfacce.
L'uso moderno, alla moda e giovanile di CMake implica che intestazioni, proprietà, ecc. trasmesso attraverso un unico bersaglio. Quindi basti dirlo e tutte le intestazioni associate alla destinazione dependency, sarà disponibile per le origini appartenenti alla destinazione target. E non ne hai bisogno [target_]include_directories. Ciò sarà dimostrato di seguito nell'analisi .
Vale anche la pena prestare attenzione al cosiddetto. .
Questo comando associa le intestazioni di cui abbiamo bisogno con la nostra libreria di interfaccia e se la nostra libreria è connessa a qualsiasi destinazione all'interno della stessa gerarchia CMake, le intestazioni della directory verranno associate ad essa ${CMAKE_CURRENT_SOURCE_DIR}/includee se la nostra libreria è installata sul sistema e connessa a un altro progetto utilizzando il comando , le intestazioni della directory verranno associate ad essa include relativo alla directory di installazione.
target_include_directories(mylib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)Stabiliamo uno standard linguistico. Ovviamente proprio l'ultimo. Allo stesso tempo, non solo includiamo lo standard, ma lo estendiamo anche a coloro che utilizzeranno la nostra libreria. Ciò si ottiene perché la proprietà impostata ha una categoria INTERFACE (Vedere. ).
target_compile_features(mylib INTERFACE cxx_std_17)Creiamo un alias per la nostra libreria. Inoltre, per bellezza, sarà in uno speciale “namespace”. Ciò sarà utile quando nella nostra libreria compaiono moduli diversi e andiamo a collegarli indipendentemente l'uno dall'altro. .
add_library(Mylib::mylib ALIAS mylib)
Installazione delle nostre intestazioni nel sistema. Tutto è semplice qui. Diciamo che la cartella con tutte le intestazioni dovrebbe andare nella directory include rispetto al luogo di installazione.
install(DIRECTORY include/mylib DESTINATION include)Successivamente informiamo il sistema di compilazione che vogliamo poter richiamare il comando in progetti di terze parti find_package(Mylib) e ottenere un obiettivo Mylib::mylib.
install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)Il prossimo incantesimo dovrebbe essere interpretato in questo modo. Quando in un progetto di terze parti chiamiamo il comando find_package(Mylib 1.2.3 REQUIRED)e la versione reale della libreria installata sarà incompatibile con la versione 1.2.3CMake genererà automaticamente un errore. Cioè, non sarà necessario tenere traccia delle versioni manualmente.
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)
Se i test sono disabilitati esplicitamente utilizzando oppure il nostro progetto è un sottoprogetto, ovvero è collegato a un altro progetto CMake utilizzando il comando , non ci spostiamo ulteriormente lungo la gerarchia e lo script, che descrive i comandi per generare ed eseguire i test, semplicemente non viene eseguito.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Anche nel caso di un sottoprogetto non verrà generata la documentazione.
if(NOT IS_SUBPROJECT)
add_subdirectory(doc)
endif()
Allo stesso modo, anche il sottoprogetto non avrà una sandbox online.
if(NOT IS_SUBPROJECT)
add_subdirectory(online)
endif()
Innanzitutto troviamo un pacchetto con il framework di test richiesto (sostituiscilo con quello preferito).
find_package(doctest 2.3.3 REQUIRED)Creiamo il nostro file eseguibile con i test. Di solito aggiungo direttamente al binario eseguibile solo il file che conterrà la funzione main.
add_executable(mylib-unit-tests test_main.cpp)E aggiungo file in cui i test stessi vengono descritti in seguito. Ma non devi farlo.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)Colleghiamo le dipendenze. Tieni presente che abbiamo collegato solo le destinazioni CMake di cui avevamo bisogno al nostro binario e non abbiamo chiamato il comando target_include_directories. Titoli dal framework di test e dal nostro Mylib::mylib, così come i parametri di creazione (nel nostro caso, questo è lo standard del linguaggio C++) sono stati raggiunti insieme a questi obiettivi.
target_link_libraries(mylib-unit-tests
PRIVATE
Mylib::mylib
doctest::doctest
)Infine, creiamo un target fittizio, la cui "build" equivale all'esecuzione di test, e aggiungiamo questo target alla build predefinita (l'attributo è responsabile di questo ALL). Ciò significa che la build predefinita attiva l'esecuzione dei test, il che significa che non dimenticheremo mai di eseguirli.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Successivamente, abilitiamo la misurazione della copertura del codice se viene specificata l'opzione appropriata. Non entrerò nei dettagli, perché si riferiscono più a uno strumento per misurare la copertura che a CMake. È importante solo notare che in base ai risultati verrà creato un obiettivo , con il quale conviene iniziare a misurare la copertura.
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)Successivamente, controlliamo se l'utente ha impostato la variabile della lingua. Se sì, non lo tocchiamo, altrimenti prendiamo il russo. Quindi configuriamo i file di sistema Doxygen. Tutte le variabili necessarie, inclusa la lingua, vanno lì durante il processo di configurazione (vedi. ).
Quindi creiamo un obiettivo , che inizierà a generare la documentazione. Poiché la generazione di documentazione non è la necessità principale nel processo di sviluppo, il target non sarà abilitato per impostazione predefinita; dovrà essere lanciato esplicitamente.
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 ()
Qui troviamo il terzo Python e creiamo un target , che genera una richiesta corrispondente all'API del servizio , e lo manda via. La risposta arriva con un collegamento al sandbox finito.
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()
Ora diamo un'occhiata a come utilizzare tutto questo.
La creazione di questo progetto, come qualsiasi altro progetto sul sistema di compilazione CMake, è composta da due fasi:
cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]Se il comando precedente non ha funzionato a causa di una versione precedente di CMake, prova a ometterlo
-S:cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]
.
cmake --build путь/к/сборочной/директории [--target target].
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]Include obiettivo , con cui puoi iniziare a misurare la copertura del codice tramite test.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]Offre la possibilità di disabilitare la creazione e la destinazione degli unit test . Di conseguenza, la misurazione della copertura del codice tramite test viene disattivata (vedi. ).
Il test viene disabilitato automaticamente anche se il progetto è connesso a un altro progetto come sottoprogetto utilizzando il comando .
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]Cambia la lingua della documentazione generata dalla destinazione a quello dato. Per un elenco delle lingue disponibili, vedere .
Il russo è abilitato per impostazione predefinita.
cmake --build path/to/build/directory
cmake --build path/to/build/directory --target allSe la destinazione non è specificata (che equivale a target all), raccoglie tutto ciò che può e chiama anche il bersaglio .
cmake --build path/to/build/directory --target mylib-unit-testsCompila test unitari. Abilitato per impostazione predefinita.
cmake --build путь/к/сборочной/директории --target checkEsegue gli unit test raccolti (raccolti, se non già). Abilitato per impostazione predefinita.
Vedi anche .
cmake --build путь/к/сборочной/директории --target coverageAnalizza i test unitari in esecuzione (esegue, se non già) per la copertura del codice da parte dei test utilizzando il programma .
Lo scarico del rivestimento sarà simile a questo:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp 2 2 100%
------------------------------------------------------------------------------
TOTAL 2 2 100%
------------------------------------------------------------------------------La destinazione è disponibile solo quando l'opzione è abilitata .
Vedi anche .
cmake --build путь/к/сборочной/директории --target docAvvia la generazione della documentazione del codice utilizzando il sistema .
cmake --build путь/к/сборочной/директории --target wandboxLa risposta del servizio è simile alla seguente:
{
"permlink" : "QElvxuMzHgL9fqci",
"status" : "0",
"url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}Il servizio viene utilizzato per questo . Non so quanto siano flessibili i loro server, ma penso che non si debba abusare di questa opportunità.
Costruisci il progetto in modalità debug con misurazione della copertura
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16Installazione di un progetto senza assemblaggio e test preliminari
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target installCompila in modalità di rilascio con un determinato compilatore
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4Generazione di documentazione in inglese
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc
3.13
Infatti, CMake versione 3.13 è necessaria solo per eseguire alcuni dei comandi della console descritti in questa guida. Dal punto di vista della sintassi degli script CMake è sufficiente la versione 3.8 se la generazione viene richiamata in altri modi.
Libreria di test
Il test può essere disabilitato (vedi ).
Per cambiare la lingua in cui verrà generata la documentazione, viene fornita un'opzione .
Interprete linguistico
Per la generazione automatica .
Con CMake e un paio di buoni strumenti puoi fornire analisi statiche con il minimo sforzo.
Cppcheck
CMake dispone del supporto integrato per uno strumento di analisi statica .
Per fare ciò è necessario utilizzare l'opzione :
cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"Successivamente, l'analisi statica verrà avviata automaticamente ogni volta che il sorgente viene compilato e ricompilato. Non è necessario fare nulla in più.
clangore
Con l'aiuto di uno strumento meraviglioso Puoi anche eseguire l'analisi statica in pochissimo tempo:
scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директорииQui, a differenza del caso con Cppcheck, è necessario eseguire la compilazione ogni volta scan-build.
CMake è un sistema molto potente e flessibile che ti consente di implementare funzionalità per ogni gusto e colore. E, sebbene la sintassi a volte lasci molto a desiderare, il diavolo non è ancora così terribile come viene dipinto. Utilizza il sistema di compilazione CMake a beneficio della società e della salute.
→
Fonte: habr.com
