Durant el desenvolupament, m'agrada canviar compiladors, construir modes, versions de dependència, realitzar anàlisis estàtiques, mesurar el rendiment, recopilar cobertura, generar documentació, etc. I m'encanta CMake perquè em permet fer el que vull.
Molts renyen CMake, i sovint merescudament, però si mireu, no està tan malament, però últimament no està gens malament, i la direcció del desenvolupament és força positiva.
En aquesta nota, vull dir-vos com de fàcil és organitzar una biblioteca de capçaleres C++ en un sistema CMake per obtenir la següent funcionalitat:
Això se centrarà principalment en com organitzar els scripts CMake, de manera que s'analitzaran en detall. La resta d'arxius pot ser visualitzat directament per qualsevol persona. a la pàgina del projecte de plantilla.
En primer lloc, heu de sol·licitar la versió desitjada del sistema CMake. CMake es desenvolupa, canvien les signatures de comandaments, comportament en diferents condicions. Perquè CMake entengui immediatament què volem d'ell, hem d'arreglar immediatament els nostres requisits.
cmake_minimum_required(VERSION 3.13)
Després denotem el nostre projecte, el seu nom, versió, idiomes utilitzats, etc. команду project).
En aquest cas, especifiqueu l'idioma CXX (que vol dir C++) perquè CMake no es molesti a buscar un compilador C (per defecte, s'inclouen dos idiomes a CMake: C i C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Aquí podeu comprovar immediatament si el nostre projecte està inclòs en un altre projecte com a subprojecte. Això ajudarà molt en el futur.
La primera opció és MYLIB_TESTING - per desactivar les proves unitàries. Això pot ser necessari si estem segurs que tot està en ordre amb les proves, i volem, per exemple, només instal·lar o empaquetar el nostre projecte. O el nostre projecte s'inclou com a subprojecte; en aquest cas, l'usuari del nostre projecte no està interessat a executar les nostres proves. No proveu les dependències que feu servir, oi?
A més, farem una opció a part MYLIB_COVERAGE per mesurar la cobertura del codi amb proves, però requerirà eines addicionals, de manera que l'haureu d'habilitar de manera explícita.
Per descomptat, som programadors fantàstics, així que volem el màxim nivell de diagnòstic en temps de compilació del compilador. Ni un sol ratolí passarà.
La nostra biblioteca consta només de fitxers de capçalera, el que significa que no tenim cap sortida en forma de biblioteques estàtiques o dinàmiques. D'altra banda, per poder utilitzar la nostra biblioteca des de l'exterior, cal instal·lar-la, poder trobar-la al sistema i connectar-la al vostre projecte, i alhora aquestes mateixes capçaleres, també com, possiblement, algunes propietats addicionals.
Per a això, creem una biblioteca d'interfícies.
add_library(mylib INTERFACE)
Enllacem les capçaleres a la nostra biblioteca d'interfícies.
L'ús modern, modern i juvenil de CMake significa que les capçaleres, propietats, etc. transmesa a través d'un únic objectiu. Així que n'hi ha prou de dir target_link_libraries(target PRIVATE dependency), i totes les capçaleres associades a l'objectiu dependency, estarà disponible per a fonts pertanyents a l'objectiu target. I no en necessites cap [target_]include_directories. Això es demostrarà a continuació en analitzar Script CMake per a proves unitàries.
Aquesta ordre associa les capçaleres que necessitem amb la nostra biblioteca d'interfícies, i si la nostra biblioteca està connectada a qualsevol objectiu dins de la mateixa jerarquia CMake, les capçaleres del directori s'hi associaran. ${CMAKE_CURRENT_SOURCE_DIR}/include, i si la nostra biblioteca està instal·lada al sistema i connectada a un altre projecte mitjançant l'ordre find_package, llavors s'associaran les capçaleres del directori include en relació al directori d'instal·lació.
Estableix l'estàndard d'idioma. Per descomptat, l'últim. Al mateix temps, no només incloem l'estàndard, sinó que també el distribuïm a aquells que utilitzaran la nostra biblioteca. Això s'aconsegueix fent que la propietat set tingui una categoria INTERFACE (Veure comanda target_compile_features).
Tenim un àlies per a la nostra biblioteca. I per a la bellesa, estarà en un "espai de noms" especial. Això serà útil quan apareguin diferents mòduls a la nostra biblioteca, i anem a connectar-los independentment els uns dels altres. Com a Busta, per exemple.
Instal·lant les nostres capçaleres al sistema. Aquí tot és senzill. Diem que la carpeta amb totes les capçaleres hauria de caure al directori include pel que fa a la ubicació de la instal·lació.
A continuació, diem al sistema de compilació que volem poder cridar l'ordre en projectes de tercers find_package(Mylib) i aconseguir un objectiu Mylib::mylib.
El següent encanteri s'ha d'entendre així. Quan en un projecte secundari anomenem l'ordre find_package(Mylib 1.2.3 REQUIRED), i al mateix temps la versió real de la biblioteca instal·lada serà incompatible amb la versió 1.2.3, CMake generarà automàticament un error. És a dir, no caldrà fer un seguiment de les versions manualment.
Si les proves estan desactivades explícitament amb opció corresponent o el nostre projecte és un subprojecte, és a dir, està connectat a un altre projecte CMake mitjançant l'ordre add_subdirectory, no anem més avall en la jerarquia i l'script, que descriu les ordres per generar i executar proves, simplement no s'inicia.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Connectem dependències. Tingueu en compte que només vam adjuntar els objectius CMake que necessitàvem al nostre binari i no vam cridar l'ordre target_include_directories. Capçaleres del marc de prova i del nostre Mylib::mylib, així com opcions de compilació (en el nostre cas, l'estàndard de llenguatge C++) es van rastrejar juntament amb aquests objectius.
Finalment, creem un objectiu fictici la "construcció" del qual és equivalent a executar proves i afegim aquest objectiu a la construcció predeterminada (això és responsabilitat de l'atribut ALL). Això vol dir que la compilació predeterminada activarà les proves per executar-se, el que significa que no oblidarem mai d'executar-les.
add_custom_target(check ALL COMMAND mylib-unit-tests)
A continuació, activeu el mesurament de la cobertura del codi, si s'ha establert l'opció corresponent. No entraré en detalls, perquè estan més relacionats amb l'eina de mesura de la cobertura que amb CMake. Només és important tenir en compte que els resultats crearan un objectiu coverage, amb la qual cosa és convenient començar a mesurar la cobertura.
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()
A continuació, comprovem si la variable amb l'idioma la defineix l'usuari. Si és així, no el toquem, si no, agafem el rus. Després configurem els fitxers del sistema Doxygen. Totes les variables necessàries, inclòs l'idioma, hi arriben durant el procés de configuració (vegeu. команду configure_file).
Aleshores creem un objectiu doc, que començarà a generar documentació. Com que la generació de documentació no és la necessitat més gran en el procés de desenvolupament, l'objectiu no s'inclourà per defecte, s'haurà d'executar de manera explícita.
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 ()
Aquí trobem el tercer Python i creem un objectiu wandbox, que genera una sol·licitud corresponent a l'API del servei Caixa de varetes, i l'envia. En resposta, arriba un enllaç a la caixa de sorra acabada.
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()
Ofereix l'opció de desactivar la creació i l'objectiu de la prova d'unitat check. Com a resultat, la mesura de la cobertura del codi mitjançant proves està desactivada (vegeu MYLIB_COVERAGE).
A més, la prova es desactiva automàticament si el projecte està connectat a un altre projecte com a subprojecte mitjançant l'ordre add_subdirectory.
De fet, la versió 3.13 de CMake només és necessària per executar algunes de les ordres de consola descrites en aquesta ajuda. Des del punt de vista de la sintaxi dels scripts CMake, la versió 3.8 és suficient si la generació s'anomena d'altres maneres.
CMake és un sistema molt potent i flexible que us permet implementar funcionalitats per a tots els gustos i colors. I, tot i que la sintaxi de vegades deixa molt a desitjar, el diable encara no és tan terrible com està pintat. Utilitzeu el sistema de construcció CMake en benefici de la societat i la salut.