iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天講了 vcpkg 的版本控制觀念。今天我們來講 Conan 的版本控制觀念。

D02 裡面我們提過,兩套套件管理系統的「版本觀完全相反」。今天就來比較一下,到底是怎麼樣個相反法。

什麼是版本範圍?

Conan 每個套件都要寫版本號碼。

vcpkg 不用寫, vcpkg.json 裡只寫套件名稱,版本交給 baseline 決定。

但是 Conan 一定要寫版本,寫法有兩種:寫死一個版本號碼,或是給一個範圍。

我們之前的範例一直都是寫死的:fmt/12.2.0。但是在開發中,有時候我們想要有彈性。現代的軟體開發大多遵守語意化版本(Semantic Versioning),X.Y.Z 裡面的第一碼X跳號代表有不相容的更新,第二碼Y是新增功能且向下相容,第三碼就是修 BUG。

團隊覺得,每次只要 fmt 有了第三碼的版本號更新,我們就應該要馬上導入,這樣的要求 Conan 做得到嗎?可以,這就要靠 Conan recipe 的版本範圍。

版本範圍的寫法是用中括號包起來:

from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloConanRecipe(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires("fmt/[>=12.2 <12.3]")
        self.requires("nlohmann_json/3.12.0")

    def layout(self):
        cmake_layout(self)

[>=12.2 <12.3] 中間用空白隔開,意思是「AND 兩個條件都要成立」:大於等於 12.2,而且小於 12.3。

Conan Install 安裝套件時,就會從範圍裡面挑出最新的那一個版本。

版本範圍語法

除了上面提的寫法,版本範圍還有好幾種寫法:

寫法 意思 實際解出來
fmt/[>=11 <13] 11 以上、13 以下 12.2.0
fmt/[~12.1] 12.1.x 前兩碼固定,只更新第三碼(修BUG) 12.1.0
fmt/[^12] 12.x 第一碼固定,更新第二三碼(相容的功能也可以更新) 12.2.0
fmt/[^12.1] 12.x 第一碼固定,更新後兩碼,但是一定要大於等於 12.1 12.2.0
fmt/[<12 || >=12.1 <12.2] || OR「或」。「12 以下,或者 12.1.x」用來跳過中間的版本號碼 12.1.0
fmt/[*] 任意版本皆可 12.2.0

這些簡寫是 npm 那邊流行起來的慣例,~ 開放第三碼、^ 開放第二三碼,剛好對應語意化版本的三個號碼。

Conan 的寫法大部分跟 npm 一樣,你可以到以下網頁玩玩版本號碼推導的各種簡寫符號:
https://jubianchi.github.io/semver-check/#/

用 conan graph info 看實際解出哪個版本

寫了範圍之後,要怎麼快速測試 Conan 到底挑了哪個版本?

用這個指令就可以了:

conan graph info .

它會爬過整棵依賴樹,並且去解析每個版本範圍的要求,並且解析成一個最終符合的版本號碼,但不會真的去下載或編譯二進位檔。

以下是部分的輸出文字:

======== Computing dependency graph ========
Graph root
    conanfile.py: /Users/matt/github/ironman-2026/D13/conanfile.py
Requirements
    fmt/12.2.0#b445244e6d0151a425e229248cd2bed8 - Cache
    nlohmann_json/3.12.0#2d634ab0ec8d9f56353e5ccef6d6612c - Cache
Resolved version ranges
    fmt/[>=12.2 <12.3]: fmt/12.2.0

仔細看印出的文字裡有一段寫 Resolved version ranges:

這裡寫了 fmt/[>=12.2 <12.3] 解出來是最終版本是 fmt/12.2.0。

也可以不寫 conanfile,直接在命令列測試某個版本範圍:

conan graph info --requires="fmt/[~12.1]"

菱形衝突

每個套件都自己指定版本,在稍微大型的專案,就很容易碰到一個經典問題:菱形依賴衝突(diamond dependency conflict)。

假設你的專案直接用了 fmt/12.2.0,同時也用 spdlog/1.15.3。

spdlog 內部依賴 fmt,ConanCenter 上的 spdlog 1.15.3 寫死的版本號碼是 fmt/11.2.0,跟你寫的 fmt 版本號碼不一樣:

    我的專案
    /      \
fmt/12.2.0  spdlog/1.15.3
               \
            fmt/11.2.0

兩條路徑都指向 fmt,但要求的是不同版本,這就是菱形衝突。

菱形衝突了怎麼辦?

Conan 不會幫你解決菱形衝突,會直接報錯:

ERROR: Version conflict: Conflict between fmt/11.2.0 and fmt/12.2.0 in the graph.
Conflict originates from spdlog/1.15.3

告訴你哪兩個版本打架,衝突是從 spdlog 來的。它沒辦法自動幫你挑一個,因為換版本可能會造成 ABI 不相容。

這時候怎麼辦呢?有兩個解法。

解法一:force

如果你確定用 fmt/12.2.0 沒問題,可以在 conanfile.py 裡加上選項 force=True :

def requirements(self):
    self.requires("fmt/12.2.0", force=True)
    self.requires("spdlog/1.15.3")

force=True 的意思是:「不要管其他套件的意見了,我強制指定用 12.2.0。」

再跑一次 conan graph info .,錯誤不見了,輸出多了一段 Overrides:

Requirements
    fmt/12.2.0#b445244e6d0151a425e229248cd2bed8 - Cache
    spdlog/1.15.3#855569172102c04e3e0db21ae95cf017 - Cache
Overrides
    fmt/11.2.0: ['fmt/12.2.0']

意思是 spdlog 原本要的 fmt/11.2.0,被換成了 fmt/12.2.0。

解法二:override

另一個寫法是加上選項 override=True:

def requirements(self):
    self.requires("fmt/12.2.0", override=True)
    self.requires("spdlog/1.15.3")

override 的不同之處在於:override 只在有衝突的時候才介入。如果依賴樹裡沒人要 fmt,override 不會把 fmt 加進來;force 則不管有沒有衝突,都會強制加入。

簡單說:override:「如果有人要這個套件,就用我指定的版本。」

不過這裡要提醒:不管是 force 還是 override,強制替換版本之後,API 和 ABI 的相容問題要自己負責處理,編譯能過不代表行為正確。

小結

這邊放一個表格,給大家比較 Conan 和 vcpkg

Conan vcpkg
指定版本 每個套件自己指定版本 整批跟著 baseline
版本範圍 有,[>=11 <13] 只有 version>=
選版方向 範圍內最新的版本 滿足條件的最舊的版本
衝突處理 force / override overrides

上一篇
D12:vcpkg 的版本控制與 baseline 概念
下一篇
D14:Conan Package ID 是什麼東東?
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言