在開發過程中,我喜歡更改編譯器、建置模式、依賴版本、執行靜態分析、測量效能、收集覆蓋率、產生文件等。我真的很喜歡 CMake,因為它允許我做我想做的一切。
很多人批評 CMake,而且常常是當之無愧的,但如果你仔細看看,你會發現並不是一切都那麼糟糕,最近 一點也不差,而且發展方向是相當正面的。
在這篇筆記中,我想告訴你如何在CMake系統中簡單地組織一個C++的頭庫來獲得以下功能:
- 集會;
- 自動運行測試;
- 代碼覆蓋率測量;
- 安裝;
- 自動記錄;
- 線上沙箱生成;
- 靜態分析。
任何已經了解優勢和 C-make 的人都可以簡單地
下載專案模板 並開始使用它。
Содержание
從內部進行專案
項目結構
.
├── 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_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(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.3
CMake會自動產生錯誤。也就是說,您不需要手動追蹤版本。
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)
測試
如果使用明確停用測試 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)
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
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
預設情況下啟用俄語。
組裝目標
默認情況下,
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
使用系統開始產生程式碼文檔
魔杖盒
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
工具
-
CMake的 3.13事實上,CMake 版本 3.13 僅需要執行本說明中所述的一些控制台指令。從CMake腳本語法的角度來看,如果以其他方式呼叫生成,3.8版本就足夠了。
-
測試庫
文檔測試 可以停用測試(請參閱
).опцию MYLIB_TESTING
-
要切換生成文件的語言,提供了一個選項
.MYLIB_DOXYGEN_LANGUAGE
-
語言翻譯
Python的3 用於自動生成
線上沙箱 .
靜態分析
借助 CMake 和一些優秀的工具,您可以輕鬆提供靜態分析。
Cpp檢查
CMake 內建了靜態分析工具的支持
為此,您需要使用該選項 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