iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Software Development

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

[Day 14] 加入 System library

  • 分享至 

  • xImage
  •  

本日內容

  • fmt Build from Source
  • find_package()
  • FindPkgConfig
  • FetchContent
  • 我要怎麼知道 Package 有什麼 Targets 能用?
  • 預告

連結: Day 14 - Colab

Day 13 介紹了 CMake 提供讓我們搜尋並加入 modules 和 packages 的幾個指令, 今天會來實際使用到我們的 CMake 專案中, 下面會附上今天的範例 code, 也可以去 Colab 自己玩玩看

我們會用 C++ 的一個 open source 專案 fmtlib/fmt 做為例子, 示範如何 從系統上加入該套件
當然, 就如 Day 13 提過的, 我們也可以直接用 FetchContent_* 系列 function 下載並加入我們的 CMake 專案中, 但這部分就留到 Day 15 再介紹吧~

fmt Build from Source

  1. 我們先去 fmtlib/fmtfmt 的專案下載下來
git clone https://github.com/fmtlib/fmt
cd fmt
  1. 然後去 官網 看如何 build, 可以和前幾天看到的稍微對應
  2. 我們現在已經有基本的 CMake 知識了, 所以我們可以試著自己 build 看看, 先 generate build scripts
cmake -S . \
      -B build \
      -G "Unix Makefiles" \
      -DCMAKE_BUILD_TYPE:STRING=Release
  1. 然後 build code
cmake --build build
  1. 最後安裝的部分會在 Day 20 介紹, 這邊先看過指令即可
cmake --install build

這樣就會把 fmt 裝到系統上了, 路徑在 /usr/local/ 底下, 可以看到有

  • lib/cmake/
    • fmt 提供的 CMake modules
  • include/fmt/*.h
    • fmt export 的 header files, 可以讓我們 link 時使用
    • fmt export
    • Day 13 提過的給 pkg-config 使用的 .pc 檔, 來偷看一下裡面有什麼
    prefix=/usr/local
    exec_prefix=/usr/local
    libdir=${exec_prefix}/lib
    includedir=${prefix}/include
    
    Name: fmt
    Description: A modern formatting library
    Version: 10.1.1
    Libs: -L${libdir} -lfmt
    Cflags: -I${includedir}
    
    有沒有覺得很熟悉呢?

裝好 fmt 之後, 我們就可以將他拉近我們的 CMake 專案使用了
接著就可以使用 Day 13 講過的幾個指令和 modules 了

來看看這次的 project 架構

cmake-example/
├─ src/
│  ├─ cmake/ 👈
│  │  ├─ dependencies.cmake 👈
│  ├─ CMakeLists.txt
│  ├─ main.cpp
├─ CMakeLists.txt

可以看到, 這次我們多了新的 cmake directory 和其中的 dependencies.cmake module
根據 CMake 的 convention, cmake 一般會存放專案自己的 modules, 而外部套件則建議單獨在 dependencies.cmake module 集中處理

再來看看幾個 CMakeLists.txt 和 executable 的設定

CMakeLists.txt

cmake_minimum_required(VERSION 3.25)
project(ItHome2023
  LANGUAGES C CXX
  VERSION 0.1.0
)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_subdirectory(src)

src/main.cpp

#include <iostream>

#include <fmt/core.h>

int main() {
  std::cout << fmt::format("Test fmt {}", "specifier") << std::endl;
  return 0;
}

find_package()

src/cmake/dependencies.cmake

find_package(fmt REQUIRED)
if (TARGET fmt::fmt-header-only)
  message(DEBUG "🎉 Found fmt::fmt-header-only")
else()
  message(FATAL_ERROR "Failed to find fmt::fmt-header-only")
endif()

src/CMakeLists.txt

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)

include(dependencies)

add_executable(MyApp
  main.cpp
)
target_link_libraries(MyApp PRIVATE
  fmt::fmt-header-only
)

FindPkgConfig

src/cmake/dependencies.cmake

include(FindPkgConfig)
pkg_check_modules(FMT REQUIRED IMPORTED_TARGET fmt)

if (TARGET PkgConfig::FMT)
  message(DEBUG "🎉 Found PkgConfig::FMT")
else()
  message(FATAL_ERROR "Failed to find PkgConfig::FMT")
endif()

這裡用 pkg_check_modules(IMPORTED_TARGET) 讓 CMake 幫我們建出 target PkgConfig::FMT, 我們的 executable 就可以 link 他

src/CMakeLists.txt

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)

include(dependencies)

add_executable(MyApp
  main.cpp
)
target_link_libraries(MyApp PRIVATE
  PkgConfig::FMT
)

FetchContent

src/cmake/dependencies.cmake

include(FetchContent)
FetchContent_Declare(FMT
  SOURCE_DIR ${PROJECT_SOURCE_DIR}/../fmt
)
FetchContent_MakeAvailable(FMT)

if (TARGET fmt::fmt-header-only)
  message(DEBUG "🎉 Found fmt::fmt-header-only")
else()
  message(FATAL_ERROR "Failed to find fmt::fmt-header-only")
endif()

這邊我們設定 SOURCE_DIR 為剛剛下載的 fmt 專案的路徑
FetchContent_MakeAvailable() 內部就會用 add_subdirectory() 加入該專案, 所以該專案現在就相當於是我們的 sub-project, 所以我們就可以用 fmt 的 targets 了~

src/CMakeLists.txt

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)

include(dependencies)

add_executable(MyApp
  main.cpp
)
target_link_libraries(MyApp PRIVATE
  fmt::fmt-header-only
)

可以看到這個用法的結果和 find_package() 是一樣的
Day 15 會介紹 FetchContent module 真正好用的功能: 下載 Github 專案並指定 tag 或 commit hash!

我要怎麼知道 Package 有什麼 Targets 能用?

以今天的例子來說, 除了用 FindPkgConfig 拉進來的 target name 是由我們自己定義以外, find_package()FetchContent 都拉進來了和 套件名稱不完全相同的 targets, ex. fmt::fmt-header-onlyfmt::fmt

第一時間可以去查文件, 通常文件會寫要怎麼 link 他的 targets
另一種是直接去 build 底下找 <packageName>-config.cmake 或是 <packageName>-targets.cmake 檔案
fmt 來說, 他會產出 fmt-targets.cmake, 且裡面有一段指令是這樣

foreach(_cmake_expected_target IN ITEMS fmt::fmt fmt::fmt-header-only) 👈
  list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
  if(TARGET "${_cmake_expected_target}")
    list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
  else()
    list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
  endif()
endforeach()

從這裡也可以看出來該套件有哪些 targets 能用

預告

明天會繼續來介紹 FetchContent 真正好用的用法, 並且會寫出我們的第一個測試!


上一篇
[Day 13] 更多 Library
下一篇
[Day 15] 加入 Open source library
系列文
30 天 CMake 跨平台之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言