cmake-developer(7)

簡介

本手冊旨在供開發人員參考,這些開發人員使用 cmake-language(7) 程式碼,無論是編寫自己的模組、撰寫自己的建置系統,還是使用 CMake 本身。

請參閱 https://cmake.dev.org.tw/get-involved/ 以參與 CMake 上游的開發。它包含貢獻說明的連結,而貢獻說明又連結到 CMake 本身的開發人員指南。

存取 Windows 登錄檔

CMake 提供一些功能來存取 Windows 平台上的登錄檔。

查詢 Windows 登錄檔

版本 3.24 新增。

cmake_host_system_information() 命令提供了查詢本機電腦登錄檔的可能性。 有關更多資訊,請參閱 cmake_host_system(QUERY_WINDOWS_REGISTRY)

使用 Windows 登錄檔尋找

版本 3.24 變更。

find_file()find_library()find_path()find_program()find_package() 命令的 HINTSPATHS 選項,在 Windows 平台上提供了查詢登錄檔的可能性。

使用帶有常規擴展的 BNF 表示法指定的登錄檔查詢的正式語法如下

registry_query  ::=  '[' sep_definition? root_key
                         ((key_separator sub_key)? (value_separator value_name_)?)? ']'
sep_definition  ::=  '{' value_separator '}'
root_key        ::=  'HKLM' | 'HKEY_LOCAL_MACHINE' | 'HKCU' | 'HKEY_CURRENT_USER' |
                     'HKCR' | 'HKEY_CLASSES_ROOT' | 'HKCC' | 'HKEY_CURRENT_CONFIG' |
                     'HKU' | 'HKEY_USERS'
sub_key         ::=  element (key_separator element)*
key_separator   ::=  '/' | '\\'
value_separator ::=  element | ';'
value_name      ::=  element | '(default)'
element         ::=  character\+
character       ::=  <any character except key_separator and value_separator>

可選項目 sep_definition 提供了指定用於分隔 sub_keyvalue_name 項目的字串的可能性。 如果未指定,則使用字元 ;。 可以將多個 registry_query 項目指定為路徑的一部分。

# example using default separator
find_file(... PATHS "/root/[HKLM/Stuff;InstallDir]/lib[HKLM\\\\Stuff;Architecture]")

# example using different specified separators
find_library(... HINTS "/root/[{|}HKCU/Stuff|InstallDir]/lib[{@@}HKCU\\\\Stuff@@Architecture]")

如果未指定 value_name 項目,或具有特殊名稱 (default),則將返回預設值的內容(如果有的話)。 value_name 的支援類型為

  • REG_SZ.

  • REG_EXPAND_SZ。 返回的資料已擴展。

  • REG_DWORD.

  • REG_QWORD.

當登錄檔查詢失敗時,通常是因為金鑰不存在或資料類型不受支援,字串 /REGISTRY-NOTFOUND 將替換為 [] 查詢表達式。

尋找模組

「尋找模組」是一個 Find<PackageName>.cmake 檔案,當為 <PackageName> 調用 find_package() 命令時載入。

尋找模組的主要任務是確定套件是否可用,設定 <PackageName>_FOUND 變數以反映這一點,並提供使用該套件所需的任何變數、巨集和導入目標。 在上游函式庫未提供 組態檔套件 的情況下,尋找模組非常有用。

傳統方法是對所有事物使用變數,包括函式庫和可執行檔:請參閱下面的「標準變數名稱」部分。 這就是 CMake 提供的大多數現有尋找模組所做的事情。

比較現代的方法是盡可能像 組態檔套件 檔案一樣運作,方法是提供 導入目標。 這具有將 使用需求 傳播給使用者的優點。

在任何一種情況下(甚至在同時提供變數和導入目標時),尋找模組都應提供與具有相同名稱的舊版本的向後相容性。

FindFoo.cmake 模組通常會由以下命令載入

find_package(Foo [major[.minor[.patch[.tweak]]]]
             [EXACT] [QUIET] [REQUIRED]
             [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

有關為尋找模組設定哪些變數的詳細資訊,請參閱 find_package() 文件。 其中大多數是透過使用 FindPackageHandleStandardArgs 處理的。

簡而言之,模組應僅定位與請求版本相容的套件版本,如 Foo_FIND_VERSION 變數系列所述。 如果 Foo_FIND_QUIETLY 設定為 true,則應避免列印訊息,包括任何抱怨找不到套件的訊息。 如果 Foo_FIND_REQUIRED 設定為 true,如果找不到套件,模組應發出 FATAL_ERROR。 如果兩者都未設定為 true,如果找不到套件,則應列印非嚴重錯誤訊息。

尋找多個半獨立部分(例如函式庫捆綁包)的套件應搜尋 Foo_FIND_COMPONENTS 中列出的組件(如果已設定),並且僅當對於每個未找到的搜尋組件 <c>Foo_FIND_REQUIRED_<c> 未設定為 true 時,才將 Foo_FOUND 設定為 true。 find_package_handle_standard_args()HANDLE_COMPONENTS 參數可用於實現此目的。

如果未設定 Foo_FIND_COMPONENTS,則搜尋和需要的模組取決於尋找模組,但應記錄在文件中。

對於內部實作,普遍接受的慣例是以底線開頭的變數僅供臨時使用。

標準變數名稱

對於採用設定變數方法(代替或除了建立導入目標之外)的 FindXxx.cmake 模組,應使用以下變數名稱來保持尋找模組之間的一致性。 請注意,所有變數都以 Xxx_ 開頭,除非另有說明,否則必須與 FindXxx.cmake 檔案的名稱完全匹配,包括大小寫。 變數名稱上的此前綴確保它們不會與其他尋找模組的變數衝突。 對於尋找模組定義的任何巨集、函數和導入目標,也應遵循相同的模式。

Xxx_INCLUDE_DIRS

用戶端程式碼使用的一個變數中列出的最終包含目錄集。 這不應是快取項目(請注意,這也表示此變數不應用作 find_path() 命令的結果變數 - 請參閱下面的 Xxx_INCLUDE_DIR)。

Xxx_LIBRARIES

與模組一起使用的函式庫。 這些可以是 CMake 目標、函式庫二進位檔的完整絕對路徑,或連結器必須在其搜尋路徑中找到的函式庫名稱。 這不應是快取項目(請注意,這也表示此變數不應用作 find_library() 命令的結果變數 - 請參閱下面的 Xxx_LIBRARY)。

Xxx_DEFINITIONS

編譯使用模組的程式碼時要使用的編譯定義。 這實際上不應包括諸如 -DHAS_JPEG 之類的選項,用戶端原始碼檔案使用這些選項來決定是否要 #include <jpeg.h>

Xxx_EXECUTABLE

可執行檔的完整絕對路徑。 在這種情況下,Xxx 可能不是模組的名稱,它可能是工具的名稱(通常轉換為全部大寫),假設該工具具有如此知名的名稱,以至於不太可能存在另一個同名的工具。 將其用作 find_program() 命令的結果變數是合適的。

Xxx_YYY_EXECUTABLE

Xxx_EXECUTABLE 類似,不同之處在於此處 Xxx 始終是模組名稱,而 YYY 是工具名稱(同樣,通常為全部大寫)。 如果工具名稱不是很廣為人知或有可能與另一個工具衝突,請優先使用此形式。 為了獲得更好的一致性,如果模組提供多個可執行檔,也請優先使用此形式。

Xxx_LIBRARY_DIRS

可選地,用戶端程式碼使用的一個變數中列出的最終函式庫目錄集。 這不應是快取項目。

Xxx_ROOT_DIR

安裝 Xxx 的基本目錄,如果找到 Xxx,則可以由尋找模組選擇性地設定。 這對於需要相對於通用基底(或根)目錄引用許多檔案的大型套件很有用。 不要與從外部設定的 Xxx_ROOT 提示變數混淆,尋找模組透過該變數知道在哪裡尋找 Xxx

Xxx_VERSION_VV

這種形式的變數指定提供的 Xxx 模組是否為模組的版本 VV。 對於給定的模組,不應有多個這種形式的變數設定為 true。 例如,模組 Barry 可能已經發展多年,並經歷了許多不同的主要版本。 Barry 模組的版本 3 可能將變數 Barry_VERSION_3 設定為 true,而較舊版本的模組可能將 Barry_VERSION_2 設定為 true。 Barry_VERSION_3Barry_VERSION_2 都設定為 true 將是錯誤的。

Xxx_WRAP_YY

當這種形式的變數設定為 false 時,表示不應使用相關的包裝命令。 包裝命令取決於模組,它可能由模組名稱暗示,或者可能由變數的 YY 部分指定。

Xxx_Yy_FOUND

對於這種形式的變數,Yy 是模組組件的名稱。 它應與可能傳遞給模組的 find_package() 命令的有效組件名稱之一完全匹配。 如果這種形式的變數設定為 false,則表示找不到或無法使用模組 XxxYy 組件。 這種形式的變數通常用於可選組件,以便調用者可以檢查可選組件是否可用。

Xxx_FOUND

find_package() 命令返回給調用者時,如果模組被認為已成功找到,則此變數將設定為 true。

Xxx_NOT_FOUND_MESSAGE

如果組態檔已將 Xxx_FOUND 設定為 FALSE,則應由組態檔設定。 find_package() 命令和 find_package_handle_standard_args() 將列印包含的訊息,以告知使用者問題。 使用此變數而不是直接調用 message() 來報告找不到模組或套件的原因。

Xxx_RUNTIME_LIBRARY_DIRS

可選地,執行連結到共用函式庫的可執行檔時使用的執行時期函式庫搜尋路徑。 使用者程式碼應使用此列表在 windows 上建立 PATH,或在 UNIX 上建立 LD_LIBRARY_PATH。 這不應是快取項目。

Xxx_VERSION

如果有的話,找到的套件的完整版本字串。 請注意,許多現有模組都提供 Xxx_VERSION_STRING 來代替。

Xxx_VERSION_MAJOR

如果有的話,找到的套件的主要版本。

Xxx_VERSION_MINOR

如果有的話,找到的套件的次要版本。

Xxx_VERSION_PATCH

如果有的話,找到的套件的修補程式版本。

以下名稱通常不應在 CMakeLists.txt 檔案中使用。 它們旨在供尋找模組使用,以指定和快取特定檔案或目錄的位置。 使用者通常可以設定和編輯這些變數,以控制尋找模組的行為(例如手動輸入函式庫的路徑)

Xxx_LIBRARY

函式庫的路徑。 僅當模組提供單個函式庫時才使用此形式。 將其用作 find_library() 命令的結果變數是合適的。

Xxx_Yy_LIBRARY

模組 Xxx 提供的函式庫 Yy 的路徑。 當模組提供多個函式庫,或者其他模組也可能提供同名函式庫時,請使用此形式。 將此形式用作 find_library() 命令的結果變數也是合適的。

Xxx_INCLUDE_DIR

當模組僅提供單個函式庫時,可以使用此變數指定在哪裡找到使用該函式庫的標頭(或更準確地說,函式庫的使用者應將其新增到標頭搜尋路徑的路徑)。 將其用作 find_path() 命令的結果變數是合適的。

Xxx_Yy_INCLUDE_DIR

如果模組提供多個函式庫,或者其他模組也可能提供同名函式庫,則建議使用此形式來指定在哪裡找到使用模組提供的函式庫 Yy 的標頭。 同樣,將其用作 find_path() 命令的結果變數是合適的。

為了防止使用者被過多的設定淹沒,請盡量將許多選項保留在快取之外,至少保留一個可用於停用模組使用或定位未找到的函式庫的選項(例如 Xxx_ROOT_DIR)。 出於同樣的原因,將大多數快取選項標記為進階。 對於同時提供偵錯和發行二進位檔的套件,通常會建立帶有 _LIBRARY_<CONFIG> 後綴的快取變數,例如 Foo_LIBRARY_RELEASEFoo_LIBRARY_DEBUGSelectLibraryConfigurations 模組在這種情況下可能會有所幫助。

雖然這些是標準變數名稱,但您應為任何實際使用的舊名稱提供向後相容性。 確保將它們註釋為已棄用,這樣就沒有人開始使用它們。

範例尋找模組

我們將描述如何為函式庫 Foo 建立一個簡單的尋找模組。

模組的頂部應以授權聲明開始,後跟一個空白行,然後後跟 括號註解。 註解應以 .rst: 開頭,以指示其餘內容為 reStructuredText 格式的文件。 例如

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file LICENSE.rst or https://cmake.dev.org.tw/licensing for details.

#[=======================================================================[.rst:
FindFoo
-------

Finds the Foo library.

Imported Targets
^^^^^^^^^^^^^^^^

This module provides the following imported targets, if found:

``Foo::Foo``
  The Foo library

Result Variables
^^^^^^^^^^^^^^^^

This will define the following variables:

``Foo_FOUND``
  True if the system has the Foo library.
``Foo_VERSION``
  The version of the Foo library which was found.
``Foo_INCLUDE_DIRS``
  Include directories needed to use Foo.
``Foo_LIBRARIES``
  Libraries needed to link to Foo.

Cache Variables
^^^^^^^^^^^^^^^

The following cache variables may also be set:

``Foo_INCLUDE_DIR``
  The directory containing ``foo.h``.
``Foo_LIBRARY``
  The path to the Foo library.

#]=======================================================================]

模組文件包含

  • 指定模組名稱的帶底線標題。

  • 模組尋找內容的簡單描述。 某些套件可能需要更多描述。 如果模組使用者應注意任何注意事項或其他詳細資訊,請在此處指定。

  • 列出模組提供的導入目標(如果有的話)的部分。

  • 列出模組提供的結果變數的部分。

  • 可選地,列出模組使用的快取變數(如果有的話)的部分。

如果套件提供任何巨集或函數,則應將它們列在額外的部分中,但可以使用定義這些巨集或函數的位置正上方的額外 .rst: 註解區塊進行記錄。

尋找模組實作可以從文件區塊下方開始。 現在必須找到實際的函式庫等等。 此處的程式碼顯然會因模組而異(畢竟,處理此問題是尋找模組的重點),但函式庫往往有一種常見模式。

首先,我們嘗試使用 pkg-config 尋找函式庫。 請注意,我們不能依賴此功能,因為它可能不可用,但它提供了一個良好的起點。

find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
  pkg_check_modules(PC_Foo QUIET Foo)
endif()

這應該定義一些以 PC_Foo_ 開頭的變數,這些變數包含來自 Foo.pc 檔案的資訊。

現在我們需要尋找函式庫和包含檔案; 我們使用來自 pkg-config 的資訊,在檢查其他預設路徑之前,向 CMake 提供有關在哪裡尋找的提示。

find_path(Foo_INCLUDE_DIR
  NAMES foo.h
  HINTS ${PC_Foo_INCLUDE_DIRS}
  PATH_SUFFIXES Foo
)
find_library(Foo_LIBRARY
  NAMES foo
  HINTS ${PC_Foo_LIBRARY_DIRS}
)

或者,如果函式庫有多種組態可用,則可以使用 SelectLibraryConfigurations 來自動設定 Foo_LIBRARY 變數。

find_library(Foo_LIBRARY_RELEASE
  NAMES foo
  HINTS ${PC_Foo_LIBRARY_DIRS}/Release
)
find_library(Foo_LIBRARY_DEBUG
  NAMES foo
  HINTS ${PC_Foo_LIBRARY_DIRS}/Debug
)

include(SelectLibraryConfigurations)
select_library_configurations(Foo)

如果您有取得版本的好方法(例如從標頭檔),則可以使用該資訊來設定 Foo_VERSION(儘管請注意,尋找模組傳統上使用 Foo_VERSION_STRING,因此您可能需要同時設定兩者)。 否則,請嘗試使用來自 pkg-config 的資訊

set(Foo_VERSION ${PC_Foo_VERSION})

現在我們可以使用 FindPackageHandleStandardArgs 為我們完成大部分剩餘工作

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo
  REQUIRED_VARS
    Foo_LIBRARY
    Foo_INCLUDE_DIR
  VERSION_VAR Foo_VERSION
)

這將檢查 REQUIRED_VARS 是否包含值(不以 -NOTFOUND 結尾),並適當地設定 Foo_FOUND。 它還將快取這些值。 如果設定了 Foo_VERSION,並且將所需版本傳遞給 find_package(),它將根據 Foo_VERSION 中的版本檢查請求的版本。 它還將根據需要列印訊息; 請注意,如果找到套件,它將列印第一個所需變數的內容,以指示找到的位置。

在此時,我們必須為尋找模組的使用者提供一種連結到已找到的函式庫或多個函式庫的方法。 如上面的「尋找模組」部分所述,有兩種方法。 傳統的變數方法如下所示

if(Foo_FOUND)
  set(Foo_LIBRARIES ${Foo_LIBRARY})
  set(Foo_INCLUDE_DIRS ${Foo_INCLUDE_DIR})
  set(Foo_DEFINITIONS ${PC_Foo_CFLAGS_OTHER})
endif()

如果找到多個函式庫,則應將所有函式庫都包含在這些變數中(有關更多資訊,請參閱「標準變數名稱」部分)。

提供導入目標時,這些目標應具有命名空間(因此使用 Foo:: 前綴); CMake 將識別傳遞給 target_link_libraries() 的值,這些值在其名稱中包含 ::,應該是導入目標(而不僅僅是函式庫名稱),並且如果該目標不存在,將產生適當的診斷訊息(請參閱原則 CMP0028)。

if(Foo_FOUND AND NOT TARGET Foo::Foo)
  add_library(Foo::Foo UNKNOWN IMPORTED)
  set_target_properties(Foo::Foo PROPERTIES
    IMPORTED_LOCATION "${Foo_LIBRARY}"
    INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
    INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
  )
endif()

關於這一點需要注意的一件事是,INTERFACE_INCLUDE_DIRECTORIES 和類似屬性應僅包含有關目標本身的資訊,而不包含其任何相依性。 相反,這些相依性也應是目標,並且應告知 CMake 它們是此目標的相依性。 然後,CMake 將自動組合所有必要的資訊。

add_library() 命令中建立的 IMPORTED 目標的類型始終可以指定為 UNKNOWN 類型。 這簡化了可能找到靜態或共用變體的程式碼,CMake 將透過檢查檔案來確定類型。

如果函式庫有多種組態可用,則也應填充 IMPORTED_CONFIGURATIONS 目標屬性

if(Foo_FOUND)
  if (NOT TARGET Foo::Foo)
    add_library(Foo::Foo UNKNOWN IMPORTED)
  endif()
  if (Foo_LIBRARY_RELEASE)
    set_property(TARGET Foo::Foo APPEND PROPERTY
      IMPORTED_CONFIGURATIONS RELEASE
    )
    set_target_properties(Foo::Foo PROPERTIES
      IMPORTED_LOCATION_RELEASE "${Foo_LIBRARY_RELEASE}"
    )
  endif()
  if (Foo_LIBRARY_DEBUG)
    set_property(TARGET Foo::Foo APPEND PROPERTY
      IMPORTED_CONFIGURATIONS DEBUG
    )
    set_target_properties(Foo::Foo PROPERTIES
      IMPORTED_LOCATION_DEBUG "${Foo_LIBRARY_DEBUG}"
    )
  endif()
  set_target_properties(Foo::Foo PROPERTIES
    INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
    INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
  )
endif()

RELEASE 變體應在屬性中首先列出,以便在使用者使用的組態與任何列出的 IMPORTED_CONFIGURATIONS 不完全匹配時選擇該變體。

大多數快取變數應隱藏在 ccmake 介面中,除非使用者明確要求編輯它們。

mark_as_advanced(
  Foo_INCLUDE_DIR
  Foo_LIBRARY
)

如果此模組取代了較舊的版本,則應設定相容性變數,以盡可能減少干擾。

# compatibility variables
set(Foo_VERSION_STRING ${Foo_VERSION})