Managing dependencies with CMake

After struggling all day yesterday with dependencies in CMake, this is what I got so far:

When looking to manage dependencies using CMake abandon any hope for quick tricks and rapid fixes. You must understand the system deeply or be prepared to suffer several headaches. That’s why I advise you to take half a day (or in my case couple days) to properly understand how CMake find dependencies.

There are two kinds of dependencies in CMake, internal dependencies and external dependencies.  Internal dependencies are the libraries that you create within your project. Those are rather easy to handle, because you know beforehand where they are going to be.

TODO: input a description of how handling internal dependencies.

External dependencies is when everything gets tricky. CMake uses the find_package command to aid in finding the external dependencies. I want to remark “aid in finding ”, as opposed to “automatically finding”, since the former is only true under certain conditions, not always applying to your situation.

Supposedly, finding external dependencies is as simple and magically as calling the find_package like this:

find_package (YourExternalDependencyName)

This confused me a lot the first time I saw it, since I wonder how CMake could know, just from a simple name, where to locate all the include and library files of the dependency.  Obviously, there is no way to find all dependencies in all the version/distribution of all operating system in the world.  However, it can make good guesses based on popular operating systems and common practices of developers. Kitware implemented these good guesses in find_package and they actually may save you a lot of work if you use a common library in a popular S.O. (let’s say Boost in Ubuntu).

Unfortunately, if the command fails to properly guess where are your dependencies (this happens a lot in Windows), all you have is an abstraction that you don’t understand.  Therefore, is very important to learn howfind_package works in order to properly manage dependencies in CMake.

Before you we continue, let us clarify the difference between library and dependency from the CMake point of view, which is vital to understand find_package. A library is one compiled file (.dll , .lib, .so,  .a, etc.) On the other hand, a dependency is a set of header and library files. A dependency will always contain at least two files: one header and one library.

This is very important, since it will allow us to understand the difference between find_library, find_pathand find_packages commands in CMake, which is confusing starting with CMake.

 

One library is one compiled file. A dependency, on the other hand, is a set of headers and library files.

 

What did find_package does?

Let us suppose that we have a dependency which is called DepX. To tell CMake to look for DepX we write in our CMakeList.txt something like this:

find_package( DepX )

In order to find the dependency called DepX , find_package will search for a file named FindDepX.cmake, which have the CMake commands to describe the location of your dependency. CMake came with many built-in packages files which are stored in the CMAKE_MODULE_PATH. In Ubuntu Linux this path points to /usr/share/cmake/Modules/. CMake also allows you to create your own package file, which you must store in <your project root>/cmake/Modules/.

You may also find package files in Internet made by CMake users for their own dependencies. However, in my personal experience, this is not so good, because they will create the Find<dep>.cmake file according to their needs. There is no one-size-fits-all and not even the ones bundled with CMake will work always.  Sometimes you need to look at the Find file in order to understand how you can solve the issue when CMake can’t find your dependencies.

Each Find<Dependency>.cmake will defines a set of variables that you can use to link to your targets. After the execution of this file, you will always have a newly defined set of CMake variables telling you if CMake could find the libraries and headers of the dependency and where CMake found them:

  • <Dependency >_FOUND
  • <Dependency >_INCLUDE_DIRS or < Dependency >_INCLUDES
  • <Dependency >_LIBRARIES or < Dependency >_LIBRARIES or < Dependency >_LIBS
  • <Dependency >_DEFINITIONS

The package file may also define other optional variables like the version of the library found.

In order to resolve the dependency we must use mainly two methods: find_path andfind_library. Let’s assume we are searching for a dependency called DepX. A very simple code in CMake for resolving dependencies would look something like this:

find_path(DEPX_INCLUDE_DIR depe_includes/depx.h depe_includes/depxversion.h

HINTS ${THIRD_PARTY_INCLUDEDIR} ${FOUR_PARTY_INCLUDE_DIRS})

find_library(DEPX_LIBRARY NAMES xml2 DEPX

HINTS ${PC_LIBXML_LIBDIR} ${PC_LIBXML_LIBRARY_DIRS} )

set(DEPX_LIBRARIES ${DEPX_LIBRARY} )

set(DEPX_INCLUDE_DIRS ${DEPX_INCLUDE_DIR} )

include(FindPackageHandleStandardArgs)

# handle the QUIETLY and REQUIRED arguments and set DEPX_FOUND to TRUE

# if all listed variables are TRUE

find_package_handle_standard_args(DEPX DEFAULT_MSG

DEPX_LIBRARY DEPX_INCLUDE_DIR)

There is no magic or weird stuff in the resolving process, which is basically a path finding process.

The first parameter to both methods is the name of the returning variable. In our example,find_path is going to do its voodoo and return the result into DEPX_INCLUDE_DIR. The second parameter, is the name of the path or library we are looking for. After that, all the parameters are to configure the way the search is going to be made. In our example we want find_path to resolve the path to the includes of my DepX dependency, knowing that, wherever that is, there are two files (depx.h and depxversion.h) inside a folder named depe_includes. Since is too ineffective to search in the whole computer, we HINT find_path that it should look in all directories stored in ${THIRD_PARTY_INCLUDEDIR} ${FOUR_PARTY_INCLUDE_DIRS} wich in our example are variables defined before or by the user.

Both find_path and find_library uses CMake environment variables into account when looking for paths. Is worth taking a look at them here:http://www.cmake.org/Wiki/CMake_Useful_Variables#Environment_Variables

This approach allows to pass custom locations to the CMake file for your dependencies.It also seems to be the prefered aproach to do it.

For example, for cross compilation purposes, CMake will always prepend the CMAKE_FIND_ROOT_PATH to all path-strings.

The name of the dependency

The name of the the variables is important and extremely recommendable to keep it consistent through all you CMake files. Most of the times, find_path returning variable is expected to be in the form  <YourDepName>_INCLUDE_DIR and find_library to be in the form of <YourDepName>_LIBRARY.

If you want to wrap up the previous code to call it from other CMakeList.txt using find_package, the first part of the name must be consistent with the one passed to find_package command and with the name of the Find<YourDepName>.cmake file. In our case since we are resolving DepX we should name package file FindDEPX.cmake like and call find_package like this:

find_package (DEPX)

The names are case sensitive and must match exactly with the dependency name in the a file.

Tell CMake you find what you where searching for

The call to find_package_handle_standard_args handles the repetitive task of setting the<YourDepName>_FOUND to true or false depending on whether the find methods where able to actually found the path.

This is only a commodity, if it suits you best, you can always check it manually. In our example with the DepX dependency would be like this:

if ( DEPX_INCLUDE_DIR AND DEPX_LIBRARY )

set ( DEPX_FOUND 1 )

else()

set ( DEPX_FOUND 0 )

endif()

 

Leave a Reply

Your email address will not be published. Required fields are marked *