今天會解釋 Conan 幾個重要的名詞:Package ID、Package Revision、Recipe Revision。
看起來很嚇人嗎?其實不會啦,看下去就知道了。
我們都知道 C++ 套件管理是個特別麻煩的東西,尤其是處理二進位檔的時候,同一份 fmt/12.2.0 的原始碼,編譯出來一份給 Windows、一份給 macOS,Debug/Release 又各要一份,同平台上的每個編譯器版本又各一份 (gcc10/13、Xcode16/26、Visual Studio 2019/2022)。
Conan 要怎麼知道該拿哪一份二進位檔?
答案就是透過這些 Package ID、Revision 之類的東西來分辨的。

今天的目標:搞懂 Recipe Revision、Package ID、Package Revision 這三個東西各代表什麼,還有 Package ID是怎麼算出來的。
如果你之前跑過這個系列的範例,請再跑一次 conan install 指令:
conan install . --build=missing
看輸出的這一行:
fmt/12.2.0#b445244e6d0151a425e229248cd2bed8:8db65e2827b8126426a61b6cb7bf0c8a67f5915d#7c7bb68c3f6404d3a32a9e4682a011d9 - Cache
哇,除了套件版本號碼(fmt/12.2.0)以外,後面還有一個好長的隨機字串!
這是在我的 MacBook Air (Xcode26) 上跑出來的結果,你看到的字串可能會跟我的不一樣。
仔細看,這一串字可以拆成四段結構:
fmt/12.2.0 是套件名稱和版本。#b445244e... 是 Recipe revision,也就是 recipe 的版本。:8db65e28... 是 Package ID,代表是哪一個二進位檔,這是今天的主角。#7c7bb68c... 是 Package revision,也就是這個二進位檔的版本。
這一串字才是完整的套件,接下來,我們一段一段看。
Recipe 這個字的中文意思是「食譜」,因為準備套件的過程,很像照著食譜上的步驟,一步一步進行烹煮,最後完成一道料理,是很生動的比喻。
所以我們用 Recipe 來統稱 conanfile.py 以及一些打包套件的周邊輔助檔案。
Conan 官方幫 fmt 寫了一份食譜,裡面寫著 fmt 的原始碼去哪裡下載、要怎麼編譯、編完之後哪些檔案要打包起來。然後官方就用這份食譜實際去打包 fmt 產生二進位檔,然後我們才有 fmt 套件可以直接抓下來用。
這個 Recipe Revision 就是食譜版本,也就是那串長字串裡的第二段:
fmt/12.2.0#b445244e6d0151a425e229248cd2bed8
這個 Recipe Revision 是根據 conanfile.py 加上幾個周邊檔案內容計算出來的 hash 字串。
有時候 ConanCenter 會修改食譜,例如修正某個平台的編譯問題、改一下打包方式。
食譜的內容變了,但是 fmt 的原始碼都沒變,版本號也還是 12.2.0,此時就會多出一個 Recipe Revision。因為 recipe 一有改動,算出來的 hash 就不一樣。
來看看 fmt/10.2.1 在 ConanCenter 上一共改過五次食譜,套件重新打包了五次。
conan list "fmt/10.2.1#*" -r conancenter
conancenter
fmt
fmt/10.2.1
revisions
9199a7a0611866dea5c8849a77467b25 (2024-01-05 00:38:40 UTC)
8d582aeebdf05b6f0abe2911b926c3f4 (2024-07-03 14:13:49 UTC)
658771bb858b77f380be2ebb22c338e9 (2025-01-03 10:12:47 UTC)
25441914a1da0c98628fb50c865639f8 (2026-05-07 09:24:38 UTC)
15db2a3ea0646365bf05a6f50369ac59 (2026-08-03 10:56:10 UTC)
接下來講字串中的第三段:Package ID。
C++ 在編譯原始碼的時候,有很多互不相容的設定,這就是所謂的 settings/options,詳細的內容可以回去看 D08 Conan Profile。
而把這些 settings/options 揉起來計算出一個 hash,這就是 Package ID。
只要用同樣的 settings/options 來編譯套件,就會計算出一模一樣的 Package ID。相對的,只要換了編譯器,或者改動任何一項 settings/options,就會計算出不同的 Package ID。
所以 Package ID 就是對應到一組編譯設定、選項和依賴。
來查看看 ConanCenter 上面的 fmt/12.2.0 有幾個 Package ID:
conan list "fmt/12.2.0:*" -r conancenter
這張圖就是我執行上面指令的結果,數一數裡面應該一共有 11 個 Package。從截圖裡可以看到最上頭的兩個 Package ID,都是 MSVC 編譯器,一個是 x86_64 架構,另一個是 armv8 架構。

Header only 的套件比較特別。
像是 nlohmann_json 的 package ID 只有一個da39a3ee5e6b4b0d3255bfef95601890afd80709
這是因為 nlohmann_json 是 header-only,套件裡只有標頭檔,沒有預編譯 lib,所以不需要區分編譯器、Debug、Release 等等設定。所有的平台跟編譯器都拿到同一份 Package。da39a3ee...其實就是「空字串」的 SHA-1。什麼都沒放進去算,算出來就是這個值。
最後一段是 Package Revision。
Package ID 告訴你二進位檔是用哪一個編譯器組態編譯出來的(例如 Release、msvc 194、靜態庫)。
Package Revision 則是拿打包好的最終產出二進位檔案內容算出 hash。
所以一直到走到第四步 Package Revision 為止,我們才終於拿到了一包二進位檔。
看到這裡你可能會有個疑問:既然食譜(Recipe)沒變、編譯設定(Package ID)也都一樣,為什麼產出的二進位檔還需要版本號?
原來啊,就算原始碼和參數都一樣,換一台機器、編譯器更新一下,編出來的二進位檔還是可能有一點點不同。
Conan 不會偷偷蓋掉舊的那包,它會再給新的那包一個新的 Package Revision,就像是這次編譯結果的「指紋」,所以每一包都找得回來。
那我們平常開發需要管它嗎?通常是不用啦!
在日常開發中,Conan 預設自動幫你挑最新的那包 Package Revision。
回頭看 conan install 指令的輸出,從頭到尾仔細看,你會發現 Conan 其實分兩個階段做事。
第一階段先處理食譜(Recipe Revision):
======== Computing dependency graph ========
...
Requirements
fmt/12.2.0#b445244e6d0151a425e229248cd2bed8 - Cache
nlohmann_json/3.12.0#2d634ab0ec8d9f56353e5ccef6d6612c - Cache
這裡每一個套件都只有 Recipe Revision,後面兩段都沒有,因為這時候 Conan 還在計算套件的相依關係。
第二階段 Conan 開始找二進位檔,套件名後面才多了 Package ID & Package Revision:
======== Computing necessary packages ========
Requirements
fmt/12.2.0#b445244e6d0151a425e229248cd2bed8:8db65e2827b8126426a61b6cb7bf0c8a67f5915d#7c7bb68c3f6404d3a32a9e4682a011d9 - Cache
nlohmann_json/3.12.0#2d634ab0ec8d9f56353e5ccef6d6612c:da39a3ee5e6b4b0d3255bfef95601890afd80709#2ea4b814fc9da53f7cdf5b7d68052688 - Cache