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

編譯使用模組的程式碼時要使用的編譯定義。這真的不應包含用戶端原始程式碼檔案用來決定是否 #include <jpeg.h> 的選項,例如 -DHAS_JPEG

Xxx_EXECUTABLE

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

Xxx_YYY_EXECUTABLE

Xxx_EXECUTABLE 類似,但此處的 Xxx 始終是模組名稱,而 YYY 是工具名稱(同樣,通常為全部大寫)。 如果工具名稱不是很廣為人知或有可能與其他工具衝突,則首選此形式。 為了更好的一致性,如果模組提供多個可執行檔,也首選此形式。

Xxx_LIBRARY_DIRS

(可選)最終的一組程式庫目錄,列在一個變數中,供用戶端程式碼使用。 這不應該是快取條目。

Xxx_ROOT_DIR

尋找模組基本目錄的位置。

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

如果 config-file 將 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 檔案中使用。 它們旨在供 Find 模組使用,以指定和快取特定檔案或目錄的位置。 使用者通常可以設定和編輯這些變數,以控制 Find 模組的行為(例如,手動輸入程式庫的路徑)

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 模組對這種情況很有幫助。

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

範例 Find 模組

我們將說明如何為程式庫 Foo 建立一個簡單的 find 模組。

模組的頂部應以許可聲明開始,後跟一個空白行,然後是 Bracket Comment。 註解應以 .rst: 開頭,以表示其餘內容為 reStructuredText 格式的文件。 例如

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt 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)
pkg_check_modules(PC_Foo QUIET Foo)

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

現在我們需要找到函式庫和包含檔案;我們使用來自 pkg-config 的資訊,向 CMake 提供關於尋找位置的提示。

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

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

find_library(Foo_LIBRARY_RELEASE
  NAMES foo
  PATHS ${PC_Foo_LIBRARY_DIRS}/Release
)
find_library(Foo_LIBRARY_DEBUG
  NAMES foo
  PATHS ${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
  FOUND_VAR Foo_FOUND
  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})