|
| 1 | +Customizing the binary compatibility |
| 2 | +==================================== |
| 3 | + |
| 4 | +The default binary compatibility requires an almost exact match of settings and options, and a versioned match |
| 5 | +of dependencies versions, as explained in the :ref:`previous section about dependencies<reference_binary_model_dependencies>`. |
| 6 | + |
| 7 | +In summary, the required binaries ``package_id`` when installing dependencies should match by default: |
| 8 | + |
| 9 | +- All the settings in the ``package_id`` except ``compiler.cppstd`` should match exactly the ones provided in the input profiles, including the compiler version. So ``compiler.version=9`` is different than ``compiler.version=9.1``. |
| 10 | +- 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 the ``cppstd`` required by the input profile does not exist. This is controlled by the ``compatibility.py`` plugin, that can be customized by users. |
| 11 | +- All the options in the ``package_id`` should match exactly the ones provided in the input profiles. |
| 12 | +- The versions of the dependencies should match: |
| 13 | + |
| 14 | + - In case of "embedding dependencies", should match the exact version, including the recipe-revision and the dependency ``package_id``. The ``package_revision`` is never included as it is assumed to be ill-formed to have more than one ``package_revision`` for the same ``package_id``. |
| 15 | + - In case of "non-embedding dependencies", the versions of the dependencies should match down to the ``minor`` version, being the ``patch``, ``recipe_revision`` and further information not taken into account. |
| 16 | + - In case of "tool dependencies", the versions of the dependencies do not affect at all by default to the consumer ``package_id``. |
| 17 | + |
| 18 | + |
| 19 | +These rules can be customized and changed using different approaches, depending on the needs, as explained in following sections |
| 20 | + |
| 21 | +Customizing binary compatibility of settings and options |
| 22 | +-------------------------------------------------------- |
| 23 | + |
| 24 | +Information erasure in package_id() method |
| 25 | +++++++++++++++++++++++++++++++++++++++++++ |
| 26 | + |
| 27 | +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: |
| 28 | + |
| 29 | +.. code-block:: python |
| 30 | +
|
| 31 | + def package_id(self): |
| 32 | + del self.info.settings.compiler |
| 33 | + del self.info.settings.build_type |
| 34 | +
|
| 35 | +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: |
| 36 | + |
| 37 | +.. code-block:: python |
| 38 | +
|
| 39 | + def package_id(self): |
| 40 | + if self.info.settings.compiler == "gcc": |
| 41 | + version = Version(self.info.settings.compiler.version) |
| 42 | + if version >= "5.0" and version < "7.0": |
| 43 | + self.info.settings.compiler.version = "gcc5-6" |
| 44 | +
|
| 45 | +
|
| 46 | +.. note:: |
| 47 | + |
| 48 | + **Best practice** |
| 49 | + |
| 50 | + 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). |
| 51 | + |
| 52 | + 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. |
| 53 | + |
| 54 | +Read more about ``package_id()`` in: |
| 55 | + |
| 56 | +- :ref:`creating_packages_configure_options_settings` |
| 57 | +- :ref:`package_id() method reference<reference_conanfile_methods_package_id>` |
| 58 | + |
| 59 | + |
| 60 | +The compatibility() method |
| 61 | +++++++++++++++++++++++++++ |
| 62 | + |
| 63 | +Recipes can define their binary compatibility rules, using their ``compatibility()`` method. |
| 64 | +For example, if we want that binaries |
| 65 | +built with gcc versions 4.8, 4.7 and 4.6 to be considered compatible with the ones compiled |
| 66 | +with 4.9 we could declare a ``compatibility()`` method like this: |
| 67 | + |
| 68 | +.. code-block:: python |
| 69 | +
|
| 70 | + def compatibility(self): |
| 71 | + if self.settings.compiler == "gcc" and self.settings.compiler.version == "4.9": |
| 72 | + return [{"settings": [("compiler.version", v)]} |
| 73 | + for v in ("4.8", "4.7", "4.6")] |
| 74 | + |
| 75 | +
|
| 76 | +Read more about the ``compatibility()`` method in :ref:`the compatibility() method reference<reference_conanfile_methods_compatibility>` |
| 77 | + |
| 78 | + |
| 79 | +The ``compatibility.py`` plugin |
| 80 | ++++++++++++++++++++++++++++++++ |
| 81 | + |
| 82 | +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. |
| 83 | + |
| 84 | +Check the binary compatibility :ref:`compatibility.py extension <reference_extensions_binary_compatibility>`. |
| 85 | + |
| 86 | + |
| 87 | + |
| 88 | +Customizing binary compatibility of dependencies versions |
| 89 | +--------------------------------------------------------- |
| 90 | + |
| 91 | +Global default package_id modes |
| 92 | ++++++++++++++++++++++++++++++++ |
| 93 | + |
| 94 | +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 |
| 95 | + |
| 96 | +.. code-block:: ini |
| 97 | +
|
| 98 | + core.package_id:default_build_mode: By default, 'None' |
| 99 | + core.package_id:default_embed_mode: By default, 'full_mode' |
| 100 | + core.package_id:default_non_embed_mode: By default, 'minor_mode' |
| 101 | + core.package_id:default_python_mode: By default, 'minor_mode' |
| 102 | + core.package_id:default_unknown_mode: By default, 'semver_mode' |
| 103 | +
|
| 104 | +.. note:: |
| 105 | + |
| 106 | + **Best practices** |
| 107 | + |
| 108 | + 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. |
| 109 | + |
| 110 | + It should also be consistent and shared with the consumers of generated packages if those packages are |
| 111 | + shared outside the organization, in that case sharing the ``global.conf`` file via ``conan config install`` |
| 112 | + could be recommended. |
| 113 | + |
| 114 | + 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. |
| 115 | + |
| 116 | + |
| 117 | +Custom package_id modes for recipe consumers |
| 118 | +++++++++++++++++++++++++++++++++++++++++++++ |
| 119 | + |
| 120 | +Recipes can define their default effect to their consumers, via some ``package_id_xxxx_mode`` attributes. |
| 121 | + |
| 122 | +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: |
| 123 | + |
| 124 | +.. code-block:: python |
| 125 | +
|
| 126 | + from conan import ConanFile |
| 127 | +
|
| 128 | + class Pkg(ConanFile): |
| 129 | + ... |
| 130 | + package_id_embed_mode = "full_mode" |
| 131 | + package_id_non_embed_mode = "patch_mode" |
| 132 | + package_id_unknown_mode = "minor_mode" |
| 133 | +
|
| 134 | +
|
| 135 | +Read more in :ref:`reference_conanfile_attributes_package_id_modes` |
| 136 | + |
| 137 | +Custom package_id from recipe dependencies |
| 138 | +++++++++++++++++++++++++++++++++++++++++++ |
| 139 | + |
| 140 | +Recipes can define how their dependencies affect their ``package_id``, using the ``package_id_mode`` trait: |
| 141 | + |
| 142 | +.. code-block:: python |
| 143 | +
|
| 144 | + from conan import ConanFile |
| 145 | +
|
| 146 | + class Pkg(ConanFile): |
| 147 | + def requirements(self): |
| 148 | + self.requires("mydep/1.0", package_id_mode="patch_mode") |
| 149 | +
|
| 150 | +
|
| 151 | +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``. |
| 152 | + |
| 153 | +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. |
| 154 | +In this case it might be preferred to use the ``package_id()`` method. |
| 155 | + |
| 156 | +The ``package_id()`` method can define how the dependencies affect the current package with: |
| 157 | + |
| 158 | +.. code-block:: python |
| 159 | +
|
| 160 | + from conan import ConanFile |
| 161 | +
|
| 162 | + class Pkg(ConanFile): |
| 163 | + def package_id(self): |
| 164 | + self.info.requires["mydep"].major_mode() |
| 165 | +
|
| 166 | +The different modes that can be used are defined in :ref:`reference_conanfile_attributes_package_id_modes` |
0 commit comments