Med razvojem rad spreminjam prevajalnike, gradim načine, različice odvisnosti, izvajam statično analizo, merim zmogljivost, zbiram pokritost, ustvarjam dokumentacijo itd. In res obožujem CMake, ker mi omogoča, da naredim vse, kar želim.
Mnogi ljudje kritizirajo CMake in pogosto zasluženo, a če pogledate, ni vse tako slabo in pred kratkim sploh ni slabo, smer razvoja pa je precej pozitivna.
V tej opombi vam želim povedati, kako preprosto organizirati knjižnico glav v C++ v sistemu CMake, da dobite naslednjo funkcionalnost:
Govorili bomo predvsem o tem, kako organizirati skripte CMake, zato jih bomo podrobneje obravnavali. Preostale datoteke si lahko vsakdo ogleda neposredno na strani s predlogo projekta.
Najprej morate zahtevati zahtevano različico sistema CMake. CMake se razvija, podpisi ukazov in obnašanje v različnih pogojih se spreminjajo. Da bi CMake takoj razumel, kaj želimo od njega, moramo takoj zabeležiti naše zahteve zanj.
cmake_minimum_required(VERSION 3.13)
Nato bomo določili naš projekt, njegovo ime, različico, uporabljene jezike itd. (glej. команду project).
V tem primeru navedemo jezik CXX (in to pomeni C++), tako da se CMake ne napreza in išče prevajalnik jezika C (privzeto CMake vključuje dva jezika: C in C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Tukaj lahko takoj preverite, ali je naš projekt vključen v drug projekt kot podprojekt. To bo v prihodnosti zelo pomagalo.
Prva možnost je MYLIB_TESTING — da onemogočite teste enot. To je morda potrebno, če smo prepričani, da je s testi vse v redu, želimo pa na primer samo namestiti ali zapakirati naš projekt. Ali pa je naš projekt vključen kot podprojekt - v tem primeru uporabnik našega projekta ni zainteresiran za izvajanje naših testov. Ne testirate odvisnosti, ki jih uporabljate, kajne?
Poleg tega bomo naredili ločeno možnost MYLIB_COVERAGE za merjenje pokritosti kode s testi, vendar bo zahtevalo dodatna orodja, zato ga bo treba izrecno omogočiti.
Naša knjižnica je sestavljena samo iz datotek glave, kar pomeni, da nimamo nobenega izpuha v obliki statičnih ali dinamičnih knjižnic. Po drugi strani pa mora biti naša knjižnica za zunanjo uporabo nameščena, zaznavna v sistemu in povezana z vašim projektom, hkrati pa te iste glave in morda še kakšne dodatne, so nanjo povezane lastnosti.
V ta namen izdelamo vmesniško knjižnico.
add_library(mylib INTERFACE)
Glave povežemo z našo knjižnico vmesnika.
Sodobna, modna, mladinska uporaba CMake pomeni, da glave, lastnosti itd. prenašajo skozi eno samo tarčo. Torej dovolj je reči target_link_libraries(target PRIVATE dependency)in vse glave, ki so povezane s ciljem dependency, bo na voljo za vire, ki pripadajo cilju target. In ne potrebuješ nobenega [target_]include_directories. To bo prikazano v nadaljevanju analize Skript CMake za teste enot.
Ta ukaz poveže glave, ki jih potrebujemo, z našo vmesniško knjižnico, in če je naša knjižnica povezana s katerim koli ciljem znotraj iste hierarhije CMake, bodo glave iz imenika povezane z njo ${CMAKE_CURRENT_SOURCE_DIR}/include, in če je naša knjižnica nameščena v sistemu in povezana z drugim projektom z ukazom find_package, bodo z njim povezane glave iz imenika include glede na namestitveni imenik.
Postavimo jezikovni standard. Seveda čisto zadnjega. Hkrati pa standard ne samo vključimo, temveč ga razširimo na tiste, ki bodo uporabljali našo knjižnico. To je doseženo zaradi dejstva, da ima nastavljena lastnost kategorijo INTERFACE (glej ukaz target_compile_features).
Ustvarimo vzdevek za našo knjižnico. Še več, zaradi lepote bo v posebnem "imenskem prostoru". To bo uporabno, ko se v naši knjižnici pojavijo različni moduli in jih povezujemo neodvisno drug od drugega. Kot na primer v Busti.
Naslednji urok je treba razumeti tako. Ko v projektu tretje osebe pokličemo ukaz find_package(Mylib 1.2.3 REQUIRED), in prava različica nameščene knjižnice ne bo združljiva z različico 1.2.3CMake bo samodejno ustvaril napako. To pomeni, da vam ne bo treba ročno slediti različicam.
Če so testi izrecno onemogočeni z uporabo ustrezno možnost ali pa je naš projekt podprojekt, to pomeni, da je z ukazom povezan z drugim projektom CMake add_subdirectory, se ne premaknemo naprej po hierarhiji in skripta, ki opisuje ukaze za generiranje in izvajanje testov, preprosto ne teče.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Povezujemo odvisnosti. Upoštevajte, da smo z binarno datoteko povezali samo cilje CMake, ki smo jih potrebovali, in nismo poklicali ukaza target_include_directories. Naslovi iz testnega okvira in iz našega Mylib::mylib, kot tudi parametri gradnje (v našem primeru je to jezikovni standard C++) so prišli skupaj s temi cilji.
Nazadnje ustvarimo navidezno tarčo, katere »zgradba« je enakovredna izvajanju testov, in dodamo ta tarča k privzeti gradnji (za to je odgovoren atribut ALL). To pomeni, da privzeta zgradba sproži izvajanje testov, kar pomeni, da jih ne bomo nikoli pozabili izvesti.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Nato omogočimo merjenje pokritosti kode, če je navedena ustrezna možnost. V podrobnosti se ne bom spuščal, ker se nanašajo bolj na orodje za merjenje pokritosti kot na CMake. Pomembno je le omeniti, da bo na podlagi rezultatov ustvarjen cilj coverage, s katerim je priročno začeti meriti pokritost.
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()
Nato preverimo, ali je uporabnik nastavil jezikovno spremenljivko. Če da, potem se ga ne dotikamo, če ne, potem vzamemo ruščino. Nato konfiguriramo sistemske datoteke Doxygen. Vse potrebne spremenljivke, vključno z jezikom, gredo tja med postopkom konfiguracije (glejte. команду configure_file).
Nato ustvarimo cilj doc, ki bo začel z izdelavo dokumentacije. Ker ustvarjanje dokumentacije ni največja potreba v procesu razvoja, cilj ne bo privzeto omogočen; treba ga bo izrecno zagnati.
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 ()
Tukaj najdemo tretji Python in ustvarimo cilj wandbox, ki ustvari zahtevo, ki ustreza API-ju storitve Wandbox, in ga pošlje stran. Odgovor vsebuje povezavo do dokončanega peskovnika.
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()
Zagotavlja možnost onemogočanja gradnje in ciljanja testa enote check. Zaradi tega je merjenje pokritosti kode s testi izklopljeno (glej. MYLIB_COVERAGE).
Testiranje je tudi samodejno onemogočeno, če je projekt z ukazom povezan z drugim projektom kot podprojekt add_subdirectory.
Pravzaprav je CMake različice 3.13 potreben samo za izvajanje nekaterih ukazov konzole, opisanih v tej pomoči. Z vidika sintakse skriptov CMake je različica 3.8 zadostna, če se generiranje kliče na druge načine.
CMake je zelo zmogljiv in prilagodljiv sistem, ki vam omogoča implementacijo funkcionalnosti za vsak okus in barvo. In čeprav sintaksa včasih pušča veliko želenega, hudič še vedno ni tako grozen, kot je naslikan. Uporabite gradbeni sistem CMake v dobro družbe in zdravja.