CMake și C++ sunt frați pentru totdeauna

CMake și C++ sunt frați pentru totdeauna

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

  1. Asamblare;
  2. teste de rulare automată;
  3. Măsurarea acoperirii codului;
  4. Instalare;
  5. Auto-documentare;
  6. Generare online sandbox;
  7. Analiza statica.

Oricine înțelege deja avantajele și C-make poate pur și simplu descărcați șablonul de proiect și începe să-l folosești.


Conținut

  1. Proiect din interior
    1. Structura proiectului
    2. Fișierul principal CMake (./CMakeLists.txt)
      1. Informatii despre proiect
      2. Opțiuni de proiect
      3. Opțiuni de compilare
      4. Scopul principal
      5. Instalare
      6. teste
      7. Documentație
      8. Cutie cu nisip online
    3. Scriptul de testare (test/CMakeLists.txt)
      1. Testarea
      2. Acoperire
    4. Script pentru documentație (doc/CMakeLists.txt)
    5. Script pentru sandbox online (online/CMakeLists.txt)
  2. Proiect afara
    1. asamblare
      1. generație
      2. asamblare
    2. Opțiuni
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Țintele de asamblare
      1. În mod implicit
      2. mylib-unit-tests
      3. verifica
      4. acoperire
      5. medic
      6. cutie de baghete
    4. exemple
  3. Instrumente
  4. Analiza statica
  5. postfață

Proiect din interior

Structura proiectului

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

Vom 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 pe pagina de proiect șablon.

Fișierul principal CMake (./CMakeLists.txt)

Informatii despre proiect

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

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

Opțiuni de proiect

Vă vom oferi două opțiuni.

Prima opțiune este MYLIB_TESTING — 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ă MYLIB_COVERAGE 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)

Opțiuni de compilare

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

Scopul principal

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 target_link_libraries(target PRIVATE dependency), ș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ă Scriptul CMake pentru testele unitare.

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

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. Ca la Busta, de exemplu.

add_library(Mylib::mylib ALIAS mylib)

Instalare

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)

teste

Dacă testele sunt dezactivate explicit folosind opțiunea corespunzătoare sau proiectul nostru este un subproiect, adică este conectat la un alt proiect CMake folosind comanda add_subdirectory, 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()

Documentație

Nici documentația nu va fi generată în cazul unui subproiect.

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

Cutie cu nisip online

La fel, subproiectul nu va avea nici un sandbox online.

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

Scriptul de testare (test/CMakeLists.txt)

Testarea

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

Acoperire

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

Script pentru documentație (doc/CMakeLists.txt)

Am găsit Doxygen.

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

Apoi ne creăm un obiectiv doc, 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 ()

Script pentru sandbox online (online/CMakeLists.txt)

Aici găsim al treilea Python și creăm o țintă wandbox, care generează o solicitare corespunzătoare API-ului serviciului Cutie cu baghete, ș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()

Proiect afara

Acum să vedem cum să folosim toate acestea.

asamblare

Construirea acestui proiect, ca orice alt proiect pe sistemul de construcție CMake, constă în două etape:

generație

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 путь/к/сборочной/директории [опции ...]

Mai multe despre opțiuni.

Construirea proiectului

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

Mai multe despre obiectivele de asamblare.

Opțiuni

MYLIB_COVERAGE

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

Include ținta coverage, cu care puteți începe măsurarea acoperirii codului prin teste.

MYLIB_TESTING

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

Oferă posibilitatea de a dezactiva construirea și țintirea testului unitar check. Ca urmare, măsurarea acoperirii codului prin teste este dezactivată (vezi. MYLIB_COVERAGE).

Testarea este, de asemenea, dezactivată automat dacă proiectul este conectat la un alt proiect ca subproiect folosind comanda add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Schimbă limba documentației pe care o generează ținta doc la cel dat. Pentru o listă a limbilor disponibile, consultați Site-ul web al sistemului Doxygen.

Rusă este activată implicit.

Țintele de asamblare

În mod implicit

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

Dacă ținta nu este specificată (care este echivalentă cu ținta all), colectează tot ce poate și, de asemenea, cheamă ținta check.

mylib-unit-tests

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

Compilează teste unitare. Activat implicit.

verifica

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

Rulează testele unitare colectate (colectate, dacă nu deja). Activat implicit.

Vezi de asemenea mylib-unit-tests.

acoperire

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

Analizează testele unitare care rulează (rulează, dacă nu deja) pentru acoperirea codului prin teste folosind programul gcovr.

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

Vezi de asemenea check.

medic

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

Începe generarea documentației de cod folosind sistemul Oxigen.

cutie de baghete

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

Răspunsul serviciului arată cam așa:

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

Serviciul este folosit pentru aceasta Cutie cu baghete. Nu știu cât de flexibile sunt serverele lor, dar cred că această oportunitate nu trebuie abuzată.

exemple

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 16

Instalarea unui proiect fără asamblare și testare preliminară

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

Construiț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 4

Generarea documentatiei in limba engleza

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

Instrumente

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

  2. Biblioteca de testare doctest

    Testarea poate fi dezactivată (vezi опцию MYLIB_TESTING).

  3. Oxigen

    Pentru a schimba limba în care va fi generată documentația, este oferită o opțiune MYLIB_DOXYGEN_LANGUAGE.

  4. Interpret de limbă Python 3

    Pentru generare automată cutii cu nisip online.

Analiza statica

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

Pentru a face acest lucru, trebuie să utilizați opțiunea CMAKE_CXX_CPPCHECK:

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

postfață

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.

Descărcați șablonul de proiect

Sursa: www.habr.com

Adauga un comentariu