CMake è C++ sò fratelli per sempre

CMake è C++ sò fratelli per sempre

Durante u sviluppu, mi piace à cambià i compilatori, i modi di custruisce, e versioni di dependenza, eseguisce analisi statiche, misurà u rendiment, cullà a cobertura, generà documentazione, etc. È mi piace veramente CMake perchè mi permette di fà ciò chì vogliu.

Parechji scold CMake, è spessu meritatamente, ma s'è vo circate, ùn hè micca cusì male, ma ultimamente micca male in tuttu, è a direzzione di u sviluppu hè abbastanza pusitiva.

In questa nota, vogliu dicu quantu hè faciule d'urganizà una biblioteca di header C++ in un sistema CMake per uttene e seguenti funziunalità:

  1. assemblea;
  2. testi di l'Autorun;
  3. misurazione di a cobertura di codice;
  4. stallazione;
  5. autodocumentazione;
  6. generazione di sandbox in linea;
  7. Analisi statica.

Quale hè digià capitu i pros è si-make pò ghjustu Scaricate u mudellu di prughjettu è cumincià à aduprà.


Cuntenuti

  1. Prughjettu da l'internu
    1. Struttura di u prugettu
    2. File CMake principale (./CMakeLists.txt)
      1. L'infurmazione di u prughjettu
      2. Opzioni di prughjettu
      3. Opzioni di compilazione
      4. Scopu primariu
      5. rimarchevuli
      6. Testi
      7. Documentazione
      8. Sandbox in linea
    3. Script per i testi (test/CMakeLists.txt)
      1. Prucessioni
      2. Revestimentu
    4. Script di documentazione (doc/CMakeLists.txt)
    5. Script per sandbox in linea (in linea/CMakeLists.txt)
  2. prughjettu fora
    1. Assemblea
      1. Generazione
      2. Assemblea
    2. Opzioni
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. obiettivi di assemblea
      1. automaticamente
      2. mylib-test-unità
      3. cuntrolla
      4. teamă
      5. doc
      6. bacchetta
    4. esempi
  3. Ferramenti
  4. Analisi statica
  5. Afterword

Prughjettu da l'internu

Struttura di u prugettu

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

Questu principarmenti fucalizza nantu à cumu urganizà script CMake, cusì seranu analizati in detail. U restu di i schedari ponu esse vistu direttamente da qualcunu. nantu à a pagina di u prughjettu di u mudellu.

File CMake principale (./CMakeLists.txt)

L'infurmazione di u prughjettu

Prima di tuttu, avete bisognu di dumandà a versione desiderata di u sistema CMake. CMake si sviluppa, i cumandamenti di signatura cambianu, u cumpurtamentu in diverse cundizioni. Per chì CMake capisce immediatamente ciò chì vulemu da ellu, avemu bisognu di riparà immediatamente i nostri bisogni per questu.

cmake_minimum_required(VERSION 3.13)

Allora denotemu u nostru prughjettu, u so nome, a versione, e lingue usate, etc. команду project).

In questu casu, specificate a lingua CXX (chì significa C++) per chì CMake ùn si preoccupa micca di circà un compilatore C (per difettu, duie lingue sò incluse in CMake: C è C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Quì pudete verificà immediatamente se u nostru prughjettu hè inclusu in un altru prughjettu cum'è sottuprogettu. Questu aiuterà assai in u futuru.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Opzioni di prughjettu

Cunsideremu duie opzioni.

A prima opzione hè MYLIB_TESTING - per disattivà e teste di unità. Questu pò esse necessariu se simu sicuri chì tuttu hè in ordine cù e teste, è vulemu, per esempiu, solu per installà o pacchettu u nostru prughjettu. O u nostru prughjettu hè inclusu cum'è un subprogettu - in questu casu, l'utilizatori di u nostru prughjettu ùn hè micca interessatu à eseguisce i nostri testi. Ùn pruvate micca e dipendenze chì utilizate, nò?

option(MYLIB_TESTING "Включить модульное тестирование" ON)

Inoltre, faremu una opzione separata MYLIB_COVERAGE per a misurazione di a cobertura di codice cù testi, ma hà bisognu di strumenti supplementari, cusì vi tuccherà à attivà esplicitamente.

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

Opzioni di compilazione

Di sicuru, simu programatori positivi belli, cusì vulemu u livellu massimu di diagnostichi in tempu di compilazione da u compilatore. Ùn passa micca un solu mouse.

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
)

Disableremu ancu l'estensioni per cumplettà cumplettamente cù u standard di lingua C ++. Sò attivati ​​per difettu in CMake.

if(NOT CMAKE_CXX_EXTENSIONS)
    set(CMAKE_CXX_EXTENSIONS OFF)
endif()

Scopu primariu

A nostra biblioteca hè custituita solu da i fugliali di l'intestazione, chì significa chì ùn avemu micca alcuna pruduzzioni in forma di biblioteche statiche o dinamiche. Per d 'altra banda, per aduprà a nostra biblioteca da l'esternu, avete bisognu à stallà, avete bisognu di pudè truvà lu in u sistema è cunnette à u vostru prughjettu, è à u stessu tempu sti stessi intestazioni, ancu. cum'è, possibbilmente, alcune proprietà supplementari.

Per questu scopu, creemu una biblioteca di l'interfaccia.

add_library(mylib INTERFACE)

Lighemu l'intestazione à a nostra biblioteca di l'interfaccia.

L'usu mudernu, mudernu, ghjovanu di CMake significa chì l'intestazione, proprietà, etc. trasmesse à traversu una sola mira. Allora basta à dì target_link_libraries(target PRIVATE dependency), è tutti l'intestazione chì sò assuciati cù u target dependency, serà dispunibule per e fonti chì appartenenu à u mira target. È ùn avete micca bisognu [target_]include_directories. Questu serà dimustratu quì sottu quandu analizà CMake script per teste unità.

Hè ancu vale a pena attenti à u cusì chjamatu. выражения-генераторы: $<...>.

Stu cumandimu associa l'intestazione chì avemu bisognu cù a nostra biblioteca di l'interfaccia, è se a nostra biblioteca hè cunnessa à qualsiasi destinazione in a listessa ghjerarchia CMake, allora l'intestazione da u cartulare seranu assuciati cun ella. ${CMAKE_CURRENT_SOURCE_DIR}/include, è se a nostra biblioteca hè stallata nantu à u sistema è cunnessu à un altru prughjettu cù u cumandimu find_package, allura l'intestazione da u cartulare seranu assuciati cun ellu include relative à u cartulare di stallazione.

target_include_directories(mylib INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

Stabbilisce u standard di lingua. Di sicuru, l'ultime. À u listessu tempu, ùn avemu micca solu include u standard, ma ancu distribuisce à quelli chì anu da aduprà a nostra biblioteca. Questu hè ottenutu da avè a pruprietà stabilita avè una categuria INTERFACE (vede para. cumanda di target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Avemu un alias per a nostra biblioteca. È per a bellezza, serà in un "namespace" speciale. Questu serà utile quandu diversi moduli appariscenu in a nostra biblioteca, è andemu à cunnette indipindentamente l'un l'altru. Cum'è in Busta, per esempiu.

add_library(Mylib::mylib ALIAS mylib)

rimarchevuli

Stallà i nostri headers in u sistema. Tuttu hè simplice quì. Dicemu chì u cartulare cù tutti l'intestazione deve falà in u cartulare include riguardu à u locu di stallazione.

install(DIRECTORY include/mylib DESTINATION include)

Dopu, dicemu à u sistema di custruzzione chì vulemu esse capace di chjamà u cumandamentu in i prughjetti di terzu find_package(Mylib) è uttene un scopu Mylib::mylib.

install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)

L'incantazione seguente deve esse cumpresa cusì. Quandu in un prughjettu laterale chjamemu u cumandamentu find_package(Mylib 1.2.3 REQUIRED), è à u stessu tempu a versione vera di a biblioteca installata serà incompatibile cù a versione 1.2.3, CMake generà automaticamente un errore. Questu hè, ùn avete micca bisognu di mantene a traccia di e 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)

Testi

Se i testi sò esplicitamente disattivati ​​cù opzione corrispondente o u nostru prughjettu hè un sottuprogettu, vale à dì, hè cunnessu à un altru prughjettu CMake cù u cumandimu add_subdirectory, Ùn andemu più in a ghjerarchia, è u script, chì descrive i cumandamenti per generà è eseguisce testi, simpricimenti ùn principia micca.

if(NOT MYLIB_TESTING)
    message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
    add_subdirectory(test)
endif()

Documentazione

A documentazione ùn serà ancu generata in u casu di un sottuprogettu.

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

Sandbox in linea

In listessu modu, u sottuprogettu ùn hà micca ancu un sandbox in linea.

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

Script per i testi (test/CMakeLists.txt)

Prucessioni

Prima di tuttu, truvamu un pacchettu cù u quadru di prova desideratu (sustituitu cù u vostru preferitu).

find_package(doctest 2.3.3 REQUIRED)

Creemu u nostru schedariu eseguibile cù testi. Di solitu, direttamente à u binariu eseguibile, aghju aghjunghje solu u schedariu in quale a funzione serà main.

add_executable(mylib-unit-tests test_main.cpp)

È i schedarii in quale i testi stessi sò descritti, aghju aghjustatu dopu. Ma ùn hè micca necessariu di fà cusì.

target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)

Cunnettemu dipendenze. Per piacè nutate chì avemu attaccatu solu i miri CMake chì avemu bisognu à u nostru binariu, è ùn hà micca chjamatu u cumandamentu target_include_directories. Intestazioni da u quadru di prova è da u nostru Mylib::mylib, è ancu l'opzioni di custruzzione (in u nostru casu, u standard di lingua C ++) rastreu cù questi miri.

target_link_libraries(mylib-unit-tests
    PRIVATE
        Mylib::mylib
        doctest::doctest
)

Infine, creamu un target fittiziu chì "custruisce" hè equivalente à l'esecuzione di teste, è aghjunghje stu mira à a custruzione predeterminata (questu hè a rispunsabilità di l'attributu). ALL). Questu significa chì a custruzione predeterminata attivarà e teste per eseguisce, vale à dì chì ùn ci scurderemu mai di eseguisce.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Revestimentu

Dopu, attivate a misura di a cobertura di codice, se l'opzione currispondente hè stabilita. Ùn andaraghju micca in i dettagli, perchè sò più ligati à l'uttellu di misurazione di a cobertura cà à CMake. Hè impurtante solu di nutà chì i risultati creanu un scopu coverage, cù quale hè cunvenutu per inizià a misurazione di a cobertura.

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 di documentazione (doc/CMakeLists.txt)

Trovu Doxygen.

find_package(Doxygen)

Dopu, avemu verificatu se a variàbile cù a lingua hè stabilita da l'utilizatore. Sì iè, allora ùn avemu micca toccu, se no, allora pigliamu u russu. Allora cunfiguremu i schedarii di u sistema Doxygen. Tutte e variàbili necessarii, cumprese a lingua, ghjunghjenu quì durante u prucessu di cunfigurazione (vede. команду configure_file).

Allora criemu un scopu doc, chì cumincià à generà documentazione. Siccomu a generazione di documentazione ùn hè micca u più grande bisognu in u prucessu di sviluppu, u mira ùn serà micca inclusu per automaticamente, duverà esse eseguitu 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 in linea (in linea/CMakeLists.txt)

Quì truvamu u terzu Python è creanu un mira wandbox, chì genera una dumanda currispondente à l'API di serviziu bacchetta, è u manda. In risposta, vene un ligame à u sandbox finitu.

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

prughjettu fora

Avà guardemu cumu aduprà tuttu.

Assemblea

Custruì stu prughjettu, cum'è qualsiasi altru prughjettu nantu à u sistema di creazione CMake, hè custituitu di duie tappe:

Generazione

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

Se u cumandamentu sopra ùn hà micca travagliatu per via di una versione più vechja di CMake, pruvate d'omette -S:

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

Più nantu à l'opzioni.

Custruzzione di prughjettu

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

Più nantu à i scopi di l'assemblea.

Opzioni

MYLIB_COVERAGE

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

Include u target coverage, cù quale pudete inizià a misurazione di a cobertura di codice cù testi.

MYLIB_TESTING

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

Fornisce l'opzione per disattivà a creazione è a destinazione di test di unità check. In u risultatu, a misurazione di a cobertura di codice per teste hè disattivata (vede MYLIB_COVERAGE).

Inoltre, a prova hè automaticamente disattivata se u prughjettu hè cunnessu à un altru prughjettu cum'è sottuprogettu cù u cumandimu add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Cambia a lingua di a documentazione chì u target genera doc à quellu datu. Per una lista di lingue dispunibili, vede U situ web di Doxygen.

Russu hè attivatu per difettu.

obiettivi di assemblea

automaticamente

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

Se u mira ùn hè micca specificatu (chì hè equivalente à u mira all), raccoglie tuttu ciò chì hè pussibule, è chjama ancu u mira check.

mylib-test-unità

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

Cumpila i testi di unità. Abilitatu per difettu.

cuntrolla

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

Esegue teste di unità custruitu (custruisce se micca digià). Abilitatu per difettu.

Vede ancu mylib-unit-tests.

teamă

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

Analizeghja in esecuzione (esegue, se micca ancora) teste di unità per a copertura di codice per teste cù u prugramma gcovr.

L'output di copertura sarà simile à questu:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

U target hè dispunibule solu quandu l'opzione hè attivata. MYLIB_COVERAGE.

Vede ancu check.

doc

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

Accumincia a generazione di documentazione per u codice cù u sistema Dossigenu.

bacchetta

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

A risposta da u serviziu s'assumiglia à questu:

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

U serviziu hè usatu per questu. bacchetta. Ùn sò micca sapè cumu i servitori di gomma anu, ma pensu chì ùn deve micca abusu di sta funzione.

esempi

Custruisce u prugettu in modu di debug cù a misurazione di a cobertura

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16

Installazione di un prughjettu senza pre-custruzzione è teste

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

Custruite in modu di liberazione da u compilatore datu

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

Ferramenti

  1. CMake 3.13

    In fattu, a versione CMake 3.13 hè necessaria solu per eseguisce alcuni di i cumandamenti di cunsola descritti in questu aiutu. Da u puntu di vista di a sintassi di i script CMake, a versione 3.8 hè abbastanza se a generazione hè chjamata in altri modi.

  2. Pruvate a biblioteca dottu

    A prova pò esse disattivata (vede опцию MYLIB_TESTING).

  3. Dossigenu

    Per cambià a lingua in quale a documentazione serà generata, ci hè una opzione MYLIB_DOXYGEN_LANGUAGE.

  4. interprete PL pitone 3

    Per a generazione automatica sandbox in linea.

Analisi statica

Cù l'aiutu di CMake è un coppiu di boni strumenti, pudete furnisce l'analisi statica cù u minimu fidling.

Cppcheck

Supportu per u strumentu di analisi statica integrata in CMake Cppcheck.

Per fà questu, utilizate l'opzione CMAKE_CXX_CPPCHECK:

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"

Dopu questu, l'analisi statica serà automaticamente lanciata ogni volta durante a compilazione è a ricumpilazione di e fonti. Ùn ci hè nunda di più da fà.

chjappà

Cù un strumentu maravigliu scan-build Pudete ancu eseguisce l'analisi statica in pocu tempu:

scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директории

Quì, à u cuntrariu di u casu cù Cppcheck, hè necessariu di eseguisce a custruzione ogni volta scan-build.

Afterword

CMake hè un sistema assai putente è flessibile chì vi permette di implementà funziunalità per ogni gustu è culore. E, ancu s'è a sintassi qualchì volta lascià assai per esse desideratu, u diavulu ùn hè micca cusì terribili quant'ellu hè dipintu. Aduprate u sistema di creazione CMake per u benefiziu di a sucità è a salute.

Scaricate u mudellu di prughjettu

Source: www.habr.com

Add a comment