I have a problem with my CMake build system. There are CMakeLists.txt
files defining runtimes or libraries or using ExternalProjects_Add()
to download and build external code. Because of dependencies, those projects have to find each other. Now I want to have a CMakeLists.txt
at the top level that builds all those at once. In order to find a project, is must be installed. But finding projects is already done at configuration time in CMake.
repository
├─project
│ ├─game (Depends on engine, uses EngineConfig.cmake once installed)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ ├─src
│ │ └─textures
│ ├─engine (Depends on boost, uses built-in FindBoost.cmake)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ └─src
│ ├─boost (Not the source code, just an ExternalProject_Add call)
│ : └─CMakeLists.txt
│
├─build
│ ├─game
│ ├─engine
│ ├─boost (Source will be downloaded and built here)
│ : ├─download
│ ├─source
│ :
│
├─install
│ ├─game
│ │ ├─bin
│ │ └─textures
│ ├─engine
│ │ ├─include
│ │ │ └─engine
│ │ │ ├─EngineConfig.cmake (Needed to find the library)
│ │ │ :
│ │ │
│ │ └─lib
│ ├─boost (Layout is up to the external library)
│ : └─ ...
│
└─CMakeLists.txt (Calls add_subdirectory for all inside the project folder)
Run a CMake process for every project: Using execute_process(${CMAKE_COMMAND} ...)
, I can configure and build each project after another at configure time. However, this means I always have to run CMake after editing the code and cannot compile from within the IDE I generated project files for.
Linking to CMake targets: Running a CMake process for all external libraries is okay since I don't work on them. My own libraries could be used by calling target_link_libraries()
with their target names. However, linking isn't enough. My libraries include directories of external libraries. Those must be made available to the using project, as well.
How can I use libraries within my CMake project that need to be installed first?
Thanks @Tsyvarev and @tamas.kenez you for the two good answers. I ended up using the super-build pattern. The top-level project doesn't do much at configure time. At build time, it runs external CMake processes to configure, build and install the projects.
Usually, this is implemented using
ExternalProject_Add()
instead ofadd_subdirectory()
to add the projects. I foundadd_custom_command()
to work better since it doesn't do additional tasks in the background like creating stamp files and so on.Here are the two helper functions. It took me quite some time to figure out the right way to pass list parameters to other CMake processes without them being interpreted and passes as multiple parameters.
The only downside is that every project needs to have an install target for this. So you need to add a dummy install command like
install(CODE "")
to projects that have no install command otherwise, e.g. those who just callExternalProject_Add
.When export library from
engine
project you need to specify its include directories. Code below is a simplification of example provided at http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages. Paths are adjasted for use installation prefixinstall/engine
for build and installengine
component.engine/CMakeLists.txt:
engine/cmake/EngineConfig.cmake:
This provides interface of the exported target. So when it will be linked by executable, the executable gets proper
INCLUDE_DIRECTORIES
property:CMakeLists.txt:
game/CMakeLists.txt:
You can classify your projects into three groups:
You need to configure, build and install the project in groups #1 and #2 before configuring the super-project:
CMakeLists.txt
, for example, from a shell-scriptCMakeLists.txt
, usingexecute_process(${CMAKE_COMMAND} ...)
. You can do it conditionally using the result of an appropriatefind_package(... QUIET)
command.You need to decide if projects in group #3, like
engine
will be used solely in projects that uses them as subdirectories or you intend to use them as standalone libraries, built in their own build trees.Also, you mentioned that: "My libraries include directories of external libraries". Let's cover all such possible libraries the
engine
can be dependent on:LIB1
andLIB2
are private and public external dependencies ofengine
and their config-modules export old-schoolLIB1_*
andLIB2_*
variablesLIB3
andLIB4
are private and public external dependencies ofengine
and their config-modules export theLIB3
andLIB4
imported librariesBy public and private dependencies I mean whether the particular library is used or not used on the interface of
engine
.Now, if
engine
is to be used only as a subdirectory then the relevant section ofengine/CMakeLists.txt
is:in
repository/CMakeLists.txt
:in
game/CMakeLists.txt
:The include dirs of both of the engine and of its public dependencies will be correctly forwarded to
game
.If
engine
will also be built in its own build tree (in another project) you need to add the exporting code toengine/CMakeLists.txt
and maybe a custom config-module that callsfind_package
(orfind_dependency
) for its dependencies. See How to use CMake to find and link to a library using install-export and find_package? for details. One issue not discussed in that answer is finding the dependencies of a library in the library's config module:The referenced SO answer simply installs the
<lib>-targets.cmake
script, generated by theinstall(EXPORT ...)
command, as the config-module:This solution is fine when
engine
has no further dependencies. If it does, they need to be found at the beginning of the config module, which should be written manually.engine/engine-config.cmake:
and in
engine/CMakeLists.txt
:Note: The
CMakeFindDependencyMacro
has been introduced in CMake 3.0. With an older CMake you can usefind_package
instead offind_dependency
(handling of QUIET and REQUIRED options will not be forwarded to the dependency).