CMake 和 C++ 是永遠的兄弟

CMake 和 C++ 是永遠的兄弟

在開發過程中,我喜歡更改編譯器、建置模式、依賴版本、執行靜態分析、測量效能、收集覆蓋率、產生文件等。我真的很喜歡 CMake,因為它允許我做我想做的一切。

很多人批評 CMake,而且常常是當之無愧的,但如果你仔細看看,你會發現並不是一切都那麼糟糕,最近 一點也不差,而且發展方向是相當正面的。

在這篇筆記中,我想告訴你如何在CMake系統中簡單地組織一個C++的頭庫來獲得以下功能:

  1. 集會;
  2. 自動運行測試;
  3. 代碼覆蓋率測量;
  4. 安裝;
  5. 自動記錄;
  6. 線上沙箱生成;
  7. 靜態分析。

任何已經了解優勢和 C-make 的人都可以簡單地 下載專案模板 並開始使用它。


Содержание

  1. 從內部進行專案
    1. 項目結構
    2. 主 CMake 檔案 (./CMakeLists.txt)
      1. 專案資訊
      2. 項目選項
      3. 編譯選項
      4. 主要目的
      5. 安裝
      6. 測試
      7. Документация
      8. 線上沙箱
    3. 測試腳本(test/CMakeLists.txt)
      1. 測試
      2. Покрытие
    4. 文件腳本 (doc/CMakeLists.txt)
    5. 線上沙箱腳本(online/CMakeLists.txt)
  2. 外部專案
    1. 裝配
      1. 裝配
    2. 選項
      1. MYLIB_COVERAGE
      2. MYLIB_測試
      3. MYLIB_DOXYGEN_LANGUAGE
    3. 組裝目標
      1. 默認情況下,
      2. mylib 單元測試
      3. 覆蓋
      4. DOC
      5. 魔杖盒
    4. Примеры
  3. 工具
  4. 靜態分析
  5. 後記

從內部進行專案

項目結構

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

我們主要講如何組織CMake腳本,所以會詳細討論。任何人都可以直接查看其餘文件 在範本項目頁面上.

主 CMake 檔案 (./CMakeLists.txt)

專案資訊

首先,需要請求所需的CMake系統版本。 CMake 正在不斷發展,不同條件下的命令簽章和行為正在改變。為了讓 CMake 立即了解我們想要從中得到什麼,我們需要立即記錄我們對它的要求。

cmake_minimum_required(VERSION 3.13)

然後我們將指定我們的項目,它的名稱、版本、使用的語言等(參見. команду project).

在這種情況下,我們指定語言 CXX (這意味著 C++),這樣 CMake 就不會緊張並蒐索 C 語言編譯器(預設情況下,CMake 包括兩種語言:C 和 C++)。

project(Mylib VERSION 1.0 LANGUAGES CXX)

在這裡您可以立即檢查我們的項目是否作為子項目包含在另一個項目中。這對未來會有很大幫助。

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

項目選項

我們將提供兩種選擇。

第一個選項是 MYLIB_TESTING — 停用單元測試。如果我們確定測試一切正常,那麼這可能是必要的,但我們只想安裝或打包我們的專案。或者我們的專案作為子項目包含在內 - 在這種情況下,我們專案的用戶對運行我們的測試不感興趣。您沒有測試您使用的依賴項,對嗎?

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

另外,我們會單獨做一個選項 MYLIB_COVERAGE 用於透過測試測量程式碼覆蓋率,但它需要額外的工具,因此需要明確啟用。

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

編譯選項

當然,我們是酷程式設計師,因此我們希望編譯器能夠進行最高層級的編譯時診斷。沒有一隻老鼠會溜過去。

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++ 語言標準。它們在 CMake 中預設為啟用。

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

主要目的

我們的庫僅由頭文件組成,這意味著我們沒有靜態或動態庫形式的任何消耗。另一方面,為了在外部使用我們的庫,需要安裝它,需要在系統中可檢測到它並連接到您的項目,同時這些相同的標頭,以及可能的一些附加標頭,附加到它的屬性。

為此,我們創建了一個介面庫。

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 相對於安裝目錄。

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

讓我們制定一個語言標準。當然是最後一張了。同時,我們不僅包含該標準,還將其擴展到將使用我們的庫的人。這是由於 set 屬性具有類別這一事實而實現的 INTERFACE (見。 target_compile_features 指令).

target_compile_features(mylib INTERFACE cxx_std_17)

讓我們為我們的庫建立一個別名。而且,為了美觀,它將位於一個特殊的「命名空間」中。當我們的庫中出現不同的模組並且我們將它們彼此獨立地連接時,這將很有用。 例如在布斯塔.

add_library(Mylib::mylib ALIAS mylib)

安裝

將我們的標頭安裝到系統中。這裡一切都很簡單。我們說包含所有標題的資料夾應該進入目錄 include 相對於安裝位置。

install(DIRECTORY include/mylib DESTINATION include)

接下來,我們通知建置系統我們希望能夠在第三方專案中呼叫該命令 find_package(Mylib) 並獲得一個目標 Mylib::mylib.

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

接下來的咒語應該是這樣理解的。當在第三方專案中我們呼叫命令 find_package(Mylib 1.2.3 REQUIRED),並且安裝的庫的真實版本會與版本不相容 1.2.3CMake會自動產生錯誤。也就是說,您不需要手動追蹤版本。

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)

測試

如果使用明確停用測試 相應選項 或者我們的項目是一個子項目,即使用指令連接到另一個CMake項目 add_subdirectory,我們不會沿著層次結構進一步移動,並且描述用於產生和運行測試的命令的腳本根本不會運行。

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

Документация

如果是子項目,也不會產生文件。

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

線上沙箱

同樣,子項目也不會有線上沙箱。

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

測試腳本(test/CMakeLists.txt)

測試

首先,我們找到一個包含所需測試框架的套件(替換為您最喜歡的框架)。

find_package(doctest 2.3.3 REQUIRED)

讓我們透過測試來建立可執行檔。通常我只將包含該函數的檔案直接新增到可執行二進位檔案中 main.

add_executable(mylib-unit-tests test_main.cpp)

我添加了稍後描述測試本身的文件。但你不必這樣做。

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

我們連結依賴關係。請注意,我們僅將所需的 CMake 目標連結到二進位文件,並且沒有呼叫命令 target_include_directories。來自測試框架和我們的標題 Mylib::mylib以及建構參數(在我們的例子中,這是 C++ 語言標準)與這些目標一起實現。

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

最後,我們建立一個虛擬目標,其「建置」相當於執行測試,並將此目標新增至預設建置(該屬性負責此操作) 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()

文件腳本 (doc/CMakeLists.txt)

發現 Doxygen.

find_package(Doxygen)

接下來,我們檢查使用者是否設定了語言變數。如果是,那麼我們就不碰它,如果不是,那我們就學俄語。然後我們配置 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 ()

線上沙箱腳本(online/CMakeLists.txt)

這裡我們找到第三個Python並創建一個目標 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 建置系統上的任何其他專案一樣,建置此專案由兩個階段組成:

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

如果由於舊版本的 CMake,上述命令不起作用,請嘗試省略 -S:

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

有關選項的更多信息.

建構專案

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

有關裝配目標的更多信息.

選項

MYLIB_COVERAGE

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

包括目標 coverage,您可以開始透過測試來測量程式碼覆蓋率。

MYLIB_測試

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

提供禁用單元測試建置和目標的能力 check。結果,測試對程式碼覆蓋率的測量被關閉(請參閱。 MYLIB_COVERAGE).

如果使用命令將項目作為子項目連接到另一個項目,測試也會自動停用 add_subdirectory.

MYLIB_DOXYGEN_LANGUAGE

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

切換目標產生的文檔的語言 doc 到給定的一個。有關可用語言的列表,請參閱 Doxygen系統網站.

預設情況下啟用俄語。

組裝目標

默認情況下,

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

如果未指定目標(相當於目標 all),收集它所能收集的一切,也調用目標 check.

mylib 單元測試

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

編譯單元測試。預設啟用。

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

運行收集的(收集的,如果尚未收集的)單元測試。預設啟用。

另見 mylib-unit-tests.

覆蓋

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

透過使用程式的測試來分析正在運行(如果尚未運行)單元測試的程式碼覆蓋率 總冠狀病毒.

塗層排氣看起來像這樣:

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

僅當啟用該選項時目標才可用 MYLIB_COVERAGE.

另見 check.

DOC

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

使用系統開始產生程式碼文檔 Doxygen的.

魔杖盒

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

該服務的回應如下所示:

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

該服務用於此目的 魔杖盒。我不知道他們的伺服器有多靈活,但我認為這個機會不應該被濫用。

Примеры

透過覆蓋率測量在調試模式下建立項目

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

無需預先組裝和測試即可安裝項目

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

使用給定的編譯器以發布模式構建

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

產生英文文檔

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

工具

  1. CMake的 3.13

    事實上,CMake 版本 3.13 僅需要執行本說明中所述的一些控制台指令。從CMake腳本語法的角度來看,如果以其他方式呼叫生成,3.8版本就足夠了。

  2. 測試庫 文檔測試

    可以停用測試(請參閱 опцию MYLIB_TESTING).

  3. Doxygen的

    要切換生成文件的語言,提供了一個選項 MYLIB_DOXYGEN_LANGUAGE.

  4. 語言翻譯 Python的3

    用於自動生成 線上沙箱.

靜態分析

借助 CMake 和一些優秀的工具,您可以輕鬆提供靜態分析。

Cpp檢查

CMake 內建了靜態分析工具的支持 Cpp檢查.

為此,您需要使用該選項 CMAKE_CXX_CPPCHECK:

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

此後,每次編譯和重新編譯原始碼時都會自動啟動靜態分析。無需執行任何額外操作。

在一個很棒的工具的幫助下 scan-build 您也可以立即執行靜態分析:

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

在這裡,與 Cppcheck 的情況不同,您需要每次都運行構建 scan-build.

後記

CMake 是一個非常強大且靈活的系統,可讓您實現適合各種品味和顏色的功能。而且,儘管語法有時還有很多不足之處,但魔鬼仍然不像描繪的那麼可怕。使用 CMake 建構系統造福社會和健康。

下載專案模板

來源: www.habr.com

添加評論