CMake e C++ sono fratelli per sempre

CMake e C++ sono fratelli per sempre

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à:

  1. Assemblea;
  2. Test di esecuzione automatica;
  3. Misurazione della copertura del codice;
  4. Installazione;
  5. Documentazione automatica;
  6. Generazione di sandbox online;
  7. Analisi statica.

Chi già comprende i vantaggi e C-make può farlo, semplicemente scarica il modello di progetto e iniziare a usarlo.


contenuto

  1. Progetto dall'interno
    1. Struttura del progetto
    2. File CMake principale (./CMakeLists.txt)
      1. Informazioni sul progetto
      2. Opzioni del progetto
      3. Opzioni di compilazione
      4. L'obiettivo principale
      5. Installazione
      6. Test
      7. Documentazione
      8. Sandbox in linea
    3. Script di prova (test/CMakeLists.txt)
      1. Test
      2. Покрытие
    4. Script per la documentazione (doc/CMakeLists.txt)
    5. Script per sandbox online (online/CMakeLists.txt)
  2. Progetto all'esterno
    1. montaggio
      1. generazione
      2. montaggio
    2. Opzioni
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Obiettivi dell'assemblea
      1. Per impostazione predefinita
      2. mylib-unit-test
      3. dai un'occhiata
      4. copertura
      5. doc
      6. scatola per bacchette
    4. Примеры
  3. Strumenti
  4. Analisi statica
  5. postfazione

Progetto dall'interno

Struttura del progetto

.
├── 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

Parleremo principalmente di come organizzare gli script CMake, quindi verranno discussi in dettaglio. Chiunque può visualizzare direttamente il resto dei file nella pagina del progetto modello.

File CMake principale (./CMakeLists.txt)

Informazioni sul progetto

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. команду project).

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)

Opzioni del progetto

Forniremo due opzioni.

La prima opzione è MYLIB_TESTING - 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 MYLIB_COVERAGE per misurare la copertura del codice tramite test, ma richiederà strumenti aggiuntivi, quindi dovrà essere abilitato esplicitamente.

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

Opzioni di compilazione

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

L'obiettivo principale

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 target_link_libraries(target PRIVATE dependency)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 Script CMake per test unitari.

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 find_package, 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. comando target_compile_features).

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. Come a Busta, per esempio.

add_library(Mylib::mylib ALIAS mylib)

Installazione

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)

Test

Se i test sono disabilitati esplicitamente utilizzando opzione corrispondente oppure il nostro progetto è un sottoprogetto, ovvero è collegato a un altro progetto CMake utilizzando il comando add_subdirectory, 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()

Documentazione

Anche nel caso di un sottoprogetto non verrà generata la documentazione.

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

Sandbox in linea

Allo stesso modo, anche il sottoprogetto non avrà una sandbox online.

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

Script di prova (test/CMakeLists.txt)

Test

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

Script per la documentazione (doc/CMakeLists.txt)

Trovato Doxygen.

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. команду configure_file).

Quindi creiamo un obiettivo doc, 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 ()

Script per sandbox online (online/CMakeLists.txt)

Qui troviamo il terzo Python e creiamo un target wandbox, che genera una richiesta corrispondente all'API del servizio Scatola delle bacchette, 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()

Progetto all'esterno

Ora diamo un'occhiata a come utilizzare tutto questo.

montaggio

La creazione di questo progetto, come qualsiasi altro progetto sul sistema di compilazione CMake, è composta da due fasi:

generazione

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

Se il comando precedente non ha funzionato a causa di una versione precedente di CMake, prova a ometterlo -S:

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

Ulteriori informazioni sulle opzioni.

Costruire il progetto

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

Maggiori informazioni sugli obiettivi dell'assemblea.

Opzioni

MYLIB_COVERAGE

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

Include obiettivo coverage, con cui puoi iniziare a misurare la copertura del codice tramite test.

MYLIB_TESTING

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

Offre la possibilità di disabilitare la creazione e la destinazione degli unit test check. Di conseguenza, la misurazione della copertura del codice tramite test viene disattivata (vedi. MYLIB_COVERAGE).

Il test viene disabilitato automaticamente anche se il progetto è connesso a un altro progetto come sottoprogetto utilizzando il comando add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Cambia la lingua della documentazione generata dalla destinazione doc a quello dato. Per un elenco delle lingue disponibili, vedere Sito web del sistema Doxygen.

Il russo è abilitato per impostazione predefinita.

Obiettivi dell'assemblea

Per impostazione predefinita

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

Se la destinazione non è specificata (che equivale a target all), raccoglie tutto ciò che può e chiama anche il bersaglio check.

mylib-unit-test

cmake --build path/to/build/directory --target mylib-unit-tests

Compila test unitari. Abilitato per impostazione predefinita.

dai un'occhiata

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

Esegue gli unit test raccolti (raccolti, se non già). Abilitato per impostazione predefinita.

Vedi anche mylib-unit-tests.

copertura

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

Analizza i test unitari in esecuzione (esegue, se non già) per la copertura del codice da parte dei test utilizzando il programma gcovr.

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 MYLIB_COVERAGE.

Vedi anche check.

doc

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

Avvia la generazione della documentazione del codice utilizzando il sistema Doxygen.

scatola per bacchette

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

La risposta del servizio è simile alla seguente:

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

Il servizio viene utilizzato per questo Scatola delle bacchette. 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 16

Installazione di un progetto senza assemblaggio e test preliminari

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

Compila 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 4

Generazione di documentazione in inglese

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

Strumenti

  1. CMake 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.

  2. Libreria di test dottore

    Il test può essere disabilitato (vedi опцию MYLIB_TESTING).

  3. Doxygen

    Per cambiare la lingua in cui verrà generata la documentazione, viene fornita un'opzione MYLIB_DOXYGEN_LANGUAGE.

  4. Interprete linguistico Python 3

    Per la generazione automatica sandbox online.

Analisi statica

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 Cppcheck.

Per fare ciò è necessario utilizzare l'opzione CMAKE_CXX_CPPCHECK:

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 scan-build 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.

postfazione

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.

Scarica il modello di progetto

Fonte: habr.com

Aggiungi un commento