3.17.12.2. Good way¶
3.17.12.2.1. Package manager¶
Use system package manager to manage a
and b
dependencies. Install
them to your system and then integrate into CMake using
find_package:
cmake_minimum_required(VERSION 3.2)
project(foo)
option(FOO_WITH_A "Use 'a' module" ON)
add_executable(foo foo.cpp)
if(FOO_WITH_A)
find_package(a CONFIG REQUIRED)
target_link_libraries(foo PUBLIC a::a)
target_compile_definitions(foo PUBLIC FOO_WITH_A)
endif()
find_package(b CONFIG REQUIRED)
target_link_libraries(foo PUBLIC b::b)
enable_testing()
add_test(NAME foo COMMAND foo)
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-find-package -B_builds -DFOO_WITH_A=ON
[examples]> cmake --build _builds
Result of running test with module a
enabled:
[examples]> cd _builds
[examples/_builds]> ctest -V
1: Test command: /.../_builds/foo
1: Test timeout computed to be: 9.99988e+06
1: Running 'a' module
1: x say: nice
1: Running 'b' module
1: x say: nice
1/1 Test #1: foo .............................. Passed 0.00 sec
With module a
disabled:
[examples]> cmake -Hdep-examples/deps-find-package -B_builds -DFOO_WITH_A=OFF
Third parties remains the same of course, only foo
executable rebuild:
[examples]> cmake --build _builds
Scanning dependencies of target foo
[ 50%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX executable foo
[100%] Built target foo
Behavior of module b
is the same:
[examples]> cd _builds
[examples/_builds]> ctest -V
1: Test command: /.../_builds/foo
1: Test timeout computed to be: 9.99988e+06
1: Running 'a' module
1: (Module 'a' disabled)
1: Running 'b' module
1: x say: nice
1/1 Test #1: foo .............................. Passed 0.00 sec
Pros:
Locally shareable. Root directory with libraries can be reused by any number of local project.
Globally shareable. Usually dependencies distributed as binaries shared across many local machines. You don’t have to build all dependencies from sources.
Option friendly. Whatever options you’ve enabled the same set of third parties will be used.
Cons:
Not much customization over third party dependencies
Different system package managers have different set of packages and available versions
Usually only one root directory
3.17.12.2.2. ExternalProject_Add¶
With the help of ExternalProject_Add module you can create so-called “super-build” project with dependencies:
cmake_minimum_required(VERSION 3.2)
project(super-build-example)
include(ExternalProject)
ExternalProject_Add(
x
URL https://github.com/cgold-examples/x/archive/v1.0.tar.gz
URL_HASH SHA1=3c15777fddee4fbf41a57241effc59a821562f65
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
)
ExternalProject_Add(
a
URL https://github.com/cgold-examples/a/archive/v1.0.tar.gz
URL_HASH SHA1=9adb3574369cf3c186b4984eb6778fca5866e347
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
DEPENDS x
)
ExternalProject_Add(
b
URL https://github.com/cgold-examples/b/archive/v1.0.tar.gz
URL_HASH SHA1=7ef127ddc31d6a9b510d9cdc318c68c7709a8204
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
DEPENDS x
)
Using such project you can install all dependencies to some custom root
_ep_install
directory:
[examples]> rm -rf _ep_build
[examples]> cmake -Hdep-examples/deps-super-build -B_ep_build -DCMAKE_INSTALL_PREFIX=_ep_install
[examples]> cmake --build _ep_build
...
-- Downloading...
dst='/.../examples/_ep_build/x-prefix/src/v1.0.tar.gz'
timeout='none'
-- Using src='https://github.com/cgold-examples/x/archive/v1.0.tar.gz'
...
Install the project...
-- Install configuration: ""
-- Installing: /.../_ep_install/lib/libx.a
-- Installing: /.../_ep_install/include/x/x.hpp
-- Installing: /.../_ep_install/lib/cmake/x/xConfig.cmake
-- Installing: /.../_ep_install/lib/cmake/x/xTargets.cmake
-- Installing: /.../_ep_install/lib/cmake/x/xTargets-noconfig.cmake
...
-- Downloading...
dst='/.../examples/_ep_build/a-prefix/src/v1.0.tar.gz'
timeout='none'
-- Using src='https://github.com/cgold-examples/a/archive/v1.0.tar.gz'
...
Install the project...
-- Install configuration: ""
-- Installing: /.../_ep_install/lib/liba.a
-- Installing: /.../_ep_install/include/a/a.hpp
-- Installing: /.../_ep_install/lib/cmake/a/aConfig.cmake
-- Installing: /.../_ep_install/lib/cmake/a/aTargets.cmake
-- Installing: /.../_ep_install/lib/cmake/a/aTargets-noconfig.cmake
...
-- Downloading...
dst='/.../examples/_ep_build/b-prefix/src/v1.0.tar.gz'
timeout='none'
-- Using src='https://github.com/cgold-examples/b/archive/v1.0.tar.gz'
...
Install the project...
-- Install configuration: ""
-- Installing: /.../_ep_install/lib/libb.a
-- Installing: /.../_ep_install/include/b/b.hpp
-- Installing: /.../_ep_install/lib/cmake/b/bConfig.cmake
-- Installing: /.../_ep_install/lib/cmake/b/bTargets.cmake
-- Installing: /.../_ep_install/lib/cmake/b/bTargets-noconfig.cmake
Now you can use same deps-find-package
example and inject _ep_install
root directory with your custom dependencies instead of system dependencies:
[examples]> rm -rf _builds
[examples]> cmake -Hdep-examples/deps-find-package -B_builds -DCMAKE_PREFIX_PATH=/.../examples/_ep_install -DCMAKE_VERBOSE_MAKEFILE=ON
[examples]> cmake --build _builds
/usr/bin/c++ ... -o foo
/.../_ep_install/lib/liba.a
/.../_ep_install/lib/libb.a
/.../_ep_install/lib/libx.a
Pros:
Locally shareable. Root directory with libraries can be reused by any number of local project.
Option friendly. Whatever options you’ve enabled the same set of third parties will be used.
Third party customization. You have full control over your dependencies.
Same set of packages across all platforms.
You can create as many independent root directories as you need.
Cons:
Only build from sources. There is no built-in mechanism for supporting distribution of binaries and meta information. Usually user have to build everything from scratch on new machine.
You have to know everything about your dependencies and carefully manage the build order, including implicit dependencies. For example if project
a
depends onx
optionally you have to do something like this:option(EP_A_WITH_X "Enable A_WITH_X for 'a' package" ON) if(EP_A_WITH_X) # We need 'x' project ExternalProject_Add( x ... ) set(a_dependencies x) endif() ExternalProject_Add( a ... CMAKE_ARGS -DA_WITH_X=${EP_A_WITH_X} DEPENDS ${a_dependencies} )
If dependency tree is complex it can be hard to maintain such super-build.
Writing correct customizable
ExternalProject_Add
rules is not a trivial task.
3.17.12.2.3. Requirements¶
Good dependency management system should satisfy next requirements:
Locally shareable. Root directory with libraries should be easily reused by any number of local project. CMake has find_package facility for injecting code into project and semi-automatic generation of
XXXConfig.cmake
configs for consumer (see Creating packages). Dependency management system should be friendly to this approach.Globally shareable. For the performance purposes there should be an ability to reuse binaries without building them from sources.
Option friendly. Whatever options you’ve enabled the same set of third parties should be used.
Third party customization. You should have an ability to control the way how third party code built: CMake options, CMake build types, compiler flags, etc.