Installing an ExternalProject with CMake
Asked Answered
M

1

9

I have the following code in one of my CMakeLists.txt:

FIND_PACKAGE(sphinxbase)
if (${SPHINXBASE_FOUND})
    INCLUDE_DIRECTORIES(${SPHINXBASE_INCLUDE_DIR}/sphinxbase/)
else ()
    ExternalProject_Add(
        sphinxbase
        GIT_REPOSITORY      "https://github.com/cmusphinx/sphinxbase.git"
        GIT_TAG             "e34b1c632392276101ed16e8a05862e43f038a7c"
        SOURCE_DIR          ${CMAKE_CURRENT_SOURCE_DIR}/lib/sphinxbase
        CONFIGURE_COMMAND   ${CMAKE_CURRENT_SOURCE_DIR}/lib/sphinxbase/autogen.sh
        BUILD_COMMAND       ${MAKE}
        UPDATE_COMMAND      ""
        INSTALL_COMMAND     ""
        BUILD_IN_SOURCE     ON
        LOG_DOWNLOAD        ON
        LOG_UPDATE          ON
        LOG_CONFIGURE       ON
        LOG_BUILD           ON
        LOG_TEST            ON
        LOG_INSTALL         ON
    )
    ExternalProject_Get_Property(sphinxbase SOURCE_DIR)
    ExternalProject_Get_Property(sphinxbase BINARY_DIR)
    SET(SPHINXBASE_SOURCE_DIR ${SOURCE_DIR})
    SET(SPHINXBASE_BINARY_DIR ${BINARY_DIR})
    SET(SPHINXBASE_LIBRARIES ${SPHINXBASE_BINARY_DIR}/src/libsphinxbase/.libs/libsphinxbase.a)
    INCLUDE_DIRECTORIES(${SPHINXBASE_SOURCE_DIR}/include)
endif ()
SET(DEPENDENCIES ${DEPENDENCIES} sphinxbase)
SET(LIBS ${LIBS} ${SPHINXBASE_LIBRARIES})

In addition to the TARGET that I'm trying to install, how would I go about installing this ExternalProject? Right now I'm trying to do it like this:

install(TARGETS ${LIBS}
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib
        OPTIONAL
        )

install(TARGETS ${PROJECT_NAME}
        RUNTIME DESTINATION bin
        )

But I'm getting the following error thrown at me:

CMake Error at CMakeLists.txt:197 (install):
  install TARGETS given target
  "/Users/syb0rg/Dropbox/Development/Khronos/Khronos/lib/sphinxbase/src/libsphinxbase/.libs/libsphinxbase.a"
  which does not exist in this directory.

Any suggestions?

Meaty answered 11/3, 2016 at 7:1 Comment(0)
A
7

Command flow install(TARGETS) installs targets, which are created with add_executable or add_library commands. For install concrete files, you need to use command flow install(FILES).

Instead of installing selected files from subproject's build directory, you may install subproject as is. For that you can use

make DESTDIR=<...> install

command as INSTALL option of ExeternalProject_Add. This command will install whole subproject into directory given as DESTDIR parameter for make (more information about this parameter can be found here).

Then you may use install(DIRECTORY) command for install all subproject's files at once:

# It is better to use binary directory for download or build 3d-party project 
set(sphinxbase_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib/sphinxbase)
# This will be used as DESTDIR on subproject's `make install`.
set(sphinxbase_DESTDIR ${CMAKE_CURRENT_BINARY_DIR}/lib/sphinxbase_install)

ExternalProject_Add(
    sphinxbase
    GIT_REPOSITORY      "https://github.com/cmusphinx/sphinxbase.git"
    GIT_TAG             "e34b1c632392276101ed16e8a05862e43f038a7c"
    SOURCE_DIR          ${sphinxbase_SOURCE_DIR}
    # Specify installation prefix for configure.sh (autogen.sh).
    CONFIGURE_COMMAND   ./autogen.sh --prefix=${CMAKE_INSTALL_PREFIX}
    BUILD_COMMAND       ${MAKE}
    UPDATE_COMMAND      ""
    # Fake installation: copy installed files into DESTDIR.
    INSTALL_COMMAND     make DESTDIR=${sphinxbase_DESTDIR} install
    ...
)
# Actually install subproject.
install(
    DIRECTORY ${sphinxbase_DESTDIR}/
    DESTINATION "/"
    USE_SOURCE_PERMISSIONS # Remain permissions (rwx) for installed files
)

Note, that root directory is used as DESTINATION. This is because files under sphinxbase_DESTDIR directory already located in accordance to CMAKE_INSTALL_PREFIX, which is passed as --prefix option to configure.sh (autogen.sh in the given case).

If you know, that subprojects installs its files only under installation prefix (given by --prefix), you can make main project installation more packaging-friendly:

install(
    DIRECTORY ${sphinxbase_DESTDIR}/${CMAKE_INSTALL_PREFIX}/
    DESTINATION "."
    USE_SOURCE_PERMISSIONS
)

Because now relative path is used for DESTINATION option, the project will correctly support packaging. For example, make DESTDIR=<...> install will work for main project too.


Note, that even if ExternalProject_Add is used for build CMake subproject, targets created in this subroject are not seen by the main project.

Aubrette answered 11/3, 2016 at 9:2 Comment(7)
What's the difference between the CMAKE_INSTALL_PREFIX + DESTDIR dance, versus just using ./autogen.sh --prefix=${sphinxbase_DESTDIR}/${CMAKE_INSTALL_PREFIX} and not overriding INSTALL_COMMAND? For this use case, it's inconvenient that DESTDIR is relative to the configure prefix.Favata
@HunterZ: In autotools, --prefix is not just a directory under which all needed files will be copied on make install: 1. In case of system-wide prefix, like /, /usr/local, /opt, some files may be installed outside of the prefix. 2. It could be absolute path stored in the one project's file for refer to the other project file. Because of (2), it is better to not use "fake" --prefix - it would confuse the project. As opposite DESTDIR is never uses inside the project's files, and there is a garantee that all project's files will be installed under given directory.Aubrette
I think this answer might only work on Linux; I can see why you gave a Linux answer because the OP is clearly working with .a files, just wanted to mention this in case somebody else is looking for a Windows answer. I get the feeling that there's a more cross platform option that might work with both, but unfortunately I'm not sure what it is.Archon
@jrh: More precisely, this is GNU Make-specific approach, which supports DESTDIR=... semantic. Or even autotools-specific ones. Possibly some CMake generators which works on Windows has similar semantic too. But "Linux way", "Windows way" or even "cross-platform way" ... I am very unsure they exist. "I can see why you gave a Linux answer because the OP is clearly working with .a files" - No, this is not because of .a but because of autogen.sh is used for configure the project. It is part of autotools which as far as I know works only on Linux.Aubrette
This topic seems to get rather poor coverage online unfortunately; cmake's official docs imply that there's a way to install() the targets of an external project but there's no "project" overload of this (I'm hoping they didn't mean "install every individual built file from the external project"; other pages mention that using -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>, and omitting INSTALL_COMMAND will work (no dice). I'd guess it's possible somehow.Archon
a very useful answer! don't forget to add install(DIRECTORY ... USE_SOURCE_PERMISSIONS ...) to keep the executable permissions for binaries in that directory (if you build binaries)Loesceke
@xealits: Thanks for the comment, I have added USE_SOURCE_PERMISSIONS to install(DIRECTORY) calls.Aubrette

© 2022 - 2024 — McMap. All rights reserved.