Define components for Conan packages that provide multiple libraries

At the section of the tutorial about the package_info() method, we learned how to define information in a package for consumers, such as library names or include and library folders. In the tutorial, we created a package with only one library that consumers linked to. However, in some cases, libraries provide their functionalities separated into different components. These components can be consumed independently, and in some cases, they may require other components from the same library or others. For example, consider a library like OpenSSL that provides libcrypto and libssl, where libssl depends on libcrypto.

Conan provides a way to abstract this information using the components attribute of the CppInfo object to define the information for each separate component of a Conan package. Consumers can also select specific components to link against but not the rest of the package.

Let’s take a game-engine library as an example, which provides several components such as algorithms, ai, rendering, and network. Both ai and rendering depend on the algorithms component.

digraph components { node [fillcolor=”lightskyblue”, style=filled, shape=box] algorithms -> ai; algorithms -> rendering; ai; algorithms; network; }

components of the game-engine package

Please, first clone the sources to recreate this project. You can find them in the examples2 repository in GitHub:

  1. $ git clone https://github.com/conan-io/examples2.git
  2. $ cd examples2/examples/conanfile/package_info/components

You can check the contents of the project:

  1. .
  2. ├── CMakeLists.txt
  3. ├── conanfile.py
  4. ├── include
  5. ├── ai.h
  6. ├── algorithms.h
  7. ├── network.h
  8. └── rendering.h
  9. ├── src
  10. ├── ai.cpp
  11. ├── algorithms.cpp
  12. ├── network.cpp
  13. └── rendering.cpp
  14. └── test_package
  15. ├── CMakeLists.txt
  16. ├── CMakeUserPresets.json
  17. ├── conanfile.py
  18. └── src
  19. └── example.cpp

As you can see, there are sources for each of the components and a CMakeLists.txt file to build them. We also have a test_package that we are going to use to test the consumption of the separate components.

First, let’s have a look at package_info() method in the conanfile.py and how we declared the information for each component that we want to provide to the consumers of the game-engine package:

  1. ...
  2. def package_info(self):
  3. self.cpp_info.components["algorithms"].libs = ["algorithms"]
  4. self.cpp_info.components["algorithms"].set_property("cmake_target_name", "algorithms")
  5. self.cpp_info.components["network"].libs = ["network"]
  6. self.cpp_info.components["network"].set_property("cmake_target_name", "network")
  7. self.cpp_info.components["ai"].libs = ["ai"]
  8. self.cpp_info.components["ai"].requires = ["algorithms"]
  9. self.cpp_info.components["ai"].set_property("cmake_target_name", "ai")
  10. self.cpp_info.components["rendering"].libs = ["rendering"]
  11. self.cpp_info.components["rendering"].requires = ["algorithms"]
  12. self.cpp_info.components["rendering"].set_property("cmake_target_name", "rendering")

There are a couple of relevant things:

  • We declare the libraries generated by each of the components by setting information in the cpp_info.components attribute. You can set the same information for each of the components as you would for the self.cpp_info object. The cpp_info for components has some defaults defined, just like it does for self.cpp_info. For example, the cpp_info.components object provides the .includedirs and .libdirs properties to define those locations, but Conan sets their value as ["lib"] and ["include"] by default, so it’s not necessary to add them in this case.

  • We are also declaring the components’ dependencies using the .requires attribute. With this attribute, you can declare requirements at the component level, not only for components in the same recipe but also for components from other packages that are declared as requires of the Conan package.

  • We are changing the default target names for the components using the properties model. By default, Conan sets a target name for components like <package_name::component_name>, but for this tutorial we will set the component target names just with the component names omitting the ::.

You can have a look at the consumer part by checking the test_package folder. First the conanfile.py:

  1. ...
  2. def generate(self):
  3. deps = CMakeDeps(self)
  4. deps.check_components_exist = True
  5. deps.generate()

You can see that we are setting the check_components_exist property for CMakeDeps. This is not needed, just to show how you can do if you want your consumers to fail if the component does not exist. So, the CMakeLists.txt could look like this:

  1. cmake_minimum_required(VERSION 3.15)
  2. project(PackageTest CXX)
  3. find_package(game-engine REQUIRED COMPONENTS algorithms network ai rendering)
  4. add_executable(example src/example.cpp)
  5. target_link_libraries(example algorithms
  6. network
  7. ai
  8. rendering)

And the find_package() call would fail if any of the components targets do not exist.

Let’s run the example:

  1. $ conan create .
  2. ...
  3. game-engine/1.0: RUN: cmake --build "/Users/barbarian/.conan2/p/t/game-d6e361d329116/b/build/Release" -- -j16
  4. [ 12%] Building CXX object CMakeFiles/algorithms.dir/src/algorithms.cpp.o
  5. [ 25%] Building CXX object CMakeFiles/network.dir/src/network.cpp.o
  6. [ 37%] Linking CXX static library libnetwork.a
  7. [ 50%] Linking CXX static library libalgorithms.a
  8. [ 50%] Built target network
  9. [ 50%] Built target algorithms
  10. [ 62%] Building CXX object CMakeFiles/ai.dir/src/ai.cpp.o
  11. [ 75%] Building CXX object CMakeFiles/rendering.dir/src/rendering.cpp.o
  12. [ 87%] Linking CXX static library libai.a
  13. [100%] Linking CXX static library librendering.a
  14. [100%] Built target ai
  15. [100%] Built target rendering
  16. ...
  17. ======== Launching test_package ========
  18. ...
  19. -- Conan: Component target declared 'algorithms'
  20. -- Conan: Component target declared 'network'
  21. -- Conan: Component target declared 'ai'
  22. -- Conan: Component target declared 'rendering'
  23. ...
  24. [ 50%] Building CXX object CMakeFiles/example.dir/src/example.cpp.o
  25. [100%] Linking CXX executable example
  26. [100%] Built target example
  27. ======== Testing the package: Executing test ========
  28. game-engine/1.0 (test package): Running test()
  29. game-engine/1.0 (test package): RUN: ./example
  30. I am the algorithms component!
  31. I am the network component!
  32. I am the ai component!
  33. └───> I am the algorithms component!
  34. I am the rendering component!
  35. └───> I am the algorithms component!

You could check that requiring a component that does not exist will raise an error. Add the nonexistent component to the find_package() call:

  1. cmake_minimum_required(VERSION 3.15)
  2. project(PackageTest CXX)
  3. find_package(game-engine REQUIRED COMPONENTS nonexistent algorithms network ai rendering)
  4. add_executable(example src/example.cpp)
  5. target_link_libraries(example algorithms
  6. network
  7. ai
  8. rendering)

And test the package again:

  1. $ conan test test_package game-engine/1.0
  2. ...
  3. Conan: Component 'nonexistent' NOT found in package 'game-engine'
  4. Call Stack (most recent call first):
  5. CMakeLists.txt:4 (find_package)
  6. -- Configuring incomplete, errors occurred!
  7. ...
  8. ERROR: game-engine/1.0 (test package): Error in build() method, line 22
  9. cmake.configure()
  10. ConanException: Error 1 while executing

See also

If you want to use recipes defining components in editable mode, check the example in Using components and editable packages.