cmakego: Simpler access to external libraries in CMake
CMake has greatly simplified the process of building libraries and applications across different platforms, and it is adopted by several large projects. Libraries can be easily found through find_package but the result of the lookup has to be manually added to the target through different commands. This post discusses how include the external libraries in an easier way:
add_executable(glapp1 glapp1.cpp ...)
target_link_libraries(glapp1 p::glew p::glfw p::boost ...)
The usage of cmakego is similar to the one of packages or meta-packages such as catkin:
find_package(cmakego REQUIRED COMPONENTS glew glfw)
For boost the sub-components can be specified as:
set(CMAKEGO_BOOST program_options)
find_package(cmakego REQUIRED COMPONENTS boost)
Details after the break.
For a general package management of sources there is catkin for the ROS world and hunter for the others.
One of the elements for success of CMake is the possibility of finding external packages and obtaining all the elements for compiling and linking our project against them. This functionality is obtained through the find_package command that invokes a per-package scripts that looks for the include directory, the compilation options and the static and import libraries. Many find_package scripts are provided with CMake (140 in version 3.0.0) and many others can be found online for many libraries.
The structure of such scripts is similar across the libraries, except for the most complex ones such as boost and qt. In general they rely on find_library and find_path commands that looks for specific libraries or include files in different possible locations. What is important is that the find_package scripts provide an output with a common structure: for a package X the following global variables are defined:
ROS users use catkin for obtaining packages known to ROS but in that case all the resulting includes and libraries are collected inside two variables catkin specific ${catkin_LIBRARIES}. Here instead we expose the dependencies as targets.
Additional properties that are useful are the following: INTERFACE_COMPILE_DEFINITIONS specifies the additional definitions, INTERFACE_COMPILE_OPTIONS, specifies the extra options required. The man page for all the options is cmake-properties.
add_executable(glapp1 glapp1.cpp ...)
target_link_libraries(glapp1 p::glew p::glfw p::boost ...)
The usage of cmakego is similar to the one of packages or meta-packages such as catkin:
find_package(cmakego REQUIRED COMPONENTS glew glfw)
For boost the sub-components can be specified as:
set(CMAKEGO_BOOST program_options)
find_package(cmakego REQUIRED COMPONENTS boost)
Details after the break.
For a general package management of sources there is catkin for the ROS world and hunter for the others.
One of the elements for success of CMake is the possibility of finding external packages and obtaining all the elements for compiling and linking our project against them. This functionality is obtained through the find_package command that invokes a per-package scripts that looks for the include directory, the compilation options and the static and import libraries. Many find_package scripts are provided with CMake (140 in version 3.0.0) and many others can be found online for many libraries.
The structure of such scripts is similar across the libraries, except for the most complex ones such as boost and qt. In general they rely on find_library and find_path commands that looks for specific libraries or include files in different possible locations. What is important is that the find_package scripts provide an output with a common structure: for a package X the following global variables are defined:
- X_FOUND: used for checking if the library has been found
- X_INCLUDE_DIR: the location(s) for the include files
- X_LIBRARY: the list of the libraries exported
- X_DEFINITIONS (rarely): the extra definitions
- CMAKE_X_CXX_FLAGS (sometimes): the extra flags
The point is that these options have to be added manually to our target by means of the specific commands such as target_link_library, (target_)include_directories and (target_/add_)compile_options.
Anyway CMake offers the possibility of creating target libraries that are externally defined and that can be referenced in target_link_libraries as user defined targets. When a target library is referenced in target_link_libraries then the library is included not only to the link phase but also to the compiler options and include paths. This approach is not generally adopted by the various find scripts except exceptions such as findqt4.
In the following we adopt the convention to define an imported target library p::X for a library X using the colon scope notation. The following example is used for including the GLEW library:
find_package(GLEW REQUIRED)
if(GLEW_FOUND)
add_library(p::glew UNKNOWN IMPORTED)
set_property(TARGET p::glew PROPERTY IMPORTED_LOCATION ${GLEW_LIBRARIES})
set_property(TARGET p::glew PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIR}")
endif()
The above states that when the GLEW library has been found a new imported library p::glew is being added. Then the set_property commands set the additional information to the library, that is first the location of the library itself (IMPORTED_LOCATION) and then the location of the includes (INTERFACE_INCLUDE_DIRECTORIES).
Given the above it is possible add includes and link options in a simple way:
add_executable(glapp1 glapp1.cpp)
target_link_libraries(glapp1 p:glew ...)
Qt5 from cmake 3.0 example supports this approach as follows:
ROS users use catkin for obtaining packages known to ROS but in that case all the resulting includes and libraries are collected inside two variables catkin specific ${catkin_LIBRARIES}. Here instead we expose the dependencies as targets.
Additional properties that are useful are the following: INTERFACE_COMPILE_DEFINITIONS specifies the additional definitions, INTERFACE_COMPILE_OPTIONS, specifies the extra options required. The man page for all the options is cmake-properties.
There are also some cases in which the the X_LIBRARIES is not made by a single library, but is a list of libraries, or, under OSX, a mix of libraries and frameworks. Unfortunately this does not work well with the IMPORTED_LOCATION that requires a single library. The simplest way to overcome this problem is to use the CMake 3.0 new feature of INTERFACE libraries that are libraries made only of include files: this type of library does not require the imported location property, and the INTERFACE_LINK_LIBRARIES allows to specifies all the required libraries.
The following is the example for adding OpenGL, for which, under OSX, the OPENGL_LIBRARIES variable contains only frameworks:
if(OPENGL_FOUND)
add_library(p::gl INTERFACE IMPORTED)
set_property(TARGET p::gl PROPERTY INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARIES})
set_property(TARGET p::gl PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENGL_INCLUDE_DIR})
endif()
Finally the imported and interface libraries can be connected together, allowing to make p::glew depend on OpenGL. It is sufficient to add the following line to the definition of GLEW as follows.
set_property(TARGET p::glew PROPERTY INTERFACE_LINK_LIBRARIES p::gl)
As a closing remark the proposed approach comes handy when there are several target applications in your project, and you want to state the dependencies of the different project in a clean and compact way without referencing the specific, and sometime different, variables of the libraries.
The recipe for using this approach is the following:
For a practical use of this technique a script can be obtained from github. The scripts support several libraries from the domain of OpenGL, computer vision and media.- Invoke the required find_package of the libraries
- Include an adaptor script that makes the found libraries as target libraries with scope p::
- Add a library with the scope p:: to each target using target_link_libraries, or globally using link_libraries
Comments