Customizing the binary compatibility¶
The default binary compatibility requires an almost exact match of settings and options, and a versioned match of dependencies versions, as explained in the previous section about dependencies.
In summary, the required binaries package_id
when installing dependencies should match by default:
All the settings in the
package_id
exceptcompiler.cppstd
should match exactly the ones provided in the input profiles, including the compiler version. Socompiler.version=9
is different thancompiler.version=9.1
.The default behavior will assume binary compatibility among different
compiler.cppstd
values for C++ packages, being able to fall back to other values rather than the one specified in the input profiles, if thecppstd
required by the input profile does not exist. This is controlled by thecompatibility.py
plugin, that can be customized by users.All the options in the
package_id
should match exactly the ones provided in the input profiles.The versions of the dependencies should match:
In case of “embedding dependencies”, should match the exact version, including the recipe-revision and the dependency
package_id
. Thepackage_revision
is never included as it is assumed to be ill-formed to have more than onepackage_revision
for the samepackage_id
.In case of “non-embedding dependencies”, the versions of the dependencies should match down to the
minor
version, being thepatch
,recipe_revision
and further information not taken into account.In case of “tool dependencies”, the versions of the dependencies do not affect at all by default to the consumer
package_id
.
These rules can be customized and changed using different approaches, depending on the needs, as explained in following sections
Customizing binary compatibility of settings and options¶
Information erasure in package_id() method¶
Recipes can erase information from their package_id
using their package_id()
method. For example, a package containing only an executable can decide to remove the information from settings.compiler
and settings.build_type
from their package_id
, assuming that an executable built with any compiler will be valid, and that it is not necessary to store different binaries built with different compilers:
def package_id(self):
del self.info.settings.compiler
del self.info.settings.build_type
It is also possible to assign a value for a given setting, for example if we want to have one single binary for all gcc versions included in the [>=5 <7>] range, we could do:
def package_id(self):
if self.info.settings.compiler == "gcc":
version = Version(self.info.settings.compiler.version)
if version >= "5.0" and version < "7.0":
self.info.settings.compiler.version = "gcc5-6"
Note
Best practice
Note that information erasure in package_id()
means that 1 single package_id
will represent a whole range of different settings, but the information of what exact setting was used to create the binary will be lost, and only 1 binary can be created for that range. Re-creating the package with different settings in the range, will create a new binary that overwrites the previous one (with a new package-revision).
If we want to be able to create, store and manage different binaries for different input settings, information erasure can’t be used, and using the below compatibility
approaches is recommended.
The compatibility() method¶
Recipes can define their binary compatibility rules, using their compatibility()
method.
For example, if we want that binaries
built with gcc versions 4.8, 4.7 and 4.6 to be considered compatible with the ones compiled
with 4.9 we could declare a compatibility()
method like this:
def compatibility(self):
if self.settings.compiler == "gcc" and self.settings.compiler.version == "4.9":
return [{"settings": [("compiler.version", v)]}
for v in ("4.8", "4.7", "4.6")]
Read more about the compatibility()
method in the compatibility() method reference
The compatibility.py
plugin¶
Compatibility can be defined globally via the compatibility.py
plugin, in the same way that the compatibility()
method does for one recipe, but for all packages globally.
Check the binary compatibility compatibility.py extension.
Customizing binary compatibility of dependencies versions¶
Global default package_id modes¶
The core.package_id:default_xxx
configurations defined in global.conf
can be used to globally change the defaults of how dependencies affect their consumers
core.package_id:default_build_mode: By default, 'None'
core.package_id:default_embed_mode: By default, 'full_mode'
core.package_id:default_non_embed_mode: By default, 'minor_mode'
core.package_id:default_python_mode: By default, 'minor_mode'
core.package_id:default_unknown_mode: By default, 'semver_mode'
Note
Best practices
It is strongly recommended that the core.package_id:default_xxx
should be global, consistent and immutable accross organizations. It can be confusing to change these defaults for different projects or teams, because it will result in missing binaries.
It should also be consistent and shared with the consumers of generated packages if those packages are
shared outside the organization, in that case sharing the global.conf
file via conan config install
could be recommended.
Consider using the Conan defaults, they should be a good balance between efficiency and safety, ensuring exact re-building for embed cases, and good control via versions for non-embed cases.
Custom package_id modes for recipe consumers¶
Recipes can define their default effect to their consumers, via some package_id_xxxx_mode
attributes.
The package_id_embed_mode, package_id_non_embed_mode, package_id_unknown_mode
are class attributes that can be defined in recipes to define the effect they have on their consumers package_id
. Can be declared as:
from conan import ConanFile
class Pkg(ConanFile):
...
package_id_embed_mode = "full_mode"
package_id_non_embed_mode = "patch_mode"
package_id_unknown_mode = "minor_mode"
Read more in package_id_{embed,non_embed,python,unknown}_mode
Custom package_id from recipe dependencies¶
Recipes can define how their dependencies affect their package_id
, using the package_id_mode
trait:
from conan import ConanFile
class Pkg(ConanFile):
def requirements(self):
self.requires("mydep/1.0", package_id_mode="patch_mode")
Using package_id_mode
trait does not differentiate between the “embed” and “non-embed” cases, it is up to the user to define the correct value. It is likely that this approach should only be used for very special cases that do not have variability of shared/static libraries controlled via options
.
Note that the requirements()
method is evaluated while the graph is being expanded, the dependencies do not exist yet (haven’t been computed), so it is not possible to know the dependencies options.
In this case it might be preferred to use the package_id()
method.
The package_id()
method can define how the dependencies affect the current package with:
from conan import ConanFile
class Pkg(ConanFile):
def package_id(self):
self.info.requires["mydep"].major_mode()
The different modes that can be used are defined in package_id_{embed,non_embed,python,unknown}_mode