iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Software Development

30 天 CMake 跨平台之旅系列 第 13

[Day 13] 更多 Library

  • 分享至 

  • xImage
  •  

本日內容

  • 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 提供的都能夠很方便拉進來使用
比如今天要介紹的 FindPkgConfigFetchContent

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_PATHFind 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
    • 該 function 會幫我們根據 prefix 設定一連串變數方便我們之後 link 他, 比如
      • <prefix>_FOUND
      • <prefix>_LINK_LIBRARIES
      • <prefix>_INCLUDE_DIRS
      • <prefix>_CFLAGS
      • etc.
    • 如果 .pc 檔中有多個 moduleSpec, 上面的 <prefix> 會變成 <prefix>_<moduleName>
  • REQUIRED
    • 如果找不到該套件會直接 fail 並終止 build process
  • NO_CMAKE_PATH, NO_CMAKE_ENVIRONMENT_PATH
    • 將 CMake 的路徑從 pkg-config 的搜尋路徑中移除
    • 前者是 cache variable, 後者是 environtment variable
  • IMPORTED_TAGET
    • 會幫我們將該套件建出 imported target PkgConfig::<prefix>
    • Day 14 會介紹
  • GLOBAL
    • 讓上面的 imported target 變成 global scope, 所有人都可以 link 他
  • <moduleSpec>
    • 要找的目標 modules, 多個的話會影響最後產出的變數名數, 如上所述

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 name

FetchContent

個人覺得最好用的 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 幾大類的 options
    • 比如 DownloadGIT_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_DIRfind_package() 能夠搜尋
    • 所以要記得用 FetchContent_MakeAvailable() 才不會 error
  • FIND_PACKAGE_ARGS
    • 如果將變數 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 設為 true, CMake 就會在呼叫 FetchContent_MakeAvailable() 的時候優先用 find_package() 找該 package, 這叫做 opt-in
    • 這個參數會被傳給 find_package() 使用

FetchContent_MakeAvailable()

FetchContent_MakeAvailable(<name1> [<name2>...])
  • 用法很簡單, 就是將 FetchContent_Declare() 時宣告的套件名稱加入 CMake target 中
  • 需要注意的是, 最好先用 FetchContent_Declare() 將所有的 dependencies 都下載回來或更新, 否則如果 package 間有 dependency, 且該 package 也有用 FetchContent 加入 dependency, 我們自己的之後就會被跳過了
    • 比如 package 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 專案吧~


上一篇
[Day 12] 寫出我的第一個 Library
下一篇
[Day 14] 加入 System library
系列文
30 天 CMake 跨平台之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言