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>)

這會將延遲呼叫 id 的以分號分隔的清單儲存在 <var> 中。這些 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 Messagedeferred_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 的值以及是否向 FetchContent_Declare() 給出了 OVERRIDE_FIND_PACKAGE 選項,設定 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)。基礎 Shell 或平台可能不支援大於 255 的退出碼,並且某些 Shell 可能會特別解釋大於 125 的值。因此,建議僅指定範圍從 0 到 125 的 <exit-code>