У працэсе распрацоўкі я люблю мяняць кампілятары, рэжымы зборкі, версіі залежнасцяў, вырабляць статычны аналіз, замяраць прадукцыйнасць, збіраць пакрыццё, генераваць дакументацыю і г.д. І вельмі люблю CMake, таму што ен дазваляе мне рабіць усё тое, што я хачу.
Многія лаюць CMake, і часта заслужана, але калі разабрацца, то не ўсё так дрэнна, а ў апошні час вельмі нават нядрэнна, і кірунак развіцця цалкам пазітыўны.
У дадзенай нататцы я жадаю распавесці, як досыць проста арганізаваць загалоўкавую бібліятэку на мове C++ у сістэме CMake, каб атрымаць наступную функцыянальнасць:
Зборку;
Аўтазапуск тэстаў;
Замер пакрыцця кода;
Устаноўку;
Аўтадакументаванне;
Генерацыю анлайн-пясочніцы;
Статычны аналіз.
Хто і так разбіраецца ў плюсах і сі-мейку можа проста спампаваць шаблон праекта і пачаць ім карыстацца.
Галоўным чынам гаворка пойдзе аб тым, як арганізаваць CMake-скрыпты, таму яны будуць разабраны падрабязна. Астатнія файлы кожны жадаючы можа паглядзець непасрэдна на старонцы праекта-шаблона.
У першую чаргу трэба запатрабаваць патрэбную версію сістэмы CMake. CMake развіваецца, мяняюцца сігнатуры каманд, паводзіны ў розных умовах. Каб CMake адразу разумеў, чаго мы ад яго жадаем, трэба адразу зафіксаваць нашы да яго патрабаванні.
cmake_minimum_required(VERSION 3.13)
Затым абазначым наш праект, яго назву, версію, якія выкарыстоўваюцца мовы і іншае (гл. команду project).
У дадзеным выпадку паказваем мову CXX (а гэта значыць C++), каб CMake не напружваўся і не шукаў кампілятар мовы C (па змаўчанні ў CMake уключаны дзве мовы: C і C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Тутака ж можна адразу праверыць, ці ўключаны наш праект у іншы праект у якасці падпраекта. Гэта моцна дапаможа ў далейшым.
Першая опцыя - MYLIB_TESTING - Для выключэння модульных тэстаў. Гэта можа спатрэбіцца, калі мы ўпэўненыя, што з тэстамі ўсё ў парадку, а мы хочам, напрыклад, толькі ўсталяваць ці запакетаваць наш праект. Або наш праект уключаны ў якасці падпраекта - у гэтым выпадку карыстачу нашага праекта не цікава запускаць нашы тэсты. Вы ж не тэстуеце залежнасці, якімі карыстаецеся?
Акрамя таго, мы зробім асобную опцыю MYLIB_COVERAGE для замераў пакрыцця кода тэстамі, але яна запатрабуе дадатковых прылад, таму ўключаць яе трэба будзе відавочна.
Наша бібліятэка складаецца толькі з загалоўкавых файлаў, а значыць, мы не размяшчаем якія-небудзь выхлапам у выглядзе статычных або дынамічных бібліятэк. З іншага боку, каб выкарыстоўваць нашу бібліятэку звонку, яе трэба ўстанавіць, трэба, каб яе можна было знайсці ў сістэме і падключыць да свайго праекта, і пры гэтым разам з ёй былі прывязаны гэтыя самыя загалоўкі, а таксама, магчыма, нейкія дадатковыя уласцівасці.
Для гэтай мэты ствараем інтэрфейсную бібліятэку.
add_library(mylib INTERFACE)
Прывязваем загалоўкі да нашай інтэрфейснай бібліятэкі.
Сучаснае, моднае, моладзевае выкарыстанне CMake мае на ўвазе, што загалоўкі, уласцівасці і да т.п. перадаюцца праз адну адзіную мэту. Такім чынам, дастаткова сказаць target_link_libraries(target PRIVATE dependency), і ўсе загалоўкі, якія асацыіраваны з мэтай dependency, будуць даступныя для зыходнікаў, якія належаць мэты target. І не патрабуецца ніякіх [target_]include_directories. Гэта будзе прадэманстравана ніжэй пры разборы CMake-скрыпта для модульных тэстаў.
Дадзеная каманда асацыюе патрэбныя нам загалоўкі з нашай інтэрфейснай бібліятэкай, прычым, у выпадку, калі наша бібліятэка будзе падключана да якой-небудзь мэты ў рамках адной іерархіі CMake, то з ёй будуць асацыіраваны загалоўкі з дырэкторыі. ${CMAKE_CURRENT_SOURCE_DIR}/include, а калі наша бібліятэка ўстаноўлена ў сістэму і падключана ў іншы праект з дапамогай каманды find_package, то з ёй будуць асацыіраваны загалоўкі з дырэкторыі include адносна дырэкторыі ўстаноўкі.
Усталюем стандарт мовы. Зразумела, самы апошні. Пры гэтым не проста ўключаем стандарт, але і распаўсюджваем яго на тых, хто будзе выкарыстоўваць нашу бібліятэку. Гэта дасягаецца за кошт таго, што ўсталяваная ўласцівасць мае катэгорыю. INTERFACE (гл. каманду target_compile_features).
Заводзім псеўданім для нашай бібліятэкі. Прычым для прыгажосці ён будзе ў спецыяльнай «прасторы імёнаў». Гэта будзе карысна, калі ў нашай бібліятэцы з'явяцца розныя модулі, і мы заходзім падключаць іх незалежна сябар ад сябра. Як у Бусце, напрыклад.
Ўстаноўка нашых загалоўкаў у сістэму. Тут усё проста. Які гаворыцца, што тэчка са ўсімі загалоўкамі павінна патрапіць у дырэкторыю include адносна месцы ўстаноўкі.
Далей паведамляем сістэме зборкі аб тым, што мы хочам мець магчымасць у іншых праектах зваць каманду. find_package(Mylib) і атрымліваць мэту Mylib::mylib.
Наступны заклік трэба разумець так. Калі ў іншым праекце мы выклічам каманду find_package(Mylib 1.2.3 REQUIRED), і пры гэтым рэальная версія ўсталяванай бібліятэкі апынецца несумяшчальнай з версіяй 1.2.3, CMake аўтаматычна згенеруе памылку. Гэта значыць, не трэба будзе сачыць за версіямі ўручную.
Калі тэсты выключаны відавочна з дапамогай адпаведнай опцыі ці наш праект з'яўляецца падпраектам, гэта значыць падлучаны ў іншы CMake-праект з дапамогай каманды add_subdirectory, мы не пераходзім далей па іерархіі, і скрыпт, у якім апісаны каманды для генерацыі і запуску тэстаў, проста не запускаецца.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Падлучальны залежнасці. Звярніце ўвагу, што да нашага бінарніка мы прывязалі толькі патрэбныя нам CMake-мэты, і не выклікалі каманду target_include_directories. Загалоўкі з тэставага фрэймворка і з нашай Mylib::mylib, а таксама параметры зборкі (у нашым выпадку гэта стандарт мовы C++) пралезлі разам з гэтымі мэтамі.
Нарэшце, ствараем фіктыўную мэту, «зборка» якой эквівалентная запуску тэстаў, і дадаем гэтую мэту ў зборку па змаўчанні (за гэта адказвае атрыбут ALL). Гэта значыць, што зборка па змаўчанні ініцыюе запуск тэстаў, гэта значыць мы ніколі не забудземся іх запусціць.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Далей уключаем замер пакрыцця кода, калі зададзеная адпаведная опцыя. У дэталі ўдавацца не буду, таму што яны ставяцца больш да прылады для замераў пакрыцця, чым да CMake. Важна толькі адзначыць, што па выніках будзе створана мэта coverage, з дапамогай якой зручна запускаць замер пакрыцця.
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()
Далей правяраем, ці ўсталяваная карыстачом зменная з мовай. Калі так, то не чапаем, калі не, то бярэм рускі. Затым які канфігуруецца файлы сістэмы Doxygen. Усе неабходныя зменныя, у тым ліку і мова трапляюць туды ў працэсе канфігурацыі (гл. команду configure_file).
Пасля чаго ствараем мэту doc, Якая будзе запускаць генераванне дакументацыі. Паколькі генераванне дакументацыі – не самая вялікая неабходнасць у працэсе распрацоўкі, то па змаўчанні мэта ўключана не будзе, яе давядзецца запускаць відавочна.
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 ()
Тут знаходзім трэці Пітон і ствараем мэту wandbox, якая генеруе запыт, які адпавядае API сэрвісу Скрыня для палачак, і адсылае яго. У адказ прыходзіць спасылка на гатовую пясочніцу.
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()
Для гэтага выкарыстоўваецца сэрвіс Скрыня для палачак. Не ведаю, наколькі ў іх гумовыя серверы, але думаю, што марнатравіць дадзенай магчымасцю не варта.
Насамрэч версія CMake 3.13 патрабуецца толькі для запуску некаторых кансольных каманд, апісаных у дадзенай даведцы. З пункту гледжання сінтаксісу CMake-скрыптоў дастаткова версіі 3.8, калі генерацыю выклікаць іншымі спосабамі.
Пасля гэтага статычны аналіз будзе аўтаматычна запускацца кожны раз падчас кампіляцыі і перакампіляванні зыходнікаў. Нічога дадатковага рабіць ня трэба.
Лапатка
Пры дапамозе цудоўнай прылады scan-build таксама можна запускаць статычны аналіз у два рахункі:
CMake - вельмі магутная і гнуткая сістэма, якая дазваляе рэалізоўваць функцыянальнасць на любы густ і колер. І, хоць, сінтаксіс часам пакідае жадаць лепшага, усё ж не такі страшны чорт, як яго малююць. Карыстайцеся сістэмай зборкі CMake на карысць супольнасці і з карысцю для здароўя.