Managing package metadata files¶
Warning
This feature is experimental and subject to breaking changes. See the Conan stability section for more information.
A Conan package is typically composed by several C and C++ artifacts, headers, compiled libraries and executables. But there are other files that might not be necessary for the normal consumption of such a package, but which could be very important for compliance, technical or business reasons, for example:
Full build logs
The tests executables
The tests results from running the test suite
Debugging artifacts like heavy .pdb files
Coverage, sanitizers, or other source or binary analysis tools results
Context and metadata about the build, exact machine, environment, author, CI data
Other compliance and security related files
There are several important reasons to store and track these files like regulations, compliance, security, reproducibility and traceability.
The problem with these files is that they can be large/heavy, if we store them inside the package (just copying the artifacts in the package()
method), this will make the packages much larger, and it will affect the speed of downloading, unzipping and using packages in general. And this typically happens a lot of times, both in developer machines but also in CI, and it can have an impact on the developer experience and infrastructure costs. Furthermore, packages are immutable, that is, once a package has been created, it shouldn’t be modified. This might be a problem if we want to add extra metadata files after the package has been created, or even after the package has been uploaded.
The metadata files feature allows to create, upload, append and store metadata associated to packages in an integrated and unified way, while avoiding the impact on developers and CI speed and costs, because metadata files are not downloaded and unzipped by default when packages are used.
It is important to highlight that there are two types of metadata:
Recipe metadata, associated to the
conanfile.py
recipe, the metadata should be common to all binaries created from this recipe (package name, version and recipe revision). This metadata will probably be less common, but for example results of some scanning of the source code, that would be common for all configurations and builds, can be recipe metadata.Package binary metadata, associated to the package binary for a given specific configuration and represented by a
package_id
. Build logs, tests reports, etc, that are specific to a binary configuration will be package metadata.
Creating metadata in recipes¶
Recipes can directly define metadata in their methods. A common use case would be to store logs.
Using the self.recipe_metadata_folder
and self.package_metadata_folder
, the recipe can store files
in those locations.
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"))
Note that “recipe” methods (those that are common for all binaries, like export()
and source()
) should use
self.recipe_metadata_folder
, while “package” specific methods (build()
, package()
) should use the
self.package_metadata_folder
.
Doing a conan create
over this recipe, will create “metadata” folders in the Conan cache. We can have a look at those folders with:
$ 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
It is also possible to use the “local flow” commands and get local “metadata” folders. If we want to do this, it is very recommended
to use a layout()
method like above to avoid cluttering the current folder. Then the local commands will allow to test and debug the functionality:
$ conan source .
# check local metadata/logs/src.log file
$ conan build .
# check local mybuild/metadata/logs/mylogs.txt file
NOTE: This metadata is not valid for the conan export-pkg
flow. If you want to use the export-pkg
flow you might want to check the
“Adding metadata” section below.
Creating metadata with hooks¶
If there is some common metadata accross recipes, it is possible to capture it without modifying the recipes, using hooks. Let’s say that we have a simpler recipe:
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!!!")
As we can see, this is not using the metadata folders at all. Let’s define now the following hooks:
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"))
The usage of these hooks will have a very similar effect to the in-recipe approach: the metadata files
will be created in the cache when conan create
executes, and also locally for the conan source
and conan build
local flow.
Adding metadata with commands¶
Metadata files can be added or modified after the package has been created. To achieve this, using the conan cache path
command will return the folders to do that operation, so copying, creating or modifying files in that location will achieve this.
$ 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
This metadata is added locally, in the Conan cache. If you want to update the server metadata, uploading it from the cache is necessary.
Uploading metadata¶
So far the metadata has been created locally, stored in the Conan cache. Uploading the metadata to the server is integrated with
the existing conan upload
command:
$ 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
By default, conan upload
will upload recipes and packages metadata when a recipe or a package is uploaded to the server.
But there are some situations that Conan will completely avoid this upload, if it detects that the revisions do already exist
in the server, it will not upload the recipes or the packages.
If the metadata has been locally modified or added new files, we can force the upload explicitly with:
# 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
The --metadata
argument allows to specify the metadata files that we are uploading. If we structure them in folders,
we could specify --metadata="logs*"
to upload only the logs metadata, but not other possible ones like test
metadata.
# 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/*"
Sometimes it might be useful to upload packages without uploading the metadata, even if the metadata cache folders contain files. To ignore uploading the metadata, use an empty argument as metadata pattern:
# Upload only the packages, not the metadata
$ conan upload "*" -r=remote -c --metadata=""
The case of mixing --metadata=""
with --metadata="*"
is not allowed, and it will raise an error.
# 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.
Downloading metadata¶
As described above, metadata is not downloaded by default. When packages are downloaded with a conan install
or conan create
fetching dependencies from the servers, the metadata from those servers will not be downloaded.
The way to recover the metadata from the server is to explicitly specify it with the conan download
command:
# 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"
The retrieval of the metadata is done with download
per-package. If we want to download the metadata for a whole dependency graph, it is necessary to use “package-lists”:
$ 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
Note that the “package-list” will only contain associated to the “remote” origin the packages that were downloaded. If they were previously in the cache, then, they will not be listed under the “remote” origin and the metadata will not be downloaded. If you want to collect the dependencies metadata, recall to download it when the package is installed from the server. There are other possibilities, like a custom command that can automatically collect and download dependencies metadata from the servers.
Removing metadata¶
At the moment it is not possible to remove metadata from the server side using Conan, as the metadata are “additive”, it is possible to add new data, but not to remove it (otherwise it would not be possible to add new metadata without downloading first all the previous metadata, and that can be quite inefficient and more error prone, specially sensitive to possible race conditions).
The recommendation to remove metatada from the server side would be to use the tools, web interface or APIs that the server might provide.
Note
Best practices
Metadata shouldn’t be necessary for using packages. It should be possible to consume recipes and packages without downloading their metadata. If metadata is mandatory for a package to be used, then it is not metadata and should be packaged as headers and binaries.
Metadata reading access should not be a frequent operation, or something that developers have to do. Metadata read is intended for excepcional cases, when some build logs need to be recovered for compliance, or some test executables might be needed for debugging or re-checking a crash.
Conan does not do any compression or decompression of the metadata files. If there are a lot of metadata files, consider zipping them yourself, otherwise the upload of those many files can take a lot of time. If you need to handle different types of metadata (logs, tests, reports), zipping the files under each category might be better to be able to filter with the
--metadata=xxx
argument.
test_package as metadata¶
This is an illustrative example of usage of metadata, storing the full test_package
folder as
metadata to later recover it and execute it. Note that this is not necessarily intended for production.
Let’s start with a hook that automatically stores as recipe metadata the test_package
folder
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"))
Note that this hook doesn’t take into account that test_package
can be dirty with tons of temporary build
objects (it should be cleaned before being added to metadata), and it doesn’t check that test_package
might
not exist at all and crash.
When a package is created and uploaded, it will upload to the server the recipe metadata containing the test_package
:
$ conan create ...
$ conan upload "*" -c -r=default # uploads metadata
...
pkg/0.1: Recipe metadata: 1 files
Let’s remove the local copy, and assume that the package is installed, but the metadata is not:
$ conan remove "*" -c # lets remove the local packages
$ conan install --requires=pkg/0.1 -r=default # this will not download metadata
If at this stage the installed package is failing in our application, we could recover the test_package
,
downloading it, and copying it to our current folder:
$ 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()
See also
TODO: Examples how to collect the metadata of a complete dependency graph with some custom deployer or command
This is an experimental feature. We are looking forward to hearing your feedback, use cases and needs, to keep improving this feature. Please report it in Github issues