CMake i C++ són germans per sempre

CMake i C++ són germans per sempre

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:

  1. muntatge;
  2. proves d'execució automàtica;
  3. Mesura de la cobertura del codi;
  4. instal·lació;
  5. Autodocumentació;
  6. Generació de sandbox en línia;
  7. Anàlisi estàtica.

Qui ja entén els pros i si-make només pot descarregar la plantilla del projecte i començar a utilitzar-lo.


Contingut

  1. Projecte des de dins
    1. Estructura del projecte
    2. Fitxer CMake principal (./CMakeLists.txt)
      1. Informació del projecte
      2. Opcions del projecte
      3. Opcions de compilació
      4. L'objectiu principal
      5. Instal · lació
      6. Proves
      7. Registres
      8. Sandbox en línia
    3. Script per a proves (test/CMakeLists.txt)
      1. Proves
      2. Cobertura
    4. Script de documentació (doc/CMakeLists.txt)
    5. Script per a sandbox en línia (en línia/CMakeLists.txt)
  2. projecte a l'exterior
    1. assemblea
      1. Generació
      2. assemblea
    2. Opcions
      1. MYLIB_COBERTURA
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. objectius de muntatge
      1. Per defecte
      2. mylib-unit-tests
      3. comprovar
      4. cobertura
      5. doctor
      6. caixa de varetes
    4. Примеры
  3. Instruments
  4. Anàlisi estàtica
  5. Paraula posterior

Projecte des de dins

Estructura del projecte

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

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.

Fitxer CMake principal (./CMakeLists.txt)

Informació del projecte

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.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Opcions del projecte

Considerem dues opcions.

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?

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

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.

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

Opcions de compilació

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à.

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
)

També desactivarem les extensions per complir totalment amb l'estàndard del llenguatge C++. Estan habilitats per defecte a CMake.

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

L'objectiu principal

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.

També val la pena parar atenció als anomenats. выражения-генераторы: $<...>.

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ó.

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

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

target_compile_features(mylib INTERFACE cxx_std_17)

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.

add_library(Mylib::mylib ALIAS mylib)

Instal · lació

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ó.

install(DIRECTORY include/mylib DESTINATION include)

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.

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

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.

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)

Proves

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

Registres

Tampoc es generarà documentació en el cas d'un subprojecte.

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

Sandbox en línia

De la mateixa manera, el subprojecte tampoc tindrà un sandbox en línia.

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

Script per a proves (test/CMakeLists.txt)

Proves

En primer lloc, trobem un paquet amb el marc de prova desitjat (substituïu-lo pel vostre preferit).

find_package(doctest 2.3.3 REQUIRED)

Creem el nostre fitxer executable amb proves. Normalment, directament al binari executable, afegeixo només el fitxer en què estarà la funció main.

add_executable(mylib-unit-tests test_main.cpp)

I els fitxers en què es descriuen les proves, els afegeixo més endavant. Però no cal fer-ho.

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

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.

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

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)

Cobertura

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

Script de documentació (doc/CMakeLists.txt)

Trobat Doxygen.

find_package(Doxygen)

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

Script per a sandbox en línia (en línia/CMakeLists.txt)

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

projecte a l'exterior

Ara mirem com utilitzar-ho tot.

assemblea

La construcció d'aquest projecte, com qualsevol altre projecte del sistema de compilació CMake, consta de dues etapes:

Generació

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

Si l'ordre anterior no va funcionar a causa d'una versió anterior de CMake, proveu d'ometre -S:

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

Més informació sobre les opcions.

Construcció del projecte

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

Més informació sobre els objectius de l'assemblea.

Opcions

MYLIB_COBERTURA

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

Inclou objectiu coverage, amb el qual podeu començar a mesurar la cobertura del codi amb proves.

MYLIB_TESTING

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

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.

MYLIB_DOXYGEN_LANGUAGE

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

Canvia l'idioma de la documentació que genera l'objectiu doc al donat. Per obtenir una llista dels idiomes disponibles, vegeu Lloc web de Doxygen.

El rus està activat per defecte.

objectius de muntatge

Per defecte

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

Si no s'especifica l'objectiu (que equival a l'objectiu all), recull tot el que és possible i també anomena l'objectiu check.

mylib-unit-tests

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

Compila proves unitàries. Habilitat per defecte.

comprovar

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

Executa proves d'unitat construïdes (completa si no ja). Habilitat per defecte.

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

cobertura

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

Analitza proves d'unitat en execució (execució, si encara no) per a la cobertura de codi mitjançant proves utilitzant el programa gcovr.

La sortida de cobertura tindrà un aspecte semblant a això:

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

L'objectiu només està disponible quan l'opció està activada. MYLIB_COVERAGE.

См. также check.

doctor

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

Inicia la generació de documentació per al codi utilitzant el sistema Oxigen.

caixa de varetes

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

La resposta del servei és una cosa així:

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

El servei s'utilitza per a això. Caixa de varetes. No sé com tenen els servidors de goma, però crec que no hauríeu d'abusar d'aquesta funció.

Примеры

Construeix el projecte en mode de depuració amb mesura de cobertura

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

Instal·lació d'un projecte sense preconstrucció i proves

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

Construït en mode de llançament pel compilador especificat

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

Generació de documentació en anglès

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

Instruments

  1. CMake 3.13

    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.

  2. Biblioteca de proves doctest

    Les proves es poden desactivar (vegeu опцию MYLIB_TESTING).

  3. Oxigen

    Per canviar l'idioma en què es generarà la documentació, hi ha una opció MYLIB_DOXYGEN_LANGUAGE.

  4. intèrpret de PL 3 Python

    Per a la generació automàtica caixes de sorra en línia.

Anàlisi estàtica

Amb l'ajuda de CMake i un parell de bones eines, podeu proporcionar anàlisi estàtica amb un mínim de joc.

Cppcheck

Suport per a l'eina d'anàlisi estàtica integrada a CMake Cppcheck.

Per fer-ho, utilitzeu l'opció CMAKE_CXX_CPPCHECK:

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

Després d'això, l'anàlisi estàtica es llançarà automàticament cada cop durant la compilació i recompilació de fonts. No cal fer res addicional.

Clang

Amb una eina meravellosa scan-build També podeu executar l'anàlisi estàtica en molt poc temps:

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

Aquí, a diferència del cas de Cppcheck, cal executar la compilació cada vegada scan-build.

Paraula posterior

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.

Descarrega la plantilla del projecte

Font: www.habr.com

Afegeix comentari