CMakeDeps

CMakeDeps 生成器会为每个依赖项生成必要的文件,以便能够使用 cmake 的 find_package() 函数来定位这些依赖项。它的用法如下:

from conan import ConanFile

class App(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    requires = "hello/0.1"
    generators = "CMakeDeps"

可以在 generate() 方法中完成完整的实例化,这允许自定义配置。

from conan import ConanFile
from conan.tools.cmake import CMakeDeps

class App(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    requires = "hello/0.1"

    def generate(self):
        cmake = CMakeDeps(self)
        cmake.generate()
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(compressor C)

find_package(hello REQUIRED)

add_executable(${PROJECT_NAME} src/main.c)
target_link_libraries(${PROJECT_NAME} hello::hello)

默认情况下,对于一个 hello 依赖项,您需要使用 find_package(hello) 并链接到目标 hello::hello。请参阅 影响 CMakeDeps 的属性,例如 cmake_target_name,以自定义 conanfile.py 中依赖项及其组件的文件和目标名称。

注意

CMakeDeps 旨在与 CMakeToolchain 生成器一起运行。它会将 CMAKE_PREFIX_PATHCMAKE_MODULE_PATH 设置为正确的文件夹(conanfile.generators_folder),以便 CMake 可以找到生成的配置文件/模块文件。

生成的文件

  • XXX-config.cmake:默认情况下,CMakeDeps 生成器将创建声明依赖项及其组件(如果已声明)的目标的配置文件。

  • FindXXX.cmake:仅当依赖项将属性 cmake_find_mode 设置为“module”或“both”时才会生成。请参阅 影响 CMakeDeps 的属性,该属性在依赖项中设置。

  • 其他必需的 *.cmake:例如版本、标志和目录数据或配置等文件。

请注意,它还将生成一个 **conandeps_legacy.cmake** 文件。这是一个提供与 Conan 1 的 cmake 生成器类似行为的文件,允许使用 include(${CMAKE_BINARY_DIR}/generators/conandeps_legacy.cmake) 来包含此文件,并提供一个单独的 CMake 变量 CONANDEPS_LEGACY,允许链接所有直接和间接依赖项,而无需显式列出它们,例如:target_link_libraries(app ${CONANDEPS_LEGACY})。这是为 Conan 1.X 用户提供的一种便利,以便在不更改其整体开发流程的情况下升级到 Conan 2,但否则不推荐这样做。使用 CMake 的规范流程,即显式使用 find_package()target_link_libraries(... pkg1::pkg1 pkg2::pkg2) 以及目标,才是正确的方法。

自定义

您可以调整创建的 CMakeDeps 对象中的一些属性来更改默认行为。

配置

允许定义标准的 Release、Debug 等之外的自定义用户 CMake 配置。

def generate(self):
    deps = CMakeDeps(self)
    # By default, ``deps.configuration`` will be ``self.settings.build_type``
    if self.options["hello"].shared:
        # Assuming the current project ``CMakeLists.txt`` defines the ReleasedShared configuration.
        deps.configuration = "ReleaseShared"
    deps.generate()

CMakeDeps 是一个*多配置*生成器,它可以正确地为 Release/Debug 配置创建文件,以便 IDE(如 Visual Studio)可以同时使用它们。在单配置环境中,有必要定义一个配置,该配置必须通过命令行参数 cmake ... -DCMAKE_BUILD_TYPE=<build-type> 提供(Conan 会在必要时自动执行此操作,在 CMake.configure() 助手中)。

build_context_activated

当您有*build-require*时,默认情况下不会生成配置文件(*xxx-config.cmake*)。但您可以使用 **build_context_activated** 属性激活它。

tool_requires = ["my_tool/0.0.1"]

def generate(self):
    cmake = CMakeDeps(self)
    # generate the config files for the tool require
    cmake.build_context_activated = ["my_tool"]
    cmake.generate()

build_context_suffix

当同一个包既是*build-require*又是*常规 require*时,这会在生成器中引起冲突,因为配置文件、目标名称、变量名称等的文件名会发生冲突。

例如,这是某些需求(capnproto、protobuf…)的典型情况,这些需求包含一个用于在构建时生成源代码的工具(因此它是一个*build_require*),但也提供了一个用于链接到最终应用程序的库,因此您还有一个*常规 require*。当进行交叉构建时,解决此冲突尤为重要,因为工具(将在构建机器上运行)属于与库(将在主机机器上“运行”)不同的二进制包。

您可以使用 **build_context_suffix** 属性为某个需求指定一个后缀,这样构建上下文(工具需求)中的需求的文件/目标/变量将被重命名。

tool_requires = ["my_tool/0.0.1"]
requires = ["my_tool/0.0.1"]

def generate(self):
    cmake = CMakeDeps(self)
    # generate the config files for the tool require
    cmake.build_context_activated = ["my_tool"]
    # disambiguate the files, targets, etc
    cmake.build_context_suffix = {"my_tool": "_BUILD"}
    cmake.generate()

build_context_build_modules

此外,*build_modules* 还有一个其他问题。如您所知,需求的配方可以声明一个 cppinfo.build_modules 条目,其中包含一个或多个 **.cmake** 文件。当需求由 cmake 的 find_package() 函数找到时,Conan 将自动包含这些文件。

默认情况下,Conan 只会包含来自*host*上下文(常规需求)的构建模块,以避免冲突,但您可以更改默认行为。

使用 **build_context_build_modules** 属性指定需求名称,以包含来自 **tool_requires** 的 **build_modules**。

tool_requires = ["my_tool/0.0.1"]

def generate(self):
    cmake = CMakeDeps(self)
    # generate the config files for the tool require
    cmake.build_context_activated = ["my_tool"]
    # Choose the build modules from "build" context
    cmake.build_context_build_modules = ["my_tool"]
    cmake.generate()

check_components_exist

警告

check_components_exist 属性是*实验性*的,可能会发生更改。

此属性默认为 False。如果您希望在消费者中使用组件调用 find_package() 时添加一个检查,请使用此属性。例如,如果我们正在使用一个声明了多个组件的 Conan 包(如 Boost)。如果我们将其设置为 True,则消费者的 find_package() 调用将检查所需的组件是否存在,否则将引发错误。您可以在 generate() 方法中设置此属性。

requires = "boost/1.81.0"

...

def generate(self):
    deps = CMakeDeps(self)
    deps.check_components_exist = True
    deps.generate()

然后,在使用 Boost 时,find_package() 将会报错,因为 fakecomp 不存在。

cmake_minimum_required(VERSION 3.15)
...
find_package(Boost COMPONENTS random regex fakecomp REQUIRED)
...

参考

class CMakeDeps(conanfile)
generate()

此方法会将生成的文件保存到 conanfile.generators_folder 文件夹。

set_property(dep, prop, value, build_context=False)

使用此方法,您可以从使用者覆盖 Conan 配方设置的 属性 值。这可以用于 cmake_file_namecmake_target_namecmake_find_modecmake_module_file_namecmake_module_target_namecmake_additional_variables_prefixescmake_config_version_compatsystem_package_versioncmake_build_modulesnosonamecmake_target_aliasescmake_extra_variables

参数:
  • dep – 要设置 属性 的依赖项名称。对于组件,请使用语法:dep_name::component_name

  • prop属性 的名称。

  • value – 属性的值。使用 None 可使上游配方设置的任何值失效。

  • build_context – 如果要为属于构建上下文的依赖项设置属性,则设置为 True(默认为 False)。

get_cmake_package_name(dep, module_mode=None)

获取 find_package(XXX) 调用文件的名称。

get_find_mode(dep)
参数:

dep – 需求。

返回:

可以是 "none""config""module""both" 之一。如果未设置,则默认为 "config"

属性

以下属性会影响 CMakeDeps 生成器。

  • cmake_file_name:为当前包生成的配置文件将遵循 <VALUE>-config.cmake 模式,因此要查找该包,您需要编写 find_package(<VALUE>)

  • cmake_target_name:要使用的目标名称。

  • cmake_target_aliases:Conan 将为现有目标创建的别名列表。

  • cmake_find_mode:默认为 config。可能的值为:

    • config:CMakeDeps 生成器将为依赖项创建配置文件。

    • module:将为依赖项创建模块配置文件 (FindXXX.cmake)。

    • both:将同时生成配置文件和模块。

    • none:不会生成任何文件。例如,可用于创建系统包装器包,以便使用者在 CMake 安装配置路径中找到配置文件,而不是在 Conan 生成的配置文件中(因为它已被跳过)。

  • cmake_module_file_name:与 **cmake_file_name** 相同,但在使用 cmake_find_mode=module/both 生成模块时。如果未指定,则默认为 **cmake_file_name**。

  • cmake_module_target_name:与 **cmake_target_name** 相同,但在使用 cmake_find_mode=module/both 生成模块时。如果未指定,则默认为 **cmake_target_name**。

  • cmake_build_modules:包含 .cmake 文件的列表(路径相对于根包文件夹),当使用者运行 find_package() 时,这些文件将被自动包含。此属性不能在组件中设置,只能在根 self.cpp_info 中设置。

  • cmake_set_interface_link_directories:(2.14 版本中*已弃用*)。它曾经用于 #pragma comment(lib, "foo"),但对于 CMakeDeps 生成器已不再需要。在新的 CMakeConfigDeps 生成器中,将需要根据 CPS 实践正确地在 cpp_info 中指定库详细信息。

  • nosoname:一个布尔值,仅应由定义为 SHARED 且没有 soname 标志选项的库使用。

  • cmake_config_version_compat:默认为 SameMajorVersion,它可以取值 "AnyNewerVersion""SameMajorVersion""SameMinorVersion""ExactVersion"。它将在生成的 <PackageName>ConfigVersion.cmake 文件中使用该策略。

  • system_package_version:用于生成 <PackageName>ConfigVersion.cmake 文件的包版本。当创建系统包或其他包装器包时,包的 Conan 版本与最终引用的包版本不同,以保持与 find_package(<PackageName> <Version>) 调用的兼容性时,这可能很有用。

  • cmake_additional_variables_prefixes:在配置文件中创建 CMake 变量时使用的前缀列表。这些变量默认以 file_name 作为前缀创建,但设置此属性将与默认的 file_name 前缀一起创建其他带有指定前缀的变量。

  • cmake_extra_variables:要添加到生成配置文件的额外变量的字典。字典的键是变量名,值是变量值,它可以是纯字符串、数字或类字典的 Python 对象,该对象必须指定 value(字符串/数字)、cache(布尔值)、type(CMake 缓存类型)以及可选的 docstring(字符串:默认为变量名)和 force(布尔值)键。请注意,这比 tools.cmake.cmaketoolchain:extra_variables conf 中定义的值优先级低。

示例

def package_info(self):
    ...
    # MyFileName-config.cmake
    self.cpp_info.set_property("cmake_file_name", "MyFileName")
    # Names for targets are absolute, Conan won't add any namespace to the target names automatically
    self.cpp_info.set_property("cmake_target_name", "Foo::Foo")
    # Automatically include the lib/mypkg.cmake file when calling find_package()
    # This property cannot be set in a component.
    self.cpp_info.set_property("cmake_build_modules", [os.path.join("lib", "mypkg.cmake")])

    # Create a new target "MyFooAlias" that is an alias to the "Foo::Foo" target
    self.cpp_info.set_property("cmake_target_aliases", ["MyFooAlias"])

    self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "Foo::Var")

    # Create a new target "VarComponent" that is an alias to the "Foo::Var" component target
    self.cpp_info.components["mycomponent"].set_property("cmake_target_aliases", ["VarComponent"])

    # Skip this package when generating the files for the whole dependency tree in the consumer
    # note: it will make useless the previous adjustments.
    # self.cpp_info.set_property("cmake_find_mode", "none")

    # Generate both MyFileNameConfig.cmake and FindMyFileName.cmake
    self.cpp_info.set_property("cmake_find_mode", "both")

    # Add extra variables to the generated config file
    self.cpp_info.set_property("cmake_extra_variables", {
                                   "FOO": 42,
                                   "CMAKE_GENERATOR_INSTANCE": "${GENERATOR_INSTANCE}/buildTools/",
                                   "CACHE_VAR_DEFAULT_DOC": {"value": "hello world",
                                                             "cache": True, "type": "STRING"}
                               })

使用 CMakeDeps.set_property() 从使用者覆盖属性

使用 CMakeDeps.set_property() 方法,您可以从使用者覆盖 Conan 配方设置的属性值。这可以对上面列出的所有属性进行。

假设我们有一个*compressor/1.0*包,它依赖于*zlib/1.3.1*。*zlib*配方定义了一些属性。

Zlib conanfile.py
class ZlibConan(ConanFile):
    name = "zlib"

    ...

    def package_info(self):
        self.cpp_info.set_property("cmake_find_mode", "both")
        self.cpp_info.set_property("cmake_file_name", "ZLIB")
        self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB")
        ...

此配方定义了几个属性。例如,cmake_find_mode 属性设置为 both。这意味着为 Zlib 生成了模块和配置文件。也许我们需要更改此行为,只生成配置文件。您可以在*compressor*配方中使用 CMakeDeps.set_property() 方法来执行此操作。

compressor conanfile.py
class Compressor(ConanFile):
    name = "compressor"

    requires = "zlib/1.3.1"
    ...

    def generate(self):
        deps = CMakeDeps(self)
        deps.set_property("zlib", "cmake_find_mode", "config")
        deps.generate()
        ...

您还可以使用 set_property() 方法使上游配方设置的属性值失效,并使用 Conan 默认分配的值。要做到这一点,请将属性值设置为 None,如下所示。

compressor conanfile.py
class Compressor(ConanFile):
    name = "compressor"

    requires = "zlib/1.3.1"
    ...

    def generate(self):
        deps = CMakeDeps(self)
        deps.set_property("zlib", "cmake_target_name", None)
        deps.generate()
        ...

完成此操作后,Zlib 库生成的目标名称将是 zlib::zlib 而不是 ZLIB::ZLIB

此外,CMakeDeps.set_property() 也可用于具有组件的包。在这种情况下,您需要提供包名称及其组件,用双冒号(::)分隔。示例如下:

def generate(self):
    deps = CMakeDeps(self)
    deps.set_property("pkg::component", "cmake_target_name", <new_component_target_name>)
    deps.generate()
    ...

为已安装的 CMake 配置文件禁用 CMakeDeps

一些项目可能希望为下游使用者禁用 CMakeDeps 生成器。这可以通过将 cmake_find_mode 设置为 none 来实现。如果项目希望提供自己的配置文件目标,它应该将这些目标追加到 cpp_infobuildirs 属性。

此方法旨在与使用 CMakeToolchain 生成器的下游使用者配合使用,该生成器将使用 builddirs 属性进行填充。

示例

def package(self):
    ...
    cmake.install()

def package_info(self):
    self.cpp_info.set_property("cmake_find_mode", "none") # Do NOT generate any files
    self.cpp_info.builddirs.append(os.path.join("lib", "cmake", "foo"))

将项目配置映射到导入的目标配置

如上所述,CMakeDeps 支持多配置环境(Debug、Release 等)。这通过在安装依赖项时根据 build_type 设置填充导入目标上的属性来实现。但是,当使用单配置 CMake 生成器配置使用者项目时,有必要使用与已安装依赖项匹配的值来定义 CMAKE_BUILD_TYPE

如果使用者 CMake 项目的构建类型与依赖项的构建类型不同,则有必要通过设置 CMAKE_MAP_IMPORTED_CONFIG_<CONFIG> CMake 变量来告诉 CMake 如何将当前项目的配置映射到导入的目标。

cd build-coverage/
conan install .. -s build_type=Debug
cmake .. -DCMAKE_BUILD_TYPE=Coverage -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_MAP_IMPORTED_CONFIG_COVERAGE=Debug