iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Software Development

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

[Day 9] 第一個 CMake 專案!

  • 分享至 

  • xImage
  •  

本日內容

  • 設計專案架構
  • CMakeLists.txt
  • CMAKE_<LANG>_STANDARD, CMAKE_<LANG>_STANDARD_REQUIRED, CMAKE_<LANG>_EXTENSIONS
  • 決定 Generator
  • 來 build code 吧!
  • Debug build
  • 預告

連結: Day 9 - Colab

就如 Day 3 所說, CMake 將 build code 的流程分成 configure, build, test, install, package 幾個階段
我們會先著重在 configure 和 build 的部分, 先把最重要的可執行檔建出來

由於範例皆是在 Linux 開發, 對於 Linux 的檔案系統可以額外參考 Filesystem Hierarchy Standard (FHS)
可以大概知道常見的系統資料夾代表什麼意義, 這部分會在 Day 20 介紹

設計專案架構

初期的專案架構會長這樣

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

非常陽春, 只有

  • CMakeLists.txt
    • 給 CMake 用的 build script
  • src
    • cmake 執行時的 source files 的路徑
    • Day 4 所說, CMake 會根據此路徑自動建出一連串的 cache variables
  • src/main.cpp
    • 主程式入口點

Build directory 會在之後讓 CMake 自動幫我們產生

CMakeLists.txt

內容長這樣

cmake_minimum_required(VERSION 3.26)
project(HelloWorld
    LANGUAGES C CXX
)

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

add_subdirectory(src)

src/CMakeLists.txt

add_executable(HelloWorld_Main
    main.cpp
)

以下簡單說明每行指令的意思

cmake_minimum_required()

cmake_minimum_required(VERSION major.minor[.patch[.tweak]])
  • 一定要在第一行執行!!
  • 主要是相容性考量, 由於 CMake 3.0 開始有 breaking change
    如果太舊會有問題
  • 每個小版本可能也會有不同的行為, 不過本系列不會深入討論
    有興趣可以參考 cmake_policy()
  • major, minor
    • 代表有新 feature
  • patch
    • 代表有修復 bugs
  • tweak
    • 不太會用到

project()

project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])
  • 建議要在 cmake_minimum_required() 下一行就執行
  • PROJECT-NAME
    • 不能有空格
    • 通常只會有 letters, 下底線
    • 給某些 generator 使用 (ex. Xcode, Visual Studio)
  • VERSION
    • 好習慣是寫清楚 project 的版本, 讓 CMake 其他部分可以 reference
  • LANGUAGES
    • CMake 會幫忙檢查指定語言的 compiler 和 linker 能否正常直接
    • project 用的語言
    • 預設是 C, CXX
    • 可以給 none 就不會檢查語言
    • 多語言用空格分隔
    • 支援以下語言
      • C
      • CXX (i.e. C++)
      • CSharp (i.e. C#) (CMake >= 3.8)
      • CUDA, OBJC (i.e. Objective-C) (CMake >= 3.8)
      • OBJCXX (i.e. Objective-C++)
      • Fortran
      • HIP
      • ISPC
      • Swift
      • ASM
      • ASM_NASM
      • ASM_MARMASM
      • ASM_MASM
      • ASM-ATT

CMAKE_<LANG>_STANDARD, CMAKE_<LANG>_STANDARD_REQUIRED, CMAKE_<LANG>_EXTENSIONS

這三個 project-scope 變數通常是搭配使用的, 所以一起講 (variable scope 可以回去看 Day 4 介紹)

由於 C/C++ 版本需要有對應的 compiler / linker 版本, 最好在 CMakeLists.txt 一開始就設定好
也就是在 project()

比如以下範例 (Note: CXX 可以換成前面提過的其他語言, ex. C)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
  • CMAKE_CXX_STANDARD
    • 指定 language standard
    • 只是 建議 , 但該設定沒有強制力
      如果 compiler 不支援該 standard, 還是會 fallback 到最近的 standard 版本, 有點恐怖
    • 要搭配 CMAKE_CXX_STANDARD_REQUIRED 設為 ON 才有強制力
  • CMAKE_CXX_STANDARD_REQUIRED
    • Standard requirements 最小版本, 如果低於指定版本會噴錯給你看
    • 需要搭配 CMAKE_CXX_STANDARD
  • CMAKE_CXX_EXTENSIONS
    • 決定是否要用 compiler extensions
    • 我們會希望讓專案容易被其他人使用, 所以建議不要用 compiler-specific 的 extensions, 故設為 OFF

add_executable()

新增 executable target, Day 8 有介紹過, 這邊就不重複介紹了

add_subdirectory()

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
  • source_dir
    • relative path
      • source tree 內的話用這種
      • 相對於 source directory
    • absolute path
      • source tree 外才用
      • 就是絕對路徑
  • binary_dir
    • 預設會在 binary directory 建立和 source_dir 相同名稱的 folder
    • relative path
      • 相對於 binary directory
    • absolute path
      • source_dir 在 source tree 外才用
  • EXCLUDE_FROM_ALL
    • 用來控制 subdirectory 的 target 是否應該包含在 project 的 ALL target
  • SYSTEM
    • 指定 SYSTEM 會讓 subdirectory 的 non-imported targets 把該 subdirectory 加入 system header search paths
    • 重點: 是給 consuming targets 使用, 也就是 link 該 subdirectory 中 non-imported targets 的 targets
    • 尷尬的是, 如果該資料夾已經在我們 project 中了, 就不必把他加到 system header search path
      需要加到 system header search path 的是 imported targets, 也就是外部資源 (見 Day 8)
      但 imported targets 在被我們加進來的時候 CMake 就會幫我們做這件事了
    • 所以通常是用在 vendor directory, 不過如果可以, 還是建議用 imported targets 的方式比較好~

決定 Generator

我們會先用大家比較熟悉的 Makefile 作為 Generator
由於 Makefile 是 single-config generator, 我們一次只能 build 一種 config 的 binary
關於 single-config generators 和 multi-config generators 的比較在 Day 6 有詳細介紹

來 build code 吧!

我們先 cdcmake-example 底下, 執行

cmake -S src -B build -G "Unix Makefiles"

確認 build 底下有 config files 後, 就可以 build 了!

cmake --build build

Debug build

為了之後 debug 方便, 我們來 build debug 版本, 關於 build config 請見 Day 7

我們指定一個新的 build directory build-Debug 重新 configure 一次 (因為是 single-config generator, 所以比較麻煩), 然後加上 CMAKE_BUILD_TYPE:BOOL=Debug

cmake -S src -B build-Debug -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:BOOL=Debug

再 build 一次

cmake --build build-Debug

然後把原本的 build directory 改成 build-Release 後再做一次

cmake -S . -B build-Release -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:BOOL=Release
cmake --build build-Release

這樣我們就有 debug 和 release 版本了!

cmake-example/
├─ CMakeLists.txt
├─ build-Debug/
├─ build-Release/
├─ src/
│  ├─ CMakeLists.txt
│  ├─ main.cpp

如何檢查自己 build 的版本是否正確?

對於目前的 single-config generator 來說, 可以先簡單用 CMAKE_BUILD_TYPE 確認即可

message(DEBUG "Build type: ${CMAKE_BUILD_TYPE}")
cmake -S . -B build-Debug -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:BOOL=Debug --log-level=Debug

但是, 記得 Day 6 說過的, multi-config generators 是在 build stage 才決定 build type
所以用 cache variable 這招會失敗QQ

那要怎麼辦呢?
本系列會在 Day 26 介紹比較可靠的 generator expression
用法大概如下, 搭配 add_custom_target() (見 Day 8)

set(isDebug "$<IF:$<CONFIG:Debug>, is debug, not debug>")
add_custom_target(mytarget
    COMMAND ${CMAKE_COMMAND} -E echo "isDebug? ${isDebug}"
    VERBATIM
)

預告

現在, 我們有了第一版簡單的 CMake 專案, 是時候自己寫一些 libraries 了
不過在那之前, 下一篇會先介紹 link libraries 時的一些注意事項


上一篇
[Day 8] Target 類型
下一篇
[Day 10] Build Basics
系列文
30 天 CMake 跨平台之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言