套件明明裝好了,CMake 卻說找不到設定檔。
好不容易 find_package 成功了,連結時又說 target 不存在。
到底哪裡出錯了?vcpkg 跟 CMake 的關係千絲萬縷,你中有我,我中有你,到底是哪邊有問題,今天就來看看常見的的出錯位置跟除錯方式。

vcpkg 套件裡面有三個非常容易混淆在一起的名字,只要某個名字或檔案位置沒對上,安裝套件或編譯時就有可能出錯。
這三個名字就是「port 名稱」、「CMake 套件名稱」以及「CMake Target 名稱」。
以 GoogleTest 套件為例,這三個名字分別是
gtest
GTest
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 為例:
照這個名,安裝套件要這樣下指令 vcpkg add port super-lib
然後 CMakeListst.txt 要這樣寫
find_package(super-lib CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE super-lib::super-lib)
再看一個三個名字都不一樣的例子:OpenSSL
使用端的安裝指令是小寫的 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 卻用底線)
cURL
zlib (port 是小寫,CMake 是全大寫)
那要怎麼知道正確的名字呢?
vcpkg 裝完套件時會印出一段 usage 說明,裡面就寫了 find_package() 跟 target_link_libraries() 該怎麼寫,照抄就對了。
萬一安裝套件的過程中發生問題了,該怎麼辦呢?
如果 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>/,不是在你的專案目錄下,第一次找很容易找錯地方。
通過第零關,假設我們剛剛成功安裝了 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 還是找得到,換到區分大小寫的檔案系統之後就會爆炸了。
如果是自己寫的套件,找到設定檔後,依序確認:
install() 和 install(EXPORT ...) 有沒有寫對。vcpkg_cmake_config_fixup() 、有沒有設定對CONFIG_PATH
find_package() 的名稱和設定檔名稱相符。修了設定黨的名字,設定檔找到了,但下一步卻拿到 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
照這個順序檢查:
vcpkg install 指令有沒有出現 BUILD_FAILED?
buildtrees/<port 名稱>/ 這個目錄看 log。vcpkg.json 宣告的 port 名稱是否正確?share/<port 名稱>/ 目錄?find_package() 的套件名稱和 target_link_libraries() 的 target 是否各自寫對?大小寫呢?vcpkg.json 要宣告,說明書裡也要 find_dependency()。