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 可以定位生成的 config/module 文件。

生成的文件

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

  • FindXXX.cmake:仅当依赖项通过“module”或“both”设置了 cmake_find_mode 属性时。请参阅 影响 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 时,默认情况下不会生成 config 文件(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-requireregular require 时,会在生成器中引起冲突,因为 config 文件的文件名以及目标名称、变量名称等也会冲突。

例如,这是一个典型的情况,其中一些需求(capnproto、protobuf…)包含一个用于在构建时生成源代码的工具(因此它是一个 build_require),但同时也提供了一个库来链接到最终应用程序,因此您也有一个 regular 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 还存在另一个问题。 您可能知道,需求的 recipe 可以声明一个 cppinfo.build_modules 条目,其中包含一个或多个 .cmake 文件。 当 cmake find_package() 函数找到需求时,Conan 将自动包含这些文件。

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

使用 build_context_build_modules 属性指定需求名称以包含来自 tool_requiresbuild_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() 中需要指定组件时添加检查,请使用此属性。 例如,如果我们使用像 Boost 这样的 Conan 软件包,该软件包声明了多个组件。 如果我们将属性设置为 True,则使用者的 find_package() 调用将检查所需的组件是否存在,否则会引发错误。 您可以在 generate() 方法中设置此属性

requires = "boost/1.81.0"

...

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

然后,当使用 Boost 时,由于 fakecomp 不存在,find_package() 将引发错误

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 recipe 设置的 property 值。 这可以为 cmake_file_namecmake_target_namecmake_find_modecmake_module_file_namecmake_module_target_namecmake_additional_variables_prefixescmake_config_version_compatsystem_package_versioncmake_build_modulesnosonamecmake_target_aliases 完成。

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

  • propproperty 的名称。

  • value – property 的值。 使用 None 使上游 recipe 设置的任何值无效。

  • 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 生成器将为依赖项创建 config 脚本。

    • module: 将为依赖项创建模块 config (FindXXX.cmake) 脚本。

    • both: 将生成 config 和 module。

    • none: 不会生成任何文件。 例如,它可以用于创建系统包装包,以便使用者在 CMake 安装 config 路径中找到 config 文件,而不是在 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: 用于在 config 文件中创建 CMake 变量时使用的前缀列表。 默认情况下,这些变量使用 file_name 作为前缀创建,但设置此属性将创建带有指定前缀的附加变量,以及默认的 file_name 变量。

示例

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 adjustements.
    # 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")

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

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

假设我们有一个依赖于 zlib/1.2.11compressor/1.0 软件包。 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 生成了模块和 config 文件。 也许我们需要更改此行为,只生成 config 文件。 您可以使用 CMakeDeps.set_property() 方法在 compressor 配方中执行此操作

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

    requires = "zlib/1.2.11"
    ...

    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.2.11"
    ...

    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_infobuilddirs 属性。

此方法旨在与使用 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