管理包元数据文件

警告

此功能为实验性功能,可能会有破坏性更改。更多信息请参阅Conan 稳定性部分。

Conan 包通常由多个 C 和 C++ 构件组成,包括头文件、编译后的库和可执行文件。但还有其他文件可能不需要用于包的正常使用,但对于合规性、技术或业务原因来说可能非常重要,例如

  • 完整的构建日志

  • 测试可执行文件

  • 运行测试套件后的测试结果

  • 调试工件,如大型 .pdb 文件

  • 覆盖率、消毒器或其他源代码或二进制分析工具的结果

  • 关于构建的上下文和元数据,确切的机器、环境、作者、CI 数据

  • 其他合规性和安全相关文件

存储和跟踪这些文件有几个重要原因,例如法规、合规性、安全性、可重现性和可追溯性。这些文件的难题在于它们可能很大/很重,如果我们将其存储在包内(只是在 package() 方法中复制工件),这将使包变得更大,并会影响下载、解压缩和使用包的速度。这通常会发生很多次,在开发人员机器和 CI 中都是如此,并且可能对开发人员体验和基础设施成本产生影响。此外,包是不可变的,也就是说,一旦创建了包,就不应该对其进行修改。如果我们想在创建包后,甚至在上传包后添加额外的元数据文件,这可能会成为一个问题。

元数据文件 功能允许创建、上传、附加和存储与包关联的元数据,以集成和统一的方式进行,同时避免对开发人员和 CI 速度和成本的影响,因为默认情况下,当使用包时,元数据文件不会被下载和解压缩。

重要的是要强调,有两种类型的元数据

  • 配方元数据,与 conanfile.py 配方关联,元数据应与从该配方创建的所有二进制文件通用(包名称、版本和配方修订版)。这种元数据可能不太常见,但例如,对源代码进行某些扫描的结果,对于所有配置和构建都是通用的,可以是配方元数据。

  • 包二进制元数据,与给定特定配置的包二进制文件关联,并由 package_id 表示。构建日志、测试报告等,这些内容特定于二进制配置,将是包元数据。

在配方中创建元数据

配方可以直接在其方法中定义元数据。一个常见的用例是存储日志。使用 self.recipe_metadata_folderself.package_metadata_folder,配方可以将文件存储在这些位置。

import os
from conan import ConanFile
from conan.tools.files import save, copy

class Pkg(ConanFile):
   name = "pkg"
   version = "0.1"

   def layout(self):
      # Or something else, like the "cmake_layout(self)" built-in layout
      self.folders.build = "mybuild"
      self.folders.generators = "mybuild/generators"

   def export(self):
      # logs that might be generated in the recipe folder at "export" time.
      # these would be associated with the recipe repo and original source of the recipe repo
      copy(self, "*.log", src=self.recipe_folder,
           dst=os.path.join(self.recipe_metadata_folder, "logs"))

   def source(self):
      # logs originated in the source() step, for example downloading files, patches or other stuff
      save(self, os.path.join(self.recipe_metadata_folder, "logs", "src.log"), "srclog!!")

   def build(self):
      # logs originated at build() step, the most common ones
      save(self, "mylogs.txt", "some logs!!!")
      copy(self, "mylogs.txt", src=self.build_folder,
           dst=os.path.join(self.package_metadata_folder, "logs"))

请注意,“配方”方法(那些对所有二进制文件通用的方法,例如 export()source())应使用 self.recipe_metadata_folder,而“包”特定方法(build()package())应使用 self.package_metadata_folder

对这个配方执行 conan create,将在 Conan 缓存中创建“metadata”文件夹。我们可以查看这些文件夹

$ conan create .
$ conan cache path pkg/0.1 --folder=metadata
# folder containing the recipe metadata
$ conan cache path pkg/0.1:package_id --folder=metadata
# folder containing the specific "package_id" binary metadata

也可以使用“本地流程”命令并获取本地“metadata”文件夹。如果我们要这样做,强烈建议使用类似于上面的 layout() 方法,以避免弄乱当前文件夹。然后,本地命令将允许测试和调试功能

$ conan source .
# check local metadata/logs/src.log file
$ conan build .
# check local mybuild/metadata/logs/mylogs.txt file

注意:请注意,在 conan export-pkg 命令期间,本地创建的元数据不会导出到 Conan 缓存。一些元数据,例如在 export() 方法中生成的内容,可以在缓存中生成,因为 conan export-pkg 命令会调用该方法,但“build”文件夹中的元数据不会导出。如果您想将该元数据添加到导出的包中,可以在 conan export-pkg 之后使用 conan cache path 报告的路径复制它,如下文的“使用命令添加元数据”部分所述。

使用钩子创建元数据

如果配方之间存在一些通用的元数据,可以使用钩子在不修改配方的情况下捕获它。假设我们有一个更简单的配方

import os
from conan import ConanFile
from conan.tools.files import save, copy

class Pkg(ConanFile):
   name = "pkg"
   version = "0.1"
   no_copy_source = True

   def layout(self):
      self.folders.build = "mybuild"
      self.folders.generators = "mybuild/generators"

   def source(self):
      save(self, "logs/src.log", "srclog!!")

   def build(self):
      save(self, "logs/mylogs.txt", "some logs!!!")

正如我们所见,这根本没有使用元数据文件夹。现在让我们定义以下钩子

import os
from conan.tools.files import copy

def post_export(conanfile):
      conanfile.output.info("post_export")
      copy(conanfile, "*.log", src=conanfile.recipe_folder,
         dst=os.path.join(conanfile.recipe_metadata_folder, "logs"))

def post_source(conanfile):
      conanfile.output.info("post_source")
      copy(conanfile, "*", src=os.path.join(conanfile.source_folder, "logs"),
         dst=os.path.join(conanfile.recipe_metadata_folder, "logs"))

def post_build(conanfile):
      conanfile.output.info("post_build")
      copy(conanfile, "*", src=os.path.join(conanfile.build_folder, "logs"),
         dst=os.path.join(conanfile.package_metadata_folder, "logs"))

使用这些钩子的效果与配方中的方法非常相似:元数据文件将在执行 conan create 时创建在缓存中,以及本地的 conan sourceconan build 本地流程。

使用命令添加元数据

可以在创建包后添加或修改元数据文件。为此,使用 conan cache path 命令将返回执行此操作的文件夹,因此复制、创建或修改该位置的文件将实现此目的。

$ conan create . --name=pkg --version=0.1
$ conan cache path pkg/0.1 --folder=metadata
# folder to put the metadata, initially empty if we didn't use hooks
# and the recipe didn't store any metadata. We can copy and put files
# in the folder
$ conan cache path pkg/0.1:package_id --folder=metadata
# same as above, for the package metadata, we can copy and put files in
# the returned folder

此元数据是本地添加的,在 Conan 缓存中。如果您想更新服务器元数据,则需要从缓存中上传它。

上传元数据

到目前为止,元数据已在本地创建,存储在 Conan 缓存中。将元数据上传到服务器与现有的 conan upload 命令集成

$ conan upload "*" -c -r=default
# Uploads recipes, packages and metadata to the "default" remote
...
pkg/0.1: Recipe metadata: 1 files
pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files

默认情况下,conan upload 在将配方或包上传到服务器时,会上传配方和包元数据。但是,在某些情况下,Conan 会完全避免此上传,如果它检测到修订版已经存在于服务器中,它将不会上传配方或包。如果元数据已在本地修改或添加了新文件,我们可以使用以下命令显式强制上传

# We added some metadata to the packages in the cache
# But those packages already exist in the server
$ conan upload "*" -c -r=default --metadata="*"
...
pkg/0.1: Recipe metadata: 1 files
pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files

--metadata 参数允许指定我们上传的元数据文件。如果我们将它们组织在文件夹中,我们可以指定 --metadata="logs*" 以仅上传日志元数据,而不是其他可能的元数据,例如 test 元数据。

# Upload only the logs metadata of the zlib/1.2.13 binaries
# This will upload the logs even if zlib/1.2.13 is already in the server
$ conan upload "zlib/1.2.13:*" -r=remote -c --metadata="logs/*"
# Multiple patterns are allowed:
$ conan upload "*" -r=remote -c --metadata="logs/*" --metadata="tests/*"

有时,即使元数据缓存文件夹包含文件,上传包而不上传元数据也可能很有用。要忽略上传元数据,请使用空参数作为元数据模式

# Upload only the packages, not the metadata
$ conan upload "*" -r=remote -c --metadata=""

不允许混合使用 --metadata=""--metadata="*",并且会引发错误。

# Invalid command, it will raise an error
$ conan upload "*" -r=remote -c --metadata="" --metadata="logs/*"
ERROR: Empty string and patterns can not be mixed for metadata.

下载元数据

如上所述,默认情况下不会下载元数据。当使用 conan installconan create 从服务器获取依赖项时,服务器上的元数据不会被下载。

恢复服务器元数据的方法是使用 conan download 命令显式指定它

# Get the metadata of the "pkg/0.1" package
$ conan download pkg/0.1 -r=default --metadata="*"
...
$ conan cache path pkg/0.1 --folder=metadata
# Inspect the recipe metadata in the returned folder
$ conan cache path pkg/0.1:package_id --folder=metadata
# Inspect the package metadata for binary "package_id"

元数据的检索是按包进行的 download。如果我们要下载整个依赖图的元数据,则需要使用“包列表”

$ conan install . --format=json -r=remote > graph.json
$ conan list --graph=graph.json --format=json > pkglist.json
# the list will contain the "remote" origin of downloaded packages
$ conan download --list=pkglist.json --metadata="*" -r=remote

请注意,“包列表”仅包含与“远程”来源关联的已下载包。如果它们先前在缓存中,则它们不会在“远程”来源下列出,并且元数据不会被下载。如果您想收集依赖项元数据,请记住在从服务器安装包时下载它。还有其他可能性,例如可以自动收集和下载服务器依赖项元数据的自定义命令。

删除元数据

目前,Conan 不允许从服务器端删除元数据,因为元数据是“附加的”,可以添加新数据,但不能删除它(否则,在添加新元数据之前下载所有以前的元数据将是不可能的,这可能效率低下且更容易出错,尤其是在对潜在的竞争条件敏感的情况下)。

从服务器端删除元数据的建议是使用服务器可能提供的工具、Web 界面或 API。

注意

最佳实践

  • 元数据不应是使用包所必需的。应该能够下载其元数据而无需下载配方和包。如果元数据是使用包所必需的,那么它就不是元数据,而应该打包为头文件和二进制文件。

  • 元数据读取访问不应是频繁的操作,或者开发人员必须执行的操作。元数据读取用于特殊情况,例如需要为了合规性恢复一些构建日志,或者可能需要为了调试或重新检查崩溃而需要一些测试可执行文件。

  • Conan 不会对元数据文件进行任何压缩或解压缩。如果有很多元数据文件,请考虑自己对其进行压缩,否则上传这些文件可能需要很长时间。如果您需要处理不同类型的元数据(日志、测试、报告),将文件压缩在每个类别下可能更好,以便能够使用 --metadata=xxx 参数进行过滤。

将 test_package 作为元数据

这是一个使用元数据的示例,将整个 test_package 文件夹存储为元数据,以便以后恢复它并执行它。请注意,这不一定适用于生产环境。

让我们从一个自动将 test_package 文件夹存储为 配方元数据 的钩子开始

import os
from conan.tools.files import copy

def post_export(conanfile):
   conanfile.output.info("Storing test_package")
   folder = os.path.join(conanfile.recipe_folder, "test_package")
   copy(conanfile, "*", src=folder,
         dst=os.path.join(conanfile.recipe_metadata_folder, "test_package"))

请注意,此钩子没有考虑到 test_package 可能被大量临时构建对象弄脏(在将其添加到元数据之前应清理),并且它没有检查 test_package 可能根本不存在并崩溃。

当创建并上传包时,它会将包含 test_package 的配方元数据上传到服务器

$ conan create ...
$ conan upload "*" -c -r=default  # uploads metadata
...
pkg/0.1: Recipe metadata: 1 files

让我们删除本地副本,并假设该包已安装,但元数据未安装

$ conan remove "*" -c  # lets remove the local packages
$ conan install --requires=pkg/0.1 -r=default  # this will not download metadata

如果此时安装的包在我们的应用程序中失败,我们可以恢复 test_package,下载它,并将其复制到我们的当前文件夹

$ conan download pkg/0.1 -r=default --metadata="test_package*"
$ conan cache path pkg/0.1 --folder=metadata
# copy the test_package folder from the cache, to the current folder
# like `cp -R ...`

# Execute the test_package
$ conan test metadata/test_package pkg/0.1
pkg/0.1 (test package): Running test()

另请参阅

  • TODO:如何使用一些自定义部署程序或命令收集完整依赖图的元数据的示例

这是一个 实验性 功能。我们期待听到您的反馈、用例和需求,以不断改进此功能。请在 Github issues 上报告它