iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Software Development

C++ 套件管理器求生指南 Conan vs Vcpkg系列 第 18 篇

D18:談 Conan package_info 套件說明書

  • 分享至 

  • xImage
  •  

你拿到一個函式庫,第一個問題是什麼?

「要怎麼用啊?」

對吧?標頭檔在哪?要連結哪個 .a/.lib?要不要設定什麼環境變數?要不要連某個系統函式庫?

在套件管理器的生態裡,這些問題也同樣需要回答。這些資訊都要在依賴樹,裡忠實的往下傳達給消費端套件,套件才能被正確無痛的使用。

這些資訊都寫在哪裡?答案是寫在 Conan recipe 裡的 package_info() 方法。

以下就是今天的目標:

  1. 搞懂 package_info() 在做什麼
  2. 看看它最常設定的幾樣東西:libs、system_libs、defines
  3. 改 CMake target 名字、設定環境變數
  4. 一包好幾個函式庫的話,用 components

測試環境:Debian 13、gcc 14.2、CMake 3.31、Conan 2.33.0。

一、package_info() 是套件的使用說明書

D15 的 conanfile.py 的最後一個方法長這樣:

    def package_info(self):
        self.cpp_info.libs = ["super-lib"]

package_info() 肩負一個重要任務,就是擔任套件的說明書,告訴大家「這包函式庫要怎麼用」。

消費端的 CMakeDeps 會讀這份說明書,轉譯內容為對應的 CMake 設定。

例如這是 conan install 之後產生的 super-lib-release-x86_64-data.cmake 內容:

set(super-lib_INCLUDE_DIRS_RELEASE "${super-lib_PACKAGE_FOLDER_RELEASE}/include")
...
set(super-lib_LIB_DIRS_RELEASE "${super-lib_PACKAGE_FOLDER_RELEASE}/lib")
set(super-lib_BIN_DIRS_RELEASE )
...
set(super-lib_LIBS_RELEASE super-lib)

這些一個又一個的 CMake 變數,就是 Conan 轉譯 package_info() 的結果給消費端使用的。

二、package_info() 的常用欄位

以下就來說明書的幾個常用的欄位。

我們繼續延續 super-lib 超好用函式庫的情境,假設這個 super-lib 是靜態函式庫、編譯時需要使用系統上的 zlib,使用端需要定義 SUPER_LIB_STATIC,而且我們想沿用舊專案的 CMake 名稱:

以下一份標準的 package_info() 說明書寫法:

def package_info(self):
    self.cpp_info.includedirs = ["include"]
    self.cpp_info.libdirs = ["lib"]
    self.cpp_info.bindirs = ["bin"]
    self.cpp_info.libs = ["super-lib"]
    self.cpp_info.system_libs = ["z"]
    self.cpp_info.defines = ["SUPER_LIB_STATIC"]

    self.cpp_info.set_property("cmake_file_name", "SuperLib")
    self.cpp_info.set_property("cmake_target_name", "SuperLib::SuperLib")

    self.runenv_info.define("SUPER_LIB_LOG_DIR", "/tmp/super-lib-logs")

怎麼這麼多欄位?別擔心,不是每個套件都需要照抄這份固定清單。實際要填哪些欄位,還是要看函式庫本身的需求。接下來我們一項一項拆開來看吧。

1. 設定函式庫的 include & lib 目錄

標頭檔、函式庫和執行檔分別放在哪個目錄?

self.cpp_info.includedirs = ["include"]
self.cpp_info.libdirs = ["lib"]
self.cpp_info.bindirs = ["bin"]

includedirs、libdirs、bindirs 這三個欄位可以指定目錄。

上面分別把這森個欄位指向 include、lib、bin 三個路徑。

Conan 預設就會使用 include、lib、bin,所以如果你就是採用這些慣例目錄結構,這三個欄位也可以省略不寫。

2. libs:要連結哪些函式庫

libs 是每個套件都必寫的欄位(幾乎啦)。

self.cpp_info.libs = ["super-lib"]

注意只寫名字就好,不用寫前綴和副檔名。Conan 會依平台自動轉換,比如 Linux 上會變成 libsuper-lib.a,Windows 上會變成 super-lib.lib。

3. 系統函式庫 system_libs

假設 super-lib 需要用到系統上的 zlib,那該怎麼讓編譯器知道呢?

if self.settings.os == "Linux":
	self.cpp_info.system_libs = ["z"]

CMakeLists.txt 也記得連結 z:

target_link_libraries(super-lib PRIVATE z)

如果明明使用了系統函式庫,但是又忘了寫,那編譯 test_package 的時候就會爆出錯誤。

Linux 上最常見的系統函式庫有 pthread、m、dl 等等。

macOS 上對應的是 frameworks,可以這樣寫:

if self.settings.os == "Macos":
    self.cpp_info.frameworks = ["CoreFoundation"]

4. defines:使用者要加的巨集

有些函式庫的標頭檔,要求使用者編譯時定義某個巨集。最常見的是 Windows 上的靜態庫,像 XXX_STATIC 這種,沒定義的話標頭檔會以為你用的是 DLL。

我們用一個簡單的例子:

self.cpp_info.defines = ["SUPER_LIB_STATIC"]

產生的 CMake Toolchain 設定檔裡就會多出一行:

set(super-lib_COMPILE_DEFINITIONS_RELEASE "SUPER_LIB_STATIC")

同理還有 cxxflags、cflags、sharedlinkflags、exelinkflags 等等可以塞額外的編譯和連結參數,不過用到的機會比較少。

5. set_property 改 CMake 的名字

如果你們公司的老舊專案已經用了不同的 CMake Target 名字。跟 Conan 預設產生小寫加連字號 super-lib::super-lib 不一樣怎麼辦呢,一定要讓舊專案配合改名字嗎?不用喔。

可以用 set_property() 來改名字:

self.cpp_info.set_property("cmake_file_name", "SuperLib")
self.cpp_info.set_property("cmake_target_name", "SuperLib::SuperLib")
  • cmake_file_name:find_package() 要找的名字
  • cmake_target_name:target 的名字

改完之後,記得把 test_package/CMakeLists.txt 裡面換成新名字:

find_package(SuperLib REQUIRED)

add_executable(example src/example.cpp)
target_link_libraries(example PRIVATE SuperLib::SuperLib)

之後產生的檔案也都會變成 SuperLibConfig.cmake、SuperLibTargets.cmake 了。公司舊專案就可以直接無痛使用了。

Conan 官方的 OpenSSL 套件包也是類似做法,把 target 改寫成符合外界大眾早已習慣使用多年的 OpenSSL::SSL 和 OpenSSL::Crypto。

6. 環境變數 runenv_info、buildenv_info

除了編譯連結,有時候套件還要設定環境變數。

runenv_info 屬性是執行時用的,buildenv_info 屬性是編譯時用的。

self.runenv_info.define("SUPER_LIB_LOG_DIR", "/tmp/super-lib-logs")

使用端產生的 conanrunenv-release-x86_64.sh 裡就會出現:

export SUPER_LIB_LOG_DIR="/tmp/super-lib-logs"

最常見的用途是把套件的 bin目錄 加進 PATH,讓 Windows 執行時找得到 DLL。

三、components 一包套件好幾個模組

如果一包套件裡面其實有好幾個函式庫,就像 OpenSSL 有 ssl 和 crypto,這時候可以寫 components 來表達這種結構。

最後我們用官方的 openssl conanfile.py作為範例結尾。

package_info() 方法節錄如下:

def package_info(self):
	self.cpp_info.set_property("cmake_file_name", "OpenSSL")

	self.cpp_info.components["ssl"].libs = ["ssl"]
	self.cpp_info.components["crypto"].libs = ["crypto"]

	self.cpp_info.components["ssl"].requires = ["crypto"]

	if self.settings.os == "Windows":
		self.cpp_info.components["crypto"].system_libs.extend(["crypt32", "ws2_32", "advapi32", "user32", "bcrypt"])
	elif self.settings.os == "Linux":
		self.cpp_info.components["crypto"].system_libs.extend(["dl", "rt"])
		self.cpp_info.components["ssl"].system_libs.append("dl")
	self.cpp_info.components["crypto"].set_property("cmake_target_name", "OpenSSL::Crypto")
	self.cpp_info.components["ssl"].set_property("cmake_target_name", "OpenSSL::SSL")

今天講的東西,幾乎全部都用上了:

  • 有兩個模組 components["ssl"] 和 components["crypto"]
  • 透過 requires = ["crypto"] 設定模組間的相依關係。
  • 依作業系統分別寫不同的 system_libs,Windows 和 Linux 要連的系統函式庫不一樣。
  • 用 set_property 重新命名 CMake Target: OpenSSL::SSL , OpenSSL::Crypto,符合外界流傳已久的習慣舊名。

上一篇
D17:Conan options 自定義套件選配
下一篇
D19:Conan 本機快取與 Remote:套件怎麼分享給同事?
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言