Search code examples
gitbuildcmakebuild-processbuild-automation

CMake ExternalProject: how to specify relative path to the root CMakeLists.txt?


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:

  1. 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.

  2. 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.

  3. 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.

  4. 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?


Solution

  • 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.)