cmake_language

版本新增於 3.18。

呼叫 CMake 命令的元操作。

概要

cmake_language(CALL <command> [<arg>...])
cmake_language(EVAL CODE <code>...)
cmake_language(DEFER <options>... CALL <command> [<arg>...])
cmake_language(SET_DEPENDENCY_PROVIDER <command> SUPPORTED_METHODS <methods>...)
cmake_language(GET_MESSAGE_LOG_LEVEL <out-var>)
cmake_language(EXIT <exit-code>)

簡介

此命令將會呼叫內建 CMake 命令或透過 macro()function() 命令建立的命令的元操作。

cmake_language 不會引入新的變數或策略範圍。

呼叫命令

cmake_language(CALL <command> [<arg>...])

使用給定的參數(如果有的話)呼叫名為 <command> 的命令。例如,以下程式碼

set(message_command "message")
cmake_language(CALL ${message_command} STATUS "Hello World!")

等同於

message(STATUS "Hello World!")

注意

為了確保程式碼的一致性,以下命令是不允許的

  • if / elseif / else / endif

  • block / endblock

  • while / endwhile

  • foreach / endforeach

  • function / endfunction

  • macro / endmacro

評估程式碼

cmake_language(EVAL CODE <code>...)

<code>... 評估為 CMake 程式碼。

例如,以下程式碼

set(A TRUE)
set(B TRUE)
set(C TRUE)
set(condition "(A AND B) OR C")

cmake_language(EVAL CODE "
  if (${condition})
    message(STATUS TRUE)
  else()
    message(STATUS FALSE)
  endif()"
)

等同於

set(A TRUE)
set(B TRUE)
set(C TRUE)
set(condition "(A AND B) OR C")

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/eval.cmake "
  if (${condition})
    message(STATUS TRUE)
  else()
    message(STATUS FALSE)
  endif()"
)

include(${CMAKE_CURRENT_BINARY_DIR}/eval.cmake)

延遲呼叫

版本新增於 3.19。

cmake_language(DEFER <options>... CALL <command> [<arg>...])

排程在稍後呼叫名為 <command> 的命令,並帶有給定的參數(如果有的話)。預設情況下,延遲的呼叫會被執行,如同寫在目前目錄的 CMakeLists.txt 檔案的末尾一樣,但即使在 return() 呼叫之後也會執行。參數中的變數參考會在延遲呼叫執行時評估。

選項為

DIRECTORY <dir>

為給定目錄的末尾而不是目前目錄排程呼叫。<dir> 可以參考來源目錄或其對應的二進制目錄。相對路徑被視為相對於目前的來源目錄。

給定的目錄必須是 CMake 已知的,即頂層目錄或透過 add_subdirectory() 新增的目錄。此外,給定的目錄必須尚未完成處理。這表示它可以是目前目錄或其祖先目錄之一。

ID <id>

指定延遲呼叫的識別符。<id> 不得為空,且不得以大寫字母 A-Z 開頭。<id> 只有在是由先前使用 ID_VAR 來取得 id 的呼叫自動產生時,才能以下底線 (_) 開頭。

ID_VAR <var>

指定一個變數,用於儲存延遲呼叫的識別符。如果未給定 ID <id>,將會產生一個新的識別符,且產生的 id 將以下底線 (_) 開頭。

目前排程的延遲呼叫清單可以被檢索

cmake_language(DEFER [DIRECTORY <dir>] GET_CALL_IDS <var>)

這將會在 <var> 中儲存延遲呼叫 id 的 分號分隔清單。這些 id 是針對呼叫被延遲到的目錄範圍(即它們將被執行的位置),這可能與它們被建立的範圍不同。DIRECTORY 選項可以用於指定要檢索呼叫 id 的範圍。如果未給定該選項,將會傳回目前目錄範圍的呼叫 id。

特定呼叫的詳細資訊可以從其 id 檢索

cmake_language(DEFER [DIRECTORY <dir>] GET_CALL <id> <var>)

這將會在 <var> 中儲存一個 分號分隔清單,其中第一個元素是要呼叫的命令名稱,剩餘的元素是其未評估的參數(任何包含的 ; 字元都會逐字包含,且無法與多個參數區分)。如果使用相同的 id 排程了多個呼叫,這將會檢索第一個。如果在指定的 DIRECTORY 範圍(如果沒有給定 DIRECTORY 選項,則為目前目錄範圍)中沒有排程具有給定 id 的呼叫,這會在變數中儲存一個空字串。

延遲的呼叫可以透過其 id 取消

cmake_language(DEFER [DIRECTORY <dir>] CANCEL_CALL <id>...)

這會取消在指定的 DIRECTORY 範圍(如果沒有給定 DIRECTORY 選項,則為目前目錄範圍)中與任何給定 id 相符的所有延遲呼叫。未知的 id 會被靜默忽略。

延遲呼叫範例

例如,以下程式碼

cmake_language(DEFER CALL message "${deferred_message}")
cmake_language(DEFER ID_VAR id CALL message "Canceled Message")
cmake_language(DEFER CANCEL_CALL ${id})
message("Immediate Message")
set(deferred_message "Deferred Message")

印出

Immediate Message
Deferred Message

Canceled Message 永遠不會被印出,因為其命令被取消了。deferred_message 變數參考直到呼叫點才會被評估,因此可以在延遲呼叫排程之後設定。

為了在排程延遲呼叫時立即評估變數參考,請使用 cmake_language(EVAL) 包裝它。然而,請注意參數將會在延遲呼叫中重新評估,儘管這可以透過使用方括號參數來避免。例如

set(deferred_message "Deferred Message 1")
set(re_evaluated [[${deferred_message}]])
cmake_language(EVAL CODE "
  cmake_language(DEFER CALL message [[${deferred_message}]])
  cmake_language(DEFER CALL message \"${re_evaluated}\")
")
message("Immediate Message")
set(deferred_message "Deferred Message 2")

也印出

Immediate Message
Deferred Message 1
Deferred Message 2

相依性提供者

版本新增於 3.24。

注意

此功能的高階介紹可以在 使用相依性指南 中找到。

cmake_language(SET_DEPENDENCY_PROVIDER <command> SUPPORTED_METHODS <methods>...)

當呼叫 find_package()FetchContent_MakeAvailable() 時,呼叫可能會轉發到相依性提供者,然後該提供者有機會滿足請求。如果請求是針對設定提供者時指定的 <methods> 之一,CMake 會呼叫提供者的 <command>,並帶有一組方法特定的參數。如果提供者未滿足請求,或者如果提供者不支援請求的方法,或者未設定提供者,則會使用內建的 find_package()FetchContent_MakeAvailable() 實作,以通常的方式滿足請求。

在設定提供者時,可以為 <methods> 指定以下一個或多個值

FIND_PACKAGE

提供者命令接受 find_package() 請求。

FETCHCONTENT_MAKEAVAILABLE_SERIAL

提供者命令接受 FetchContent_MakeAvailable() 請求。它期望每個相依性一次一個地饋送到提供者命令,而不是一次性饋送整個清單。

任何時間點只能設定一個提供者。如果已經設定了提供者,當呼叫 cmake_language(SET_DEPENDENCY_PROVIDER) 時,新的提供者會取代先前設定的提供者。指定的 <command> 必須在呼叫 cmake_language(SET_DEPENDENCY_PROVIDER) 時已經存在。作為一個特例,為 <command> 提供一個空字串且沒有 <methods> 將會捨棄任何先前設定的提供者。

相依性提供者只能在處理 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 變數指定的檔案之一時設定。因此,相依性提供者只能作為第一次呼叫 project() 的一部分來設定。在該上下文之外呼叫 cmake_language(SET_DEPENDENCY_PROVIDER) 將會導致錯誤。

版本新增於 3.30: 如果相依性提供者也希望在整個專案的 try_compile() 呼叫中啟用,則可以設定 PROPAGATE_TOP_LEVEL_INCLUDES_TO_TRY_COMPILE 全域屬性。

注意

相依性提供者的選擇應始終在使用者的控制之下。為了方便起見,專案可以選擇提供一個檔案,使用者可以在他們的 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 變數中列出,但是使用此類檔案應始終是使用者的選擇。

提供者命令

提供者定義單個 <command> 以接受請求。命令的名稱應該是該提供者特定的,而不是另一個提供者也可能使用的過於通用的名稱。這使得使用者可以在他們自己的自訂提供者中組合不同的提供者。建議的形式是 xxx_provide_dependency(),其中 xxx 是提供者特定的部分(例如 vcpkg_provide_dependency()conan_provide_dependency()ourcompany_provide_dependency() 等等)。

xxx_provide_dependency(<method> [<method-specific-args>...])

由於某些方法期望在呼叫範圍中設定某些變數,因此提供者命令通常應實作為巨集而不是函數。這確保它不會引入新的變數範圍。

CMake 傳遞給相依性提供者的參數取決於請求的類型。第一個參數始終是方法,並且它將永遠只是設定提供者時指定的 <methods> 之一。

FIND_PACKAGE

<method-specific-args> 將是傳遞給請求相依性的 find_package() 呼叫的所有內容。因此,這些 <method-specific-args> 中的第一個將始終是相依性的名稱。相依性名稱對於此方法是區分大小寫的,因為 find_package() 也會將它們視為區分大小寫。

如果提供者命令滿足請求,它必須設定 find_package() 期望設定的相同變數。對於名為 depName 的相依性,如果提供者滿足了請求,則必須將 depName_FOUND 設定為 true。如果提供者返回時未設定此變數,CMake 將假定請求未滿足,並將回退到內建實作。

如果提供者需要呼叫內建的 find_package() 實作作為其處理的一部分,它可以透過包含 BYPASS_PROVIDER 關鍵字作為參數之一來做到這一點。

FETCHCONTENT_MAKEAVAILABLE_SERIAL

<method-specific-args> 將是傳遞給與請求的相依性相對應的 FetchContent_Declare() 呼叫的所有內容,但以下例外

  • 如果 SOURCE_DIRBINARY_DIR 不是原始宣告的參數的一部分,它們將會以其預設值新增。

  • 如果 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 設定為 NEVER,則任何 FIND_PACKAGE_ARGS 都將被省略。

  • OVERRIDE_FIND_PACKAGE 關鍵字始終被省略。

<method-specific-args> 中的第一個將始終是相依性的名稱。相依性名稱對於此方法是不區分大小寫的,因為 FetchContent 也會將它們視為不區分大小寫。

如果提供者滿足請求,它應該呼叫 FetchContent_SetPopulated(),將相依性的名稱作為第一個參數傳遞。只有當提供者以與內建 FetchContent_MakeAvailable() 命令完全相同的方式提供相依性的來源和建置目錄時,才應給出該命令的 SOURCE_DIRBINARY_DIR 參數。

如果提供者返回時未針對命名的相依性呼叫 FetchContent_SetPopulated(),CMake 將假定請求未滿足,並將回退到內建實作。

請注意,空參數對於此方法可能很重要(例如,GIT_SUBMODULES 關鍵字後的空字串)。因此,如果將這些參數轉發到另一個命令,則必須格外小心以避免此類參數被靜默丟棄。

如果設定了 FETCHCONTENT_SOURCE_DIR_<uppercaseDepName>,則相依性提供者將永遠不會看到針對此方法的 <depName> 相依性的請求。當使用者設定此類變數時,他們會明確覆寫從何處取得該相依性,並承擔他們的覆寫版本滿足該相依性的任何要求,並且與專案中使用的其他任何內容相容的責任。根據 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 的值以及是否將 OVERRIDE_FIND_PACKAGE 選項給予 FetchContent_Declare(),設定 FETCHCONTENT_SOURCE_DIR_<uppercaseDepName> 也可能會阻止相依性提供者看到針對 find_package(depName) 呼叫的請求。

提供者範例

第一個範例僅攔截 find_package() 呼叫。提供者命令運行一個外部工具,如果該工具知道相依性,則該工具會將相關的工件複製到提供者特定的目錄中。然後,它依靠內建實作來找到這些工件。FetchContent_MakeAvailable() 呼叫不會通過提供者。

mycomp_provider.cmake
# Always ensure we have the policy settings this provider expects
cmake_minimum_required(VERSION 3.24)

set(MYCOMP_PROVIDER_INSTALL_DIR ${CMAKE_BINARY_DIR}/mycomp_packages
  CACHE PATH "The directory this provider installs packages to"
)
# Tell the built-in implementation to look in our area first, unless
# the find_package() call uses NO_..._PATH options to exclude it
list(APPEND CMAKE_MODULE_PATH ${MYCOMP_PROVIDER_INSTALL_DIR}/cmake)
list(APPEND CMAKE_PREFIX_PATH ${MYCOMP_PROVIDER_INSTALL_DIR})

macro(mycomp_provide_dependency method package_name)
  execute_process(
    COMMAND some_tool ${package_name} --installdir ${MYCOMP_PROVIDER_INSTALL_DIR}
    COMMAND_ERROR_IS_FATAL ANY
  )
endmacro()

cmake_language(
  SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
  SUPPORTED_METHODS FIND_PACKAGE
)

然後,使用者通常會像這樣使用上面的檔案

cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/mycomp_provider.cmake ...

下一個範例示範了一個接受兩種方法的提供者,但僅處理一個特定的相依性。它強制使用 FetchContent 提供 Google Test,但將所有其他相依性留給 CMake 的內建實作來滿足。它接受幾個不同的名稱,這示範了一種解決專案硬編碼不尋常或不希望的方式將此特定相依性添加到建置中的方法。該範例還示範了如何使用 list() 命令來保留可能被呼叫 FetchContent_MakeAvailable() 覆寫的變數。

mycomp_provider.cmake
cmake_minimum_required(VERSION 3.24)

# Because we declare this very early, it will take precedence over any
# details the project might declare later for the same thing
include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        e2239ee6043f73722e7aa812a459f54a28552929 # release-1.11.0
)

# Both FIND_PACKAGE and FETCHCONTENT_MAKEAVAILABLE_SERIAL methods provide
# the package or dependency name as the first method-specific argument.
macro(mycomp_provide_dependency method dep_name)
  if("${dep_name}" MATCHES "^(gtest|googletest)$")
    # Save our current command arguments in case we are called recursively
    list(APPEND mycomp_provider_args ${method} ${dep_name})

    # This will forward to the built-in FetchContent implementation,
    # which detects a recursive call for the same thing and avoids calling
    # the provider again if dep_name is the same as the current call.
    FetchContent_MakeAvailable(googletest)

    # Restore our command arguments
    list(POP_BACK mycomp_provider_args dep_name method)

    # Tell the caller we fulfilled the request
    if("${method}" STREQUAL "FIND_PACKAGE")
      # We need to set this if we got here from a find_package() call
      # since we used a different method to fulfill the request.
      # This example assumes projects only use the gtest targets,
      # not any of the variables the FindGTest module may define.
      set(${dep_name}_FOUND TRUE)
    elseif(NOT "${dep_name}" STREQUAL "googletest")
      # We used the same method, but were given a different name to the
      # one we populated with. Tell the caller about the name it used.
      FetchContent_SetPopulated(${dep_name}
        SOURCE_DIR "${googletest_SOURCE_DIR}"
        BINARY_DIR "${googletest_BINARY_DIR}"
      )
    endif()
  endif()
endmacro()

cmake_language(
  SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
  SUPPORTED_METHODS
    FIND_PACKAGE
    FETCHCONTENT_MAKEAVAILABLE_SERIAL
)

最後一個範例示範了如何修改 find_package() 呼叫的參數。它強制所有此類呼叫都具有 QUIET 關鍵字。它使用 BYPASS_PROVIDER 關鍵字來防止針對同一相依性遞迴呼叫提供者命令。

mycomp_provider.cmake
cmake_minimum_required(VERSION 3.24)

macro(mycomp_provide_dependency method)
  find_package(${ARGN} BYPASS_PROVIDER QUIET)
endmacro()

cmake_language(
  SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
  SUPPORTED_METHODS FIND_PACKAGE
)

取得目前訊息日誌層級

版本新增於 3.25。

cmake_language(GET_MESSAGE_LOG_LEVEL <output_variable>)

將目前的 message() 日誌層級寫入給定的 <output_variable>

請參閱 message() 以取得可能的日誌層級。

目前的訊息日誌層級可以使用 --log-level cmake(1) 程式的命令列選項或使用 CMAKE_MESSAGE_LOG_LEVEL 變數來設定。

如果命令列選項和變數都已設定,則命令列選項優先。如果兩者都未設定,則傳回預設日誌層級。

終止腳本

版本新增於 3.29。

cmake_language(EXIT <exit-code>)

終止目前的 cmake -P 腳本並以 <exit-code> 退出。

此命令僅在 腳本模式 下工作。如果在該上下文之外使用,它將導致嚴重錯誤。

<exit-code> 應為非負數。如果 <exit-code> 為負數,則行為未指定(例如,在 Windows 上錯誤代碼 -1 會變成 0xffffffff,而在 Linux 上會變成 255)。超過 255 的結束代碼可能不被底層 shell 或平台支援,而且有些 shell 可能會特別解讀高於 125 的值。因此,建議僅在 0 到 125 的範圍內指定 <exit-code>