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 Foo
is 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
- (a) library
-
Case 2:
- (1) library
Foo
is not using CMake - (2) and maintainer of
Foo
are willing to transition to CMake (or at least have theCMakeLists.txt
along side their current build system) - action: I suggest to improve their build system
- (1) library
-
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.
- (1) library
-
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
, ...
- (1) for any reason the maintainer of
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.