Correct way to use third-party libraries in cmake project

Solution 1:

Answering this question requires to cover few aspects, you will find below two sections:

  • config-file package
  • ExternalProject CMake module

config-file package

If you are looking into integrating libraries that are not built within the scope of your project, the first step is to make sure the libraries all provide a config-file package.

A config-file package usually include files like FooConfig.cmake, FooConfigVersion.cmake and FooTargets.cmake.

Generally speaking, if the library Foo is already using CMake and already provide a config-file package, configuring your project with -DFoo_DIR:PATH=/path/to/build-or-install-dir/ allow you to call find_package(Foo REQUIRED) from your own project. This will import CMake targets that you can link against your own libraries or executables.

Now if the library Foois not already using CMake, there are options:

  • Case 1:

    • (a) library Foo is already using CMake
    • (b) but do NOT provide a config-file package
    • action: I suggest to improve their build system
  • Case 2:

    • (1) library Foo is not using CMake
    • (2) and maintainer of Foo are willing to transition to CMake (or at least have the CMakeLists.txt along side their current build system)
    • action: I suggest to improve their build system
  • Case 3:

    • (1) library Foo is not using CMake
    • (2) and maintainer of Foo do not want to transition to CMake
    • (3) but maintainer are willing to generate config-file package from their current build system
    • action: I suggest to help them. This is for example what was done for Qt5, it now provides a config-file package.
  • Case 4:

    • (1) library Foo is not using CMake

    • (2) and maintainer of Foo do not want (or are not ready) to transition to CMake.

    • (3) and the current build system is not working well, or the library is difficult to build with a wider range of compiler, or does not support cross-compilation

    • action: create a project (ideally on GitHub) named foo-cmake-buildsystem that will allow to build the library by either

      • configuring the project with the path to an existing source tree
      • having the project downloading the source for you
      • this is for example done for CPython. There is a project named python-cmake-buildsystem available on GitHub
  • Case 5:

    • (1) for any reason the maintainer of Foo do not want to transition, or maintaining an alternative build system is not possible, or library is already available on the system
    • action: You could create a FindFoo.cmake that would create imported targets.
      • such file could be specific to your project or could be contributed to CMake directly
      • this is for example the case of FindOpenSSL.cmake, FindGit.cmake, ...

To learn more about config-file package, see https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html

ExternalProject CMake module

If the library Foo is:

  • (1) not available on the system:
    • or can't be installed using package manager
    • or working with the community maintaining packages (debian, conda-forge, chocolatey, ...) to have such package is not possible
  • (2) or need to be compiled specially for your project

Then, the ExternalProject CMake module will allow you to download, configure, build ... these projects from within your own project.

There are few approaches to make this happen.

Here is one that has been working well: You could setup a 2-level build system that we call: SuperBuild.

To support the SuperBuild approach, your CMakeLists.txt could have the following structure:

project(AwesomeProject)

[...]

option(Awesome_ENABLE_EXTRA "Enable more awesome stuff" OFF)

option(AwesomeProject_SUPERBUILD "Build ${PROJECT_NAME} and the projects it depends on." ON)

[...]

if(AwesomeProject_SUPERBUILD)
  include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake")
  return()
endif()

find_package(Foo REQUIRED)

add_library(AwesomeLib ....)
target_library_libraries(AwesomeLib PUBLIC Foo)

[...]

Then, in the file SuperBuild.cmake you would roughly have these two calls:

ExternalProject_Add(Foo
  GIT_REPOSITORY "git://github.com/Foo/Foo"
  GIT_TAG "123456"
  SOURCE_DIR ${CMAKE_BINARY_DIR}/Foo
  BINARY_DIR ${CMAKE_BINARY_DIR}/Foo-build
  CMAKE_CACHE_ARGS
    -DFOO_ENABLE_BAR:BOOL=1
  INSTALL_COMMAND ""
  )


ExternalProject_Add(AwesomeProject
  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
  BINARY_DIR ${CMAKE_BINARY_DIR}/AwesomeProject-build
  DOWNLOAD_COMMAND ""
  UPDATE_COMMAND ""
  CMAKE_CACHE_ARGS
    -Foo_DIR:PATH=${CMAKE_BINARY_DIR}/Foo-build
    -DAwesome_ENABLE_EXTRA:BOOL=${Awesome_ENABLE_EXTRA}
  INSTALL_COMMAND ""
  )

This means that you usual build tree will now be found in the subdirectory AwesomeProject-build.

Note that Foo-build and AwesomeProject-build are two independent build tree, the link between them is the config-file package discussed above.

This is made possible by configuring AwesomeProject sub project with -Foo_DIR:PATH=${CMAKE_BINARY_DIR}/Foo-build and the calling find_package(Foo REQUIRED).

If you use tools like VisualStudio you can open the solution file found in any of these sub-directory.

To learn more about external project: https://cmake.org/cmake/help/latest/module/ExternalProject.html

conclusion

There are many more details, but I hope this will allow you to get a better understanding of what is possible.