使用 C++ 實現一個簡單的 Coding Agent: CMake 專案的建立
在 使用 C++ 實現一個簡單的 Coding Agent: 從 curl 呼叫開始 一文中,我們說明了如何透過 curl 去做出簡單的 chat 功能,現在我們想要將這個功能改成用 C++ 實做,因此就來建立我們的專案。
從 CMake 開始
CMake 是一個跨平台的建置系統工具,現在大部分的 C++ 專案(除了 GNOME 體系偏好使用 meson、Google 體系使用 GN 或 Bazel 之外),幾乎都會採用 CMake 作為主要的專案管理與編譯工具。 (Qt6 已經從 qmake 遷移到 CMake 了)
因此我們需要知道一些 CMake 的基本知識,好方便後續文章的進行。
專案結構
在後續的文章中,我們基本上會使用這樣的結構
simple-agent/
├── CMakeLists.txt # 專案的 CMake 設定(入口檔)
├── options.cmake # 專案依賴以及編譯器設置
├── cmake/ # 自訂 CMake 模組(輔助 .cmake 檔案)
│ ├── clang-format.cmake
│ ├── gtest.cmake
│ └── fmt.cmake
├── src/ # 真正的原始碼
│ ├── CMakeLists.txt # src 子目錄的 CMake
│ ├── main.cpp # 程式進入點
│ ├── agent.cpp
│ ├── ...
│ └── tools/ # 子功能(讀檔、改檔等工具)
│ ├── CMakeLists.txt
│ ├── read_file.cpp
│ ├── write_file.cpp
│ └── ...
├── tests/ # 單元測試 (optional)
├── .gitignore
├── README.md
└── ...根目錄的 CMakeLists.txt : 專案的入口
最上層的 CMakeLists.txt 是整個專案的起點,因此我們需要指定使用的 CMake 版本以及一些專案基本的設定,更多設定比如依賴之類的,會切分到 options.cmake 去
cmake_minimum_required(VERSION 3.16) # 指定最低 CMake 版本
project(simple_agent LANGUAGES C CXX) # 專案名稱與使用語言
include (xxx.cmake) # 有些要用 cmake 實作的複雜設定抽成 cmake 模組
include (options.cmake) # 載入共用設定
add_subdirectory(src) # 加入 src/ 為子專案options.cmake : 編譯選項與第三方套件
options.cmake 是這個專案最核心的設定檔。我們分段來看。
載入需要的 CMake 模組
INCLUDE (CheckLibraryExists)
INCLUDE (CheckIPOSupported)
INCLUDE (FindThreads)CMake 內建了許多模組,這些 Check* 模組可以用來檢查某個功能是否被編譯器支援(例如 LTO)。 FindThreads 則負責尋找系統的 pthread 函式庫。
設定語言標準
SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)整個專案指定使用 C++17 ,並且強制要求編譯器必須支援( REQUIRED ON ),因此如果是不支援的編譯器編譯就會報錯
啟用 LTO 最佳化
CHECK_IPO_SUPPORTED(RESULT BUILD_WITH_LTO OUTPUT ERROR_MSG)
IF(BUILD_WITH_LTO)
SET(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
ELSE()
MESSAGE(WARNING "LTO unavailable: ${ERROR_MSG}")
ENDIF()這邊會先檢查編譯器是否支援 LTO(Link-Time Optimization),如果有就啟用它。LTO 可以讓編譯器在連結階段做跨編譯單元的最佳化,可以縮減 binary 大小以及提升效率。
尋找第三方套件
對我們即將撰寫的 agent 來說,我們會需要用到 curl 、nlohmann_json 這幾個函式庫,其中 curl 負責發送 HTTP請求,大部分 Linux 系統皆內建,而 nlohmann_json 則是 C++ 上非常通用的 json 解析函式庫。
在 CMake 中,最簡單的引入方式是透過 find_package ,這會從你電腦系統已經安裝的套件裡面去尋找這些函式庫是否存在。
FIND_PACKAGE(curl REQUIRED)REQUIRED 表示如果找不到這些套件,CMake 會直接報錯並停止,這樣可以及早發現問題而不是等到連結階段才失敗。
如果你的環境還沒有安裝這些套件,可以用 FetchContent 來自動下載、編譯,以 nlohmann_json 為例,我會這樣寫:
#-------------------------------------------------------------------------------
# Third-party dependencies
#-------------------------------------------------------------------------------
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)src/CMakeLists.txt : src 子專案
進入 src/ 目錄後,我們需要告訴 CMake 這裡面有哪些東西要編譯:
建立主程式
ADD_EXECUTABLE(simple_agent
main.cpp
agent.cpp
message.cpp
)ADD_EXECUTABLE 會把列出的 .cpp 檔編譯、連結成一個執行檔 simple_agent 。
連結所需的函式庫
TARGET_LINK_LIBRARIES(simple_agent PRIVATE
${CURL_LIBRARIES}
nlohmann_json::nlohmann_json
)PRIVATE 表示這些依賴只屬於 simple_agent 自己,不會傳染給任何依賴 simple_agent 的目標。
(如果在撰寫 lib 時候,讓自己寫的 lib 依賴全部透露給外部知道,則最終的執行檔依賴路徑會變得很複雜,因此好習慣就是只洩漏最低需要依賴的資訊即可)
簡單的 CMake 專案
上面介紹的是我們即將實作的 simple-agent 裡面部分的 cmake 結構,稍微看懂後我們就可以弄一個最最簡單的 CMake 專案跑看看,就像這樣
建立一個名為 hello 的資料夾並切入進去:
mkdir hello && cd $_建立 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(hello)
set(CMAKE_CXX_STANDARD 17)
add_executable(hello hello.cpp)建立 hello.cpp
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}設定 cmake 並開始編譯
cmake -B build -S . # 告訴 cmake 我們要建立 build 資料夾用來做編譯設定,編譯專案是當前資料夾
cmake --build build # 進行編譯測試執行
$ ./build/hello
Hello, Wolrd!看,我們程式順利建立並執行囉 :)
範例程式碼
本文的程式碼已經上線,你可以用以下命令取得,對應的 tag 為 v0.1.0, happy coding :)
git clone https://github.com/coldnew/simple-agent.git
cd simple-agent
git checkout v0.1.0