It seems that CMake ExternalProject
always assumes the root directory
of the external project to be the source directory. But what if that is not
the case?
Consider the following example:
The external project uses this directory layout:
libfoo.git <--- ExternalProject assumes this as source dir.
├── ...
└── libfoo <--- However, the actual source directory is this!
├── CMakeLists.txt
└── ...
In the depending project libfoo
is configured like this:
ExternalProject_Add( libfoo
PREFIX "${CMAKE_CURRENT_BINARY_DIR}/EP_libfoo"
GIT_REPOSITORY "<link to remote which hosts libfoo.git>"
GIT_TAG "<some hash>"
)
The build then fails with the following error message:
$ cmake -H/path/to/source-dir -B/path/to/build-dir
...
$ cmake --build /path/to/build-dir/ --target all
...
CMake Error: The source directory "/path/to/build-dir/EP_libfoo/src/libfoo" does not appear to contain CMakeLists.txt.
...
$
So, as pointed out in the above directory layout, CMake thinks that the root of the external project is
/path/to/build-dir/EP_libfoo/src/libfoo
when, in fact, it is
/path/to/build-dir/EP_libfoo/src/libfoo/libfoo
My attempts to solve this problem:
Unfortunately, changing the argument SOURCE_DIR
of ExternalProject
did
not work, because the value of this variable is used as the location to
which the git repository of libfoo
is cloned into. This results in a recursive dependency hell which cannot be broken.
Changing the directory layout of libfoo
to comply with ExternalProject
.
Obviously, this would work but it might not work for other (read-only)
third party libraries.
Abusing the update/patch step of ExternalProject
, e.g. by specifying
set( EP_LIBFOO_DIR "${CMAKE_CURRENT_BINARY_DIR}/EP_libfoo" )
ExternalProject_Add( libfoo
PREFIX "${EP_LIBFOO_DIR}"
GIT_REPOSITORY "<link to remote which hosts libfoo.git>"
GIT_TAG "<some hash>"
# Copy the content of `<...>/libfoo/libfoo` into `<...>/libfoo`.
# Note to self: using symlinks instead copying is too platform-specific.
PATCH_COMMAND ${CMAKE_COMMAND} -E copy_directory "${EP_LIBFOO_DIR}/src/libfoo/libfoo" "${EP_LIBFOO_DIR}/src/libfoo"
)
This works but it's hackish and very prone to fail with other external projects.
Building on the solution to another problem: add a temporary
CMakeLists.txt
in the location where CMake assumes it. This temporary file
then includes the actual CMakeLists.txt
:
set( EP_LIBFOO_DIR "${CMAKE_CURRENT_BINARY_DIR}/EP_libfoo" )
set( GENERATED_DIR "${CMAKE_BINARY_DIR}/generated" )
file( MAKE_DIRECTORY ${GENERATED_DIR} )
file( WRITE ${GENERATED_DIR}/CMakeLists.txt
"cmake_minimum_required( VERSION 3.0 )\n"
"add_subdirectory( libfoo )\n"
)
ExternalProject_Add( libfoo
PREFIX "${EP_LIBFOO_DIR}"
GIT_REPOSITORY "<link to remote which hosts libfoo.git>"
GIT_TAG "<some hash>"
# Copy the
UPDATE_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/generated/CMakeLists.txt ${EP_LIBFOO_DIR}/src/libfoo
)
This works as well and feels better than the previous solution.
However, does a more elegant exist to do the same?
I've been struggling with the same issue in a project that I am working on and this is the solution I have been able to come up with. It results in not using ExternalProject for the git handling but results in the same behavior as far as I have been able to tell.
CMakeLists.txt
include(ExternalProject)
set(libfoo_prefix ${CMAKE_HOME_DIRECTORY}/libfoo)
# during generation remove any previous repo and clone.
file(REMOVE_RECURSE ${libfoo_prefix})
execute_process(
COMMAND git clone <link to remote which hosts libfoo.git>
WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY})
# Add the external project.
ExternalProject_Add(libfoo
PREFIX ${libfoo_prefix}
SOURCE_DIR ${libfoo_prefix}/libfoo)
# As part of the pre-build step update the git repo.
add_custom_command(
TARGET libfoo
PRE_BUILD
COMMAND ${CMAKE_COMMAND} -P GitPull.cmake)
GitPull.cmake
execute_process(
COMMAND git pull origin master
WORKING_DIRECTORY ${CMAKE_SOURCE_DIRECTORY}/libfoo)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With