C++ 編譯依賴管理系統分析以及 srcdep 介紹
如果用 C++ 寫一箇中小型軟體,有要用到很多第三方庫的話,相信不少人會覺得比較麻煩。很多新興的語言都有了統一的依賴管理系統和構建系統,但是 C/C++ 界一直沒有比較正統的。(也不奇怪,連統一的 string 都沒有,怎麼可能有統一的依賴、構建體系?)
在上一篇,我們嘗試選擇一個構建體系的時候,一開始覺得 CMake 比較接近事實標準了。同樣,CMake 也正在嘗試把手伸到依賴管理上面。CMake 的理念一開始起源於 makefile,其實是比較簡單、乾淨的,一個包有 include_dir、lib_dir 等,然後就可以構建了。但我的觀點還是,Moedern CMake 把這一切都搞複雜了,它嘗試物件導向地解決依賴問題+構建問題。但是它在 include_dir、lib_dir 之上發明了很多新的概念,增加了學習成本,掩蓋了底層細節——C++er 一般不喜歡被隱藏細節,你最好及解決問題也讓我知道是怎麼解決的。再加上 CMake 相對另類的語法以及看不懂的文件這兩個 debuff,導致學習成本比起一般新事物高得多。所以,儘管它在市佔率上可能ou 接近事實標準,我們還是把它當成一個普通的系統來看待,不給特殊待遇。何況,大家用 CMake 來構建的比例有多少、用來管理依賴的又有多少呢,說不清楚,我也沒調查過。
C++ 領域,市面上是有一些的依賴管理系統的,但可能都沒有形成大一統。我覺得可以按這幾個角度去做分類:
- 原始碼依賴還是二進位制依賴
- 是否需要包倉庫伺服器
- 是否與構建系統繫結
- 依賴包跟系統還是跟專案
依賴管理系統 | 原始碼依賴還是二進位制依賴 | 是否需要包倉庫伺服器 | 是否與構建系統繫結 | 依賴包跟系統還是跟專案 |
---|---|---|---|---|
git submodule git subtree |
原始碼 | 否 | 否 | 跟專案 |
cmake | 原始碼 | 否 | 是 | 跟專案 |
vcpkg | 二進位制 | 是 | 否,但一般和 cmake 配合 | 跟系統 |
conan | 二進位制 | 是 | 否,但一般和 cmake 配合 | 跟專案 |
gclient | 原始碼 | 否 | 否,但 google 未做開放性適配 | 跟專案 |
見識有限,我知道的大概有這些,如果其他的大家可以補充,開闊開闊眼界。
然後怎麼選呢?我想提出幾條規則,然後做分析。
第一,要原始碼依賴,不要二進位制依賴。
因為 C++ 各平臺編譯方式不盡相同,即使同一平臺,也可以有不同的編譯器引數、宏定義等。同時,也不存在二進位制相容性。因此,二進位制依賴會有很多問題。除非已選定特定平臺特定引數,才能有效地實行二進位制依賴。從通用性角度上講,原始碼依賴是合理的。
第二,不要自建倉庫的。
首先,一般依賴系統想要自建倉庫,形成生態,本來就非常難,需要由大廠牽頭或者知名社群領軍人物牽頭。在 C++ 領域,牛人隱士頗多,一個人、一個組織或一家公司,想要一言九鼎進行宣傳、號召,更難。
其次,自建倉庫需要將每個包進行標準化。這是一項不可能完成的工作。很多程式碼的歷史堪比計算機歷史,尊重其原作者的編譯方式是最相容、風險最低的方式。
最後,從開發者角度來說,去每個軟體的官網引用其程式碼是最安全、放心的做法。從某個依賴體系的中心化倉庫去引用,總是會有擔心。
從實際來看,即使現在生態最好的 vcpkg 和 conan,也只有一兩千的包量,相比 npm、maven,實在是零頭。
按這兩條規則,排除了目前如日中天的 vcpkg 和 conan。剩下的裡面,cmake 的 FetchContent 是和 cmake 強繫結的,如果都用 cmake 一條龍,那麼選它。直接用 git submodule 或 subtree,也是能當依賴系統用的,只是可能沒那麼方便和直觀,也不知道什麼原因導致業界沒這麼用?gclient 其實是理念上最符合的,它沒 cmake 那麼晦澀、抽象,而是直截了當地配置什麼包,從哪裡下載,放到專案的哪裡。但是 google 沒有特意推廣的意圖,主要還是為 chrome 及其他周邊專案服務。
所以呢,筆者按這個理念要自己寫一個,只管從哪兒下載、放到本地哪裡,把 gclient 的 runhook 也去掉,只有 sync。
起個名字,叫 srcdep,強調原始碼依賴,專案地址為 https://github.com/Streamlet/srcdep
用法就是在專案跟目錄建立一個 SRCDEP.yaml,內容為
DEPS:
path/to/local/directory: # 第一個包的目標目錄
# GIT 依賴
# 需要配置 GET_REPO 和 GIT_TAG
GIT_REPO: url_of_git_repo
GIT_TAG: git_tag_or_branch_or_commit
path/to/another/directory: # 第二個包的目標目錄
# 普通 URL 依賴
# 需要至少配置一個 URL
URL: package_url
# 如果 URL 不是一個正常的副檔名結尾,那麼需要配一下 URL_FORMAT,以便知道怎麼解壓
URL_FORMAT: tar.gz
# 如果包解壓出來是一個目錄,但我們們需要把這個目錄下面的檔案直接丟到 path/to/another/directory
# 那麼配置一下 ROOT_DIR,意思是包內的根目錄名稱,需要把這個目錄視為包的根目錄
ROOT_DIR: root_dir_in_archive
# 校驗方式,支援 MD5、SHA1、SHA224、SHA256、SHA384、SHA512
URL_HASH:
SHA256: sha256_hash_of_the_package
然後用 python 實現,把 srcdep 的目錄丟到 PATH 環境變數裡,在專案裡執行一把,就下載所有依賴包。
跟構建完全分離,構建可以走上一節的 gn+ninja。
這樣,我們完成了 C++ 下快速開發小型元件和小型應用的基礎設施的搭建。