include()
find_package()
FindPkgConfig
FetchContent
在了解各種我們自己寫的 internal libraries 後, 今天要介紹的是如何把 external libraries 拉進來使用
而要引進外部套件最困難的就是如何找到該套件與如何引入, 如果該套件有提供 CMake module 如 <module>.cmake
或是 Find<module>.cmake
, 我們就可以用 include()
和 find_package()
找到, 因為和 CMake 格式一致, 所以可以直接引入
CMake 也提供了 FetchContent
module 讓我們可以直接從網上 (ex. Github) 下載並加入我們的 CMake 專案
如果沒有提供 CMake module, 但是有 *.pc
檔, CMake 也提供了 FindPkgConfig
module, 讓我們能夠使用 pkg-config
來搜尋該套件, 並用 pkg_check_module()
或 pkg_search_module()
引入
就算如果以上都沒有, CMake 也提供 add_custom_command()
讓我們能夠將 build 該套件的流程整合進我們 CMake 流程, 然後我們就可以用 add_library(IMPORTED)
將他加入 targets 了!
今天會先介紹之後 3 天會用到的 modules 和指令概念, 等之後再實做, 所以今天沒有提供 Colab~
include()
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>]
[NO_POLICY_SCOPE])
file|module
<module>.cmake
module 拉進來var
將檔名 assign 給該變數var
記得 Day 4 介紹過的 variable scope 嗎? 雖然 include()
和 add_subdriectory()
都會執行指定的檔案 (前者執行 <module>.cmake
, 後者 <dir-CMakeLists.txt>), 但是 include()
並 不會 建立新的 variable scope, 而是直接在 caller scope 執行 module 的指令, 所以使用時要注意
他會去 CMAKE_MODULE_PATH
找指定的 module name 並執行
無論是我們自己寫的 *.cmake
或是 CMake 提供的都能夠很方便拉進來使用
比如今天要介紹的 FindPkgConfig
和 FetchContent
find_package()
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])
find_package()
有 Module mode 和 Config mode
在 Module mode 時, 他可以像 include()
一樣搜尋並執行 CMake module, 但會尋找符合 Find<package-name>.cmake
檔名的檔案
在 Config mode 時, 可以用來尋找外部套件, 他會找該套件中符合 <package-name>Config.cmake
或是 <package-name>-config.cmake
檔名的檔案, 如果該套件有提供 config 檔的話
更方便的是他可以搭配 FetchContenr
使用, 如果沒辦法從 CMAKE_MODULE_PATH
和 Find
module 內建的路徑找到提供的檔案, 我們也可以讓他將請求轉給 FetchContent
module 處理, 下面會簡單介紹
FindPkgConfig
這是 CMake 提供的 pkg-config
的抽象, 讓我們可以用 pkg_check_modules()
和 pkg_search_module()
來使用 pkg-config
的功能
pkg-config
是一個可執行檔, 我們可以用他來找到套件需要的 cflags, 就可以輕鬆的加到 compiler 的指令中
可以參考 官方文件
pkg-config
會去搜尋 *.pc
檔 (通常是 <libname>.pc
), 而該 .pc
檔中會包含 link 該套件需要的資訊, 比如
foo.pc:
prefix=/usr
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: foo
Description: The foo library
Version: 1.0.0
Cflags: -I${includedir}/foo
Libs: -L${libdir} -lfoo
bar.pc:
prefix=/usr
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: bar
Description: The bar library
Version: 2.1.2
Requires.private: foo >= 0.7
Cflags: -I${includedir}
Libs: -L${libdir} -lbar
CMake 能夠無痛將 pkg-config
讀進來的套件資訊加入我們的 build process 中, 非常方便
pkg_check_modules()
pkg_check_modules(<prefix>
[REQUIRED] [QUIET]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[IMPORTED_TARGET [GLOBAL]]
<moduleSpec> [<moduleSpec>...])
prefix
prefix
設定一連串變數方便我們之後 link 他, 比如
<prefix>_FOUND
<prefix>_LINK_LIBRARIES
<prefix>_INCLUDE_DIRS
<prefix>_CFLAGS
.pc
檔中有多個 moduleSpec
, 上面的 <prefix>
會變成 <prefix>_<moduleName>
REQUIRED
NO_CMAKE_PATH
, NO_CMAKE_ENVIRONMENT_PATH
pkg-config
的搜尋路徑中移除IMPORTED_TAGET
PkgConfig::<prefix>
GLOBAL
<moduleSpec>
pkg_search_modules()
pkg_search_module(<prefix>
[REQUIRED] [QUIET]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[IMPORTED_TARGET [GLOBAL]]
<moduleSpec> [<moduleSpec>...])
pkg_check_module()
一樣, 差別是用法不同pkg_check_module()
會去找 所有 列出來的 moduleSpec
, 並用來建立 <prefix>_<moduleName>_*
系列變數 (給一個以上的 <moduleName>
的話)pkg_search_module()
則 只會找第一個 搜尋到的 moduleSpec
<prefix>_*
系列變數看出是找到哪一個 module , 可以從 <prefix>_MODULE_NAME
得到 module nameFetchContent
個人覺得最好用的 module!
該 module 包含 FetchConetent_Declare()
和 FetchContent_MakeAvailable()
兩個 function
前者負責處理下載或更新 package 的工作
後者則負責將前面下載或更新完的 package 加入我們的 build process 中
FetchContent_Declare()
FetchContent_Declare(
<name>
<contentOptions>...
[SYSTEM]
[OVERRIDE_FIND_PACKAGE |
FIND_PACKAGE_ARGS args...]
)
<name>
FetchContent_MakeAvailable()
用的名稱<contentOptions>
Download
, Update
, Patch
幾大類的 optionsDownload
有 GIT_REPOSITORY
, GIT_TAG
, URL
, URL_HASH
等等, 其中 URL
可以是系統上的檔案路徑SYSTEM
add_subdirectory()
裡的 subdirectory 的 INTERFACE_INCLUDE_DIRECTORIES
property 都變成 system header search paths, 如果該 package 有些 header files 在 system headers 的話可以用OVERRIDE_FIND_PACKAGE
find_package()
時會去找 FetchContent_MakeAvailable()
產生的 package config 檔, 該 function 會建出路徑存放這些檔案, 並產生變數 CMAKE_FIND_PACKAGE_REDIRECTS_DIR
讓 find_package()
能夠搜尋FetchContent_MakeAvailable()
才不會 errorFIND_PACKAGE_ARGS
FETCHCONTENT_TRY_FIND_PACKAGE_MODE
設為 true, CMake 就會在呼叫 FetchContent_MakeAvailable()
的時候優先用 find_package()
找該 package, 這叫做 opt-infind_package()
使用FetchContent_MakeAvailable()
FetchContent_MakeAvailable(<name1> [<name2>...])
FetchContent_Declare()
時宣告的套件名稱加入 CMake target 中FetchContent_Declare()
將所有的 dependencies 都下載回來或更新, 否則如果 package 間有 dependency, 且該 package 也有用 FetchContent
加入 dependency, 我們自己的之後就會被跳過了
uses_other
會用 FetchContent
拉 package other
, 用以下寫法就會失敗FetchContent_Declare(uses_other ...)
FetchContent_MakeAvailable(uses_other)
FetchContent_Declare(other ...)
FetchContent_MakeAvailable(other)
所以要用以下寫法
FetchContent_Declare(uses_other ...)
FetchContent_Declare(other ...)
FetchContent_MakeAvailable(uses_other other)
接下來 3 天就來慢慢介紹如何將外部套件引入我們的 CMake 專案吧~