Use cmake modules inside a tool_requires transparently

When we want to reuse some .cmake scripts that are inside another Conan package there are several possible different scenarios, like if the .cmake scripts are inside a regular requires or a tool_requires.

Also, it is possible to want 2 different approaches:

  • The consumer of the scripts can do a explicit include(MyScript) in their CMakeLists.txt. This approach is nicely explicit and simpler to setup, just define self.cpp_info.builddirs in the recipe, and consumers with CMakeToolchain will automatically be able to do the include() and use the functionality. See the example here

  • The consumer wants to have the dependency cmake modules automatically loaded when the find_package() is executed. This current example implements this case.

Let’s say that we have a package, intended to be used as a tool_require, with the following recipe:

myfunctions/conanfile.py

  1. import os
  2. from conan import ConanFile
  3. from conan.tools.files import copy
  4. class Conan(ConanFile):
  5. name = "myfunctions"
  6. version = "1.0"
  7. exports_sources = ["*.cmake"]
  8. def package(self):
  9. copy(self, "*.cmake", self.source_folder, self.package_folder)
  10. def package_info(self):
  11. self.cpp_info.set_property("cmake_build_modules", ["myfunction.cmake"])

And a myfunction.cmake file in:

myfunctions/myfunction.cmake

  1. function(myfunction)
  2. message("Hello myfunction!!!!")
  3. endfunction()

We can do a cd myfunctions && conan create . which will create the myfunctions/1.0 package containing the cmake script.

Then, a consumer package will look like:

consumer/conanfile.py

  1. from conan import ConanFile
  2. from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain
  3. class Conan(ConanFile):
  4. settings = "os", "compiler", "build_type", "arch"
  5. tool_requires = "myfunctions/1.0"
  6. def generate(self):
  7. tc = CMakeToolchain(self)
  8. tc.generate()
  9. deps = CMakeDeps(self)
  10. # By default 'myfunctions-config.cmake' is not created for tool_requires
  11. # we need to explicitly activate it
  12. deps.build_context_activated = ["myfunctions"]
  13. # and we need to tell to automatically load 'myfunctions' modules
  14. deps.build_context_build_modules = ["myfunctions"]
  15. deps.generate()
  16. def build(self):
  17. cmake = CMake(self)
  18. cmake.configure()

And a CMakeLists.txt like:

consumer/CMakeLists.txt

  1. cmake_minimum_required(VERSION 3.0)
  2. project(test)
  3. find_package(myfunctions CONFIG REQUIRED)
  4. myfunction()

Then, the consumer will be able to automatically call the myfunction() from the dependency module:

  1. $ conan build .
  2. ...
  3. Hello myfunction!!!!

If for some reason the consumer wants to force the usage from the tool_requires() as a CMake module, the consumer could do deps.set_property("myfunctions", "cmake_find_mode", "module", build_context=True), and then find_package(myfunctions MODULE REQUIRED) will work.