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 submitted a merge request to add a SOURCE_SUBDIR
option to ExternalProject_Add
that will solve this use case. Hopefully it will be available in CMake 3.7. (You can also copy ExternalProject*.cmake
locally into your own project to take advantage of the feature immediately.)