Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 341
nils Avatar asked May 04 '15 10:05

nils


1 Answers

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)
like image 128
teddy Avatar answered Oct 13 '22 23:10

teddy