CMake dhe C++ janë vëllezër përgjithmonë

CMake dhe C++ janë vëllezër përgjithmonë

Gjatë zhvillimit, më pëlqen të ndryshoj përpiluesit, të ndërtoj mënyrat, versionet e varësisë, të kryej analiza statike, të masë performancën, të mbledh mbulimin, të gjeneroj dokumentacion, etj. Dhe unë me të vërtetë e dua CMake sepse më lejon të bëj gjithçka që dua.

Shumë njerëz e kritikojnë CMake, dhe shpesh kështu meritojnë, por nëse e shikoni, jo gjithçka është aq e keqe, dhe kohët e fundit aspak keq, dhe drejtimi i zhvillimit është mjaft pozitiv.

Në këtë shënim, unë dua t'ju tregoj se si thjesht të organizoni një bibliotekë me kokë në C++ në sistemin CMake për të marrë funksionalitetin e mëposhtëm:

  1. Kuvendi;
  2. Testet automatike;
  3. Matja e mbulimit të kodit;
  4. Instalimi;
  5. Auto-dokumentacion;
  6. Gjenerimi në internet i sandbox;
  7. Analiza statike.

Kushdo që tashmë i kupton avantazhet dhe C-make mundet thjesht shkarko shabllonin e projektit dhe filloni ta përdorni.


Përmbajtje

  1. Projekt nga brenda
    1. Struktura e projektit
    2. Skedari kryesor CMake (./CMakeLists.txt)
      1. Informacioni i projektit
      2. Opsionet e projektit
      3. Opsionet e përpilimit
      4. Qëllimi kryesor
      5. Instalim
      6. testet
      7. Records
      8. Sandbox në internet
    3. Skripti testues (test/CMakeLists.txt)
      1. Testimi
      2. Mbulim
    4. Skript për dokumentacion (doc/CMakeLists.txt)
    5. Skript për sandbox në internet (online/CMakeLists.txt)
  2. Projekti jashtë
    1. asamble
      1. brez
      2. asamble
    2. Options
      1. MYLIB_KOVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Objektivat e Kuvendit
      1. By default
      2. mylib-unit-tests
      3. shikoni
      4. mbulim
      5. doktor
      6. kuti shkopi
    4. shembuj
  3. Mjete
  4. Analiza statike
  5. pasthënje

Projekt nga brenda

Struktura e projektit

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

Ne do të flasim kryesisht për mënyrën e organizimit të skripteve CMake, kështu që ato do të diskutohen në detaje. Çdokush mund të shikojë drejtpërdrejt pjesën tjetër të skedarëve në faqen e projektit shabllon.

Skedari kryesor CMake (./CMakeLists.txt)

Informacioni i projektit

Para së gjithash, duhet të kërkoni versionin e kërkuar të sistemit CMake. CMake po evoluon, nënshkrimet e komandës dhe sjellja në kushte të ndryshme po ndryshojnë. Në mënyrë që CMake të kuptojë menjëherë se çfarë duam prej tij, ne duhet të regjistrojmë menjëherë kërkesat tona për të.

cmake_minimum_required(VERSION 3.13)

Pastaj ne do të caktojmë projektin tonë, emrin e tij, versionin, gjuhët e përdorura, etj. (shih. команду project).

Në këtë rast ne tregojmë gjuhën CXX (dhe kjo do të thotë C++) në mënyrë që CMake të mos sforcohet dhe të kërkojë një përpilues të gjuhës C (si parazgjedhje, CMake përfshin dy gjuhë: C dhe C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Këtu mund të kontrolloni menjëherë nëse projekti ynë është përfshirë në një projekt tjetër si nënprojekt. Kjo do të ndihmojë shumë në të ardhmen.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Opsionet e projektit

Ne do të ofrojmë dy opsione.

Opsioni i parë është MYLIB_TESTING — për të çaktivizuar testet e njësisë. Kjo mund të jetë e nevojshme nëse jemi të sigurt se gjithçka është në rregull me testet, por ne duam vetëm, për shembull, të instalojmë ose paketojmë projektin tonë. Ose projekti ynë përfshihet si nënprojekt - në këtë rast, përdoruesi i projektit tonë nuk është i interesuar të ekzekutojë testet tona. Ju nuk i testoni varësitë që përdorni, apo jo?

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

Përveç kësaj, ne do të bëjmë një opsion të veçantë MYLIB_COVERAGE për matjen e mbulimit të kodit me teste, por do të kërkojë mjete shtesë, kështu që do të duhet të aktivizohet në mënyrë eksplicite.

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

Opsionet e përpilimit

Natyrisht, ne jemi programues të lezetshëm plus, kështu që duam nivelin maksimal të diagnostikimit në kohën e përpilimit nga përpiluesi. Asnjë mi i vetëm nuk do të rrëshqasë.

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
)

Ne do të çaktivizojmë gjithashtu shtesat në mënyrë që të përputhen plotësisht me standardin e gjuhës C++. Ato janë aktivizuar si parazgjedhje në CMake.

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

Qëllimi kryesor

Biblioteka jonë përbëhet vetëm nga skedarë kokë, që do të thotë se nuk kemi asnjë shkarkim në formën e bibliotekave statike ose dinamike. Nga ana tjetër, për të përdorur bibliotekën tonë nga jashtë, ajo duhet të instalohet, duhet të jetë e zbulueshme në sistem dhe e lidhur me projektin tuaj, dhe në të njëjtën kohë të njëjtat tituj, si dhe mundësisht disa të tjerë shtesë. i janë bashkangjitur vetitë.

Për këtë qëllim, ne krijojmë një bibliotekë ndërfaqe.

add_library(mylib INTERFACE)

Ne lidhim titujt me bibliotekën tonë të ndërfaqes.

Përdorimi modern, në modë, rinor i CMake nënkupton që titujt, vetitë, etj. transmetohet përmes një objektivi të vetëm. Pra, mjafton të thuhet target_link_libraries(target PRIVATE dependency), dhe të gjithë titujt që janë të lidhur me objektivin dependency, do të jetë në dispozicion për burimet që i përkasin objektivit target. Dhe jo [target_]include_directories. Kjo do të tregohet më poshtë në analizë CMake skript për testet e njësisë.

Vlen gjithashtu t'i kushtohet vëmendje të ashtuquajturave. выражения-генераторы: $<...>.

Kjo komandë lidh titujt që na duhen me bibliotekën tonë të ndërfaqes dhe nëse biblioteka jonë është e lidhur me ndonjë objektiv brenda së njëjtës hierarki CMake, atëherë titujt nga drejtoria do të shoqërohen me të. ${CMAKE_CURRENT_SOURCE_DIR}/include, dhe nëse biblioteka jonë është e instaluar në sistem dhe e lidhur me një projekt tjetër duke përdorur komandën find_package, atëherë titujt nga drejtoria do të shoqërohen me të include në lidhje me direktorinë e instalimit.

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

Le të vendosim një standard gjuhësor. Sigurisht, kjo e fundit. Në të njëjtën kohë, ne jo vetëm e përfshijmë standardin, por edhe e shtrijmë atë tek ata që do të përdorin bibliotekën tonë. Kjo arrihet për faktin se vetia e vendosur ka një kategori INTERFACE (Cm. komanda target_compile_features).

target_compile_features(mylib INTERFACE cxx_std_17)

Le të krijojmë një pseudonim për bibliotekën tonë. Për më tepër, për bukurinë, do të jetë në një "hapësirë ​​emrash" të veçantë. Kjo do të jetë e dobishme kur module të ndryshme shfaqen në bibliotekën tonë dhe ne shkojmë t'i lidhim ato në mënyrë të pavarur nga njëri-tjetri. Si në Busta, për shembull.

add_library(Mylib::mylib ALIAS mylib)

Instalim

Instalimi i titujve tanë në sistem. Gjithçka është e thjeshtë këtu. Ne themi që dosja me të gjitha titujt duhet të hyjë në direktori include në lidhje me vendin e instalimit.

install(DIRECTORY include/mylib DESTINATION include)

Më pas, informojmë sistemin e ndërtimit që duam të jemi në gjendje të thërrasim komandën në projektet e palëve të treta find_package(Mylib) dhe merrni një gol Mylib::mylib.

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

Magjia tjetër duhet kuptuar në këtë mënyrë. Kur në një projekt të palës së tretë ne thërrasim komandën find_package(Mylib 1.2.3 REQUIRED), dhe versioni real i bibliotekës së instaluar do të jetë i papajtueshëm me versionin 1.2.3CMake do të gjenerojë automatikisht një gabim. Kjo do të thotë, nuk do t'ju duhet të gjurmoni versionet manualisht.

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)

testet

Nëse testet çaktivizohen duke përdorur në mënyrë të qartë opsionin përkatës ose projekti ynë është një nënprojekt, domethënë është i lidhur me një projekt tjetër CMake duke përdorur komandën add_subdirectory, ne nuk lëvizim më tej përgjatë hierarkisë dhe skripti, i cili përshkruan komandat për gjenerimin dhe ekzekutimin e testeve, thjesht nuk funksionon.

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

Records

Dokumentacioni gjithashtu nuk do të gjenerohet në rastin e një nënprojekti.

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

Sandbox në internet

Po kështu, nënprojekti nuk do të ketë as sandbox online.

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

Skripti testues (test/CMakeLists.txt)

Testimi

Para së gjithash, ne gjejmë një paketë me kornizën e kërkuar të testit (zëvendësojeni me atë të preferuarin tuaj).

find_package(doctest 2.3.3 REQUIRED)

Le të krijojmë skedarin tonë të ekzekutueshëm me teste. Zakonisht shtoj direkt në binarin e ekzekutueshëm vetëm skedarin që do të përmbajë funksionin main.

add_executable(mylib-unit-tests test_main.cpp)

Dhe unë shtoj skedarë në të cilët vetë testet përshkruhen më vonë. Por ju nuk duhet ta bëni këtë.

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

Ne lidhim varësitë. Ju lutemi vini re se ne lidhëm vetëm objektivat CMake që na duheshin me binarin tonë dhe nuk e thirrëm komandën target_include_directories. Titujt nga korniza e testit dhe nga e jona Mylib::mylib, si dhe parametrat e ndërtimit (në rastin tonë, ky është standardi i gjuhës C++) u arritën së bashku me këto qëllime.

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

Së fundi, ne krijojmë një objektiv të rremë, "ndërtimi" i të cilit është ekuivalent me testet e ekzekutimit dhe e shtojmë këtë objektiv në ndërtimin e paracaktuar (atributi është përgjegjës për këtë ALL). Kjo do të thotë që ndërtimi i parazgjedhur shkakton ekzekutimin e testeve, që do të thotë se nuk do të harrojmë kurrë t'i ekzekutojmë ato.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Mbulim

Më pas, ne aktivizojmë matjen e mbulimit të kodit nëse specifikohet opsioni i duhur. Nuk do të hyj në detaje, sepse ato lidhen më shumë me një mjet për matjen e mbulimit sesa me CMake. Është e rëndësishme vetëm të theksohet se në bazë të rezultateve do të krijohet një qëllim coverage, me të cilin është i përshtatshëm për të filluar matjen e mbulimit.

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

Skript për dokumentacion (doc/CMakeLists.txt)

Gjeti Doksigjen.

find_package(Doxygen)

Më pas, kontrollojmë nëse përdoruesi ka vendosur variablin e gjuhës. Nëse po, atëherë nuk e prekim, nëse jo, atëherë marrim rusisht. Pastaj ne konfigurojmë skedarët e sistemit Doxygen. Të gjitha variablat e nevojshme, duke përfshirë gjuhën, shkojnë atje gjatë procesit të konfigurimit (shih. команду configure_file).

Pastaj krijojmë një qëllim doc, i cili do të nisë gjenerimin e dokumentacionit. Meqenëse gjenerimi i dokumentacionit nuk është nevoja më e madhe në procesin e zhvillimit, objektivi nuk do të aktivizohet si parazgjedhje; ai do të duhet të lançohet në mënyrë eksplicite.

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

Skript për sandbox në internet (online/CMakeLists.txt)

Këtu gjejmë Python-in e tretë dhe krijojmë një objektiv wandbox, e cila gjeneron një kërkesë që korrespondon me shërbimin API Kuti me shkop, dhe e largon atë. Përgjigja vjen me një lidhje me sandboxin e përfunduar.

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

Projekti jashtë

Tani le të shohim se si ta përdorim të gjithë këtë.

asamble

Ndërtimi i këtij projekti, si çdo projekt tjetër në sistemin e ndërtimit CMake, përbëhet nga dy faza:

brez

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

Nëse komanda e mësipërme nuk funksionoi për shkak të një versioni të vjetër të CMake, provoni ta hiqni -S:

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

Më shumë rreth opsioneve.

Ndërtimi i projektit

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

Më shumë rreth qëllimeve të asamblesë.

Options

MYLIB_KOVERAGE

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

Përfshin objektivin coverage, me të cilin mund të filloni të matni mbulimin e kodit me anë të testeve.

MYLIB_TESTING

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

Ofron aftësinë për të çaktivizuar ndërtimin dhe objektivin e testit të njësisë check. Si rezultat, matja e mbulimit të kodit nga testet është çaktivizuar (shih. MYLIB_COVERAGE).

Testimi gjithashtu çaktivizohet automatikisht nëse projekti është i lidhur me një projekt tjetër si nënprojekt duke përdorur komandën add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Ndryshon gjuhën e dokumentacionit që gjeneron objektivi doc tek ai i dhënë. Për një listë të gjuhëve të disponueshme, shihni Uebsajti i sistemit Doxygen.

Rusishtja është aktivizuar si parazgjedhje.

Objektivat e Kuvendit

By default

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

Nëse objektivi nuk është i specifikuar (që është ekuivalente me objektivin all), mbledh gjithçka që mundet, dhe gjithashtu thërret objektivin check.

mylib-unit-tests

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

Përpilon testet e njësive. Aktivizuar si parazgjedhje.

shikoni

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

Kryen testet e njësive të mbledhura (të mbledhura, nëse jo tashmë). Aktivizuar si parazgjedhje.

См. также mylib-unit-tests.

mbulim

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

Analizon ekzekutimin (drejton, nëse jo tashmë) testet e njësisë për mbulimin e kodit nga testet që përdorin programin gcovr.

Shkarkimi i veshjes do të duket diçka si kjo:

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

Objektivi është i disponueshëm vetëm kur opsioni është i aktivizuar MYLIB_COVERAGE.

См. также check.

doktor

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

Fillon gjenerimin e dokumentacionit të kodit duke përdorur sistemin Oksigjeni.

kuti shkopi

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

Përgjigja nga shërbimi duket diçka si kjo:

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

Shërbimi përdoret për këtë Kuti me shkop. Nuk e di sa fleksibël janë serverët e tyre, por mendoj se kjo mundësi nuk duhet të abuzohet.

shembuj

Ndërtoni projektin në modalitetin e korrigjimit me matjen e mbulimit

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

Instalimi i një projekti pa montim dhe testim paraprak

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

Ndërtoni në modalitetin e lëshimit me një përpilues të caktuar

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

Gjenerimi i dokumentacionit në anglisht

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

Mjete

  1. Bëni 3.13

    Në fakt, versioni 3.13 CMake kërkohet vetëm për të ekzekutuar disa nga komandat e konsolës të përshkruara në këtë ndihmë. Nga pikëpamja e sintaksës së skripteve CMake, versioni 3.8 është i mjaftueshëm nëse gjenerimi thirret në mënyra të tjera.

  2. Biblioteka e testimit doktest

    Testimi mund të çaktivizohet (shih опцию MYLIB_TESTING).

  3. Oksigjeni

    Për të ndërruar gjuhën në të cilën do të gjenerohet dokumentacioni, ofrohet një opsion MYLIB_DOXYGEN_LANGUAGE.

  4. Përkthyes i gjuhës Python 3

    Për gjenerim automatik sandboxet online.

Analiza statike

Me CMake dhe disa mjete të mira, ju mund të ofroni analiza statike me përpjekje minimale.

Kontrollo Cpp

CMake ka mbështetje të integruar për një mjet analize statike Kontrollo Cpp.

Për ta bërë këtë, duhet të përdorni opsionin CMAKE_CXX_CPPCHECK:

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

Pas kësaj, analiza statike do të nisë automatikisht sa herë që burimi përpilohet dhe ripërpilohet. Nuk ka nevojë të bëni asgjë shtesë.

Zhurmë

Me ndihmën e një mjeti të mrekullueshëm scan-build Ju gjithashtu mund të kryeni analiza statike në asnjë kohë:

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

Këtu, ndryshe nga rasti me Cppcheck, ju duhet ta ekzekutoni ndërtimin çdo herë scan-build.

pasthënje

CMake është një sistem shumë i fuqishëm dhe fleksibël që ju lejon të zbatoni funksionalitet për çdo shije dhe ngjyrë. Dhe, megjithëse sintaksa ndonjëherë lë shumë për të dëshiruar, djalli nuk është ende aq i tmerrshëm sa është pikturuar. Përdorni sistemin e ndërtimit CMake për të mirën e shoqërisë dhe shëndetit.

Shkarkoni shabllonin e projektit

Burimi: www.habr.com

Shto një koment