cmake_语言

在 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 命令或通过 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

  • 函数/结束函数

  • 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() 调用之后运行。在执行延迟调用时评估参数中的变量引用。

选项是:

目录<目录>

为给定目录而不是当前目录的末尾安排调用。 <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 的 分号分隔列表。 ids 用于调用被推迟到的目录范围(即它们将被执行的位置),这可能与创建它们的范围不同。 DIRECTORY 选项可用于指定检索呼叫 ID 的范围。如果未给出该选项,则将返回当前目录范围的调用 ID。

可以从其 id 中检索特定调用的详细信息:

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

这将在 <var> 中存储一个分号分隔的列表 <CMake Language Lists>` 其中第一个元素是要调用的命令的名称,其余元素是其未评估的参数(任何包含的``;``字符都是按字面意思包含的,无法与多个参数区分开来)。如果使用相同的 ID 安排了多个呼叫,这将检索第一个。如果在指定的 DIRECTORY 范围内没有使用给定的 id 安排调用(如果没有给出 DIRECTORY 选项则为当前目录范围),这将在变量中存储一个空字符串。

延迟调用可能会被他们的 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

Cancelled 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

provider 命令接受 find_package() 请求。

FETCHCONTENT_MAKEAVAILABLE_SERIAL

provider 命令接受 FetchContent_MakeAvailable 请求。它期望每个依赖项一次一个地被提供给提供者命令,而不是一次将整个列表提供给提供者命令。

在任何时间点只能设置一个提供者。如果在调用 cmake_language(SET_DEPENDENCY_PROVIDER) 时已经设置了提供程序,则新的提供程序将替换之前设置的提供程序。当调用 cmake_language(SET_DEPENDENCY_PROVIDER) 时,指定的 <command> 必须已经存在。作为一种特殊情况,为 <command> 提供空字符串而不提供 <methods> 将丢弃任何先前设置的提供程序。

只能在处理 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 变量指定的文件之一时设置依赖项提供程序。因此,依赖提供者只能设置为第一次调用 project() 的一部分。在该上下文之外调用 cmake_language(SET_DEPENDENCY_PROVIDER) 将导致错误。

备注

依赖提供者的选择应该始终在用户的控制之下。为方便起见,项目可以选择提供用户可以在其 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() 也区分大小写。

如果 provider 命令满足请求,它必须设置 find_package 期望设置的相同变量。对于名为“depName”的依赖项,提供者必须在满足请求时将“depName_FOUND”设置为 true。如果提供者返回时没有设置此变量,CMake 将假定请求未完成并将回退到内置实现。

如果提供者需要调用内置的 find_package 实现作为其处理的一部分,它可以通过将 BYPASS_PROVIDER 关键字作为参数之一来实现。

FETCHCONTENT_MAKEAVAILABE_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 ,将依赖项的名称作为第一个参数传递。该命令的 SOURCE_DIRBINARY_DIR 参数只有在提供商以与内置 FetchContent_MakeAvailable 命令完全相同的方式提供依赖项的源和构建目录时才应给出。

如果提供者返回时没有为指定的依赖项调用 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() 调用。 provider 命令运行一个外部工具,该工具将相关工件复制到特定于提供者的目录中(如果该工具知道依赖项)。然后它依赖于内置的实现来找到那些工件。 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>

有关可能的日志记录级别,请参阅:command:message

当前消息日志记录级别可以使用 --log-level cmake(1)` 程序的命令行选项或使用 :variable: CMAKE_MESSAGE_LOG_LEVEL 变量。

如果同时设置了命令行选项和变量,则命令行选项优先。如果两者均未设置,则返回默认日志记录级别。