使用 CMake 和 VSCodium 設置一個構建系統(tǒng)

提供一個適當?shù)?CMake 配置文件來使其他人可以更容易地構建、使用和貢獻你的項目。
這篇文章是使用開源 DevOps 工具進行 C/C++ 開發(fā)系列文章的一部分。如果你從一開始就把你的項目建立在一個功能強大的工具鏈上,你的開發(fā)會更快和更安全。除此之外,這會使別人更容易地參與你的項目。在這篇文章中,我將搭建一個基于 ??CMake?? 和 ??VSCodium?? 的 C/C++ 構建系統(tǒng)。像往常一樣,相關的示例代碼可以在 ??GitHub?? 上找到。
我已經(jīng)測試了在本文中描述的步驟。這是一種適用于所有平臺的解決方案。
為什么用 CMake ?
??CMake?? 是一個構建系統(tǒng)生成器,可以為你的項目創(chuàng)建
Makefile。乍一看簡單的東西可能相當?shù)貜碗s。在較高的層次上,你可以定義你的項目的各個部分(可執(zhí)行文件、庫)、編譯選項(C/C++
標準、優(yōu)化、架構)、依賴關系項(頭文件、庫),和文件級的項目結構。CMake 使用的這些信息可以在文件 ??CMakeLists.txt?? 中獲取,它使用一種特殊的描述性語言編寫。當 CMake 處理這個文件時,它將自動地偵測在你的系統(tǒng)上已安裝的編譯器,并創(chuàng)建一個用于啟動它的 Makefile 文件。
此外,在 ??CMakeLists.txt?? 中描述的配置,能夠被很多編輯器讀取,像 QtCreator、VSCodium/VSCode 或 Visual Studio 。
示例程序
我們的示例程序是一個簡單的命令行工具:它接受一個整數(shù)來作為參數(shù),輸出一個從 1 到所提供輸入值的范圍內(nèi)的隨機排列的數(shù)字。
$ ./Producer 10
3 8 2 7 9 1 5 10 6 4
在我們的可執(zhí)行文件中的 ??main()?? 函數(shù),我們只處理輸入的參數(shù),如果沒有提供一個值(或者一個不能被處理的值)的話,就退出程序。
int main(int argc, char** argv){
if (argc != 2) {
std::cerr << "Enter the number of elements as argument" << std::endl;
return -1;
}
int range = 0;
try{
range = std::stoi(argv[1]);
}catch (const std::invalid_argument&){
std::cerr << "Error: Cannot parse \"" << argv[1] << "\" ";
return -1;
}
catch (const std::out_of_range&) {
std::cerr << "Error: " << argv[1] << " is out of range";
return -1;
}
if (range <= 0) {
std::cerr << "Error: Zero or negative number provided: " << argv[1];
return -1;
}
std::stringstream data;
std::cout << Generator::generate(data, range).rdbuf();
}
producer.cpp
實際的工作是在 ??生成器?? 中完成的,它將被編譯,并將作為一個靜態(tài)庫來鏈接到我們的??Producer?? 可執(zhí)行文件。
std::stringstream &Generator::generate(std::stringstream &stream, const int range) {
std::vector<int> data(range);
std::iota(data.begin(), data.end(), 1);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(data.begin(), data.end(), g);
for (const auto n : data) {
stream << std::to_string(n) << " ";
}
return stream;
}
Generator.cpp
函數(shù) ??generate?? 引用一個 ??std::stringstream?? 和一個整數(shù)來作為一個參數(shù)。根據(jù)整數(shù) ??range?? 的值 ??n??,制作一個在 ??1?? 到 ??n?? 的范圍之中的整數(shù)向量,并隨后打亂。接下來打亂的向量值轉換成一個字符串,并推送到 ??stringstream?? 之中。該函數(shù)返回與作為參數(shù)傳遞相同的 ??stringstream?? 引用。
頂層的 CMakeLists.txt
頂層的 ??CMakeLists.txt?? 的是我們項目的入口點。在子目錄中可能有多個 ??CMakeLists.txt?? 文件(例如,與項目所相關聯(lián)的庫或其它可執(zhí)行文件)。我們先一步一步地瀏覽頂層的 ??CMakeLists.txt??。
第一行告訴我們處理文件所需要的 CMake 的版本、項目名稱及其版本,以及預定的 C++ 標準。
cmake_minimum_required(VERSION 3.14)
project(CPP_Testing_Sample VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
我們用下面一行告訴 CMake 去查看子目錄 ??Generator??。這個子目錄包括構建 ??Generator?? 庫的所有信息,并包含它自身的一個 ??CMakeLists.txt?? 。我們很快就會談到這個問題。
add_subdirectory(Generator)
現(xiàn)在,我們將涉及一個絕對特別的功能: ??CMake 模塊?? 。加載模塊可以擴展 CMake 功能。在我們的項目中,我們加載了 ??FetchContent?? 模塊,這能使我們能夠在 CMake 運行時下載外部的資源,在我們的示例中是 ??GoogleTest?? 。
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/bb9216085fbbf193408653ced9e73c61e7766e80.zip
)
FetchContent_MakeAvailable(googletest)
在接下來的部分中,我們會做一些我們通常在普通的 Makefile 中會做的事: 指定要構建的二進制文件、它們相關的源文件、應該鏈接的庫,以及編譯器可以找到頭文件的目錄。
add_executable(Producer Producer.cpp)
target_link_libraries(Producer PUBLIC Generator)
target_include_directories(Producer PUBLIC "${PROJECT_BINARY_DIR}")
通過下面的語句,我們使 CMake 來在構建文件夾中創(chuàng)建一個名稱為 ??compile_commands.json?? 的文件。這個文件會展示項目的每個文件的編譯器選項。在 VSCodium 中加載該文件,會告知 IntelliSense 功能在哪里查找頭文件(查看 ??文檔??)。
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
最后的部分為我們的項目定義一些測試。項目使用先前加載的 GoogleTest 框架。單元測試的整個話題將會劃歸到另外一篇文章。
enable_testing()
add_executable(unit_test unit_test.cpp)
target_link_libraries(unit_test gtest_main)
include(GoogleTest)
gtest_discover_tests(unit_test)
庫層次的 CMakeLists.txt
現(xiàn)在,我們來看看包含同名庫的子目錄 ??Generator?? 中的 ??CMakeLists.txt?? 文件。這個 ??CMakeLists.txt?? 文件的內(nèi)容更簡短一些,除了單元測試相關的命令外,它僅包含 2 條語句。
add_library(Generator STATIC Generator.cpp Generator.h)
target_include_directories(Generator INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
我們使用 ??add_library(...)?? 來定義一個新的構建目標:靜態(tài)的 ??Generator?? 庫。我們使用語句 ??target_include_directories(...)?? 來把當前子目錄添加到其它構建目標的頭文件的搜索路徑之中。我們也具體指定這個屬性的范圍為類型 ??INTERFACE??:這意味著該屬性僅影響鏈接到這個庫的構建目標,而不是庫本身。
開始使用 VSCodium
通過使用 ??CMakeLists.txt?? 文件中的信息,像 VSCodium 一樣的 IDE 可以相應地配置構建系統(tǒng)。如果你還沒有使用 VSCodium 或 VS Code 的經(jīng)驗,這個示例項目會是一個很好的起點。首先,轉到它們的 ??網(wǎng)站?? ,然后針對你的系統(tǒng)下載最新的安裝軟件包。打開 VSCodium 并導航到 “擴展Extensions” 標簽頁。
為了正確地構建、調(diào)試和測試項目,搜索下面的擴展并安裝它們:

Searching extensions
如果尚未完成,通過單擊起始頁的 “克隆 Git 存儲庫Clone Git Repository” 來克隆存儲庫。

Clone Git repository
或者手動輸入:
git clone https://github.com/hANSIc99/cpp_testing_sample.git
之后,通過輸入如下內(nèi)容來簽出標簽 ??devops_1??:
git checkout tags/devops_1
或者,通過單擊 “main” 分支按鈕(紅色框),并從下拉菜單(黃色框)中選擇標簽。

Select devops_1 tag
在你打開 VSCodium 內(nèi)部中的存儲庫的根文件夾后,CMake Tools 擴展會偵測 ??CMakeLists.txt?? 文件并立即掃描你的系統(tǒng)尋找合適的編譯器。你現(xiàn)在可以單擊屏幕的底部的 “構建Build” 按鈕(紅色框)來開始構建過程。你也可以通過單擊底部區(qū)域的按鈕(黃色框)標記來更改編譯器,它顯示當前活動的編譯器。

Build compiler
要開始調(diào)試 ??Producer?? 可執(zhí)行文件,單擊調(diào)試器符號(黃色框)并從下拉菜單中選擇 “調(diào)試Debug Producer”(綠色框)。

Starting the debugger
如上所述,??Producer?? 可執(zhí)行文件要求將元素的數(shù)量作為一個命令行的參數(shù)。命令行參數(shù)可以在 ??.vscode/launch.json?? 中具體指定。

Command-line arguments
好了,你現(xiàn)在能夠構建和調(diào)試項目了。
結束語
歸功于 CMake ,不管你正在運行哪種操作系統(tǒng),上述步驟應該都能工作。特別是使用與 CMake 相關的擴展,VSCodium
變成了一個強大的 IDE 。我沒有提及 VSCodium 的 Git
集成,是因為你已經(jīng)能夠在網(wǎng)絡上查找很多的資源。我希望你可以看到:提供一個適當?shù)?CMake
配置文件可以使其他人更容易地構建、使用和貢獻于你的項目。在未來的文章中,我將介紹單元測試和 CMake 的測試實用程序 ??ctest?? 。






























