CMake run target_link_libraries after dependencies are built from external project - cmake

I am using ncurses in a project and would like the project to pull ncurses from git and to be used in the project. The external project is set up like so:
ExternalProject_Add(ncurses-extern
EXCLUDE_FROM_ALL TRUE
INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ncurses
GIT_REPOSITORY https://github.com/mirror/ncurses.git
GIT_TAG master
UPDATE_COMMAND ""
CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix <INSTALL_DIR> --with-shared --without-debug --without-manpages --includedir <INSTALL_DIR>/include
BUILD_COMMAND make -C <BINARY_DIR>
INSTALL_COMMAND make -C <BINARY_DIR> install
)
then I would like to create a interface library to make linking targets to it simpler like so:
add_library(ncurses-lib INTERFACE)
add_dependencies(ncurses-lib ncurses-extern)
target_link_libraries(ncurses-lib INTERFACE ${CMAKE_SOURCE_DIR}/libs/ncurses/lib/libncurses.so)
target_include_directories(ncurses-lib INTERFACE ${CMAKE_SOURCE_DIR}/libs/ncurses/include)
I would think as ncurses-lib depends on ncurses-extern cmake would build ncurses-extern then try to make the library but it doesn't.
If ncurses-extern has been built and installed already then the cmake works, however if it cannot find libncurses.so (because the external project hasn't been run yet), it fails to build with the error:
ninja: error: '../libs/ncurses/lib/libncurses.so', needed by 'main', missing and no known rule to make it
How can I make it so the target_link_libraries only runs after the external project.

Related

Cmake run a ExternalProject if find_package fails

I am writing an application that requires ncurses, and I have it setup to build ncurses (among others) as external projects:
ExternalProject_Add(ncurses-extern
INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ncurses
GIT_REPOSITORY https://github.com/mirror/ncurses.git
CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix <INSTALL_DIR> --with-normal --without-debug --without-manpages
BUILD_COMMAND make -C <BINARY_DIR>
INSTALL_COMMAND make -C <BINARY_DIR> install
)
add_library(ncurses-lib INTERFACE)
target_link_libraries(ncurses-lib INTERFACE -L${CMAKE_CURRENT_SOURCE_DIR}/ncurses/lib -lncurses)
target_include_directories(ncurses-lib INTERFACE ncurses/include)
However for this to work, however I either have to make it so ncurses-lib depends on ncurses extern which will make it so it runs the external project every time (even if its already installed), or I have to manually run it.
What I would like it to do is only run the external project to run, if A: ncurses can't be found on the system with find_package, or B: if ncurses doesn't already exist in the lib folder (where the external project puts it).
However I am not sure how I would achieve this, any help would be great, thanks.

ExternalProject_Add autogen project prevent configure on rebuild

I have a CMake project on linux and I'm using ExternalProject to build Google Protobuf. It works great, however any subsequent builds still call the configure step in the external project (which is annoying because protobuf is an autogen project with a rather long step). I used the UPDATE_DISCONNECTED argument so it wouldn't re-clone which helps some, but you'd think if it didn't re-clone, it wouldn't need to re-configure or re-build/install. How can I get CMake to just build it the one time and skip subsequent builds (i.e. my next make from the build directory)?
Here's my CMakeLists.txt
cmake_minimum_required(VERSION 2.8.11)
project(pbuf_test)
include(${PROJECT_SOURCE_DIR}/cmake/protogen.cmake)
set(PBUF_DIR ${PROJECT_BINARY_DIR}/protobuf)
include(ExternalProject)
ExternalProject_Add(protobuf
PREFIX ${PROTOBUF_DIR}
GIT_REPOSITORY https://github.com/google/protobuf.git
GIT_TAG v3.4.1
UPDATE_DISCONNECTED 1
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./autogen.sh COMMAND ./configure --prefix=${PBUF_DIR}
)
set(PBUF_INCLUDE_DIR ${PBUF_DIR}/include)
set(PBUF_LIBRARY ${PBUF_DIR}/lib/libprotobuf.so)
set(PBUF_PROTOC ${PBUF_DIR}/bin/protoc)
file(GLOB PBUF_FILES "${PROJECT_SOURCE_DIR}/msg/*.proto")
custom_protobuf_generate_cpp(PBUF_SRCS PBUF_HDRS ${PBUF_FILES})
include_directories(
${PROJECT_BINARY_DIR}
${PBUF_INCLUDE_DIR}
)
add_executable(${PROJECT_NAME} main.cpp ${PBUF_SRCS} ${PBUF_HDRS})
add_dependencies(${PROJECT_NAME} protobuf)
target_link_libraries(${PROJECT_NAME}
${PBUF_LIBRARY}
)
Full example project here
The only way I know is to move the steps to build the external project into a script called by add_command. And then add an dependency between your project and the external project's output/lib/binary.
Pro: Avoids unnecessary rebuilds
Pro: Avoids unexpected updates of the external lib
Con: Does not rebuild in case the external repository was updated (and this is required)
Con: Additional work to write such a script
BTW: Within my project, we do it the hard way. But we do not want any unexpected updates of external libs.
It seems the solution to this for now that still uses ExternalProject is to move the configure step into the download step.
Something like this. Note the name of the target is the directory it expects the download to create. This might only work on certain operating systems because of the use of the && joiner: I'm a little new to cmake.
ExternalProject_Add(protobuf
PREFIX ${PROTOBUF_DIR}
DOWNLOAD_COMMAND git clone https://github.com/google/protobuf.git protobuf &&
cd protobuf &&
git checkout v3.4.1 &&
./autogen.sh &&
./configure --prefix=${PBUF_DIR}
UPDATE_COMMAND git pull
UPDATE_DISCONNECTED 1
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ""
)

How can I run cmake from within cmake?

My project depends on mariadb-connector-c and I'm trying to automate the download, build and link process with cmake.
I currently download the project into a directory, I then try to execute generate ninja files and run them but I cannot run cmake at all:
execute_process(COMMAND "cmake -GNinja ." WORKING_DIRECTORY ${mariadb-connector-c_SOURCE_DIR})
I know this doesn't work because the next step, running ninja, fails:
execute_process(COMMAND "ninja" WORKING_DIRECTORY ${mariadb-connector-c_SOURCE_DIR})
cmake runs fine in CLI, I've tried using the full path to the cmake executable and replacing the dot with the variable with the full directory (which is also a valid variable, if you're wondering.)
How can I tell cmake to run cmake on that external project?
You can organize your project to a top-level CMakeLists.txt build your subprojects as ExternalProject.
This approach requires more work and maintenance of more CMake modules but it has its own benefits. I download Google Test as follows:
# Create download URL derived from version number.
set(GTEST_HOME https://github.com/google/googletest/archive)
set(GTEST_DOWNLOAD_URL ${GTEST_HOME}/release-${GTEST_VERSION}.tar.gz)
unset(GTEST_HOME)
# Download and build the Google Test library and add its properties to the third party arguments.
set(GTEST_ROOT ${THIRDPARTY_INSTALL_PATH}/gtest CACHE INTERNAL "")
ExternalProject_Add(gtest
URL ${GTEST_DOWNLOAD_URL}
CMAKE_ARGS -DBUILD_GTEST=ON -DBUILD_GMOCK=ON -DCMAKE_INSTALL_PREFIX=${GTEST_ROOT}
INSTALL_COMMAND make install
)
list(APPEND GLOBAL_THIRDPARTY_LIB_ARGS "-DGTEST_ROOT:PATH=${GTEST_ROOT}")
unset(GTEST_DOWNLOAD_URL)
unset(GTEST_ROOT)
The code abowe is inside my ExternalGoogleTest.cmake module which is included by CMakeLists.txt of third-party libraries:
set_directory_properties(PROPERTIES EP_BASE ${CMAKE_BINARY_DIR}/ThirdParty)
get_directory_property(THIRDPARTY_BASE_PATH EP_BASE)
set(THIRDPARTY_INSTALL_PATH ${THIRDPARTY_BASE_PATH}/Install)
set(GTEST_VERSION 1.8.0)
include(ExternalProject)
include(ExternalGoogleTest)
Your own project which depends on an external library will need a CMake module to build it as ExternalProject too. It can looks like:
ExternalProject_Add(my_project
DEPENDS gtest whatever
SOURCE_DIR ${CMAKE_SOURCE_DIR}/lib
CMAKE_ARGS
${GLOBAL_DEFAULT_ARGS}
${GLOBAL_THIRDPARTY_LIB_ARGS}
-DCMAKE_INSTALL_PREFIX=${DESIRED_INSTALL_PATH}/my_project
BUILD_COMMAND make
)
You can found more tips about this pattern here.

CMake External Project not Downloading

I am using CMake 3.5.1 with CLion, and I am attempting to download and install an external project from a URL. My CMakeLists.txt include:
include(ExternalProject)
set(EXTERNAL ${PROJECT_SOURCE_DIR}/external)
ExternalProject_Add(eigen_test
PREFIX ${EXTERNAL}/eigen
DOWNLOAD_DIR ${EXTERNAL}/eigen/download
SOURCE_DIR ${EXTERNAL}/eigen/src
BINARY_DIR ${EXTERNAL}/eigen/build
INSTALL_DIR ${EXTERNAL}/eigen/install
URL http://bitbucket.org/eigen/eigen/get/3.2.4.tar.gz
URL_MD5 4d0d77e06fef87b4fcd2c9b72cc8dc55
CONFIGURE_COMMAND cd <BINARY_DIR> && cmake -D CMAKE_INSTALL_PREFIX=$<INSTALL_DIR> <SOURCE_DIR>
)
When building, this directory structure is is created inside ${PROJECT_SOURCE_DIR}/external/
Although the cmake files exist to download and extract the project, these actions never occur. What am I doing wrong here?
I don't see a reason to pollute your project by downloading and building the library in ${PROJECT_SOURCE_DIR}, I always do that in ${CMAKE_BINARY_DIR}.
The eigen library uses CMakeLists.txt, it will work seamlessly as external project, you don't have to extensively configure each step. So CONFIGURE_COMMAND can be removed and the install prefix set with CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=...
This is the simplified and working configuration:
include(ExternalProject)
set(EXTERNALS_DIR ${CMAKE_BINARY_DIR}/external)
ExternalProject_Add(eigen_test
PREFIX ${EXTERNALS_DIR}
URL http://bitbucket.org/eigen/eigen/get/3.2.4.tar.gz
URL_MD5 4d0d77e06fef87b4fcd2c9b72cc8dc55
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNALS_DIR}/installed
)
How to use the library:
Add ${EXTERNALS_DIR}/installed/include to include directories
Build the eigen_test target

How to not make install step when building external project with cmake

I'm building dependency project with cmake ExternalProject_Add command:
include(ExternalProject)
...
set(COMMON_BASE_PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../CommonBase)
ExternalProject_Add(CommonBaseProject
SOURCE_DIR ${COMMON_BASE_PROJECT_DIR}
BINARY_DIR ${COMMON_BASE_PROJECT_DIR}/build
INSTALL_COMMMAND ""
)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
include_directories(${COMMON_BASE_PROJECT_DIR}/include)
add_library(
${LIBRARY_NAME}
SHARED
${SRC_FILES}
${INCLUDE_FILES}
)
target_link_libraries (Bios ${COMMON_BASE_PROJECT_DIR}/build/libCommonBase.dll)
add_dependencies(Bios CommonBaseProject)
but i get error:
[100%] Linking CXX shared library libCommonBase.dll
[100%] Built target CommonBase
[ 50%] Performing install step for 'CommonBaseProject'
make[3]: *** No rule to make target 'install'. Stop.
I don't need to make install step, so my question is: how to disable it?
You almost had it: Instead of INSTALL_COMMAND "" put something like
INSTALL_COMMAND cmake -E echo "Skipping install step."
You can generate a target for the build step with STEP_TARGETS build and add dependency on this particular target. The step targets are named <external-project-name>-<step-name> so in this case the target representing the build step will be named CommonBaseProject-build.
You probably also want to exclude the CommonBaseProject from the "all" target with EXCLUDE_FROM_ALL TRUE.
ExternalProject_Add(CommonBaseProject
SOURCE_DIR ${COMMON_BASE_PROJECT_DIR}
BINARY_DIR ${COMMON_BASE_PROJECT_DIR}/build
STEP_TARGETS build
EXCLUDE_FROM_ALL TRUE
)
add_dependencies(Bios CommonBaseProject-build)
Not relevant to your question, which it was already answered, but in my case I had the following ExternalProject_Add directive:
ExternalProject_Add(external_project
# [...]
# Override build/install command
BUILD_COMMAND ""
INSTALL_COMMAND
"${CMAKE_COMMAND}"
--build .
--target INSTALL # Wrong casing for "install" target
--config ${CMAKE_BUILD_TYPE}
)
In this case cmake quits with very similar error (*** No rule to make target 'INSTALL'), but in this case it's the external project that is looking for incorrect uppercase INSTALL target: correct case is install instead. Apparently, that worked in Windows with MSVC but fails in unix operating systems.
Since at least CMake 3.10 the empty string is sufficient to suppress the install step:
Passing an empty string as the <cmd> makes the install step do nothing.
The same goes for the other stages; see the docs for more.
If you're still building with CMake <3.10 then you need to update CMake ;)