iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Software Development

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

D22:vcpkg port 常見錯誤與 CMake Target 陷阱

  • 分享至 

  • xImage
  •  

套件明明裝好了,CMake 卻說找不到設定檔。

好不容易 find_package 成功了,連結時又說 target 不存在。

到底哪裡出錯了?vcpkg 跟 CMake 的關係千絲萬縷,你中有我,我中有你,到底是哪邊有問題,今天就來看看常見的的出錯位置跟除錯方式。

一、三個名字

vcpkg 套件裡面有三個非常容易混淆在一起的名字,只要某個名字或檔案位置沒對上,安裝套件或編譯時就有可能出錯。

這三個名字就是「port 名稱」、「CMake 套件名稱」以及「CMake Target 名稱」。

以 GoogleTest 套件為例,這三個名字分別是

  • port 名稱:gtest
  • CMake 套件名稱:GTest
  • CMake target 名稱:GTest::gtest、GTest::gtest_main

吼吼,三個名字都不一樣,而且沒有一個叫做 googletest,看起來有點迷惑吧。

以消費端的角度來看這三個名字怎麼用:

名稱 消費端怎麼用
port 名稱 消費端在 vcpkg.json 裡面寫 "gtest"
CMake 套件名稱 呼叫 find_package(GTest CONFIG REQUIRED)
target 名稱 呼叫 target_link_libraries(my-app PRIVATE GTest::gtest)

那轉換到套件生產端的角度,三個名字是在哪裡設定的呢?

第一個是 port 名稱「gtest」,寫在 gtest port vcpkg.json 裡:

{
  "name": "gtest",
  "version-semver": "1.18.0"
}

第二個是 CMake 套件名稱,是在 GoogleTest 自己的 googletest/CMakeLists.txt 裡設定的:

set(cmake_package_name GTest CACHE INTERNAL "")
...
set(config_file "${generated_dir}/${cmake_package_name}Config.cmake")

它產生出來的設定檔叫做 GTestConfig.cmake,這個檔名就是所謂的 CMake 套件名稱。消費端的用法是 find_package(GTest)

第三個是 target 名稱,由函式庫名稱加上 NAMESPACE 組成:

cxx_library(gtest "${cxx_strict}" src/gtest-all.cc)
cxx_library(gtest_main "${cxx_strict}" src/gtest_main.cc)
...
install(EXPORT ${targets_export_name}
  NAMESPACE ${cmake_package_name}::
  ...)

使用端連結的 target_link_libraries(app PRIVATE GTest::gtest)

我們再多看幾個例子。以我們前幾天用的範例套件 super-lib 為例:

  • port 名稱: super-lib
  • CMake 套件名稱: super-lib
  • 連結 target: super-lib::super-lib

照這個名,安裝套件要這樣下指令 vcpkg add port super-lib

然後 CMakeListst.txt 要這樣寫

find_package(super-lib CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE super-lib::super-lib)

再看一個三個名字都不一樣的例子:OpenSSL

  • port 名稱: openssl
  • CMake 套件名稱: OpenSSL
  • 連結 target: OpenSSL::SSL、OpenSSL::Crypto

使用端的安裝指令是小寫的 vcpkg add port openssl,CMake 卻是大寫的 OpenSSL,連結時有兩個 target:

find_package(OpenSSL REQUIRED)
target_link_libraries(my-app PRIVATE OpenSSL::SSL OpenSSL::Crypto)

其他容易搞混的還有這些

nlohmann JSON (port 用連字號,CMake 卻用底線)

  • port 名稱: nlohmann-json
  • CMake 套件名稱: nlohmann_json
  • 連結 target: nlohmann_json::nlohmann_json

cURL

  • port 名稱: curl
  • CMake 套件名稱: CURL
  • 連結 target: CURL::libcurl

zlib (port 是小寫,CMake 是全大寫)

  • port 名稱: zlib
  • CMake 套件名稱: ZLIB
  • 連結 target: ZLIB::ZLIB

那要怎麼知道正確的名字呢?

vcpkg 裝完套件時會印出一段 usage 說明,裡面就寫了 find_package() 跟 target_link_libraries() 該怎麼寫,照抄就對了。

二、看錯誤訊息,判斷哪一關出錯了

萬一安裝套件的過程中發生問題了,該怎麼辦呢?

第零關:port 編譯沒過

如果 port 本身有問題,vcpkg install 這一步就會直接失敗。

我故意寫了一個編譯不過的 broken-lib port,輸出看起來長這樣:

-- Building x64-windows-dbg
CMake Error at scripts/cmake/vcpkg_execute_build_process.cmake:134 (message):
    Command failed: ... cmake.exe --build . --config Debug --target install -- -v -j17
    Working Directory: C:/github/vcpkg/buildtrees/broken-lib/x64-windows-dbg
    See logs for more information:
      C:\github\vcpkg\buildtrees\broken-lib\install-x64-windows-dbg-out.log

error: building broken-lib:x64-windows failed with: BUILD_FAILED

咦,真正的編譯錯誤呢?看不到,因為 vcpkg 寫進 log 檔了,沒直接顯示出來。

照著 "See logs for more information" 後面寫的路徑打開,就看得到到底哪裡編譯沒過了:

broken.cpp(2): error C2065: 'this_does_not_exist': undeclared identifier

要注意,這些 log 放在 vcpkg repo 底下的 buildtrees/<port name>/,不是在你的專案目錄下,第一次找很容易找錯地方。

第一關:找不到設定檔 (config.cmake)

通過第零關,假設我們剛剛成功安裝了 Google Test 套件,現在準備開始編譯應用程式。

第一步跑 CMake configure 卻發生以下錯誤:

Could not find a package configuration file provided by "googletest" with any
of the following names:

這是怎麼回事?CMake 找不到到套件設定檔?怎麼會?

這時候,第一步就是先去看看設定檔的檔案名了。
設定檔的標準位置是 build/vcpkg_installed/<triplet>/share/<port名稱>/

Google Test 的設定檔是這個名字,位於你的專案目錄下:

build/vcpkg_installed/x64-linux/share/gtest/GTestConfig.cmake

所以啊,專案裡面就要照這個檔名寫 find_package(GTest CONFIG),這才是正確的寫法。如果寫 port name find_package(gtest CONFIG) 可能就找不到囉。

但是 Windows 檔案系統不分大小寫,所以在 Windows 上面寫 gtest 還是找得到,換到區分大小寫的檔案系統之後就會爆炸了。

如果是自己寫的套件,找到設定檔後,依序確認:

  1. 完全找不到設定檔:回頭檢查函式庫的 install() 和 install(EXPORT ...) 有沒有寫對。
  2. 有設定檔但是位置錯了:檢查 portfile 有沒有呼叫 vcpkg_cmake_config_fixup() 、有沒有設定對CONFIG_PATH
  3. 設定檔的位置正確:確認 find_package() 的名稱和設定檔名稱相符。

第二關:找不到 CMake target

修了設定黨的名字,設定檔找到了,但下一步卻拿到 target 不存在的錯誤:

CMake Error at CMakeLists.txt:5 (target_link_libraries):
  Target "super-app" links to:

    googletest::googletest

  but the target was not found.

這個錯誤代表 find_package() 已經成功,但 target_link_libraries() 要連結的 target 名稱不存在。target 名稱不一定跟 port 名稱或套件名稱一樣,所以很容易猜錯。

這時候不要隨便亂踹,真正定義 target 的地方,是 share/<port名稱>/ 這個目錄裡面的裡的 cmake 檔案。

我打開安裝好的套件目錄,發現 GoogleTest 的檔案名叫 GTestTargets.cmake。裡面有這幾行:

add_library(GTest::gtest STATIC IMPORTED)
add_library(GTest::gtest_main STATIC IMPORTED)
add_library(GTest::gmock STATIC IMPORTED)
add_library(GTest::gmock_main STATIC IMPORTED)

每一行 add_library() 括號裡面的第一個字,就是target名字囉。所以看到這邊,你就知道應該要寫 GTest::gtest (一個大寫一個小寫),而不是 googletest::googletest 囉。

第三關:自己套件的間接依賴

接著我們來看一個比較進階的狀況。

假設我們自己寫的套件 super-lib 用了外部函式庫 zlib 來做壓縮。

那現在的依賴狀況是這樣:

app -> super-lib -> zlib

當消費端的應用程式安裝 super-lib 的時候,還要連帶一起安裝 zlib 才行,否則沒辦法編譯。這就是所謂的「間接依賴」。那有間接依賴的套件,該注意什麼呢?

首先在套件的入口檔案 vcpkg.json,要把間接依賴 zlib 加進去 dependencies 欄位,這樣子消費端安裝此套件的時候,才會連帶一起安裝 zlib:

"dependencies": [
  "zlib",
  { "name": "vcpkg-cmake", "host": true },
  { "name": "vcpkg-cmake-config", "host": true }
]

vcpkg 搞定了,但是事情還沒結束,因為 CMake 那邊也要串起來。

關鍵是要讓消費端的 CMake 看到這一行:find_dependency(ZLIB)

這時候用自動產生的 config.cmake 就不夠了,我們需要另外手刻一個 super-lib-config.cmake。

先改 CMakeLists.txt,把裡面產生的檔名改成 super-lib-targets.cmake (原本是 super-lib-config.cmake)

install(EXPORT super-lib-targets  #改檔名
    FILE super-lib-targets.cmake
    NAMESPACE super-lib::
    DESTINATION lib/cmake/super-lib
)
install(FILES cmake/super-lib-config.cmake DESTINATION lib/cmake/super-lib)

接著手寫一個 super-lib-config.cmake:

include(CMakeFindDependencyMacro)
find_dependency(ZLIB)
include("${CMAKE_CURRENT_LIST_DIR}/super-lib-targets.cmake")

先用 find_dependency(ZLIB) 找到壓縮函式庫,接著再載入 CMake 產生的 target 檔。

重新建置,這次就過關囉:

-- Found ZLIB: .../vcpkg_installed/x64-linux/lib/libz.a (found version "1.3.2")
...
super-lib 0.1.0, zlib 1.3.2

除錯順序

照這個順序檢查:

  1. vcpkg install 指令有沒有出現 BUILD_FAILED?
    1. 有的話去 buildtrees/<port 名稱>/ 這個目錄看 log。
  2. 使用端 vcpkg.json 宣告的 port 名稱是否正確?
  3. 設定檔是否正確安裝在 share/<port 名稱>/ 目錄?
  4. find_package() 的套件名稱和 target_link_libraries() 的 target 是否各自寫對?大小寫呢?
  5. 套件自己有依賴?port 的 vcpkg.json 要宣告,說明書裡也要 find_dependency()。

上一篇
D21:vcpkg features 客製化套件
下一篇
D23:團隊共享:建立自己的 vcpkg Custom Git Registry
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言