CMake kaj C++ estas fratoj por ĉiam

CMake kaj C++ estas fratoj por ĉiam

Dum evoluo, mi ŝatas ŝanĝi kompililojn, konstrui reĝimojn, dependecajn versiojn, fari statikan analizon, mezuri rendimenton, kolekti kovradon, generi dokumentadon ktp. Kaj mi vere amas CMake ĉar ĝi permesas al mi fari kion ajn mi volas.

Multaj riproĉas CMake, kaj ofte merite, sed se vi rigardas, ĝi ne estas tiel malbona, sed lastatempe tute ne malbone, kaj la direkto de evoluo estas sufiĉe pozitiva.

En ĉi tiu noto, mi volas diri al vi, kiom facile estas organizi C++-kapobibliotekon en sistemo CMake por akiri la sekvan funkcion:

  1. Asembleo;
  2. Aŭtorunaj provoj;
  3. Mezurado de koda kovrado;
  4. instalado;
  5. Aŭtodokumentado;
  6. Interreta sandbox-generacio;
  7. Statika analizo.

Kiu jam komprenas la avantaĝojn kaj si-make povas nur elŝutu projekt-ŝablonon kaj komencu uzi ĝin.


Enhavo

  1. Projekto de interne
    1. Projekta strukturo
    2. Ĉefa CMake-dosiero (./CMakeLists.txt)
      1. Projektaj informoj
      2. Projektaj Opcioj
      3. Kompilaj Opcioj
      4. ĉefa celo
      5. fikso
      6. Provoj
      7. Dokumentado
      8. Enreta sablokesto
    3. Skripto por testoj (test/CMakeLists.txt)
      1. Testado
      2. Priraportado
    4. Dokumenta skripto (doc/CMakeLists.txt)
    5. Skripto por reta sablokesto (rete/CMakeLists.txt)
  2. projekto ekstere
    1. Asembleo
      1. Generacio
      2. Asembleo
    2. Opcioj
      1. MYLIB_KOVRADO
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. asembleaj celoj
      1. defaŭlte
      2. mylib-unuo-testoj
      3. kontroli
      4. kovrado
      5. dokumento
      6. vergoskatolo
    4. ekzemploj
  3. Iloj
  4. Statika Analizo
  5. Antaŭparolo

Projekto de interne

Projekta strukturo

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

Ĉi tio ĉefe koncentriĝos pri kiel organizi CMake-skriptojn, do ili estos detale analizitaj. La ceteraj dosieroj povas esti rigardataj rekte de iu ajn. sur la ŝablona projektpaĝo.

Ĉefa CMake-dosiero (./CMakeLists.txt)

Projektaj informoj

Antaŭ ĉio, vi devas peti la deziratan version de la sistemo CMake. CMake evoluas, komando subskriboj ŝanĝiĝas, konduto sub malsamaj kondiĉoj. Por ke CMake tuj komprenu, kion ni volas de ĝi, ni devas tuj ripari niajn postulojn por ĝi.

cmake_minimum_required(VERSION 3.13)

Tiam ni indikas nian projekton, ĝian nomon, version, uzatajn lingvojn ktp. команду project).

En ĉi tiu kazo, specifu la lingvon CXX (kio signifas C++) por ke CMake ne ĝenu serĉi C-kompililon (defaŭlte, du lingvoj estas inkluzivitaj en CMake: C kaj C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Ĉi tie vi povas tuj kontroli ĉu nia projekto estas inkluzivita en alia projekto kiel subprojekto. Ĉi tio multe helpos estonte.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Projektaj Opcioj

Ni konsideru du eblojn.

La unua opcio estas MYLIB_TESTING - malŝalti unutestojn. Ĉi tio povas esti necesa, se ni certas, ke ĉio estas en ordo kun la testoj, kaj ni volas, ekzemple, nur instali aŭ paki nian projekton. Aŭ nia projekto estas inkluzivita kiel subprojekto - en ĉi tiu kazo, la uzanto de nia projekto ne interesiĝas pri rulado de niaj testoj. Vi ne testas la dependecojn, kiujn vi uzas, ĉu?

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

Krome, ni faros apartan opcion MYLIB_COVERAGE por mezuri kodan kovradon per testoj, sed ĝi postulos pliajn ilojn, do vi devos ebligi ĝin eksplicite.

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

Kompilaj Opcioj

Kompreneble, ni estas bonegaj pozitivaj programistoj, do ni volas la maksimuman nivelon de kompiltempaj diagnozoj de la kompililo. Ne unu muso trairos.

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
)

Ni ankaŭ malŝaltos etendaĵojn por plene plenumi la normon de la lingvo C++. Ili estas ebligitaj defaŭlte en CMake.

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

ĉefa celo

Nia biblioteko konsistas nur el kapdosieroj, kio signifas, ke ni havas neniun eliron en la formo de statikaj aŭ dinamikaj bibliotekoj. Aliflanke, por uzi nian bibliotekon de ekstere, vi devas instali ĝin, vi devas povi trovi ĝin en la sistemo kaj konekti ĝin al via projekto, kaj samtempe ankaŭ ĉi tiujn titolojn. kiel, eble, iuj pliaj ecoj.

Tiucele ni kreas interfacan bibliotekon.

add_library(mylib INTERFACE)

Ni ligas la kapliniojn al nia interfaca biblioteko.

Moderna, laŭmoda, juneca uzo de CMake signifas, ke kaplinioj, propraĵoj ktp. transdonita tra ununura celo. Do sufiĉas diri target_link_libraries(target PRIVATE dependency), kaj ĉiuj kaplinioj kiuj estas asociitaj kun la celo dependency, estos disponebla por fontoj apartenantaj al la celo target. Kaj vi ne bezonas [target_]include_directories. Ĉi tio estos montrita sube dum analizado CMake-skripto por unutestoj.

Ankaŭ indas atenti la tn. выражения-генераторы: $<...>.

Ĉi tiu komando asocias la titolojn, kiujn ni bezonas kun nia interfaca biblioteko, kaj se nia biblioteko estas konektita al iu celo ene de la sama CMake-hierarkio, tiam la kaplinioj de la dosierujo estos asociitaj kun ĝi. ${CMAKE_CURRENT_SOURCE_DIR}/include, kaj se nia biblioteko estas instalita en la sistemo kaj konektita al alia projekto uzante la komandon find_package, tiam la kaplinioj de la dosierujo estos asociitaj kun ĝi include rilate al la instala dosierujo.

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

Agordu la lingvan normon. Kompreneble, la lasta. Samtempe ni ne nur inkluzivas la normon, sed ankaŭ distribuas ĝin al tiuj, kiuj uzos nian bibliotekon. Ĉi tio estas atingita havante la aran posedaĵon havas kategorion INTERFACE (vidu komando target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Ni ricevas kaŝnomon por nia biblioteko. Kaj por beleco, ĝi estos en speciala "nomspaco". Ĉi tio estos utila kiam malsamaj moduloj aperos en nia biblioteko, kaj ni iras por konekti ilin sendepende unu de la alia. Kiel en Busta, ekzemple.

add_library(Mylib::mylib ALIAS mylib)

fikso

Instalante niajn kapliniojn en la sistemon. Ĉio estas simpla ĉi tie. Ni diras, ke la dosierujo kun ĉiuj kaplinioj devas fali en la dosierujon include pri la loko de instalado.

install(DIRECTORY include/mylib DESTINATION include)

Poste, ni diras al la konstrusistemo, ke ni volas povi voki la komandon en triaj projektoj find_package(Mylib) kaj akiri celon Mylib::mylib.

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

La sekva sorĉo estas komprenenda tiel. Kiam en flanka projekto ni nomas la komandon find_package(Mylib 1.2.3 REQUIRED), kaj samtempe la vera versio de la instalita biblioteko estos nekongrua kun la versio 1.2.3, CMake aŭtomate generos eraron. Tio estas, vi ne bezonos konservi trakon de versioj permane.

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)

Provoj

Se testoj estas eksplicite malŝaltitaj kun responda opcio aŭ nia projekto estas subprojekto, tio estas, ĝi estas konektita al alia projekto CMake uzante la komandon add_subdirectory, ni ne iras pli malsupre en la hierarkio, kaj la skripto, kiu priskribas la komandojn por generi kaj ruli testojn, simple ne komenciĝas.

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

Dokumentado

Dokumentado ankaŭ ne estos generita en la kazo de subprojekto.

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

Enreta sablokesto

Simile, la subprojekto ankaŭ ne havos retan sablokeston.

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

Skripto por testoj (test/CMakeLists.txt)

Testado

Antaŭ ĉio, ni trovas pakaĵon kun la dezirata testa kadro (anstataŭigu ĝin per via plej ŝatata).

find_package(doctest 2.3.3 REQUIRED)

Ni kreas nian ruleblan dosieron kun testoj. Kutime, rekte al la plenumebla binaro, mi aldonas nur la dosieron en kiu estos la funkcio main.

add_executable(mylib-unit-tests test_main.cpp)

Kaj mi aldonas la dosierojn en kiuj la testoj mem estas priskribitaj poste. Sed ne necesas fari tion.

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

Ni konektas dependecojn. Bonvolu noti, ke ni alfiksis nur la CMake-celojn, kiujn ni bezonis al nia binaro, kaj ne vokis la komandon target_include_directories. Titoloj el la testa kadro kaj el nia Mylib::mylib, same kiel konstruopcioj (en nia kazo, la C++-lingva normo) rampis kune kun ĉi tiuj celoj.

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

Fine, ni kreas imitan celon, kies "konstruo" estas ekvivalenta al rulado de testoj, kaj aldonas ĉi tiun celon al la defaŭlta konstruo (ĉi tio estas la respondeco de la atributo ALL). Ĉi tio signifas, ke la defaŭlta konstruo ekfunkciigos la testojn, kio signifas, ke ni neniam forgesos ruli ilin.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Priraportado

Poste, ŝaltu mezuradon de koda kovrado, se la responda opcio estas agordita. Mi ne eniros la detalojn, ĉar ili estas pli rilataj al la priraporta mezura ilo ol al CMake. Gravas nur rimarki, ke la rezultoj kreos celon coverage, per kiu estas oportune komenci mezuri kovradon.

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

Dokumenta skripto (doc/CMakeLists.txt)

Trovita Doksigeno.

find_package(Doxygen)

Poste, ni kontrolas ĉu la variablo kun la lingvo estas agordita de la uzanto. Se jes, tiam ni ne tuŝas ĝin, se ne, tiam ni prenas la rusan. Poste ni agordas la Doxygen-sistemajn dosierojn. Ĉiuj necesaj variabloj, inkluzive de la lingvo, atingas tie dum la agorda procezo (vidu. команду configure_file).

Tiam ni kreas celon doc, kiu komencos generi dokumentadon. Ĉar generi dokumentadon ne estas la plej granda bezono en la evoluprocezo, la celo ne estos inkluzivita defaŭlte, ĝi devos esti eksplicite funkciigata.

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

Skripto por reta sablokesto (rete/CMakeLists.txt)

Ĉi tie ni trovas la trian Python kaj kreas celon wandbox, kiu generas peton respondan al la servo API Vergkesto, kaj sendas ĝin. En respondo, ligo al la finita sablokesto venas.

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

projekto ekstere

Nun ni rigardu kiel uzi ĉion.

Asembleo

Konstrui ĉi tiun projekton, kiel ajnan alian projekton sur la konstrusistemo CMake, konsistas el du stadioj:

Generacio

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

Se la supra komando ne funkciis pro malnova versio de CMake, provu ellasi -S:

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

Pli pri ebloj.

Projekto konstruo

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

Pli pri asembleaj celoj.

Opcioj

MYLIB_KOVRADO

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

Inkluzivas celon coverage, per kiu vi povas komenci mezuri kodan kovradon per testoj.

MYLIB_TESTING

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

Provizas la eblon malŝalti unutestan konstruon kaj celon check. Kiel rezulto, la mezurado de koda kovrado per testoj estas malŝaltita (vidu MYLIB_COVERAGE).

Ankaŭ, testado estas aŭtomate malŝaltita se la projekto estas konektita al alia projekto kiel subprojekto uzante la komandon add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Ŝanĝas la lingvon de la dokumentado, kiun la celo generas doc al la donita. Por listo de disponeblaj lingvoj, vidu Doxygen retejo.

La rusa estas ebligita defaŭlte.

asembleaj celoj

defaŭlte

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

Se la celo ne estas precizigita (kio estas ekvivalenta al la celo all), kolektas ĉion eblan, kaj ankaŭ nomas la celon check.

mylib-unuo-testoj

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

Kompilas unutestojn. Ebligita defaŭlte.

kontroli

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

Kuras konstruitajn (konstruas se ne jam) unutestojn. Ebligita defaŭlte.

Vidu ankaŭ mylib-unit-tests.

kovrado

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

Analizas kurantajn (funkcias, se ankoraŭ ne) unutestojn por koda kovrado per testoj uzante la programon gcovr.

La priraporta eligo aspektos kiel ĉi tio:

------------------------------------------------------------------------------
                           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 celo disponeblas nur kiam la opcio estas ebligita. MYLIB_COVERAGE.

Vidu ankaŭ check.

dokumento

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

Komencas la generacion de dokumentado por la kodo uzante la sistemon Doksigeno.

vergoskatolo

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

La respondo de la servo aspektas kiel ĉi tio:

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

La servo estas uzata por ĉi tio. Vergkesto. Mi ne scias kiel kaŭĉukaj serviloj ili havas, sed mi pensas, ke vi ne devas misuzi ĉi tiun funkcion.

ekzemploj

Konstruu projekton en sencimiga reĝimo kun mezurado de kovrado

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

Instali projekton sen antaŭkonstruado kaj testado

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

Konstruu en liberiga reĝimo de la donita kompililo

cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4

Generado de dokumentado en la angla

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

Iloj

  1. CMake 3.13

    Fakte, CMake-versio 3.13 estas bezonata nur por ruli iujn el la konzolaj komandoj priskribitaj en ĉi tiu helpo. El la vidpunkto de la sintakso de CMake-skriptoj, la versio 3.8 sufiĉas se la generacio estas vokita alimaniere.

  2. Testa Biblioteko doktesto

    Testado povas esti malŝaltita (vidu опцию MYLIB_TESTING).

  3. Doksigeno

    Por ŝanĝi la lingvon en kiu la dokumentaro estos generita, ekzistas opcio MYLIB_DOXYGEN_LANGUAGE.

  4. PL interpretisto python 3

    Por aŭtomata generacio interretaj sablokestoj.

Statika Analizo

Kun la helpo de CMake kaj kelkaj bonaj iloj, vi povas provizi statikan analizon kun minimuma ludado.

Cppcheck

Subteno por senmova analizilo konstruita en CMake Cppcheck.

Por fari tion, uzu la opcion CMAKE_CXX_CPPCHECK:

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

Post tio, senmova analizo estos aŭtomate lanĉita ĉiufoje dum kompilo kaj rekompilo de fontoj. Nenio kroma devas esti farita.

clang

Kun mirinda ilo scan-build Vi ankaŭ povas ruli statikan analizon en neniu tempo:

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

Ĉi tie, male al la kazo kun Cppcheck, necesas ruli la konstruon ĉiufoje scan-build.

Antaŭparolo

CMake estas tre potenca kaj fleksebla sistemo, kiu ebligas al vi efektivigi funkciecon por ĉiu gusto kaj koloro. Kaj, kvankam la sintakso foje lasas multon por deziri, la diablo ankoraŭ ne estas tiel terura kiel li estas pentrita. Uzu la CMake konstrusistemon por la avantaĝo de socio kaj sano.

Elŝutu projekt-ŝablonon

fonto: www.habr.com

Aldoni komenton