【conan 入门教程】介绍 conanfile.py中的默认方法的作用

简介: 【conan 入门教程】介绍 conanfile.py中的默认方法的作用

1.介绍默认方法

  1. config_options(): 此方法用于根据设置配置或移除包选项。例如,如果包是为 Windows 构建的,您可能会使用此方法移除 fPIC 选项,因为 fPIC 在该平台上不相关。
  2. configure(): 它与 config_options() 类似,但是在定义选项之后执行。它用于根据其他选项或设置的值调整选项或设置。
  3. layout(): 此方法定义了项目的布局,指定源文件、构建文件和其他重要目录的位置。例如,使用 cmake_layout(self) 设置适用于 CMake 项目的布局。
  4. generate(): 此方法通过生成必要的文件(如 conan_toolchain.cmake)来准备构建环境,该文件将 Conan 设置和选项转换为 CMake 语法。这对于将 Conan 与构建系统集成至关重要。
  5. build(): 此方法包含从源代码构建包的指令。它通常涉及使用适当的命令调用构建系统(例如,CMake)来配置和构建项目。
  6. package(): 构建完成后,此方法负责收集构建工件(例如,二进制文件、头文件)并将它们放置在包文件夹中。这通常涉及将文件从构建目录复制到包目录。
  7. package_info(): 此方法定义了其他项目应如何使用您的包。例如,它指定需要链接的库名称、任何必需的预处理器定义以及可能需要的额外库或链接器标志。如果包由多个库或可执行文件组成,它还可以定义包的组件。

这些方法之间的调用时序通常是:config_options() -> configure() -> layout() -> generate() -> build() -> package() -> package_info()。但是,具体的调用顺序和使用方式可能会根据你的项目需求和构建流程有所不同。

2. config_options()

2.1 config_options()介绍

config_options() 方法用于在为包中的选项赋值之前配置或限制可用选项。一个典型的用例是在给定平台中移除一个选项。例如,SSE2 标志在非 32 位架构中不存在,因此应该像下面这样在这个方法中移除它:

def config_options(self):
    if self.settings.arch != "x86_64":
        del self.options.with_sse2

config_options() 方法执行的时机如下:

  • 在调用 configure() 方法之前。Before calling the configure() method.
  • 在为选项赋值之前。 Before assigning the options values.
  • 在设置已经定义之后。After settings are already defined.

可用的自动实现

警告

此功能是实验性的,可能会发生变化。有关更多信息,请参阅 Conan 稳定性部分。

当未在配方中定义 config_options() 方法时,Conan 可以自动管理一些常规选项,如果在 implements ConanFile 属性中指定了它们:

auto_shared_fpic

自动管理的选项:

fPIC (True, False)。

可以像这样将其添加到配方中:

from conan import ConanFile
class Pkg(ConanFile):
    implements = ["auto_shared_fpic"]
    ...

然后,如果在配方中未指定 config_options() 方法,Conan 将自动在 config_options 步骤中管理 fPIC 设置,如下所示:

if conanfile.settings.get_safe("os") == "Windows":
    conanfile.options.rm_safe("fPIC")

请注意,将此实现添加到配方中可能也会影响 configure 步骤。

如果您需要在配方中实现自定义行为,但也需要这个逻辑,它必须明确声明:

def config_options(self):
    if conanfile.settings.get_safe("os") == "Windows":
        conanfile.options.rm_safe("fPIC")
    if self.settings.arch != "x86_64":
        del self.options.with_sse2

2.2 config_options()调用时机

config_options() 方法在 Conan 生命周期中的特定阶段被调用,具体如下:

  1. 解析设置和选项:在解析 conanfile.py 中的设置(settings)和选项(options)之后,但在这些值被赋予具体值之前,Conan 会调用 config_options() 方法。
  2. configure() 方法之前config_options() 被调用的时机早于 configure() 方法,这意味着它用于在配置过程开始之前进行一些预处理或设置调整。

本质上,config_options() 方法是如何执行的:

  • 调整选项:在这个方法中,你可以根据当前的设置(如操作系统、编译器、架构等)来添加、删除或修改选项。这对于创建跨平台、支持多种配置的包非常有用。
  • 执行条件逻辑:可以使用条件语句来检查设置的值,并据此调整选项。例如,你可以根据操作系统移除不相关的选项,或根据编译器版本启用或禁用特定的编译标志。
  • 预处理config_options() 方法允许在实际配置和构建过程开始之前对包进行预处理。这是确保包配置正确并优化构建过程的重要步骤。

总的来说,config_options() 方法提供了一种灵活的方式来根据不同的环境和需求调整包的配置,从而使得包更加通用和灵活。

2.3 示例

config_options() 方法的其他用法主要涉及根据不同的设置或选项来调整包的配置。以下是一些示例:

示例 1:根据操作系统移除选项

def config_options(self):
    if self.settings.os == "Windows":
        del self.options.fPIC  # Windows 平台通常不需要 fPIC 选项

示例 2:根据编译器版本调整选项

def config_options(self):
    if self.settings.compiler == "gcc" and float(str(self.settings.compiler.version)) < 5.0:
        del self.options.with_cxx11  # 在 GCC 5.0 之前,C++11 支持不是默认的

示例 3:根据构建类型调整选项

def config_options(self):
    if self.settings.build_type == "Debug":
        self.options.with_debug_symbols = True  # 在调试构建中启用调试符号

示例 4:根据自定义选项调整依赖

def config_options(self):
    if not self.options.with_ssl:
        self.requires.remove("openssl/1.1.1g")  # 如果不需要 SSL 支持,则移除 OpenSSL 依赖

这些示例展示了 config_options() 方法如何用于根据不同的设置或条件来调整包的配置,包括移除不需要的选项、调整依赖关系等。在实际开发中,可以根据具体需求灵活使用这个方法。

在本节中,我们将介绍如何在Conan包中配置设置和选项。如果某些设置或选项不适用于Conan包,可以在配方中进行配置。我们还将简要介绍Conan如何模拟二进制兼容性,以及这与选项和设置的关系。

首先,请克隆源代码以重现此项目。你可以在GitHub上的examples2仓库中找到它们:

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/creating_packages/configure_options_settings

你会注意到conanfile.py文件与前一个配方相比有一些变化。让我们检查相关部分:

class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"
    ...
    options = {"shared": [True, False],
               "fPIC": [True, False],
               "with_fmt": [True, False]}
    default_options = {"shared": False,
                       "fPIC": True,
                       "with_fmt": True}
    ...
    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC
    def configure(self):
        if self.options.shared:
            # 如果os=Windows,fPIC会在config_options()中被移除
            # 使用rm_safe避免重复删除错误
            self.options.rm_safe("fPIC")
    ...

你可以看到我们在配方中添加了一个configure()方法。让我们解释一下这个方法的目的以及它与我们已经在配方中定义的config_options()方法有何不同:

3. configure()

3.1 configure() 介绍

  • configure(): 使用此方法配置配方的哪些选项或设置可用。例如,在这种情况下,我们删除了fPIC选项,因为只有在将库构建为共享库时,该选项才应该为True(实际上,一些构建系统在构建共享库时会自动添加此标志)。
  • config_options(): 该方法用于在选项取值之前限制包中可用的选项。如果在此方法中删除了某个设置或选项的值,Conan将引发错误。在这种情况下,我们在Windows中删除了fPIC选项,因为该选项对该操作系统不存在。请注意,此方法在configure()方法之前执行。

请注意,使用config_options()方法删除一个选项与使用configure()方法删除一个选项的结果是不同的。在config_options()中删除选项就像我们从未在配方中声明它一样,这将引发一个异常,指出该选项不存在。然而,如果我们在configure()方法中删除它,我们可以传递该选项,但它将没有效果。例如,如果你尝试在Windows中为fPIC选项传递一个值,Conan将引发一个错误,警告该选项不存在:

Windows¶
$ conan create . --build=missing -o fPIC=True
...
-------- Computing dependency graph --------
ERROR: option 'fPIC' doesn't exist
Possible options are ['shared', 'with_fmt']

正如你所注意到的,configure()config_options()方法在满足某些条件时会删除一个选项。让我们解释一下为什么要这样做以及删除该选项的含义。这与Conan如何识别与配置文件中设置的配置二进制兼容的包有关。在下一节中,我们将介绍Conan包ID的概念。

Conan包二进制兼容性:包ID

我们之前使用Conan为不同的配置(如Debug和Release)构建。每次为这些配置中的一个创建包时,Conan都会构建一个新的二进制文件。每个二进制文件都与一个生成的哈希值相关,称为包ID。包ID只是将一组设置、选项和包的需求信息转换为唯一标识符的一种方式。

让我们为Release和Debug配置构建我们的包,并检查生成的二进制包ID

$ conan create . --build=missing -s build_type=Release -tf="" # -tf="" 将跳过测试包的构建
...
hello/1.0: Package '738feca714b7251063cc51448da0cf4811424e7c' built
$ conan create . --build=missing -s build_type=Debug -tf="" # -tf="" 将跳过测试包的构建
...
hello/1.0: Package '3d27635e4dd04a258d180fe03cfa07ae1186a828' built

正如你所看到的,Conan生成了两个包ID:

  • Release的包ID为738feca714b7251063cc51448da0cf4811424e7c
  • Debug的包ID为3d27635e4dd04a258d180fe03cfa07ae1186a828

这两个包ID是通过获取设置、选项和一些有关需求的信息的集合,并用它们计算哈希值得到的。所以,例如,在这种情况下,它们是下图所示信息的结果。

这些包ID之所以不同,是因为build_type不同。现在,当你想要安装一个包时,Conan将:

  1. 收集应用的设置和选项,以及一些有关需求的信息,并计算相应包ID的哈希值。
  2. 如果该包ID与本地Conan缓存中存储的包之一匹配,Conan将使用该包。如果没有,并且我们配置了任何Conan远程仓库,它将在远程仓库中搜索具有该包ID的包。
  3. 如果该计算出的包ID既不存在于本地缓存中,也不存在于远程仓库中,Conan将失败并显示“缺少二进制文件”的错误消息,或者尝试从源代码构建该包(这取决于--build参数的值)。这将在本地缓存中生成一个新的包ID。

这些步骤是简化的,包ID计算远不止我们在这里解释的那么简单,配方本身甚至可以调整它们的包ID计算,我们可以有不同的配方和包修订版本除了包ID之外,还有一个内置机制可以配置在Conan中,可以声明具有某个包ID的某些包与其他包兼容。

也许你现在有了为什么要在Conan配方中删除设置或选项的直觉。如果你这样做,那些值将不会被添加到包ID的计算中,所以即使你定义了它们,生成的包ID也将是相同的。你可以检查这种行为,例如使用fPIC选项,当我们使用选项shared=True构建时,该选项被删除。无论你为fPIC选项传递什么值,生成的hello/1.0二进制的包ID都将是相同的:

$ conan create . --build=missing -o shared=True -o fPIC=True -tf=""
...
hello/1.0: Package '2a899fd0da3125064bf9328b8db681cd82899d56' created
$ conan create . --build=missing -o shared=True -o fPIC=False -tf=""
...
hello/1.0: Already installed!

正如你所看到的,第一次运行创建了2a899fd0da3125064bf9328b8db681cd82899d56包,第二次运行,尽管fPIC选项的值不同,但它表示我们已经安装了2a899fd0da3125064bf9328b8db681cd82899d56包。

C库

还有其他典型情况下你可能想要删除某些设置。假设你正在打包一个C库。当你构建这个库时,有些设置,

如编译器C++标准(settings.compiler.cppstd)或使用的标准库(self.settings.compiler.libcxx),根本不会影响生成的二进制文件。那么它们影响包ID计算就没有意义,所以一个典型的模式是在configure()方法中删除它们:

def configure(self):
    self.settings.rm_safe("compiler.cppstd")
    self.settings.rm_safe("compiler.libcxx")

请注意,删除这些设置在configure()方法中将修改包ID的计算,但也会影响工具链和构建系统集成的工作方式,因为C++设置不存在。

仅头文件的库

与打包仅头文件库的情况类似。在这种情况下,我们不需要与之链接的二进制代码,只需要添加一些头文件到我们的项目中。在这种情况下,Conan包的包ID不应受设置或选项的影响。对于这种情况,有一种简化的方法来声明生成的包ID不应考虑设置、选项或任何来自需求的信息,那就是在另一个名为package_id()的配方方法中使用self.info.clear()方法:

def package_id(self):
    self.info.clear()

我们稍后将解释package_id()方法,并解释如何自定义包的包ID的计算方式。如果你想更详细地了解这个方法的工作原理,你也可以查看Conanfile方法参考。

3.2 configure() 调用时机

在 Conan 中,configure() 方法是在构建依赖关系图和扩展包依赖项的阶段被调用的。这意味着此方法在依赖项还未被解析和安装时就会执行。因此,configure() 方法中无法访问 self.dependencies,也就是说,无法在此方法中使用依赖项的信息。

本质上,configure() 方法是用于在配方中配置设置和选项的。这些设置和选项将在后续的方法(如 generate()build()package())中使用。configure() 方法允许您根据特定条件(如操作系统、编译器或其他选项)调整设置和选项,以确保配方能够正确地构建和打包。

例如,如果您的库只使用 C 而不是 C++,您可能希望在 configure() 方法中移除与 C++ 相关的设置,如 compiler.libcxxcompiler.cppstd。这样可以确保这些设置不会影响构建过程。

def configure(self):
    # Not all compilers have libcxx subsetting, so we use rm_safe
    # to avoid exceptions
    self.settings.rm_safe("compiler.libcxx")
    self.settings.rm_safe("compiler.cppstd")

总的来说,configure() 方法提供了一个在依赖项被解析之前调整配方设置和选项的机会,从而确保配方能够根据特定的构建环境正确地执行。

3.3 示例

configure()方法在Conan配方中用于根据包的依赖关系、设置或选项来调整包的配置。除了前面提到的删除不适用的选项之外,还有一些其他常见的用法:

  1. 强制设置依赖项的选项
    如果你的包依赖于另一个包,并且你需要确保该依赖项使用特定的选项配置,你可以在configure()方法中设置这些选项。
def configure(self):
    if self.options.my_option:
        self.options["dependency_pkg"].dependency_option = True
  1. 在这个例子中,如果当前包的my_option选项为真,则强制设置依赖项dependency_pkgdependency_option选项为真。
  2. 根据设置调整选项的默认值
    你可以根据当前的设置(如操作系统或编译器)来调整选项的默认值。
def configure(self):
    if self.settings.os == "Windows":
        self.options.my_option = False
  1. 在这个例子中,如果操作系统是Windows,则将my_option选项的默认值设置为假。
  2. 根据依赖项的选项调整当前包的选项
    你可以根据依赖项的选项来调整当前包的选项。
def configure(self):
    if self.options["dependency_pkg"].dependency_option:
        self.options.my_option = True
  1. 在这个例子中,如果依赖项dependency_pkgdependency_option选项为真,则将当前包的my_option选项设置为真。
  2. 根据编译器版本调整选项
    你可以根据编译器的版本来调整选项。
def configure(self):
    if self.settings.compiler == "gcc" and self.settings.compiler.version >= "9":
        self.options.my_option = True
  1. 在这个例子中,如果编译器是gcc且版本大于或等于9,则将my_option选项设置为真。

这些只是一些示例,configure()方法的使用非常灵活,可以根据具体需求来调整包的配置。

4. layout()

4.1 layout() 介绍

layout() 方法是 Conan 包中的一个重要方法,它允许你调整包的文件结构和构建信息。以下是一些主要的组成部分:

self.folders

  • self.folders.source:指定源代码的子文件夹。self.source_folder 属性在 source(self)build(self) 方法中将设置为此子文件夹。
  • self.folders.build:指定构建文件的子文件夹。self.build_folder 属性和 build(self) 方法中的当前工作目录将设置为此子文件夹。
  • self.folders.generators:指定生成器和工具链文件的子文件夹。
  • self.folders.root:当 conanfile.py 位于一个单独的子目录中时,指定源代码、生成器等的父目录。
  • self.folders.subproject:指定 conanfile.py 相对于项目根目录的子文件夹。
  • self.folders.build_folder_vars:使用设置和选项来生成不同的构建文件夹和不同的 CMake 预设名称。

self.cpp

layout() 方法允许在 self.source_folderself.build_folder 中声明 cpp_info 对象,而不仅仅是最终包中的对象。

  • self.cpp.package:用于描述最终包的内容,与 package_info() 中的 self.cpp_info 相同。
  • self.cpp.source:用于描述“可编辑”包中 self.source_folder 下的构件。
  • self.cpp.build:用于描述“可编辑”包中 self.build_folder 下的构件。

环境变量和配置

一些包可能会在其 package_info() 方法中通过 self.buildenv_infoself.runenv_info 定义一些环境变量,或者使用 self.conf_info 向其消费者传递配置。如果这些值需要使用 self.package_folder,则需要在 layout() 方法中定义它们,以确保在可编辑模式下仍然正确。

例如:

from conan import ConanFile
class SayConan(ConanFile):
    ...
    def layout(self):
        # The final path will be relative to the self.source_folder
        self.layouts.source.buildenv_info.define_path("MYDATA_PATH", "my/source/data/path")
        # The final path will be relative to the self.build_folder
        self.layouts.build.buildenv_info.define_path("MYDATA_PATH2", "my/build/data/path")
        # The final path will be relative to the self.build_folder
        self.layouts.build.conf_info.define_path("MYCONF", "my_conf_folder")

在这个例子中,我们定义了不同的环境变量和配置,它们将在可编辑模式下正确指向源代码或构建文件夹中的相对路径。

4.2 layout() 调用时机

layout() 方法在 Conan 生命周期的几个阶段被调用,主要是在需要确定包的文件结构和构建信息的时候。具体来说,layout() 方法会在以下时机被调用:

  1. 解析依赖关系:当 Conan 解析依赖关系时,它需要知道包的布局来正确地找到头文件、库文件等。此时会调用 layout() 方法来确定这些路径。
  2. 构建过程:在构建过程中,layout() 方法被调用来设置 self.source_folderself.build_folder 属性,以便在 source()build() 方法中使用正确的路径。
  3. 打包过程:在打包过程中,Conan 需要知道哪些文件和目录应该被包含在最终的包中。layout() 方法在这个阶段被调用来确定 self.cpp.package 的内容。
  4. 安装过程:当使用 Conan 安装一个包时,layout() 方法被调用来确定如何设置包的使用环境,例如环境变量和配置。

执行原理

layout() 方法的执行原理基于 Conan 的内部机制来处理包的布局和构建信息。具体来说,执行原理包括以下几个方面:

  1. 设置路径layout() 方法中设置的 self.foldersself.cpp 属性会被 Conan 用来确定源代码、构建、包和生成器文件的位置。
  2. 动态调整layout() 方法可以根据不同的设置和选项动态调整包的布局。例如,根据不同的构建类型(如 Debug 或 Release)调整构建目录。
  3. 支持可编辑模式:在可编辑模式下,包的源代码和构建文件不在 Conan 缓存中,而是位于本地开发目录中。layout() 方法允许开发者定义这些本地路径,使得在可编辑模式下,包的消费者仍然可以正确地使用包。
  4. 与其他方法协作layout() 方法与 source()build()package_info() 等方法协作,确保在整个 Conan 生命周期中使用一致的路径信息。

总的来说,layout() 方法的调用时机和执行原理是为了确保在 Conan 的不同阶段,包的布局和构建信息能够被正确地处理和使用。

4.3 layout() 示例

以下是一些其他使用 layout() 方法的示例,展示了如何为不同的项目结构和需求定制文件布局和构建信息:

示例 1:多层次源代码结构

如果你的项目具有多层次的源代码结构,你可以使用 layout() 方法来指定不同层次的源代码目录:

def layout(self):
    self.folders.source = "src"
    self.folders.build = "build"
    self.cpp.source.includedirs = ["include", "src/include"]
    self.cpp.source.libdirs = ["src/lib"]
    self.cpp.build.libdirs = ["build/lib"]

在这个例子中,我们指定了两个包含目录(includesrc/include)和一个库目录(src/lib)用于源代码,以及一个库目录(build/lib)用于构建结果。

示例 2:自定义构建目录结构

如果你的构建系统产生了非标准的目录结构,你可以在 layout() 方法中指定这些目录:

def layout(self):
    self.folders.source = "src"
    self.folders.build = "out"
    self.cpp.build.libdirs = ["out/bin", "out/lib"]
    self.cpp.build.bindirs = ["out/bin"]

在这个例子中,我们假设构建系统将可执行文件和库文件分别放在 out/binout/lib 目录中。

示例 3:使用不同的构建配置

如果你的项目支持不同的构建配置(例如,调试和发布),你可以使用 self.settings 来调整 layout() 方法中的路径:

def layout(self):
    self.folders.source = "src"
    self.folders.build = "build/{}".format(self.settings.build_type)
    self.cpp.build.libdirs = ["lib"]
    self.cpp.build.bindirs = ["bin"]

在这个例子中,构建目录将根据构建类型(self.settings.build_type)进行调整,例如 build/Debugbuild/Release

示例 4:支持多个子项目

如果你的项目包含多个子项目,你可以在 layout() 方法中为每个子项目指定不同的路径:

def layout(self):
    self.folders.source = "src"
    self.folders.build = "build"
    self.cpp.source.includedirs = ["project1/include", "project2/include"]
    self.cpp.build.libdirs = ["project1/lib", "project2/lib"]

在这个例子中,我们为两个子项目 project1project2 分别指定了包含目录和库目录。

示例 5:使用自定义生成器目录

如果你想将生成的文件(例如,CMake 预设文件)放在一个自定义目录中,你可以在 layout() 方法中指定 self.folders.generators

def layout(self):
    self.folders.source = "src"
    self.folders.build = "build"
    self.folders.generators = "cmake"

在这个例子中,所有由 Conan 生成器产生的文件将被放置在 cmake 目录中。

5. generate()

5.1 generate() 介绍

  • 生成的文件在使用conan install安装依赖或在缓存中构建包时,将根据generate()方法中提供的信息以及当前设置的信息,生成以下文件:
  • conan_toolchain.cmake:包含将 Conan 设置转换为 CMake 变量的内容。该文件中将定义一些内容:
  • CMake 生成器平台和生成器工具集的定义
  • 基于 fPIC 选项定义的 CMAKE_POSITION_INDEPENDENT_CODE
  • 必要时定义 C++ 标准
  • 定义用于 C++ 的标准库
  • 在 OSX 中禁用 rpaths
  • conanvcvars.bat:在某些情况下,需要正确定义 Visual Studio 环境才能进行构建,例如使用 Ninja 或 NMake 生成器时。如果需要,CMakeToolchain 将生成此脚本,以便更容易地定义正确的 Visual Studio 提示。
  • CMakePresets.json:此工具链生成标准的CMakePresets.json文件。有关更多信息,请参阅文档。当前使用的 JSON 架构版本为“3”。Conan 将向 JSON 文件中添加配置、构建和测试预设项:
  • configurePresets存储以下信息:
  • 要使用的生成器
  • 指向 conan_toolchain.cmake 的路径
  • 与指定设置相对应的缓存变量,如果在工具链中指定,这些变量将无法工作
  • 单配置生成器的 CMAKE_BUILD_TYPE 变量
  • 当配置工具 build:skip_test 为真时,将 BUILD_TESTING 变量设置为 OFF
  • 一个环境部分,设置与 VirtualBuildEnv 相关的所有环境信息(如果适用)。这个环境可以在配方的 generate() 方法中通过传递一个环境通过 CMakeToolchain.presets_build_environment 属性进行修改。可以使用 tools.cmake.cmaketoolchain:presets_environment 配置跳过此部分的生成。
  • buildPresets存储以下信息:
  • 与此构建预设相关联的 configurePreset
  • testPresets存储以下信息:
  • 与此构建预设相关联的 configurePreset
  • 一个环境部分,设置与 VirtualRunEnv 相关的所有环境信息(如果适用)。这个环境可以在配方的 generate() 方法中通过传递一个环境通过 CMakeToolchain.presets_run_environment 属性进行修改。请注意,由于此预设继承自一个 configurePreset,它也将继承其环境。可以使用 tools.cmake.cmaketoolchain:presets_environment 配置跳过此部分的生成。
  • CMakeUserPresets.json:如果在配方中声明了一个 layout(),并且你的 CMakeLists.txt 文件位于 conanfile.source_folder 文件夹中,则将生成一个 CMakeUserPresets.json 文件(如果尚不存在),以便自动包含 CMakePresets.json(位于 conanfile.generators_folder),从而允许你的 IDE(Visual Studio、Visual Studio Code、CLion 等)或 cmake 工具定位 CMakePresets.json。生成的 CMakeUserPresets.json 的位置可以通过 user_presets_path 属性进一步调整,如下文所述。生成的 CMakeUserPresets.json 的版本架构为“4”,需要 CMake >= 3.23。此文件的文件名可以使用 CMakeToolchain.user_presets_path = "CMakeUserPresets.json" 属性进行配置,因此如果你想生成一个“ConanPresets.json”来包含在你自己的文件中,你可以在 generate() 方法中定义 tc.user_presets_path = "ConanPresets.json"。有关扩展你自己的 CMake 预设的完整示例,请参阅扩展你自己的 CMake 预设
  • 注意:如果 CMakeUserPresets.json 已经存在且不是由 Conan 生成的,Conan 将跳过生成 CMakeUserPresets.json
    注意:要列出所有可用的预设,请使用 cmake --list-presets 命令:
    注意
    生成的 CMakeUserPresets.json 的版本架构为 4(与 CMake >= 3.23 兼容),而 CMakePresets.json 的架构为 3(与 CMake >= 3.21 兼容)。
  • 定制preprocessor_definitions该属性允许为多个配置(如 Debug、Release 等)定义编译器预处理器定义。
def generate(self):
    tc = CMakeToolchain(self)
    tc.preprocessor_definitions["MYDEF"] = "MyValue"
    tc.preprocessor_definitions.debug["MYCONFIGDEF"] = "MyDebugValue"
    tc.preprocessor_definitions.release["MYCONFIGDEF"] = "MyReleaseValue"
    # 将其设置为 None 将添加没有值的定义
    tc.preprocessor_definitions["NOVALUE_DEF"] = None
    tc.generate()
  • 这将被转换为:
  • conan_toolchain.cmake 文件中为 MYDEF 添加一个 add_compile_definitions() 定义。
  • conan_toolchain.cmake 文件中使用 cmake 生成表达式添加一个 add_compile_definitions() 定义,使用不同的配置使用不同的值。
  • cache_variables
    该属性允许定义 CMake 缓存变量。这些变量与普通变量不同,是单配置的。它们将被存储在 CMakePresets.json 文件中(在 configurePresetcacheVariables 中),并在使用 CMake() 构建助手调用 cmake.configure 时通过 -D 参数应用。
def generate(self):
    tc = CMakeToolchain(self)
    tc.cache_variables["foo"] = True
    tc.cache_variables["foo2"] = False
    tc.cache_variables["var"] = "23"
  • 分配给 cache_variable 的布尔值将在 CMake 中被转换为 ONOFF 符号。
  • variables该属性允许为多个配置(如 Debug、Release 等)定义 CMake 变量。这些变量应该用于定义与工具链相关的事项,对于大多数情况,你可能想要使用的是cache_variables。另外,请注意,由于这些变量是在conan_toolchain.cmake文件中定义的,并且工具链会被 CMake 多次加载,这些变量的定义也会在这些点进行。
def generate(self):
    tc = CMakeToolchain(self)
    tc.variables["MYVAR"] = "MyValue"
    tc.variables.debug["MYCONFIGVAR"] = "MyDebugValue"
    tc.variables.release["MYCONFIGVAR"] = "MyReleaseValue"
  • 这将被转换为:
  • conan_toolchain.cmake 文件中为 MYVAR 添加一个 set() 定义。
  • conan_toolchain.cmake 文件中使用 cmake 生成表达式添加一个 set() 定义,使用不同的配置使用不同的值。
  • 分配给变量的布尔值将在 CMake 中被转换为 ONOFF 符号:
def generate(self):
    tc = CMakeToolchain(self)
    tc.variables["FOO"] = True
    tc.variables["VAR"] = False
    tc.generate()
  • 将生成语句:set(FOO ON ...)set(VAR OFF ...)
  • user_presets_path该属性允许指定生成的CMakeUserPresets.json文件的位置。接受的值:
  • 一个绝对路径
  • 相对于 self.source_folder 的路径
  • 布尔值 False,完全禁止生成该文件。
  • 例如,我们可以通过以下方式防止生成器创建 CMakeUserPresets.json
def generate(self):
    tc = CMakeToolchain(self)
    tc.user_presets_path = False
    tc.generate()
  • presets_build_environment, presets_run_environment
    这些属性通过在 generate() 方法中分配一个 Environment,允许分别修改与预设关联的构建和运行环境。
    例如,你可以覆盖在构建环境中已设置的环境变量的值:
def generate(self):
    buildenv = VirtualBuildEnv(self)
    buildenv.environment().define("MY_BUILD_VAR", "MY_BUILDVAR_VALUE_OVERRIDDEN")
    buildenv.generate()
    tc = CMakeToolchain(self)
    tc.presets_build_environment =
 buildenv.environment()
    tc.generate()
  • 或者生成一个新的环境并将其与已存在的环境组合:
def generate(self):
    runenv = VirtualRunEnv(self)
    runenv.environment().define("MY_RUN_VAR", "MY_RUNVAR_SET_IN_GENERATE")
    runenv.generate()
    env = Environment()
    env.define("MY_ENV_VAR", "MY_ENV_VAR_VALUE")
    env = env.vars(self, scope="run")
    env.save_script("other_env")
    tc = CMakeToolchain(self)
    tc.presets_run_environment = runenv.environment().compose_env(env)
    tc.generate()
  • 额外的编译标志你可以使用以下属性将额外的编译标志附加到工具链:
  • extra_cxxflags(默认为 [])用于额外的 cxxflags
  • extra_cflags(默认为 [])用于额外的 cflags
  • extra_sharedlinkflags(默认为 [])用于额外的共享链接标志
  • extra_exelinkflags(默认为 [])用于额外的可执行文件链接标志
  • 注意
    标志的优先顺序:在 tools.build 配置中指定的标志(例如 cxxflagscflagssharedlinkflagsexelinkflags)将始终优先于 CMakeToolchain 属性设置的标志。
  • presets_prefix
    默认值为 "conan",它将生成名为 “conan-xxxx” 的 CMake 预设。这样做是为了避免与用户自己的预设潜在的名称冲突。
  • 使用自定义工具链文件有两种方式提供自定义的 CMake 工具链文件:
  1. 可以完全跳过 conan_toolchain.cmake 文件并用用户自己的文件替换,通过定义配置值 tools.cmake.cmaketoolchain:toolchain_file=<filepath>
  2. 可以添加(从 conan_toolchain.cmake 中包含)一个自定义的用户工具链文件,通过使用下面描述的 user_toolchain 块,并定义配置值 tools.cmake.cmaketoolchain:user_toolchain=["<filepath>"]
  • 配置 tools.cmake.cmaketoolchain:user_toolchain=["<filepath>"] 可以在 global.conf 中定义,也可以通过创建一个用于你的工具链的 Conan 包并使用 self.conf_info 来声明工具链文件:
import os
from conan import ConanFile
class MyToolchainPackage(ConanFile):
    ...
    def package_info(self):
        f = os.path.join(self.package_folder, "mytoolchain.cmake")
        self.conf_info.define("tools.cmake.cmaketoolchain:user_toolchain", [f])
  • 如果你将前面的包声明为一个 tool_require,则工具链将自动应用。
    如果你定义了多个 tool_requires,你可以使用每个包中的 append 方法轻松地将所有用户工具链值拼接在一起,例如:
import os
from conan import ConanFile
class MyToolRequire(ConanFile):
    ...
    def package_info(self):
        f = os.path.join(self.package_folder, "mytoolchain.cmake")
        # 将值追加到任何现有的值中
        self.conf_info.append("tools.cmake.cmaketoolchain:user_toolchain", f)
  • 因此,它们将通过你的 CMakeToolchain 生成器自动应用,无需编写任何额外的代码:
from conan import ConanFile
from conan.tools.cmake import CMake
class Pkg(ConanFile):
    settings = "os", "compiler", "arch", "build_type"
    exports_sources = "CMakeLists.txt"
    tool_requires = "toolchain1/0.1", "toolchain2/0.1"
    generators = "CMakeToolchain"
    def build(self):
        cmake = CMake(self)
        cmake.configure()
  • 扩展和高级定制CMakeToolchain实现了一种强大的能力,用于扩展和定制生成的工具链文件。内容按块组织,可以进行定制。以下预定义块可用,并按此顺序添加:
  • user_toolchain:允许从 conan_toolchain.cmake 文件中包含用户工具链。如果定义了配置 tools.cmake.cmaketoolchain:user_toolchain=["xxxx", "yyyy"],其值将作为 conan_toolchain.cmake 中的第一行包含 include(xxx)\ninclude(yyyy)
  • generic_system:定义 CMAKE_SYSTEM_NAMECMAKE_SYSTEM_VERSIONCMAKE_SYSTEM_PROCESSORCMAKE_GENERATOR_PLATFORMCMAKE_GENERATOR_TOOLSETCMAKE_C_COMPILERCMAKE_CXX_COMPILER
  • android_system:定义 ANDROID_PLATFORMANDROID_STLANDROID_ABI 并包含 ANDROID_NDK_PATH/build/cmake/android.toolchain.cmake,其中 ANDROID_NDK_PATH 来自 tools.android:ndk_path 配置值。
  • apple_system:为苹果系统定义 CMAKE_OSX_ARCHITECTURESCMAKE_OSX_SYSROOT
  • fpic:当有 options.fPIC 时定义 CMAKE_POSITION_INDEPENDENT_CODE
  • arch_flags:在必要时定义 C/C++ 标志,如 -m32-m64
  • linker_scripts:定义任何提供的链接器脚本的标志。
  • libcxx:在必要时定义 -stdlib=libc++ 标志以及 _GLIBCXX_USE_CXX11_ABI
  • vs_runtime:定义 CMAKE_MSVC_RUNTIME_LIBRARY 变量,作为多配置的生成器表达式。
  • cppstd:定义 CMAKE_CXX_STANDARDCMAKE_CXX_EXTENSIONS
  • parallel:为 Visual 定义 /MP 并行构建标志。
  • cmake_flags_init:根据之前定义的 Conan 变量定义 CMAKE_XXX_FLAGS 变量。上面的块只定义 CONAN_XXX 变量,而这个块将定义像 set(CMAKE_CXX_FLAGS_INIT "${CONAN_CXX_FLAGS}" CACHE STRING "" FORCE) 这样的 CMake 变量。
  • try_compile:如果定义了 IN_TRY_COMPILE CMake 属性,则停止处理工具链,跳过此块以下的块。
  • find_paths:定义 CMAKE_FIND_PACKAGE_PREFER_CONFIGCMAKE_MODULE_PATHCMAKE_PREFIX_PATH 以便找到 CMakeDeps 生成的文件。
  • rpath:定义 CMAKE_SKIP_RPATH。默认情况下它是禁用的,如果你想激活 CMAKE_SKIP_RPATH,需要定义 self.blocks["rpath"].skip_rpath=True
  • shared:定义 BUILD_SHARED_LIBS
  • output_dirs:定义 CMAKE_INSTALL_XXX 变量。
  • CMAKE_INSTALL_PREFIX:使用 package_folder 设置,因此如果运行了“cmake install”操作,制品将进入该位置。
  • CMAKE_INSTALL_BINDIRCMAKE_INSTALL_SBINDIRCMAKE_INSTALL_LIBEXECDIR:默认设置为 bin
  • CMAKE_INSTALL_LIBDIR:默认设置为 lib
  • CMAKE_INSTALL_INCLUDEDIRCMAKE_INSTALL_OLDINCLUDEDIR:默认设置为 include
  • CMAKE_INSTALL_DATAROOTDIR:默认设置为 res
  • 如果你想更改默认值,请在 layout() 方法中调整 cpp.package 对象:
def layout(self):
    ...
    # 对于 CMAKE_INSTALL_BINDIR、CMAKE_INSTALL_SBINDIR 和 CMAKE_INSTALL_LIBEXECDIR,取第一个值:
    self.cpp.package.bindirs = ["mybin"]
    # 对于 CMAKE_INSTALL_LIBDIR,取第一个值:
    self.cpp.package.libdirs = ["mylib"]
    # 对于 CMAKE_INSTALL_INCLUDEDIR、CMAKE_INSTALL_OLDINCLUDEDIR,取第一个值:
    self.cpp.package.includedirs = ["myinclude
"]
    # 对于 CMAKE_INSTALL_DATAROOTDIR,取第一个值:
    self.cpp.package.resdirs = ["myres"]
  • 注意
    package_info() 方法中更改 self.cpp_info 是无效的。
  • 自定义内容块
    每个块都可以以不同的方式进行定制(回想一下,在定制之后调用tc.Generate()):
# tc.generate() should be called at the end of every one
# remove an existing block, the generated conan_toolchain.cmake
# will not contain code for that block at all
def generate(self):
    tc = CMakeToolchain(self)
    tc.blocks.remove("generic_system")
# remove several blocks
def generate(self):
    tc = CMakeToolchain(self)
    tc.blocks.remove("generic_system", "cmake_flags_init")
# keep one block, remove all the others
# If you want to generate conan_toolchain.cmake with only that
# block
def generate(self):
    tc = CMakeToolchain(self)
    tc.blocks.select("generic_system")
# keep several blocks, remove the other blocks
def generate(self):
    tc = CMakeToolchain(self)
    tc.blocks.select("generic_system", "cmake_flags_init")
# iterate blocks
def generate(self):
    tc = CMakeToolchain(self)
    for block_name in tc.blocks.keys():
        # do something with block_name
    for block_name, block in tc.blocks.items():
        # do something with block_name and block
# modify the template of an existing block
def generate(self):
    tc = CMakeToolchain(self)
    tmp = tc.blocks["generic_system"].template
    new_tmp = tmp.replace(...)  # replace, fully replace, append...
    tc.blocks["generic_system"].template = new_tmp
# modify one or more variables of the context
def generate(self):
    tc = CMakeToolchain(conanfile)
    # block.values is the context dictionary
    toolset = tc.blocks["generic_system"].values["toolset"]
    tc.blocks["generic_system"].values["toolset"] = "other_toolset"
# modify the whole context values
def generate(self):
    tc = CMakeToolchain(conanfile)
    tc.blocks["generic_system"].values = {"toolset": "other_toolset"}
# modify the context method of an existing block
import types
def generate(self):
    tc = CMakeToolchain(self)
    generic_block = toolchain.blocks["generic_system"]
    def context(self):
        assert self  # Your own custom logic here
        return {"toolset": "other_toolset"}
    generic_block.context = types.MethodType(context, generic_block)
# completely replace existing block
from conan.tools.cmake import CMakeToolchain
def generate(self):
    tc = CMakeToolchain(self)
    # this could go to a python_requires
    class MyGenericBlock:
        template = "HelloWorld"
        def context(self):
            return {}
    tc.blocks["generic_system"] = MyGenericBlock
# add a completely new block
from conan.tools.cmake import CMakeToolchain
def generate(self):
    tc = CMakeToolchain(self)
    # this could go to a python_requires
    class MyBlock:
        template = "Hello {{myvar}}!!!"
        def context(self):
            return {"myvar": "World"}
    tc.blocks["mynewblock"] = MyBlock
  • 交叉构建generic_system块包含一些基本的交叉构建功能。在一般情况下,用户可能希望提供他们自己的用户工具链来定义所有特定内容,这可以通过配置tools.cmake.cmaketoolchain:user_toolchain来完成。如果定义了这个配置值,generic_system块将包含提供的文件或文件,但不会进一步定义任何用于交叉构建的 CMake 变量。如果没有定义user_toolchain并且 Conan 检测到它正在进行交叉构建,因为构建和宿主配置文件包含不同的操作系统或架构,它将尝试定义以下变量:
  • CMAKE_SYSTEM_NAME:如果定义了 tools.cmake.cmaketoolchain:system_name 配置,则使用该值,否则将尝试自动检测。这个块将考虑交叉构建,如果是 Android 系统(由其他块管理),并且不是在 x86_64、sparc 和 ppc 系统中从 64 位构建到 32 位。
  • CMAKE_SYSTEM_VERSION:如果定义了 tools.cmake.cmaketoolchain:system_version 配置,则使用该值,否则使用定义的宿主 os.version 子设置。
  • CMAKE_SYSTEM_PROCESSOR:如果定义了 tools.cmake.cmaketoolchain:system_processor 配置,则使用该值,否则使用定义的宿主 arch 设置。
  • 参考
    class CMakeToolchain(conanfile, generator=None)
    generate()
    此方法将把生成的文件保存到 conanfile.generators_folder
  • confCMakeToolchain受以下[conf]变量影响:
  • tools.cmake.cmaketoolchain:toolchain_file 用户工具链文件,用于替换 conan_toolchain.cmake 文件。
  • tools.cmake.cmaketoolchain:user_toolchain 列表,包含要从 conan_toolchain.cmake 文件中包含的用户工具链。
  • tools.android:ndk_path 用于 ANDROID_NDK_PATH 的值。
  • tools.android:cmake_legacy_toolchain:布尔值,用于 ANDROID_USE_LEGACY_TOOLCHAIN_FILE。只有在给定值的情况下才会在 conan_toolchain.cmake 中定义。这由 tools.android:ndk_path 配置中指定的 Android NDK 中的 CMake 工具链考虑,适用于 r23c 及以上版本。如果通过 tools.build:cflagstools.build:cxxflags 定义编译器标志,则可能需要将此设置为 False,以防止 Android 的旧版 CMake 工具链覆盖这些值。如果将此设置为 False,请确保您使用的是 CMake 3.21 或更高版本。
  • tools.cmake.cmaketoolchain:system_name 在大多数情况下不是必需的,只用于强制定义 CMAKE_SYSTEM_NAME
  • tools.cmake.cmaketoolchain:system_version 在大多数情况下不是必需的,只用于强制定义 CMAKE_SYSTEM_VERSION
  • tools.cmake.cmaketoolchain:system_processor 在大多数情况下不是必需的,只用于强制定义 CMAKE_SYSTEM_PROCESSOR
  • tools.cmake.cmaketoolchain:toolset_arch:将在 conan_toolchain.cmake 文件的 CMAKE_GENERATOR_TOOLSET 变量中添加 ,host=xxx 指定符。
  • tools.cmake.cmaketoolchain:toolset_cuda:(实验性)将在 conan_toolchain.cmake 文件的 CMAKE_GENERATOR_TOOLSET 变量中添加 ,cuda=xxx 指定符。
  • tools.cmake.cmake_layout:build_folder_vars:将产生不同构建文件夹和不同 CMake 预设名称的设置和选项。
  • `tools.cmake.cmaketoolchain:presets
  • _environment`:设置为 ‘disabled’ 以防止将环境部分添加到生成的 CMake 预设中。
  • tools.build:cxxflags 将额外的 C++ 标志追加到 CMAKE_CXX_FLAGS_INIT 的列表。
  • tools.build:cflags 将额外的纯 C 标志追加到 CMAKE_C_FLAGS_INIT 的列表。
  • tools.build:sharedlinkflags 将额外的链接器标志追加到 CMAKE_SHARED_LINKER_FLAGS_INIT 的列表。
  • tools.build:exelinkflags 将额外的链接器标志追加到 CMAKE_EXE_LINKER_FLAGS_INIT 的列表。
  • tools.build:defines 将由 add_definitions() 使用的预处理器定义的列表。
  • tools.build:tools.apple:enable_bitcode 布尔值,用于启用/禁用 Bitcode Apple Clang 标志,例如 CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE
  • tools.build:tools.apple:enable_arc 布尔值,用于启用/禁用 ARC Apple Clang 标志,例如 CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC
  • tools.build:tools.apple:enable_visibility 布尔值,用于启用/禁用 Visibility Apple Clang 标志,例如 CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN
  • tools.build:sysroot 定义 CMAKE_SYSROOT 的值。
  • tools.microsoft:winsdk_version 根据 CMake 政策 CMP0149 定义 CMAKE_SYSTEM_VERSIONCMAKE_GENERATOR_PLATFORM
  • tools.build:compiler_executables类似于字典的 Python 对象,指定编译器作为键,编译器可执行路径作为值。这些键将按以下方式映射:
  • c:将在 conan_toolchain.cmake 中设置 CMAKE_C_COMPILER
  • cpp:将在 conan_toolchain.cmake 中设置 CMAKE_CXX_COMPILER
  • RC:将在 conan_toolchain.cmake 中设置 CMAKE_RC_COMPILER
  • objc:将在 conan_toolchain.cmake 中设置 CMAKE_OBJC_COMPILER
  • objcpp:将在 conan_toolchain.cmake 中设置 CMAKE_OBJCXX_COMPILER
  • cuda:将在 conan_toolchain.cmake 中设置 CMAKE_CUDA_COMPILER
  • fortran:将在 conan_toolchain.cmake 中设置 CMAKE_Fortran_COMPILER
  • asm:将在 conan_toolchain.cmake 中设置 CMAKE_ASM_COMPILER
  • hip:将在 conan_toolchain.cmake 中设置 CMAKE_HIP_COMPILER
  • ispc:将在 conan_toolchain.cmake 中设置 CMAKE_ISPC_COMPILER

5.2 generate() 的调用时机

在Conan 2.1中,generate()方法在计算和安装依赖关系图之后调用。这意味着它在conan install命令之后运行,或者当包在缓存中被构建时,在调用build()方法之前运行。generate()方法的目的是通过生成必要的文件来准备构建。

generate()方法可以用于显式实例化生成器,有条件地使用它们,自定义它们,或提供自定义生成逻辑。例如,您可能在不同的平台上使用不同的构建系统集成,或者自定义工具链。generate()方法的当前工作目录是当前布局中定义的self.generators_folder

以下是在Conan配方中使用generate()方法的一个简单示例:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain
class Pkg(ConanFile):
    def generate(self):
        tc = CMakeToolchain(self)
        tc.generate()

在这个示例中,generate()方法创建了一个CMakeToolchain对象,并调用它的generate()方法来创建一个conan_toolchain.cmake文件。这个文件将Conan设置和选项转换为CMake语法,然后在构建过程中使用。

在Conan 2.1中,generate()方法提供了灵活的方式来准备构建环境。以下是一些使用generate()方法的示例:

5.3 generate() 使用示例

示例1:使用不同的构建系统

根据不同的操作系统,使用不同的构建系统集成:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain
from conan.tools.gnu import AutotoolsToolchain
class Pkg(ConanFile):
    def generate(self):
        if self.settings.os == "Windows":
            tc = CMakeToolchain(self)
        else:
            tc = AutotoolsToolchain(self)
        tc.generate()

在这个示例中,如果操作系统是Windows,则使用CMake构建系统;否则,使用Autotools构建系统。

示例2:自定义工具链

在生成工具链文件时进行自定义:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain
class Pkg(ConanFile):
    def generate(self):
        tc = CMakeToolchain(self)
        tc.variables["MY_VARIABLE"] = "VALUE"
        tc.preprocessor_definitions["MY_DEFINITION"] = "VALUE"
        tc.generate()

在这个示例中,向CMake工具链添加了额外的变量和预处理器定义。

示例3:使用Python Requires

将生成逻辑放在一个公共的Python Requires中,以避免在多个配方中重复代码:

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain
class MyGenerator(ConanFile):
    def generate(self):
        tc = CMakeToolchain(self)
        # 自定义工具链逻辑
        tc.generate()
class Pkg(ConanFile):
    python_requires = "mygenerator/1.0"
    def generate(self):
        mygen = self.python_requires["mygenerator"].module.MyGenerator(self)
        mygen.generate()

在这个示例中,MyGenerator类包含生成逻辑,可以在多个配方中重用。

示例4:从依赖项收集文件

generate()方法中从依赖项收集或复制文件:

from conan import ConanFile
class Pkg(ConanFile):
    def generate(self):
        for dep in self.dependencies.values():
            self.copy("*.dylib", src=dep.cpp_info.libdirs, dst="libs")
            self.copy("*.dll", src=dep.cpp_info.bindirs, dst="bin")

在这个示例中,从所有依赖项中复制共享库文件到指定的目录。

示例5:使用CMakeDeps生成器来简化CMake项目中的依赖项管理

以下是一个示例,展示了如何在项目中使用CMakeDeps生成器:

from conan import ConanFile
from conan.tools.cmake import CMakeDeps
class MyLibraryConan(ConanFile):
    name = "mylibrary"
    version = "1.0"
    requires = "hello/1.0"
    generators = "CMakeDeps"
    def generate(self):
        cmake_deps = CMakeDeps(self)
        cmake_deps.configurations.append("Release")
        cmake_deps.configurations.append("Debug")
        cmake_deps.generate()

在这个示例中,我们首先在generators属性中指定了CMakeDeps生成器。然后在generate()方法中,我们创建了一个CMakeDeps实例,并添加了两个配置(ReleaseDebug)。这样,生成的CMake文件将包含这两种配置的相关信息。最后,我们调用generate()方法来生成CMake文件。

生成的文件将位于cmake文件夹中,并可以在CMake项目中使用,如下所示:

cmake_minimum_required(VERSION 3.15)
project(MyProject)
find_package(mylibrary CONFIG REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp mylibrary::mylibrary)

在这个CMake文件中,我们使用find_package()函数查找mylibrary包,并使用target_link_libraries()函数将其链接到我们的可执行文件myapp中。

6. build()

6.1 build() 介绍

build() 方法用于定义包的源代码构建。在实践中,这意味着调用某些构建系统,这可以通过显式地或使用 Conan 提供的任何构建助手来完成:

from conan.tools.cmake import CMake
class Pkg(ConanFile):
    def build(self):
        # 使用 Conan 内置的一些助手
        cmake = CMake(self)
        cmake.configure()  # 等同于 self.run("cmake . <其他参数>")
        cmake.build()      # 等同于 self.run("cmake --build . <其他参数>")
        cmake.test()       # 等同于 self.run("cmake --target=RUN_TESTS")
        # 或者运行您自己的构建系统或脚本
        self.run("mybuildsystem . --configure")
        self.run("mybuildsystem . --build")

build() 方法应尽可能简单,只是包装开发人员以最简单的方式执行的命令行调用。generate() 方法负责准备构建,创建工具链文件、CMake 预设或任何其他必要的文件,以便开发人员可以轻松地手动调用构建系统。这允许更好地与 IDE 集成并改善开发人员体验。结果是,实际上 build() 方法应该相对简单。

build() 方法每个唯一配置运行一次,因此如果存在一些源操作,例如根据不同配置有条件地应用补丁,它们也可以在 build() 方法中应用,然后实际构建。重要的是要注意,在这种情况下,不能将 no_copy_source 属性设置为 True。

build() 方法是在打包之前构建和运行单元测试的正确地方,并在这些测试失败时引发错误,中断流程,甚至不打包最终二进制文件。如果定义了 tools.build:skip_test 配置,则内置助手将跳过单元测试。对于自定义集成,预期该方法检查此 conf 值以跳过构建和运行测试,这对于某些 CI 场景可能很有用。

在跨构建场景中运行测试:在某些情况下,您可能希望构建测试但无法运行它们,例如在跨构建场景中。对于这些罕见的情况,您可以使用 conan.tools.build.can_run 工具,如下所示:

...
def build(self):
    cmake = CMake(self)
    cmake.configure()
    cmake.build()
    if can_run(self):
        cmake.test()

注意

最佳实践

build() 方法应尽可能简单,准备构建的繁重工作应该发生在 generate() 方法中,以实现良好的开发人员体验,可以轻松地使用 conan install . 本地构建,再直接调用构建系统或打开其 IDE。

6.2 build() 调用时机

build() 方法在 Conan 的构建过程中被调用,用于编译源代码并生成可执行文件或库。它的调用时机和本质如下:

调用时机

  1. source() 之后: source() 方法用于下载、解压和准备源代码。一旦源代码准备好,build() 方法就会被调用来编译这些源代码。
  2. generate() 之后: 如果存在 generate() 方法,它将在 build() 之前被调用。generate() 方法用于生成构建系统所需的文件,例如 CMake 的工具链文件或预设文件。
  3. package() 之前: 一旦源代码被成功编译,package() 方法将被调用来收集和打包编译产物,例如可执行文件、库文件和头文件。

命令本质

build() 方法的本质是执行一系列命令来编译源代码。这些命令可以是对构建系统的调用(如 CMake、Make、MSBuild 等)或者是自定义脚本。在 build() 方法中,你可以使用 Conan 提供的构建助手来简化这些命令的调用,例如:

cmake = CMake(self)
cmake.configure()
cmake.build()

这些构建助手会根据项目的配置和依赖关系自动生成适当的命令行参数,从而简化构建过程。

6.3 build()使用示例

这里有一些不同场景下的 build() 方法使用示例:

示例 1: 使用 CMake 构建 C++ 项目

from conans import ConanFile, CMake
class MyPackage(ConanFile):
    name = "MyPackage"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    generators = "cmake"
    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

在这个示例中,我们使用 CMake 构建助手来配置和构建一个 C++ 项目。cmake.configure() 调用 CMake 来生成构建文件,而 cmake.build() 则实际启动编译过程。

示例 2: 使用 Makefile 构建 C 项目

from conans import ConanFile
class MyPackage(ConanFile):
    name = "MyPackage"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    def build(self):
        self.run("make")

在这个示例中,我们假设项目使用 Makefile 进行构建。self.run("make") 调用将执行 make 命令来编译项目。

示例 3: 使用自定义构建脚本

from conans import ConanFile
class MyPackage(ConanFile):
    name = "MyPackage"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    def build(self):
        self.run("./configure")
        self.run("make")

在这个示例中,我们首先运行一个名为 configure 的脚本来准备构建环境,然后使用 make 命令来编译项目。

示例 4: 使用不同的构建配置

from conans import ConanFile, CMake
class MyPackage(ConanFile):
    name = "MyPackage"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    generators = "cmake"
    def build(self):
        cmake = CMake(self)
        if self.settings.build_type == "Debug":
            cmake.definitions["CMAKE_BUILD_TYPE"] = "Debug"
        else:
            cmake.definitions["CMAKE_BUILD_TYPE"] = "Release"
        cmake.configure()
        cmake.build()

在这个示例中,我们根据 Conan 设置中的 build_type 来调整 CMake 的构建类型。这允许我们为调试和发布构建生成不同的二进制文件。

7. package()

在Conan 2.1中,package()方法负责将构建的工件(如二进制文件、头文件等)从构建目录复制到包目录中。这是创建Conan包的一个重要步骤,因为它定义了哪些文件将包含在最终的包中。

以下是一些你可以在package()方法中使用的关键点和技巧:

  1. 复制文件
  • 使用copy()函数复制文件。你可以指定源目录、目标目录、以及要复制的文件的模式(如*.h*.lib等)。
  1. 处理符号链接
  • 如果你的包含有符号链接,你可以使用symlinks=True参数来保留它们。
  1. 包含许可证
  • 通常,你应该在package()方法中包含你的库的许可证文件,以便用户知道他们可以在什么条件下使用它。
  1. 条件包含
  • 你可以根据设置或选项的值来有条件地包含文件。例如,你可能只想在构建共享库时包含某些文件。

以下是一个使用这些技巧的示例:

def package(self):
    self.copy("*.h", dst="include", src="src")
    self.copy("*.lib", dst="lib", src="build/lib", keep_path=False)
    self.copy("*.dll", dst="bin", src="build/bin", keep_path=False)
    self.copy("license*", dst="licenses", src=".", keep_path=False)
    if self.options.shared:
        self.copy("*.so", dst="lib", src="build/lib", keep_path=False)
        self.copy("*.dylib", dst="lib", src="build/lib", keep_path=False)
• 1

在这个示例中,我们复制了头文件、库文件、DLL文件和许可证文件。我们还根据shared选项的值有条件地复制了共享库文件。

8. package_info()

在Conan 2.1中,package_info()方法对于定义如何使用您的包至关重要。以下是此方法中可以使用的一些关键点和参数:

  1. 库和组件
  • 使用self.cpp_info.libs指定需要链接的库的名称。
  • 使用self.cpp_info.components为具有多个库的包定义组件。每个组件可以有自己的设置,如libsdefinesrequiresset_property来指定属性,比如cmake_target_name
  1. 标志和属性
  • 您可以设置各种标志,如cflagscxxflagssharedlinkflagsexelinkflags,以便消费者正确行为。
  • 使用set_property()定义构建系统特定信息。内置属性包括cmake_file_namecmake_target_namepkg_config_name
  1. 环境变量
  • buildenv_inforunenv_info属性允许您为消费者定义环境变量。您可以使用defineprepend_pathappend等方法来设置这些变量。
  1. 配置信息
  • 使用conf_info属性向消费者传递配置信息。您可以为特定的配置名称定义和追加值。

以下是您在package_info()方法中可能使用这些参数的示例:

def package_info(self):
    self.cpp_info.libs = ["mylibrary"]
    self.cpp_info.components["mycomponent"].libs = ["mycomponent"]
    self.cpp_info.components["mycomponent"].requires = ["othercomponent"]
    self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "mycomponent_target")
    self.cpp_info.set_property("cmake_file_name", "MyLibraryConfig")
    self.buildenv_info.define("MY_BUILD_VAR", "value")
    self.runenv_info.append_path("MY_RUN_PATH", "/path/to/run")
    self.conf_info.define("tools.mytool:setting", "value")

在此示例中,我们定义了一个库和一个组件,设置了用于CMake集成的属性,定义了构建和运行环境变量,并为工具设置了一个配置值。

有关更详细的信息和示例,您可以参考官方Conan文档中关于package_info()方法的部分【9†】。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
2月前
|
存储 缓存 算法
【Conan 入门教程 】了解 Conan2.1 中默认生成器的作用
【Conan 入门教程 】了解 Conan2.1 中默认生成器的作用
43 1
|
2月前
|
测试技术 编译器 持续交付
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
69 0
|
4月前
|
Python
Python学习 -- 类的封装
Python学习 -- 类的封装
18 0
|
4月前
|
搜索推荐 Python
Python学习 -- 类的继承
Python学习 -- 类的继承
16 0
|
9月前
|
关系型数据库 MySQL
Python--深入浅出的装饰器--2
Python--深入浅出的装饰器--2
66 0
|
9月前
Python--深入浅出的装饰器--1
Python--深入浅出的装饰器--1
41 0
|
9月前
|
自然语言处理 Python
|
8月前
|
Python
Python logging模块怎么使用,你会了吗?
Python logging模块怎么使用,你会了吗?
148 0
|
9月前
|
Python
Python快速上手系列--继承--详解篇
Python快速上手系列--继承--详解篇
47 0
|
9月前
|
Python
Python快速上手系列--类、函数的导入--详解篇
Python快速上手系列--类、函数的导入--详解篇
51 0