25

The CMake's ExternalProject allows to define how to an external project is going to be downloaded, configured, built and installed. All whose steps are going to be performed at the build time.

I would like to perform the configuration step of an external project during configuration of the main project. When the external project configuration is done the description of imported targets are available so the external project can be loaded with find_package() function.

Is it possible to build some targets at configuration time?

RAM
  • 2,257
  • 2
  • 19
  • 41
Paweł Bylica
  • 3,780
  • 1
  • 31
  • 44
  • `ExternalProject` is just a sequence of steps. So you may use one `ExternalProject_Add()` call for being built at main project's *configuration stage* (e.g., as described in [that question](http://stackoverflow.com/questions/37553280/how-to-build-cmake-externalproject-while-configurating-main-one)), and second `ExternalProject_Add()`call for being build at main's project *build stage*. But I don't understand how *configured* project may be used via `find_package()`: normally this requires project to be *installed*. – Tsyvarev Jun 15 '16 at 21:19
  • 1
    The moment you run cmake configuration on the external project it outputs ExternalProjectConfig.cmake file that can be used by `find_package(... CONFIG)`. – Paweł Bylica Jun 15 '16 at 21:25
  • 2
    Hm, yes, using `*Config.cmake` from *build* tree may work in some cases. But only *installed* variant of this file is garanteed to work: the file may include other `.cmake` files, which layout in *build* and *install* trees may differ. – Tsyvarev Jun 15 '16 at 21:34

4 Answers4

13

ExternalProject is just a sequence of steps to perform. So you may use two instances of it:

  1. ExternalProject_Add() call to be built at main project's configuration stage. E.g., as described in that question:

other_project/CMakeLists.txt:

project(other_project)
include(ExternalProject)

ExternalProject_Add(<project_name> <options...>
    BUILD_COMMAND "" # Disable build step.
    INSTALL_COMMAND "" # Disable install step too.
)

CMakeLists.txt:

# The first external project will be built at *configure stage*
execute_process(
    COMMAND ${CMAKE_COMMAND} --build . ${CMAKE_SOURCE_DIR}/other_project
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/other_project
)
  1. ExternalProject_Add() call to be built at main project's build stage.

CMakeLists.txt:

# The second external project will be built at *build stage*
ExternalProject_Add(<project_name> <options...>
    CONFIGURE_COMMAND "" # Disable configure step. But other steps will be generated.
)

By using same <options> for both ExternalProject_Add() calls we achieve "preemption" of both external projects created: build and follow steps of the second project will use result of configure step of the first one.

EntangledLoops
  • 1,951
  • 1
  • 23
  • 36
Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • 3
    Just note that ``execute_process`` will not create its ``WORKING_DIRECTORY`` if it does not exist yet so before executing process you likely want to call ``file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/other_project)``. Not sure if it was case with older versions of CMake... – blami Sep 23 '18 at 10:13
  • Thanks for the valuable comment. I remember that I have checked that approach (for verify whether "preemption" of ExternalProject's works), but don't remember how I adjusted build directory for it. I will check that. – Tsyvarev Sep 23 '18 at 10:29
7

If you don't want to build the project at configure-time, but just want to download it, use FetchContent. FetchContent_Declare uses many of the same arguments as ExternalProject_Add, except it doesn't allow building the project.

The documentation has a great example on how to use this:

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()

This requires CMake 3.11 or later. For prior versions, you can download the FetchContent.cmake module from the CMake repository along with the FetchContent directory, ensuring you comply with the BSD 3-Clause license.

† Building at configure-time has some serious drawbacks. For example, users of your library can't control the build process unless you set it up very carefully. A package manager is a better solution

Justin
  • 24,288
  • 12
  • 92
  • 142
4

The Hunter C++ package manager does what I asked for. Hunter is based on CMake ExternalProject, but comes with a set of predefined projects. Also, it builds the dependencies during configuration and they can be accessed by find_package(... CONFIG). Very nice stuff!

Paweł Bylica
  • 3,780
  • 1
  • 31
  • 44
2

Since CMake 3.14 and up wards, this how you use FetchContent

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_MakeAvailable(googletest)

Then to build your test test/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(${PROJECT_NAME}_test)

# Tests for mu_project
file(GLOB_RECURSE TEST_SOURCES 
    ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp
    ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)

add_executable(${PROJECT_NAME}_test ${TEST_SOURCES})
target_link_libraries(${PROJECT_NAME}_test gtest gmock_main)

# Register the tests
add_test(NAME ${PROJECT_NAME}_test
         COMMAND ${PROJECT_NAME}_test)
enable_testing()

You're now good to go

Eddy Ekofo
  • 541
  • 6
  • 13
  • Is there a way to tell cmake not to import all targets from the external module? I' trying to use this method to include gRPC in my project but it fills my targets list with a bunch of garbage targets – Paul Belanger Jun 03 '20 at 20:27
  • Sorry but that, I just don't know. If I find something I will get back to you. – Eddy Ekofo Jun 04 '20 at 15:32