CMake ve C++ sonsuza kadar kardeştir

CMake ve C++ sonsuza kadar kardeştir

Geliştirme sırasında derleyicileri değiştirmeyi, modları, bağımlılık sürümlerini değiştirmeyi, statik analiz gerçekleştirmeyi, performansı ölçmeyi, kapsamı toplamayı, belge oluşturmayı vb. seviyorum. Ve CMake'i gerçekten seviyorum çünkü istediğim her şeyi yapmamı sağlıyor.

Pek çok kişi CMake'i eleştiriyor ve çoğu zaman bunu hak ediyor, ancak ona bakarsanız her şeyin o kadar da kötü olmadığını görüyorsunuz ve son zamanlarda hiç de fena değilve gelişme yönü oldukça olumlu.

Bu notta, aşağıdaki işlevleri elde etmek için CMake sisteminde C++ 'da bir başlık kitaplığını nasıl düzenleyeceğinizi anlatmak istiyorum:

  1. Toplantı;
  2. Otomatik çalıştırma testleri;
  3. Kod kapsamı ölçümü;
  4. Kurulum;
  5. Otomatik belgeleme;
  6. Çevrimiçi sanal alan oluşturma;
  7. Statik analiz.

Avantajlarını ve C yapımını zaten anlayan herkes kolayca yapabilir proje şablonunu indir ve kullanmaya başlayın.


Içerik

  1. İçeriden proje
    1. Proje yapısı
    2. Ana CMake dosyası (./CMakeLists.txt)
      1. Proje bilgisi
      2. Proje Seçenekleri
      3. Derleme seçenekleri
      4. Ana hedef
      5. Montaj
      6. Testler
      7. Belgeleme
      8. Çevrimiçi korumalı alan
    3. Test komut dosyası (test/CMakeLists.txt)
      1. Test
      2. Kapsama
    4. Belgelendirme komut dosyası (doc/CMakeLists.txt)
    5. Çevrimiçi sanal alan için komut dosyası (çevrimiçi/CMakeLists.txt)
  2. Dışarıdaki proje
    1. montaj
      1. nesil
      2. montaj
    2. Seçenekler
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Montaj hedefleri
      1. Varsayılan olarak
      2. mylib-birim-testleri
      3. Kontrol
      4. kapsama
      5. dok
      6. asa kutusu
    4. Örnekler
  3. Araçlar
  4. Statik analiz
  5. Послесловие

İçeriden proje

Proje yapısı

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

Biz esas olarak CMake scriptlerinin nasıl organize edileceğinden bahsedeceğiz dolayısıyla bunlar detaylı olarak ele alınacaktır. Herkes dosyaların geri kalanını doğrudan görüntüleyebilir şablon proje sayfasında.

Ana CMake dosyası (./CMakeLists.txt)

Proje bilgisi

Öncelikle CMake sisteminin gerekli sürümünü talep etmeniz gerekiyor. CMake gelişiyor, komut imzaları ve farklı koşullardaki davranışlar değişiyor. CMake'in ondan ne istediğimizi hemen anlayabilmesi için ona yönelik gereksinimlerimizi hemen kaydetmemiz gerekiyor.

cmake_minimum_required(VERSION 3.13)

Daha sonra projemizi, adını, versiyonunu, kullanılan dilleri vb. belirleyeceğiz (bkz. команду project).

Bu durumda dili belirtiyoruz CXX (ve bu C++ anlamına gelir) böylece CMake bir C dili derleyicisini zorlamaz ve aramaz (CMake varsayılan olarak iki dil içerir: C ve C++).

project(Mylib VERSION 1.0 LANGUAGES CXX)

Buradan projemizin alt proje olarak başka bir projeye dahil olup olmadığını hemen kontrol edebilirsiniz. Bu gelecekte çok yardımcı olacaktır.

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Proje Seçenekleri

İki seçenek sunacağız.

İlk seçenek MYLIB_TESTING — birim testlerini devre dışı bırakmak için. Testlerde her şeyin yolunda olduğundan eminsek ancak örneğin yalnızca projemizi kurmak veya paketlemek istiyorsak bu gerekli olabilir. Veya projemiz bir alt proje olarak dahil edilmiştir - bu durumda projemizin kullanıcısı testlerimizi yürütmekle ilgilenmez. Kullandığınız bağımlılıkları test etmiyorsunuz, değil mi?

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

Ayrıca ayrı bir seçenek yapacağız MYLIB_COVERAGE kod kapsamını testlerle ölçmek için, ancak ek araçlar gerektireceğinden açıkça etkinleştirilmesi gerekecektir.

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

Derleme seçenekleri

Elbette biz harika artı programcılarız, bu nedenle derleyiciden maksimum düzeyde derleme zamanı tanılaması istiyoruz. Tek bir fare bile kaymaz.

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
)

C++ dil standardına tam uyum sağlamak için uzantıları da devre dışı bırakacağız. CMake'te varsayılan olarak etkindirler.

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

Ana hedef

Kütüphanemiz sadece başlık dosyalarından oluşuyor, yani statik veya dinamik kütüphaneler şeklinde herhangi bir egzozumuz yok. Öte yandan kütüphanemizi harici olarak kullanabilmek için yüklü olması, sistemde algılanabilir olması ve projenize bağlanması ve aynı zamanda aynı başlıkların yanı sıra muhtemelen bazı ek başlıkların olması gerekir. onun özelliklerine bağlıdır.

Bu amaçla bir arayüz kütüphanesi oluşturuyoruz.

add_library(mylib INTERFACE)

Başlıkları arayüz kütüphanemize bağlarız.

CMake'in modern, modaya uygun, gençlik kullanımı, başlıkların, özelliklerin vb. tek bir hedef üzerinden iletilir. yani şunu söylemem yeterli target_link_libraries(target PRIVATE dependency)ve hedefle ilişkili tüm başlıklar dependency, hedefe ait kaynaklar için mevcut olacak target. Ve hiçbirine ihtiyacın yok [target_]include_directories. Bu, aşağıda analizde gösterilecektir. Birim testleri için CMake betiği.

Ayrıca sözde dikkat etmeye değer. выражения-генераторы: $<...>.

Bu komut, ihtiyacımız olan başlıkları arayüz kitaplığımızla ilişkilendirir ve eğer kitaplığımız aynı CMake hiyerarşisi içindeki herhangi bir hedefe bağlıysa dizindeki başlıklar onunla ilişkilendirilir. ${CMAKE_CURRENT_SOURCE_DIR}/include, ve eğer kütüphanemiz sistemde kuruluysa ve komutu kullanarak başka bir projeye bağlıysa find_package, daha sonra dizindeki başlıklar onunla ilişkilendirilecek include kurulum dizinine göre.

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

Dil standardını belirleyelim. Tabii ki en sonuncusu. Aynı zamanda sadece standardı dahil etmiyoruz, kütüphanemizi kullanacak kişilere de yaygınlaştırıyoruz. Bu, set özelliğinin bir kategoriye sahip olması nedeniyle elde edilir. INTERFACE (Bkz. target_compile_features komutu).

target_compile_features(mylib INTERFACE cxx_std_17)

Kütüphanemiz için bir takma ad oluşturalım. Üstelik güzellik için özel bir “isim alanı” içinde olacak. Kütüphanemizde farklı modüller göründüğünde ve bunları birbirinden bağımsız olarak bağlamaya gittiğimizde bu faydalı olacaktır. Mesela Busta'daki gibi.

add_library(Mylib::mylib ALIAS mylib)

Montaj

Başlıklarımızı sisteme yüklüyoruz. Burada her şey basit. Tüm başlıkların bulunduğu klasörün dizine gitmesi gerektiğini söylüyoruz include Kurulum konumuna göre.

install(DIRECTORY include/mylib DESTINATION include)

Daha sonra derleme sistemine üçüncü taraf projelerde komutu çağırabilmek istediğimizi bildiriyoruz. find_package(Mylib) ve bir gol bul Mylib::mylib.

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

Bir sonraki büyü bu şekilde anlaşılmalıdır. Üçüncü taraf bir projedeyken komutu çağırıyoruz find_package(Mylib 1.2.3 REQUIRED)ve yüklü kitaplığın gerçek sürümü, sürümle uyumsuz olacaktır. 1.2.3CMake otomatik olarak bir hata üretecektir. Yani sürümleri manuel olarak takip etmenize gerek kalmayacak.

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)

Testler

Testler açıkça kullanılarak devre dışı bırakılırsa karşılık gelen seçenek veya projemiz alt projedir yani başka bir CMake projesine şu komutla bağlanır add_subdirectory, hiyerarşide daha fazla ilerlemeyiz ve testleri oluşturmaya ve çalıştırmaya yönelik komutları açıklayan komut dosyası çalışmaz.

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

Belgeleme

Bir alt proje durumunda da dokümantasyon oluşturulmayacaktır.

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

Çevrimiçi korumalı alan

Benzer şekilde alt projenin de çevrimiçi bir sanal alanı olmayacaktır.

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

Test komut dosyası (test/CMakeLists.txt)

Test

Öncelikle gerekli test çerçevesine sahip bir paket buluyoruz (en sevdiğinizle değiştirin).

find_package(doctest 2.3.3 REQUIRED)

Çalıştırılabilir dosyamızı testlerle oluşturalım. Genellikle doğrudan çalıştırılabilir ikili dosyaya yalnızca işlevi içerecek dosyayı eklerim main.

add_executable(mylib-unit-tests test_main.cpp)

Ve daha sonra testlerin açıklandığı dosyaları ekliyorum. Ama bunu yapmak zorunda değilsin.

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

Bağımlılıkları birbirine bağlıyoruz. Lütfen yalnızca ihtiyacımız olan CMake hedeflerini ikili dosyamıza bağladığımızı ve komutu çağırmadığımızı unutmayın. target_include_directories. Test çerçevesinden ve bizimkilerden başlıklar Mylib::mylib, ve derleme parametreleri (bizim durumumuzda bu, C++ dil standardıdır) bu hedeflerle birlikte ortaya çıktı.

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

Son olarak, "derlemesi" test çalıştırmaya eşdeğer olan yapay bir hedef oluştururuz ve bu hedefi varsayılan yapıya ekleriz (bundan nitelik sorumludur) ALL). Bu, varsayılan yapının testleri çalıştırmayı tetiklediği anlamına gelir; bu da onları çalıştırmayı asla unutmayacağımız anlamına gelir.

add_custom_target(check ALL COMMAND mylib-unit-tests)

Kapsama

Daha sonra uygun seçeneğin belirtilmesi durumunda kod kapsamı ölçümünü etkinleştiriyoruz. Ayrıntılara girmeyeceğim çünkü bunlar CMake'ten çok kapsamı ölçmek için kullanılan bir araçla ilgilidir. Sadece sonuçlara göre bir hedefin oluşturulacağını unutmamak önemlidir. coverageKapsamı ölçmeye başlamanın uygun olduğu.

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

Belgelendirme komut dosyası (doc/CMakeLists.txt)

Doxygen'i buldum.

find_package(Doxygen)

Daha sonra kullanıcının dil değişkenini ayarlayıp ayarlamadığını kontrol ederiz. Eğer evet ise, o zaman ona dokunmayız, değilse, o zaman Rusça alırız. Daha sonra Doxygen sistem dosyalarını yapılandırıyoruz. Dil de dahil olmak üzere gerekli tüm değişkenler yapılandırma işlemi sırasında oraya gider (bkz. команду configure_file).

Daha sonra bir hedef oluşturuyoruz doc, dokümantasyon oluşturmaya başlayacak. Belge oluşturmak geliştirme sürecindeki en büyük ihtiyaç olmadığından hedef varsayılan olarak etkinleştirilmeyecek; açıkça başlatılması gerekecek.

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

Çevrimiçi sanal alan için komut dosyası (çevrimiçi/CMakeLists.txt)

Burada üçüncü Python'u buluyoruz ve bir hedef oluşturuyoruz wandboxhizmet API'sine karşılık gelen bir istek üreten Asa kutusuve onu uzaklaştırır. Yanıt, tamamlanmış sanal alana bir bağlantıyla birlikte gelir.

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

Dışarıdaki proje

Şimdi tüm bunların nasıl kullanılacağına bakalım.

montaj

Bu projenin oluşturulması, CMake derleme sistemindeki diğer projeler gibi iki aşamadan oluşur:

nesil

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

CMake'in eski bir sürümü nedeniyle yukarıdaki komut işe yaramadıysa, atlamayı deneyin -S:

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

Seçenekler hakkında daha fazla bilgi.

Projeyi inşa etmek

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

Montaj hedefleri hakkında daha fazla bilgi.

Seçenekler

MYLIB_COVERAGE

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

Hedef içerir coveragetestlerle kod kapsamını ölçmeye başlayabilirsiniz.

MYLIB_TESTING

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

Birim testi derlemesini ve hedefini devre dışı bırakma yeteneği sağlar check. Sonuç olarak, testlerle kod kapsamının ölçümü kapatılır (bkz. MYLIB_COVERAGE).

Proje, komut kullanılarak başka bir projeye alt proje olarak bağlanırsa test de otomatik olarak devre dışı bırakılır. add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

Hedefin oluşturduğu belgelerin dilini değiştirir doc verilen kişiye. Kullanılabilir dillerin listesi için bkz. Doxygen sistemi web sitesi.

Rusça varsayılan olarak etkindir.

Montaj hedefleri

Varsayılan olarak

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

Hedef belirtilmemişse (ki bu hedefe eşdeğerdir) all), toplayabildiği her şeyi toplar ve aynı zamanda hedefi çağırır check.

mylib-birim-testleri

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

Birim testlerini derler. Varsayılan olarak etkindir.

Kontrol

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

Toplanan (henüz değilse toplanan) birim testlerini çalıştırır. Varsayılan olarak etkindir.

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

kapsama

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

Programı kullanarak yapılan testlerle kod kapsamı için çalışan birim testlerini analiz eder (henüz değilse çalıştırır) gcovr.

Kaplama egzozu şuna benzer:

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

Hedef yalnızca seçenek etkinleştirildiğinde kullanılabilir MYLIB_COVERAGE.

См. также check.

dok

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

Sistemi kullanarak kod belgelerinin oluşturulmasını başlatır Doxygen.

asa kutusu

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

Hizmetten gelen yanıt şuna benzer:

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

Hizmet bunun için kullanılıyor Asa kutusu. Sunucularının ne kadar esnek olduğunu bilmiyorum ama bu fırsatın suiistimal edilmemesi gerektiğini düşünüyorum.

Örnekler

Kapsam ölçümüyle projeyi hata ayıklama modunda oluşturun

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

Ön montaj ve test yapılmadan proje kurulumu

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

Belirli bir derleyiciyle yayın modunda derleme

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

İngilizce dokümantasyon oluşturma

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

Araçlar

  1. CMake 3.13

    Aslında CMake sürüm 3.13 yalnızca bu yardımda açıklanan konsol komutlarından bazılarını çalıştırmak için gereklidir. CMake komut dosyalarının sözdizimi açısından bakıldığında, nesil başka yollarla çağrılırsa sürüm 3.8 yeterlidir.

  2. Test kütüphanesi belge testi

    Test devre dışı bırakılabilir (bkz. опцию MYLIB_TESTING).

  3. Doxygen

    Dokümantasyonun oluşturulacağı dili değiştirmek için bir seçenek sağlanmıştır MYLIB_DOXYGEN_LANGUAGE.

  4. Dil tercümanı Python 3

    Otomatik nesil için çevrimiçi sanal alanlar.

Statik analiz

CMake ve birkaç iyi araçla minimum çabayla statik analiz sağlayabilirsiniz.

Cpp kontrolü

CMake, statik analiz aracı için yerleşik desteğe sahiptir Cpp kontrolü.

Bunu yapmak için seçeneği kullanmanız gerekir CMAKE_CXX_CPPCHECK:

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

Bundan sonra, kaynak her derlendiğinde ve yeniden derlendiğinde statik analiz otomatik olarak başlatılacaktır. İlave bir şey yapmaya gerek yoktur.

çınlama

Harika bir aracın yardımıyla scan-build Ayrıca statik analizi hemen çalıştırabilirsiniz:

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

Burada, Cppcheck'teki durumun aksine, derlemeyi her seferinde çalıştırmanız gerekir. scan-build.

Послесловие

CMake, her zevke ve renge uygun işlevsellik uygulamanıza olanak tanıyan çok güçlü ve esnek bir sistemdir. Ve sözdizimi bazen arzulanan çok şey bıraksa da, şeytan hâlâ resmedildiği kadar korkunç değildir. CMake derleme sistemini toplumun ve sağlığın yararına kullanın.

Proje şablonunu indir

Kaynak: habr.com

Yorum ekle