Configure and Install required packages for a project - c++

Coming from other package managers I expect to be able to do the following on a project:
configure the required libraries (in a config file, e.g. CMakelists.txt or nuget's packages.config)
install/upgrade all the required libraries
This seems to not be possible with vcpkg and CMake:
I managed to set up vcpkg and CMakefile (linux):
# in ternimal
vcpkg install <package>
# CMakeLists.txt
find_package(nlohmann_json CONFIG REQUIRED)
target_link_libraries(<app> <package>)
This however requires to manually install all the required libraries (vcpkg install <package>).
How can I list the required packages and have vcpkg or CMake managed them as required (e.g. automatically install them if they are missing)?

I've recently spent some time working with CMake and vcpkg. I dislike the typical approach of managing vcpkg and its installed packages separately from my CMake projects, so I wrote some CMake scripts to handle vcpkg during the CMake build process.
The CMake script to install vcpkg is primarily a call to ExternalProject_Add.
# ./vcpkg_utilities.cmake (part 1)
include(ExternalProject)
function(get_vcpkg)
ExternalProject_Add(vcpkg
GIT_REPOSITORY https://github.com/microsoft/vcpkg.git
CONFIGURE_COMMAND ""
INSTALL_COMMAND ""
UPDATE_COMMAND ""
BUILD_COMMAND "<SOURCE_DIR>/bootstrap-vcpkg.sh"
)
ExternalProject_Get_Property(vcpkg SOURCE_DIR)
set(VCPKG_DIR ${SOURCE_DIR} PARENT_SCOPE)
set(VCPKG_DEPENDENCIES "vcpkg" PARENT_SCOPE)
endfunction()
The script to have vcpkg install a package requires a custom command and target.
# ./vcpkg_utilities.cmake (part 2)
function(vcpkg_install PACKAGE_NAME)
add_custom_command(
OUTPUT ${VCPKG_DIR}/packages/${PACKAGE_NAME}_x64-linux/BUILD_INFO
COMMAND ${VCPKG_DIR}/vcpkg install ${PACKAGE_NAME}:x64-linux
WORKING_DIRECTORY ${VCPKG_DIR}
DEPENDS vcpkg
)
add_custom_target(get${PACKAGE_NAME}
ALL
DEPENDS ${VCPKG_DIR}/packages/${PACKAGE_NAME}_x64-linux/BUILD_INFO
)
list(APPEND VCPKG_DEPENDENCIES "get${PACKAGE_NAME}")
set(VCPKG_DEPENDENCIES ${VCPKG_DEPENDENCIES} PARENT_SCOPE)
endfunction()
Getting the main project linked to vcpkg appropriately (via toolchain file) requires vcpkg to be available when the main project is configured, so the main project is configured as an ExternalProject.
# /CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
project(top LANGUAGES CXX)
include(vcpkg_utilities)
get_vcpkg()
vcpkg_install(nlohmann_json)
include(ExternalProject)
set_property(DIRECTORY PROPERTY EP_BASE main_projectname)
ExternalProject_Add(main_projectname
DEPENDS ${VCPKG_DEPENDENCIES}
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/src
INSTALL_COMMAND ""
CMAKE_ARGS --no-warn-unused-cli;
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
)
Using this structure, CMake will manage the installation of both vcpkg and packages maintained by it.
Note that this is a point solution. I have a more generalized template for this in my Github that covers Windows and Linux, and allows the use of a separately-maintained vcpkg instance.

I've done something similar in my projects using ExternalProject_Add.
Code will be similar to the below pattern (I've replaced my dependency with your)
find_package(nlohmann_json CONFIG) # make package not REQUIRED to avoid error...
if(NOT NLOHMANN_JSON_FOUND) # or whatever variable indicating _FOUND
...
# here ExternalProject_Add from https://github.com/nlohmann/json.git
# using specific tag...
...
ExternalProject_Get_Property(... INSTALL_DIR)
# create imported target here similar to nlohmann_json, as if we found it
...
else()
# We've found lib already installed... will use it...
...
endif()
Another option can be using FetchContent, but I haven't tried it yet.

Related

CMake Linking Header Only Library (Working Example) for which I want explanation

I am learning how to use ExternalProject to download header only libaries and link to my executable.
My workflow is following:
I download header library Eigen using ExtenalProject:
cmake -DGET_LIBS=ON -DBUILD_SHARED_LIBS=ON -DBUILD_MY_PROJECTS=OFF -G
"Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Then I run the same CMakeLists a second time, but this time I disable ExternalProject and compile the executable that links the Eigen:
cmake -DGET_LIBS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_MY_PROJECTS=ON
-G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
Question
Why I need to use both of these commands since in target_include_directories command I specify the same path as in include_directories?
In the code below I need two commands include_directories and target_include_directories.
I thought that it would be enough to use only target_include_directories, but without include_directories it wont work.
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
My full CMakeLists.txt is following:
project(superbuild LANGUAGES CXX)
cmake_minimum_required(VERSION 3.19)
########################################################################
# EIGEN
########################################################################
SET(GET_LIBS "" CACHE STRING "Set option to download dependencies")
if (GET_LIBS)
message(AUTHOR_WARNING ${GET_LIBS})
ExternalProject_Add(eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/eigen #this does nothing...
SOURCE_DIR "${CMAKE_BINARY_DIR}/install/eigen" #install in my local build dir
#SOURCE_DIR ${CMAKE_INSTALL_PREFIX}
BUILD_COMMAND "" #do not build
INSTALL_COMMAND "" #do not install
)
endif ()
###############################################################################
#EXECUTABLES
###############################################################################
if (BUILD_MY_PROJECTS)
add_executable(my_exe main.cpp)
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
endif ()
Tl;dr never use include_directories. Ever. Learn how property visibility works in CMake instead.
include_directories("${CMAKE_BINARY_DIR}/install/eigen/") #add directory of the header-only library without this the next line wort work
target_include_directories(my_exe INTERFACE "${CMAKE_BINARY_DIR}/install/eigen/")# link exe to exectable
In the code you supplied, include_directories is implicitly setting the INCLUDE_DIRECTORIES property on your my_exe target. Then the second line sets the INTERFACE_INCLUDE_DIRECTORIES property.
INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building the target. INTERFACE_INCLUDE_DIRECTORIES is a list of include directories that must be passed to the compiler when building targets that link to this one. This doesn't make much sense for an executable
Slightly better would be to write:
target_include_directories(my_exe PRIVATE "${CMAKE_BINARY_DIR}/install/eigen/")
This will just populate INCLUDE_DIRECTORIES without touching the INTERFACE_ version. If you wanted both you could write PUBLIC instead of PRIVATE or INTERFACE. By definition, PUBLIC is just both of the others.
But this isn't a great dependency management strategy anyway... digging into the source tree guts of a project isn't scalable. It's also difficult to try different versions of Eigen without editing your build files.
I would just write:
cmake_minimum_required(VERSION 3.23)
project(example)
find_package(Eigen3 REQUIRED)
add_executable(my_exe main.cpp)
target_link_libraries(my_exe PRIVATE Eigen3::Eigen)
Use a proper package manager like vcpkg or Conan to handle downloading Eigen when it isn't available on the system.

Integrate pre-compiled libraries into C++ codebase with CMake ExternalProject

I want to integrate CasADi into a CMake-based C++ codebase as an ExternalProject. For this purpose, I would like to use pre-compiled libraries because building from source is not recommended. So far, I have only managed to write the following:
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
and I noticed that all the binaries are correctly downloaded in the specified folder. However, I do not know how to link my targets to CasADi, nor how to find the package.
There is a natural problem with ExternalProject_Add:
ExternalProject_Add executes commands only on build.
Hence, download will not happen at the configure stage of your project which makes it difficult to use find_package, because the files cannot be found during your first configure run.
Take this CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(untitled)
set(CMAKE_CXX_STANDARD 17)
add_executable(untitled main.cpp)
include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
find_package(casadi HINTS ${CMAKE_BINARY_DIR}/external/casadi/src/casadi-3.5.5/casadi)
target_link_libraries(untitled casadi)
In order to use it you have to do the following:
Configure your project
mkdir build
cd build
cmake ..
Build (download) casadi-3.5.5
cmake --build . --target casadi-3.5.5
Reconfigure your project, because now find_package will find the needed files
cmake ..
Build your targets
cmake --build .
If you want a one step build, there are ways to get around this problem
Use FetchContent
Create a sub-cmake-project in a subfolder with all the ExternalProject_Add commands and execute the approriate build (download) steps manually in your own CMakeLists.txt via execute_process calls: https://stackoverflow.com/a/37554269/8088550
Here is an example for the second option, which might be better since FetchContent doesn't have the full functionality of ExternalProject.
main.cpp
#include <casadi/casadi.hpp>
int main()
{
casadi_printf("This works!");
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(untitled)
set(CMAKE_CXX_STANDARD 17)
# some default target
add_executable(untitled main.cpp)
# Configure and build external project
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/external)
execute_process(
COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/external
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external
)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/external
)
# find and link externals
find_package(casadi REQUIRED HINTS ${CMAKE_BINARY_DIR}/external/external/casadi/src/casadi-3.5.5/casadi)
target_link_libraries(untitled casadi)
external/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(external)
include(ExternalProject)
ExternalProject_Add(
casadi-3.5.5
URL https://github.com/casadi/casadi/releases/download/3.5.5/casadi-linux-py39-v3.5.5-64bit.tar.gz
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/external/casadi)
The point is to have another cmake project under external/CMakeLists.txt, which gets configured and build via execute_process calls from the main cmake project. Do note, that you can now have find_package(casadi REQUIRED ...) at configure stage, because the download will happen just before.

Include dependencies in dynamic library

I am building a library in C++ that requires a few libraries to be included, some of which being GLEW, SDL2, and GLM. I am using CMake to build this library, and have successfully set up (at least to my knowledge) a CMakeLists.txt that adequately does this, but currently without dependencies. I would like to know the proper conventions for adding these external libraries to my own library, keeping in mind that someone on a different machine may be using this library (i.e. not defined file structure/local installs).
This is my current CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(mylib VERSION 1.0.1 LANGUAGES CXX)
set (DEFAULT_BUILD_TYPE "Release")
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
include (GNUInstallDirs)
set (SOURCE_FILES "src/driver.cpp")
add_library(${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(
${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE src
)
set_target_properties (
${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
)
install (
TARGETS ${PROJECT_NAME} EXPORT mylibConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install (
DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
)
install (
EXPORT mylibConfig
DESTINATION share/mylib/cmake
)
export (
TARGETS ${PROJECT_NAME}
FILE mylibConfig.cmake
)
If you similarly notice any key errors/mistakes in my present file, please feel free to let me know, but the more important matter is how I should be properly including these libraries.
To dynamically link GLEW, SDL2, and GLM, you can use the find_package command.
find_package(GLEW REQUIRED)
find_package(SDL2 REQUIRED)
find_package(glm REQUIRED)
Then, after you've called add_library, you will need to link the libraries to your library:
target_link_libraries(mylib PRIVATE GLEW::GLEW SDL2::SDL2 glm::glm)
If you expose any of those dependencies in your API, then you can call target_link_libraries with PUBLIC (instead of PRIVATE) for those dependencies instead.
Consider using a package manager like conan. I took a look for you at conan center and there are recipes for your dependencies. You can follow the getting started guide and it should just work.
In CMake there are many ways to handle dependencies,
manually
using submodule
using fine_package
...
But here I just refer to 2 methods which are easy and portable.
Submodule
If the dependency exists on the github/gitlab or some places that uses git, then you can easily use submodule method.
with submodule your dependencies can be updated always(through their github/gitlab pages), but you can't change them yourself because your changes will be locally(like every time you clone a repository, your changed will be locally unless you do a pull request or be a contributor).
Usage
For using submodule, you need to have this section on your .CMakeLists.txt file:
#--------------------------------------------------------------
# submodule section
# here we use the following code to support
# old versions of git(that don't download submodule
# contents automatically).
#--------------------------------------------------------------
# [[[
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(
COMMAND ${GIT_EXECUTABLE} submodule update
--init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT
)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "Git submodule update --init failed with ${GIT_SUBMODULE_RESULT}, Please checkout submodules")
endif()
endif()
endif()
# ]]]
#
This easily run git submodule update --init --recursive automatically for you. and then you can use your submodule like this:
# your submodule directory that has a main .CMakeLists.txt file inside it.
add_subdirectory("YOUR_SUBMODULE_DIRECTORY")
target_include_directories(
executable_or_library_name PRIVATE
YOUR_SUBMODULE_DIRECTORY/include
)
Here's an example for submodule.
find_package
If the dependency has a find_package support, then you use find_package method, specially if it has this support and it's not on the github/gitlab or a place that uses git, you should use this method then for sure.
Usage
It's also easy to use, with just most of the times one command:
find_package(dependency)
target_include_directories(YOUR_TARGET
"${dependency_INCLUDE_DIR}"
# ... your other include directories
)
target_link_libraries(YOUR_TARGET
"${dependency_LIBRARIES}"
# ... your other libraries to add
)
There are many possible arguments like REQUIRED and COMPONENTS that are explained in the official cmake website
Not part of this question but useful (Side Note)
I usually have this at the end of my main .CMakeLists.txt to support emacs/vim/... and every editors which uses LSP for finding symbols:
#--------------------------------------------------------------
# Generating compile_commands.json file for lsp servers
#--------------------------------------------------------------
# [[[
option(CMAKE_EXPORT_COMPILE_COMMANDS "Generate lsp command file" ON)
if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json")
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json
${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json
)
endif()
# ]]]
#--------------------------------------------------------------

Fail to find gtest inside a container

I am working inside a container. I want to try gtest so first I installed it by doing this inside the container:
Download the source file from github/google/googletest
build the project by cmake CMakeLists.txt
call make
cd lib and cp * /usr/lib
cd googlemock/include and cp -r gmock /usr/local/include
cd googletest/include and cp -r gtest /usr/local/include
After this I created a CMakeLists.txt file as
cmake_minimum_required(VERSION 3.13)
set(CMAKE_CXX_STANDARD 11)
find_package(GTest REQUIRED)
message("GTest_INCLUDE_DIRS = ${GTest_INCLUDE_DIRS}")
add_executable(myExecutable main.cpp)
target_link_libraries(myExecutable ${GTEST_LIBRARIES} pthread)
With this, I could use gtest without problem. (main.cpp contained some tests)
Then I want to do the same for a ROS project I have, so after creating a workspace and a package that depends on gtest I did catkin_make and I get
- +++ processing catkin package: 'ros_gtest'
-- ==> add_subdirectory(ros_gtest)
-- Could NOT find GTest (missing: GTest_DIR)
-- Could not find the required component 'GTest'. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found.
CMake Error at /opt/ros/noetic/share/catkin/cmake/catkinConfig.cmake:83 (find_package):
Could not find a package configuration file provided by "GTest" with any of
the following names:
GTestConfig.cmake
gtest-config.cmake
Add the installation prefix of "GTest" to CMAKE_PREFIX_PATH or set
"GTest_DIR" to a directory containing one of the above files. If "GTest"
provides a separate development package or SDK, be sure it has been
installed.
Call Stack (most recent call first):
ros_gtest/CMakeLists.txt:10 (find_package)
-- Configuring incomplete, errors occurred!
See also "/root/ros_gtest_example/build/CMakeFiles/CMakeOutput.log".
See also "/root/ros_gtest_example/build/CMakeFiles/CMakeError.log".
make: *** [Makefile:320: cmake_check_build_system] Error 1
In this case the CMakeLists.txt start as
cmake_minimum_required(VERSION 3.0.2)
project(ros_gtest)
## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
GTest
roscpp
rospy
std_msgs
)
I wonder why it does not work in this case but it works in the other?
missing make install and you don't copy any gtest config file...
IMHO, You should use FetchContent() or/and learn how CMake find_package() work...
include(CTest)
if(BUILD_TESTING)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
ref: mizux/cmake-cpp

Installing an ExternalProject with CMake

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