As you may know, Conan 2.0-alpha is already released. 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 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 Conan generators like
CMakeDeps
and
PkgConfigDeps,
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.
⚠️ Note: Please, remember that the newset_property
and the current attributes model are completely independent since Conan 1.43. Settingset_property
in recipes will not affect current CMake 1.X generators (cmake
,cmake_multi
,cmake_find_package
andcmake_find_package_multi
) at all.
If you set the property cmake_target_name
in the recipe, the Conan minimum
required version should be updated to 1.43.
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:
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")
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:
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:
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 theFind<pkg>.cmake
and<pkg>-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 thecmake_file_name
value.
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 forcmake_find_package
andcmake_find_package_multi
. For these cases, besidescmake_target_name
you should also set thecmake_module_target_name
andcmake_find_mode
properties. Let's see an example:
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
generator with value both
.
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 thecmake_find_mode
property toboth
. For example, for the libraries which find modules files are included in the CMake distribution.
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:
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
generator with value both
. That will make CMakeDeps generator create both module and
config files for consumers (by default it generates just config files).
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")
...
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
uses this workaround to get a target with name
KTX::ktx
.
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.
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 arbitrary
target name. Let's see the example of the tensorflow-lite
recipe,
that uses this workaround to define a
tensorflow::tensorflowlite
target.
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).
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")
...
Previously we saw that some recipes use a build module with an alias to set an arbitrary target name.
But sometimes the declared ".build_modules" come from the original package that declares useful CMake functions, variables
etc. We need to use the property cmake_build_modules
to declare a list of cmake files instead of using cpp_info.build_modules
:
class PyBind11Conan(ConanFile):
name = "pybind11"
...
def package_info(self):
...
for generator in ["cmake_find_package", "cmake_find_package_multi"]:
self.cpp_info.components["main"].build_modules[generator].append(os.path.join("lib", "cmake", "pybind11", "pybind11Common.cmake"))
...
To translate this information to the new model we declare the cmake_build_modules
property in the root cpp_info
object:
class PyBind11Conan(ConanFile):
name = "pybind11"
...
def package_info(self):
...
self.cpp_info.set_property("cmake_build_modules", [os.path.join("lib", "cmake", "pybind11", "pybind11Common.cmake")])
...
The case of PkgConfigDeps
is much more straight forward than the CMakeDeps
case.
This is because the current
pkg_config
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:
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:
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.