【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 中使用和自定义部署器来管理依赖项的复制和部署过程。

结语

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

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

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

目录
相关文章
|
6月前
|
数据库 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天】
272 2
|
Java Unix 编译器
CMake入门教程:从零开始构建C/C++项目
CMake是一个跨平台的自动化构建工具,可以用于构建各种类型的项目,包括C++、C、Python、Java等。本文将从零开始,介绍如何使用CMake构建一个简单的C/C++项目
344 0
|
Kubernetes API 数据安全/隐私保护
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
你有没有想过, 如果要在 kubernetes 集群中 **发布** 一个最基本的 **无状态服务**, 并 **提供** 给用户访问, 最少需要配置几个 `K8S Config API` ? 自己写一个, 提升自己。
239 0
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
|
Apache 开发工具 数据安全/隐私保护
提高代码可重用性, 减少重复劳动 -- 手把手带你 Python 自定义模块并上传到 pypi, 贡献自己创造的轮子为所有人使用, 让 Python 开发更简单
我们都知道程序中可通过定义函数来减少工作量,提高代码的可重用性,从而提高我们的开发效率.我们一直import的是别人的模块,那如果我们需要使用自己的模块,或者开发一个自己的模块供别人使用,我们自己造轮子该怎么做呢? 我们实际的开发中,只在一个文件中编写代码是不太可能的,当开发大型项目的时候,一个文件过于臃肿,第一个是不利于阅读,代码可读性极差,还不利于团队协作开,所以这时候我们需要引入模块的概念.下面我们来了解什么是模块
429 1
|
数据可视化 Shell C++
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
656 0
ROS入门笔记(九):编写ROS的第一个程序hello world(重点)
|
自然语言处理 PHP Python
【Python】简单Web框架从零开始(四):300行代码搞定模板渲染【上】
通过使用学习tornado、bottle的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自tornado和bottle的语法。可以用来做一些简单的网页渲染,邮件内容生成等HTML显示方面。以下就是简单的语法使用介绍。
219 0
|
索引 Python
列表的修改 | Python从入门到精通:进阶篇之四
本文介绍了如何通过索引和切片两种方式对列表进行修改和删除的操作。
列表的修改 | Python从入门到精通:进阶篇之四
|
Java Python 开发工具
Python3中如何做的自定义模块的引用?
前言python引用与java很大区别 java中,比如jar包com.my.test 中有一个Employee类,则可以 import com.my.test; 使用: Employee employee=new Employee() python 中,Employee.
1316 0