From 313cd2534ff347ded515ad41edb172fca1599fa4 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Thu, 30 Dec 2021 13:34:09 +0100 Subject: [PATCH] (#8415) Docs: Add guide to help users updating recipes for the set_property model * wip * wip * wip * wip * wip * wip * wip * minor changes * wip * review * review * add note about cmake_find_mode * add explanation about unwanted target name --- README.md | 1 + docs/README.md | 1 + docs/preparing_recipes_for_conan2.md | 375 +++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 docs/preparing_recipes_for_conan2.md diff --git a/README.md b/README.md index 860956ce2b..e95bd38203 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This is a list of shortcuts to some interesting topics: * :warning: The conan-center **hook errors** reported by CCI Bot can be found in the [docs/error_knowledge_base.md](docs/error_knowledge_base.md). * :hammer_and_wrench: The internal changes related to infrastructure can be checked in [docs/changelog.md](docs/changelog.md). * :world_map: There are various community lead initiatives which are outlined in [docs/community_resources.md](docs/community_resources.md). +* :magic_wand: To start preparing your recipes for **Conan 2.0**, please check [docs/preparing_recipes_for_conan2.md](docs/preparing_recipes_for_conan2.md). ### Reporting Issues diff --git a/docs/README.md b/docs/README.md index 9776109f91..48946883ca 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,3 +15,4 @@ + [FAQs](faqs.md) + [Community Resources](community_resources.md) + [Review Guidelines](reviewing.md) + + [Preparing recipes for Conan 2.0](preparing_recipes_for_conan2.md) diff --git a/docs/preparing_recipes_for_conan2.md b/docs/preparing_recipes_for_conan2.md new file mode 100644 index 0000000000..5ac3f1e4d5 --- /dev/null +++ b/docs/preparing_recipes_for_conan2.md @@ -0,0 +1,375 @@ +# Preparing recipes for Conan 2.0 + +As you may know, [Conan 2.0-alpha is already +released](https://github.com/conan-io/conan/releases/tag/2.0.0-alpha1). Conan Center Index +will run Conan 1.X for a long time, but it's time to start preparing recipes to make them +as compatible as possible with Conan 2.0. Then, when the time comes that we update to 2.0 +in Conan Center, the transition will be smooth. + +For more information about Conan 2.0 breaking changes and how to prepare the migration +from Conan 1.X to 2.0, please read the [migration +guide](https://docs.conan.io/en/latest/conan_v2.html) in the Conan documentation. + +This document is a practical guide, offering extended information particular to Conan +Center Index recipes to get them ready to upgrade to Conan 2.0. + +## New cpp_info set_property model + +New Conan generators like +[CMakeDeps](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) +and +[PkgConfigDeps](https://docs.conan.io/en/latest/reference/conanfile/tools/gnu/pkgconfigdeps.html), +don't listen to *cpp_info* ``.names``, ``.filenames`` or ``.build_modules`` attributes. +There is a new way of setting the *cpp_info* information with these +generators using the ``set_property(property_name, value)`` method. + +All the information in the recipes, already set with the current model, should be +translated to the new model. These two models **will live together in recipes** to make +recipes compatible **with both new and current generators** for some time. After a stable +Conan 2.0 version is released, and when the moment arrives that we don't support the +current generators anymore in Conan Center Index, those attributes (``.names``, +``.filenames`` etc.) will disappear from recipes, and only ``set_property`` methods will +stay. + +We will cover some cases of porting all the information set with the current model to the +new one. To read more about the properties available for each generator and how the +properties model work, please check the [Conan documentation](https://docs.conan.io/en/latest/conan_v2.html#editables-don-t-use-external-templates-any-more-new-layout-model). + +> ⚠️ **Note**: Please, remember that the **new** ``set_property`` and the **current** attributes +> model are *completely independent since Conan 1.43*. Setting ``set_property`` in recipes will +> not affect current CMake 1.X generators (``cmake``, ``cmake_multi``, ``cmake_find_package`` and +> ``cmake_find_package_multi``) at all. + +### CMakeDeps + +### Update required_conan_version to ">=1.43.0" + +If you set the property ``cmake_target_name`` in the recipe, the Conan minimum +required version should be updated to 1.43. + +```python + +required_conan_version = ">=1.43.0" + +class GdalConan(ConanFile): + name = "gdal" + ... +``` + +The reason for this change is that in Conan versions previous to 1.43 the +``cmake_target_name`` values were not the final CMake target names. Those values were +completed by Conan, adding namespaces automatically the final target names. After 1.43 +``cmake_target_name`` sets the **complete target name** that is added to the ``.cmake`` +files generated by Conan. Let's see an example: + +```python +class GdalConan(ConanFile): + name = "gdal" + ... + def package_info(self): + # Before 1.43 -> Conan adds GDAL:: namespace -> Creates target with name GDAL::GDAL + # self.cpp_info.set_property("cmake_target_name", "GDAL") + + # After 1.43 -> Conan creates target with name GDAL::GDAL + self.cpp_info.set_property("cmake_target_name", "GDAL::GDAL") +``` + +### Translating .names information to cmake_target_name, cmake_module_target_name and cmake_file_name + +To translate the ``.names`` information to the new model there are some important things to +take into account: + +* The value of the ``.names`` attribute value in recipes is just a part of the final + target name for CMake generators. Conan will complete the rest of the target name by + pre-pending a namespace (with ``::`` separator) to the ``.names`` value. This namespace takes + the same value as the ``.names`` value. Let's see an example: + +```python +class SomePkgConan(ConanFile): + name = "somepkg" + ... + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "some-pkg" + self.cpp_info.names["cmake_find_package_multi"] = "some-pkg" + ... +``` + +This recipe generates the target ``some-pkg::some-pkg`` for both the +``cmake_find_package`` and the ``cmake_find_package_multi`` generators. Also, please +remember that if no ``.names`` attribute were set, Conan would create the target +``somepkg::somepkg`` for both generators by default. + +As we explained before, the ``cmake_target_name`` sets the **complete target name**, so, +to translate this information to the new model we should add the following lines: + +```python +class SomePkgConan(ConanFile): + name = "somepkg" + ... + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "some-pkg" + self.cpp_info.names["cmake_find_package_multi"] = "some-pkg" + # CMakeDeps does NOT add any namespace automatically + self.cpp_info.set_property("cmake_target_name", "some-pkg::some-pkg") + ... +``` + +* If ``.filenames`` attribute is not set, it will fall back on the ``.names`` value to + generate the files. Both the ``Find.cmake`` and ``-config.cmake`` files that + store the dependencies will take the ``.names`` value to create the complete filename. + For the previous example, to translate all the information from the current model to the + new one, we should have added one more line setting the ``cmake_file_name`` value. + +```python +class SomePkgConan(ConanFile): + name = "somepkg" + ... + def package_info(self): + # These generators fallback the filenames for the .cmake files + # in the .names attribute value and generate + self.cpp_info.names["cmake_find_package"] = "some-pkg" # generates module file Findsome-pkg.cmake + self.cpp_info.names["cmake_find_package_multi"] = "some-pkg" # generates config file some-pkg-config.cmake + + self.cpp_info.set_property("cmake_target_name", "some-pkg::some-pkg") + self.cpp_info.set_property("cmake_file_name", "some-pkg") # generates config file some-pkg-config.cmake + ... +``` + +Please note that if we hadn't set the ``cmake_file_name`` property, the ``CMakeDeps`` +generator would have taken the package name to generate the filename for the config file +and the generated filename would have resulted ``somepkg-config.cmake`` instead of +``some-pkg-config.cmake``. + +* Some recipes in Conan Center Index define different ``.names`` values for ``cmake_find_package`` + and ``cmake_find_package_multi``. For these cases, besides ``cmake_target_name`` you should also set + the ``cmake_module_target_name`` and ``cmake_find_mode`` properties. Let's see an example: + +```python +class ExpatConan(ConanFile): + name = "expat" + ... + def package_info(self): + # creates EXPAT::EXPAT target for module files FindEXPAT.cmake + self.cpp_info.names["cmake_find_package"] = "EXPAT" + # creates expat::expat target for config files expat-config.cmake + self.cpp_info.names["cmake_find_package_multi"] = "expat" + ... +``` + +Should translate to the code above. Please note we have added the ``cmake_find_mode`` +property for the +[CMakeDeps](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html#properties) +generator with value ``both``. + +```python +class ExpatConan(ConanFile): + name = "expat" + ... + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "EXPAT" + self.cpp_info.names["cmake_find_package_multi"] = "expat" + + # creates EXPAT::EXPAT target for module files FindEXPAT.cmake + self.cpp_info.set_property("cmake_target_name", "EXPAT::EXPAT") + # creates expat::expat target for config files expat-config.cmake + self.cpp_info.set_property("cmake_module_target_name", "expat::expat") + + # generates module file FindEXPAT.cmake + self.cpp_info.set_property("cmake_file_name", "EXPAT") + # generates config file expat-config.cmake + self.cpp_info.set_property("cmake_module_file_name", "expat") + + # config is the default for CMakeDeps + # we set cmake_find_mode to both to generate both module and config files + self.cpp_info.set_property("cmake_find_mode", "both") + ... +``` + +> ⚠️ **Note**: There are more cases in which you probably want to set the +> ``cmake_find_mode`` property to ``both``. For example, for the libraries which [find +> modules files are included in the CMake +> distribution](https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html#find-modules). + +### Translating .filenames information to cmake_file_name, cmake_module_file_name and cmake_find_mode + +Like in the ``.names`` case, there are some cases in Conan Center Index of recipes that +set different filenames for ``cmake_find_package`` and ``cmake_find_package_multi`` +generators. To translate that information to the ``set_property`` model we have to set the +``cmake_file_name`` and ``cmake_find_mode`` properties. Let's see an example: + +```python +class GlewConan(ConanFile): + name = "glew" + ... + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "GLEW" + self.cpp_info.names["cmake_find_package_multi"] = "GLEW" + self.cpp_info.filenames["cmake_find_package"] = "GLEW" # generates FindGLEW.cmake + self.cpp_info.filenames["cmake_find_package_multi"] = "glew" # generates glew-config.cmake + ... +``` + +In this case we have to set the ``cmake_find_mode`` property for the +[CMakeDeps](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html#properties) +generator with value ``both``. That will make CMakeDeps generator create both module and +config files for consumers (by default it generates just config files). + +```python +class GlewConan(ConanFile): + name = "glew" + ... + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "GLEW" + self.cpp_info.names["cmake_find_package_multi"] = "GLEW" + self.cpp_info.filenames["cmake_find_package"] = "GLEW" + self.cpp_info.filenames["cmake_find_package_multi"] = "glew" + + self.cpp_info.set_property("cmake_target_name", "GLEW::GLEW") + + self.cpp_info.set_property("cmake_file_name", "GLEW") # generates FindGLEW.cmake + self.cpp_info.set_property("cmake_module_file_name", "glew") # generates glew-config.cmake + + # generate both modules and config files + self.cpp_info.set_property("cmake_find_mode", "both") + ... +``` + +### Understanding some workarounds with the .names attribute model in recipes + +The ``.names`` model has some limitations. Because of this, there are some recurrent +workarounds in recipes to achieve things like setting absolute names for targets (without +the ``::`` namespace), or for setting a custom namespace. These workarounds can now be +undone with the ``set_property`` model because it allows setting arbitrary names for CMake +targets. Let's see some examples of these workarounds in recipes: + +* **Use of components to get arbitrary target names in recipes**. Some recipes add a component + whose only role is to get a target name that is not limited by the namespaces added by + the current generators automatically. For example, the [ktx + recipe](https://github.com/conan-io/conan-center-index/blob/5753f954027d9d04b6d05e326f2757ab6b1ac69c/recipes/ktx/all/conanfile.py) + uses this workaround to get a target with name ``KTX::ktx``. + +```python +class KtxConan(ConanFile): + name = "ktx" + ... + + def package_info(self): + # changes namespace to KTX:: + self.cpp_info.names["cmake_find_package"] = "KTX" + ... + # the target inherits the KTX:: namespace and sets the target KTX::ktx + self.cpp_info.components["libktx"].names["cmake_find_package"] = "ktx" + ... + # all the information is set via this "fake root" component + self.cpp_info.components["libktx"].libs = ["ktx"] + self.cpp_info.components["libktx"].defines = [ + "KTX_FEATURE_KTX1", "KTX_FEATURE_KTX2", "KTX_FEATURE_WRITE" + ] + ... +``` + +In these cases, the recommendation is to add the ``cmake_target_name`` property for both +the root and component ``cpp_info``. In the end the target that the consumer will get is +the one created for the component, but it will avoid creating an "unwanted" target if we +add the property just to the component or to the root ``cpp_info``. Please note that when +the migration to Conan 2.0 is done, there will be no need for that component anymore and +it should dissapear. At that moment, the information from the component will be set in the +root ``cpp_info`` and the ``self.cpp_info.components[]`` lines removed. + +```python +class KtxConan(ConanFile): + name = "ktx" + ... + + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "KTX" + ... + # FIXME: Remove the libktx component in Conan 2.0, this is just needed for + # compatibility with current generators + self.cpp_info.components["libktx"].names["cmake_find_package"] = "ktx" + ... + self.cpp_info.components["libktx"].libs = ["ktx"] + self.cpp_info.components["libktx"].defines = [ + "KTX_FEATURE_KTX1", "KTX_FEATURE_KTX2", "KTX_FEATURE_WRITE" + ] + + # Set the root cpp_info target name as KTX::ktx for the root and the component + # In Conan 2.0 the component should be removed + # and those properties should be added to the root cpp_info instead + self.cpp_info.set_property("cmake_target_name", "KTX::ktx") + self.cpp_info.components["libktx"].set_property("cmake_target_name", "KTX::ktx") + ... +``` + +* **Use build modules to create aliases with arbitray names for targets**. Similar to the + previous example, some recipes use a build module with an alias to set an arbitray + target name. Let's see the example of the [tensorflow-lite + recipe](https://github.com/conan-io/conan-center-index/blob/03b24bf128cbf15d23ed988b8d8ca0c0ba87d307/recipes/tensorflow-lite/all/conanfile.py), + that uses this workaround to define a ``tensorflow::tensorflowlite`` target. + +```python +class TensorflowLiteConan(ConanFile): + name = "tensorflow-lite" + ... + + def package_info(self): + # generate the target tensorflowlite::tensorflowlite + self.cpp_info.names["cmake_find_package"] = "tensorflowlite" + self.cpp_info.filenames["cmake_find_package"] = "tensorflowlite" + # this build module defines an alias tensorflow::tensorflowlite to the tensorflowlite::tensorflowlite generated target + self.cpp_info.build_modules["cmake_find_package"] = [os.path.join(self._module_subfolder, self._module_file)] + ... +``` + +To translate this information to the new model, just check which aliases are defined in the +build modules and define those for the new model. In this case it should be enough with +adding the ``tensorflow::tensorflowlite`` target with ``cmake_target_name`` to the root +cpp_info (besides the ``cmake_file_name``property). + +```python +class TensorflowLiteConan(ConanFile): + name = "tensorflow-lite" + ... + + def package_info(self): + self.cpp_info.names["cmake_find_package"] = "tensorflowlite" + self.cpp_info.filenames["cmake_find_package"] = "tensorflowlite" + self.cpp_info.build_modules["cmake_find_package"] = [os.path.join(self._module_subfolder, self._module_file)] + + # set the tensorflowlite::tensorflowlite target name directly for CMakeDeps with no need for aliases + self.cpp_info.set_property("cmake_target_name", "tensorflow::tensorflowlite") + self.cpp_info.set_property("cmake_file_name", "tensorflowlite") + ... +``` + +### PkgConfigDeps + +The case of ``PkgConfigDeps`` is much more straight forward than the ``CMakeDeps`` case. +This is because the current +[pkg_config](https://docs.conan.io/en/latest/reference/generators/pkg_config.html) +generator suports the new ``set_property`` model for most of the properties. Then, the current +model can be translated to the new one without having to leave the old attributes in the +recipes. Let's see an example: + +```python +class AprConan(ConanFile): + name = "apr" + ... + def package_info(self): + self.cpp_info.names["pkg_config"] = "apr-1" + ... +``` + +In this case, you can remove the ``.names`` attribute and just leave: + +```python +class AprConan(ConanFile): + name = "apr" + ... + def package_info(self): + self.cpp_info.set_property("pkg_config_name", "apr-1") + ... +``` + +For more information about properties supported by ``PkgConfigDeps`` generator, please check the [Conan +documentation](https://docs.conan.io/en/latest/reference/conanfile/tools/gnu/pkgconfigdeps.html#properties). \ No newline at end of file