C++ 編譯依賴管理系統分析以及 srcdep 介紹

2023-01-11 12:00:44

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++ 下快速開發小型元件和小型應用的基礎設施的搭建。