【Conan 入门教程】从零开始编写第一个自定义部署器

简介: 【Conan 入门教程】从零开始编写第一个自定义部署器

第一章: 部署器基本介绍

在Conan中,部署器(Deployers)是一种机制,用于方便地将文件从一个文件夹(通常是Conan缓存)复制到用户文件夹。Conan提供了两个内置的部署器:full_deploydirect_deploy。此外,用户还可以通过 conan config install 命令管理自己的部署器。

部署器在生成器(Generators)之前运行,它们可以更改目标文件夹。例如,如果在 CMakeDeps 生成器之前运行 --deployer=full_deploy 部署器,则 CMakeDeps 生成的文件将指向 full_deploy 部署器在用户文件夹中完成的本地副本,而不是指向Conan缓存。可以通过提供多个 --deployer= 参数来指定多个部署器,它们将按照出现的顺序运行。

部署器可以是多配置的。对不同配置文件多次运行 conan install . --deployer=full_deploy 可以实现一个完全自包含的项目,包括所有的工件、二进制文件和构建文件。这个项目将完全独立于Conan,不再需要它来构建。可以使用 --deployer-folder 参数根据需要更改部署器的基础文件夹输出路径。

内置部署器有 full_deploydirect_deployfull_deploy 部署每个依赖项的包文件夹到您的配方的 output_folder 中的一个子文件夹树中,基于构建上下文、依赖项名称和版本、构建类型和构建架构。direct_deployfull_deploy 相同,但只处理您配方的直接依赖项。这些内置部署器目前处于预览阶段。

可以通过设置 conf tools.deployer:symlinksFalse 来禁用部署器复制符号链接,这对于不支持符号链接的系统来说很方便。

自定义部署器可以通过 conan config install 管理。当寻找特定的部署器时,Conan将按以下顺序在这些位置查找部署器:

  1. 绝对路径
  2. 相对于当前工作目录(cwd)
  3. [CONAN_HOME]/extensions/deployers 文件夹中
  4. 作为内置部署器

Conan将寻找一个 deploy() 方法来调用每个已安装文件。您的自定义部署器的函数签名应如下:

def deploy(graph, output_folder: str, **kwargs):
    # Your deployment logic here

其中 **kwargs 是必须的,即使未使用,因为未来的Conan版本中可能会添加新的参数,如果没有定义 **kwargs,这些参数将会导致破坏。

您可以通过 graph.root.conanfile 访问您的 conanfile 对象。有关如何迭代其依赖项的信息,请参阅 ConanFile.dependencies。现在,您的自定义部署器可以使用其所在文件的文件名来调用,就像它是一个内置部署器一样,例如 conan install . --deployer=my_custom_deployer。请注意,提供 .py 扩展名是可选的。

第二章 :开始自定义部署器

2.1 创建目录

如果在 ~/.conan2/extensions 目录下没有 deployers 文件夹,您可以手动创建这个文件夹。在 Linux 或 macOS 系统上,您可以使用以下命令:

mkdir -p ~/.conan2/extensions/deployers

这将创建 deployers 文件夹,如果 extensions 文件夹也不存在,也会一并创建。之后,您可以将自定义部署器的脚本放入这个 deployers 文件夹中。

请确保您的自定义部署器脚本具有正确的文件权限,以便 Conan 可以执行它们。通常,您应该给这些脚本文件设置可读和可执行权限。例如,如果您的自定义部署器脚本名为 my_custom_deployer.py,您可以使用以下命令设置权限:

chmod +rx ~/.conan2/extensions/deployers/my_custom_deployer.py

之后,您就可以在 Conan 命令中使用 --deployer=my_custom_deployer 来调用您的自定义部署器了。

2.2 编写部署器

2.2.1 基本的部署器示例

首先我们需要一个部署器示例,根据Conan 2.1的文档,自定义部署器通常应该包含一个 deploy 函数,该函数接受依赖图 (graph)、输出文件夹路径 (output_folder) 和一个关键字参数字典 (**kwargs) 作为参数【16†source】【17†source】【18†source】。这个 deploy 函数负责将文件从依赖项的源文件夹复制到指定的输出文件夹中。

以下是一个自定义部署器的示例,它将所有依赖项的源文件复制到一个特定的输出文件夹中:

from conan.tools.files import copy
import os
def deploy(graph, output_folder, **kwargs):
    # Note the kwargs argument is mandatory to be robust against future changes.
    for name, dep in graph.root.conanfile.dependencies.items():
        if dep.folders is None or dep.folders.source_folder is None:
            raise ConanException(f"Sources missing for {name} dependency.\n"
                                  "This deployer needs the sources of every dependency present to work, either building from source, "
                                  "or by using the 'tools.build:download_source' conf.")
        copy(graph.root.conanfile, "*", dep.folders.source_folder, os.path.join(output_folder, "dependency_sources", str(dep)))

在这个示例中,deploy 函数遍历了配方的所有依赖项,并将每个依赖项的源文件复制到 output_folder 下的 dependency_sources 文件夹中。这个示例使用了 conan.tools.files.copy 函数来执行复制操作。需要注意的是,为了确保 dep.folders.source_folder 被正确定义,必须在调用 conan installconan graph 命令时设置 tools.build:download_source=True 配置。

这个示例展示了如何创建一个基本的自定义部署器。根据您的具体需求,您可以调整这个函数来执行更复杂的部署逻辑,例如只复制特定的文件或文件夹,或者根据不同的构建配置创建不同的目标文件夹。

2.2 理解部署器参数

在自定义部署器的 deploy 函数中,有三个主要参数:graph, output_folder, 和 **kwargs。下面是它们的详细介绍:

  1. graph:
  • 类型: ConanGraph
  • 描述: 这个参数代表了整个依赖图,其中包含了所有的依赖项节点。每个节点代表了一个依赖项,包括它的信息和属性。
  • 使用: 你可以遍历这个图来获取每个依赖项的信息。例如,node.conanfile 可以访问节点的 conanfile 对象,node.ref.namenode.ref.version 分别可以获取依赖项的名称和版本。
  1. output_folder:
  • 类型: str
  • 描述: 这个参数是一个字符串,代表了文件应该被部署到的目标文件夹的路径。
  • 使用: 在部署文件时,你应该将文件复制到这个文件夹中。你可以根据需要创建子文件夹来组织不同的依赖项。
  1. **kwargs:
  • 类型: dict
  • 描述: 这是一个关键字参数字典,用于传递额外的参数。这个参数是可选的,但是建议包含它以便于将来的扩展性。
  • 使用: 在当前版本的Conan中,这个参数可能不会被直接使用,但是在将来的版本中,可能会添加新的参数。通过包含 **kwargs,你的部署器将能够兼容未来的参数变化,而不会因为缺少新参数而出现错误。

举个例子,如果你想在部署过程中打印每个依赖项的名称和版本,你可以这样做:

def deploy(graph, output_folder: str, **kwargs):
    for node in graph.nodes:
        print(f"Deploying {node.ref.name} version {node.ref.version}")
        # 其他部署逻辑...

这里,graph.nodes 遍历了所有的依赖项节点,然后你可以通过 node.ref.namenode.ref.version 来获取每个依赖项的名称和版本,并将它们打印出来。


如果你在 conan install 命令中不指定 --deploy-folder 参数来设置 output_folder,Conan 会使用默认的输出文件夹路径。默认情况下,这个路径是当前工作目录下的一个名为 deploy 的文件夹。也就是说,如果你的当前工作目录是 /home/user/project,那么默认的 output_folder 将是 /home/user/project/deploy

如果你直接拷贝文件到这个默认的 output_folder,那么所有的依赖项将被部署到 /home/user/project/deploy 文件夹下,按照你在自定义部署器中定义的结构组织。例如,如果你使用了前面提供的示例部署器,那么 expat/2.6.0 的文件将被拷贝到 /home/user/project/deploy/expat/2.6.0

如果你想将依赖项部署到不同的路径,你可以在 conan install 命令中使用 --deploy-folder 参数来指定一个自定义的输出文件夹路径。例如:

conan install . --deploy-folder=/path/to/custom/deploy/folder

这将设置 output_folder/path/to/custom/deploy/folder,并将所有依赖项部署到这个路径下。

2.3 写一个简单示例

把依赖项拷贝到指定目录

我们可以对自定义的 deploy 函数进行一些优化和注释添加,以便更好地理解和使用:

import os
import shutil
from conan.tools.files import copy
def deploy(graph, output_folder: str, **kwargs):
    # 遍历依赖图中的所有节点
    for node in graph.nodes:
        # 获取依赖项的名称和版本
        dep_name = node.ref.name
        #dep_version 不是字符串,而是 Version 对象。可以通过在构造 dest_folder 路径时将 dep_version 转换为字符串
        dep_version = str(node.ref.version)  # Convert Version object to string
        # 打印依赖项信息
        print(f"Deploying {dep_name} version {dep_version}")
        # 获取包的路径
        package_folder = node.conanfile.package_folder
        # 构建目标文件夹路径
        dest_folder = os.path.join(output_folder, dep_name, dep_version)
        # 如果目标文件夹不存在,则创建它
        if not os.path.exists(dest_folder):
            os.makedirs(dest_folder)
        # 复制文件夹内容
        if package_folder:
            # 使用Conan的copy工具来复制文件,以便更好地处理文件复制过程中的各种情况
            copy(node.conanfile, "*", package_folder, dest_folder)

os.path.join 是一个 Python 标准库函数,用于将多个路径组件连接成一个完整的路径。这个函数会根据你的操作系统自动处理路径分隔符,确保路径是正确的。

例如,假设你有一个文件夹的路径和一个文件名,你想要得到这个文件的完整路径:

folder_path = "/home/user/documents"
file_name = "report.txt"
full_path = os.path.join(folder_path, file_name)

在这个例子中,os.path.join 会将 folder_pathfile_name 连接起来,使用适当的路径分隔符(在 Unix 系统上是 /,在 Windows 系统上是 \)。所以 full_path 的值将是 /home/user/documents/report.txt

在部署器的上下文中,os.path.join 用于构建依赖项的目标文件夹路径。例如:

dest_folder = os.path.join(output_folder, dep_name, dep_version)

这里,output_folder 是部署的根目录,dep_name 是依赖项的名称,dep_version 是依赖项的版本。os.path.join 将它们连接起来,形成一个完整的路径,用于存储特定依赖项的文件。

2.4 执行安装

在Conan 2.1中,要在安装过程中执行自定义部署器并指定输出路径,您可以使用 --deployer 参数来指定部署器,以及 --deployer-folder 参数来设置部署器的输出文件夹路径。例如,如果您的自定义部署器名为 my_custom_deployer 并且您想将输出文件夹设置为 /path/to/output_folder,您可以使用以下命令:

conan install . --deployer=my_custom_deployer --deployer-folder=/path/to/output_folder
#conan install conanfile.py --build=missing -o *:shared=True --deployer=my_custom_deployer --deployer-folder=$(pwd)/third-party

这将使得自定义部署器将文件部署到指定的 /path/to/output_folder 路径下。如果不指定 --deployer-folder 参数,那么默认的输出路径将是当前工作目录下的 output_folder 文件夹。

第三章 小结

在本博客中,我们探讨了以下知识点:

  1. Conan Deployers: Conan的部署器(Deployers)是一种机制,用于将文件从一个文件夹(通常是Conan缓存)复制到用户文件夹。Conan提供了两个内置的部署器:full_deploydirect_deploy,同时支持自定义部署器【16†source】。
  2. 自定义部署器: 自定义部署器可以通过 conan config install 管理,并且需要实现一个 deploy 函数,该函数接受依赖图 (graph)、输出文件夹路径 (output_folder) 和一个关键字参数字典 (**kwargs) 作为参数【16†source】【17†source】【18†source】。
  3. Conan Graph: 依赖图 (graph) 包含了所有的依赖项节点,每个节点代表了一个依赖项,包括它的信息和属性。可以遍历这个图来获取每个依赖项的信息。
  4. 文件复制: 在自定义部署器中,通常需要将依赖项的文件从包文件夹复制到指定的输出文件夹。可以使用原生的 shutil 模块或 Conan 的 conan.tools.files.copy 工具来实现文件复制。
  5. 优化和注释: 为了提高代码的可读性和可维护性,对自定义部署器函数进行优化和添加注释是很有帮助的。例如,使用 Conan 的 copy 工具替代原生的复制函数,并添加日志打印以跟踪部署过程中的依赖项。

通过这些知识点,我们了解了如何在 Conan 中使用和自定义部署器来管理依赖项的复制和部署过程。

结语

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

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

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

目录
相关文章
|
5月前
|
数据库 Python
Python实践:从零开始构建你的第一个Web应用
使用Python和轻量级Web框架Flask,你可以轻松创建Web应用。先确保安装了Python,然后通过`pip install Flask`安装Flask。在`app.py`中编写基本的"Hello, World!"应用,定义路由`@app.route('/')`并运行`python app.py`启动服务器。扩展应用,可添加新路由显示当前时间,展示Flask处理动态内容的能力。开始你的Web开发之旅吧!【6月更文挑战第13天】
236 2
|
6月前
|
编解码 Java API
Jmeter--控制器--详解,2024年最新系统学Python从零开始
Jmeter--控制器--详解,2024年最新系统学Python从零开始
|
11月前
|
vr&ar 开发工具 iOS开发
visionOS空间计算实战开发教程Day 1:环境安装和编写第一个程序
截至目前visionOS还未在Xcode稳定版中开放,所以需要下载Xcode Beta版。比如我们可以下载Xcode 15.1 beta 2,注意Xcode 15要求系统的版本是macOS Ventura 13.5或更新,也就是说2017年的MacBook Pro基本可以勉强一战,基本上还是推荐使用M系列芯片的电脑进行开发。
155 0
|
存储 数据可视化 Ubuntu
bcftools学习笔记丨软件简介、安装方式、使用方法、核心功能、参数解释等一文速览
bcftools学习笔记丨软件简介、安装方式、使用方法、核心功能、参数解释等一文速览
|
Kubernetes API 数据安全/隐私保护
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
你有没有想过, 如果要在 kubernetes 集群中 **发布** 一个最基本的 **无状态服务**, 并 **提供** 给用户访问, 最少需要配置几个 `K8S Config API` ? 自己写一个, 提升自己。
234 0
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
|
数据可视化 Shell C++
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
597 0
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
|
Java API Maven
第三章 Gradle构建脚本基础
从这章开始,会对Gradle有一个大概的介绍,帮助大家快速的入门Gradle。本章从整体构建脚本的角度介绍Gradle,什么是Settings文件,他有什么作用;什么是Build文件,它又有什么作用,我们可以新建多少Build文件。
175 0
第三章 Gradle构建脚本基础
|
搜索推荐 IDE Java
pinpoint插件开发之二:从零开始新建一个插件
从零开始新建pinpoint插件,本篇给出从编码到部署运行的详细步骤
976 0
pinpoint插件开发之二:从零开始新建一个插件