I have a problem with my CMake build system. There are CMakeLists.txt
files defining runtimes or libraries or using ExternalProjects_Add()
to downl
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 of add_subdirectory()
to add the projects. I found add_custom_command()
to work better since it doesn't do additional tasks in the background like creating stamp files and so on.
# add_project( [DEPENDS project...])
function(add_project PROJECT)
cmake_parse_arguments(PARAM "" "" "DEPENDS" ${ARGN})
add_custom_target(${PROJECT} ALL DEPENDS ${PARAM_DEPENDS})
# Paths for this project
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT})
set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT})
set(INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/${PROJECT})
# Configure
escape_list(CMAKE_MODULE_PATH)
escape_list(CMAKE_PREFIX_PATH)
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--no-warn-unused-cli
"-DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH_ESCAPED}"
"-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH_ESCAPED}"
-DCMAKE_BINARY_DIR=${BUILD_DIR}
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
${SOURCE_DIR}
WORKING_DIRECTORY ${BUILD_DIR})
# Build
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--build .
--target install
WORKING_DIRECTORY ${BUILD_DIR})
# Help later find_package() calls
append_global(CMAKE_PREFIX_PATH ${INSTALL_DIR})
endfunction()
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.
# escape_list()
function(escape_list LIST_NAME)
string(REPLACE ";" "\;" ${LIST_NAME}_ESCAPED "${${LIST_NAME}}")
set(${LIST_NAME}_ESCAPED "${${LIST_NAME}_ESCAPED}" PARENT_SCOPE)
endfunction()
# append_global( value...)
function(append_global NAME)
set(COMBINED "${${NAME}}" "${ARGN}")
list(REMOVE_DUPLICATES COMBINED)
set(${NAME} "${COMBINED}" CACHE INTERNAL "" FORCE)
endfunction()
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 call ExternalProject_Add
.