Search code examples
shellcmakeexternal-project

Use shell command as INSTALL_COMMAND to ExternalProject_Add


Is it possible to use any shell command for the INSTALL_COMMAND phase of cmake's ExternalProject_Add? e.g.

ExternalProject_Add(leveldb 
    GIT_REPOSITORY git@github.com:google/leveldb.git 
    GIT_TAG v1.18
    CONFIGURE_COMMAND ./build_detect_platform build.settings .
    BUILD_COMMAND make -j 8 
    BUILD_IN_SOURCE 1        
    INSTALL_COMMAND ""
)

# INSTALL_COMMAND "mkdir -p ${CMAKE_BINARY_DIR}/lib/ \
#     && find . \( -name \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\" -or -name \"*${CMAKE_STATIC_LIBRARY_SUFFIX}\" \
#     -exec cp {} ${CMAKE_BINARY_DIR}/lib/\;\) \
#     && cp -r ./include ${CMAKE_BINARY_DIR}")

I commented out the INSTALL_COMMAND I want to use, find and cp don't seem to be allowed, "No such file or directory, error 127" is the result of using this.


Solution

  • Direct Answer:

    install(CODE "execute_process(...)")
    

    The SCRIPT and CODE signature:

    install([[SCRIPT <file>] [CODE <code>]] [...])

    The SCRIPT form will invoke the given CMake script files during installation. If the script file name is a relative path it will be interpreted with respect to the current source directory. The CODE form will invoke the given CMake code during installation. Code is specified as a single argument inside a double-quoted string. For example, the code

    install(CODE "MESSAGE(\"Sample install message.\")")

    will print a message during installation.

    Food for thought:

    You might be in a hurry to get something done quickly. Do consider this if you have time or you can get back to it and fix it.

    One of the main reasons why people love cmake is because of it's cross platform nature. Any project when properly coded with this aspect in mind will work with either on Linux or Windows or any other operating supported system. The various generators will work happily if the developer had this in mind. My suggestion is to convert the shell commands into cmake in a cross platform way, put them in a separate *.cmake file and execute them using cmake -E option.

    Here is an extract from a working project that I had worked on in the past.

    project_build_steps.cmake

    message(VAR1=${VAR1}) # These variables can be passed from the invocation place
    message(VAR2=${VAR2}) # You can use them in the build steps
    
    if("${BUILD_STEP}" STREQUAL "patch")
        message("BUILD_STEP: patch")
        # Put your patch steps using cmake
    endif()
    if("${BUILD_STEP}" STREQUAL "configure")
        message("BUILD_STEP: configure")
        # Put your configure steps using cmake
    endif()
    
    if("${BUILD_STEP}" STREQUAL "build")
        message("BUILD_STEP: build")
        # Put your build steps using cmake
    endif()
    
    if("${BUILD_STEP}" STREQUAL "install")
        message("BUILD_STEP: install")
        # Put your install steps using cmake
    endif()
    

    CMakeLists.txt (Option 1)

    set(CMAKE_COMMAND /usr/bin/cmake)
    set(PROJECT_BUILD_STEPS_FILE project_build_steps.cmake)
    
    ExternalProject_Add(
        project_name
        SOURCE_DIR /path/to/project/source
        PATCH_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=patch -P ${PROJECT_BUILD_STEPS_FILE}
        CONFIGURE_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=configure -P ${PROJECT_BUILD_STEPS_FILE}
        BUILD_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=build -P ${PROJECT_BUILD_STEPS_FILE}
        INSTALL_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=install -P ${PROJECT_BUILD_STEPS_FILE}
    )
    

    If you do not want to use ExternalProject_Add you can use something like following. This will also give you individual build targets like make project_patch, make project_configure, make project_build, make project_install.

    CMakeLists.txt (Option 2)

    set(CMAKE_COMMAND /usr/bin/cmake)
    set(PROJECT_BUILD_STEPS_FILE project_build_steps.cmake)
    
    set(STAMP_FILE_PROJECT_PATCH .project_patch_done)
    add_custom_command(
        OUTPUT ${STAMP_FILE_PROJECT_PATCH}
        COMMAND ${CMAKE_COMMAND} -DVAR1=value1 -DSTEP=patch -P ${PROJECT_BUILD_STEPS_FILE}
        COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_PATCH}
    )
    
    add_custom_target(project_patch DEPENDS ${STAMP_FILE_PROJECT_PATCH})
    
    set(STAMP_FILE_PROJECT_CONFIGURE .project_configure_done)
    add_custom_command(
        OUTPUT ${STAMP_FILE_PROJECT_CONFIGURE}
        COMMAND ${CMAKE_COMMAND} -DSTEP=configure -P ${PROJECT_BUILD_STEPS_FILE}
        COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_CONFIGURE}
    )
    
    add_custom_target(project_configure DEPENDS project_patch ${STAMP_FILE_PROJECT_CONFIGURE})
    
    
    set(STAMP_FILE_PROJECT_BUILD .project_build_done)
    add_custom_command(
        OUTPUT ${STAMP_FILE_PROJECT_BUILD}
        COMMAND ${CMAKE_COMMAND} -DSTEP=build -P ${PROJECT_BUILD_STEPS_FILE}
        COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_BUILD}
        VERBATIM
    )
    
    add_custom_target(project_build DEPENDS project_configure ${STAMP_FILE_PROJECT_BUILD})
    set(STAMP_FILE_PROJECT_INSTALL .project_install_done)
    add_custom_command(
        OUTPUT ${STAMP_FILE_PROJECT_INSTALL}
        COMMAND ${CMAKE_COMMAND} -DSTEP=install -P ${PROJECT_INSTALL_STEPS_FILE}
        COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_INSTALL}
        VERBATIM
    )
    
    add_custom_target(project_install DEPENDS project_build ${STAMP_FILE_PROJECT_INSTALL})