How to pass C/CXX flags to an external project in CMake - cmake

I'm using CMake to drive the build of a project, but there are some sub-projects that need to be built using autotools. I use the ExternalProject feature in CMake to build it, but I'm having trouble passing the complete set of default C/CXX flags to the configure script for the subproject.
Currently I just pass CMAKE_C_FLAGS, CMAKE_CXX_FLAGS, etc. The issue is that this does not include the build_type specific flags, which leads to issues when compiling with debug info (for instance), as I have to manually propegate the -g flag.
Current external project config looks like:
ExternalProject_Add(
openthread-build
SOURCE_DIR ${openthread_SOURCE_DIR}
BINARY_DIR ${openthread_BINARY_DIR}
CONFIGURE_COMMAND cd ${openthread_SOURCE_DIR}
COMMAND ${openthread_SOURCE_DIR}/configure ${OPENTHREAD_CONFIGURE_OPTS}
"CXX=${CMAKE_CXX_COMPILER}"
"CPP=${CMAKE_C_COMPILER} -E"
"CC=${CMAKE_C_COMPILER}"
"AR=${CMAKE_C_COMPILER_AR}"
"RANLIB=${CMAKE_C_COMPILER_RANLIB}"
"NM=${CMAKE_NM}"
"STRIP=${CMAKE_STRIP}"
"CFLAGS=${CMAKE_C_FLAGS} ${OPENTHREAD_INCLUDE_ARG}"
"CPPFLAGS=${CMAKE_C_FLAGS} ${OPENTHREAD_INCLUDE_ARG}"
"CXXFLAGS=${CMAKE_CXX_FLAGS} ${OPENTHREAD_INCLUDE_ARG}"
"LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}"
"--host=${MACHINE_NAME}"
BUILD_COMMAND ${OPENTHREAD_MAKE} ${OPENTHREAD_MAKE_ARGS}
BUILD_ALWAYS ON
DOWNLOAD_COMMAND cd ${openthread_SOURCE_DIR} && ./bootstrap
INSTALL_COMMAND ""
TEST_COMMAND ""
)
Problem lines are the ones like:
"CFLAGS=${CMAKE_C_FLAGS} ${OPENTHREAD_INCLUDE_ARG}"
Which could ideally be something like ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}}, but that doesn't work due to case sensitivity of variable names.
"CFLAGS=${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}} ${OPENTHREAD_INCLUDE_ARG}"
So I guess the final option is to write a function which forms CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}, converts it to upper and then expands that, but this seems quite clunky.
Is there any simple way to pass the default base set of C/CXX flags to an external project, without the need for manual hard-coding for each build type or adding functions? I expect the default CMAKE_C_FLAGS and the CMAKE_C_FLAGS_* for build type to be included.

I am still open to other answers, but for now I have settled on creating an uppercase version of the build_type, and using that to extract the build_type specific flags.
# get an uppercase version of the build type, for extracting build_type specific flags
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UC)
endif()
So the individual flag lines look like:
"CFLAGS=${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE_UC}} ${OPENTHREAD_INCLUDE_ARG}"
and the full externalproject call is pretty crowded but looks like:
ExternalProject_Add(
openthread-build
SOURCE_DIR ${openthread_SOURCE_DIR}
BINARY_DIR ${openthread_BINARY_DIR}
CONFIGURE_COMMAND cd ${openthread_SOURCE_DIR}
COMMAND ${openthread_SOURCE_DIR}/configure ${OPENTHREAD_CONFIGURE_OPTS}
"CXX=${CMAKE_CXX_COMPILER}"
"CPP=${CMAKE_C_COMPILER} -E"
"CC=${CMAKE_C_COMPILER}"
"AR=${CMAKE_C_COMPILER_AR}"
"RANLIB=${CMAKE_C_COMPILER_RANLIB}"
"NM=${CMAKE_NM}"
"STRIP=${CMAKE_STRIP}"
"CFLAGS=${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE_UC}} ${OPENTHREAD_INCLUDE_ARG}"
"CPPFLAGS=${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE_UC}} ${OPENTHREAD_INCLUDE_ARG}"
"CXXFLAGS=${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_TYPE_UC}} ${OPENTHREAD_INCLUDE_ARG}"
"LDFLAGS=${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS_${BUILD_TYPE_UC}}"
"--host=${MACHINE_NAME}"
BUILD_COMMAND ${OPENTHREAD_MAKE} ${OPENTHREAD_MAKE_ARGS}
BUILD_ALWAYS ON
DOWNLOAD_COMMAND cd ${openthread_SOURCE_DIR} && ./bootstrap
INSTALL_COMMAND ""
TEST_COMMAND ""
)

Related

CMake run target_link_libraries after dependencies are built from external project

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.

CMakeLists.txt not in root directory, can't do an automatic build

I have an issue with a repository not having its CMakeLists in the root directory, namely https://github.com/lz4/lz4
The CMakeLists.txt is in the subfolder contrib/cmake_unofficial.
I already checked similar questions on SO (Is it possible to have cmake build file (CMakeLists.txt) not in root in CLion, cmake - CMakeLists.txt is not in root folder (but is included in source)), but they only provide alternatives, and not a solution applicable to my situation.
Heres the cmake module I came up with:
if(ENABLE_LZ4)
message(STATUS "Using LZ4.")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_LZ4")
# Enable ExternalProject CMake module
include(ExternalProject)
set(LZ4_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/lz4)
set(LZ4_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib)
# Download and install lz4
ExternalProject_Add(
lz4
GIT_REPOSITORY https://github.com/lz4/lz4.git
GIT_TAG dev
SOURCE_DIR ${LZ4_SOURCE_DIR}
BINARY_DIR ${LZ4_BINARY_DIR}
INSTALL_COMMAND ""
CMAKE_ARGS
${LZ4_SOURCE_DIR}/contrib/cmake_unofficial
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
)
# Get lz4 source and binary directories from CMake project
ExternalProject_Get_Property(lz4 source_dir binary_dir)
# Create a liblz4 target to be used as a dependency by the program
add_library(liblz4 IMPORTED SHARED GLOBAL)
add_dependencies(liblz4 lz4)
include_directories(
${LZ4_SOURCE_DIR}/lib
)
set(LZ4_LIB ${LZ4_BINARY_DIR}/liblz4.so)
else()
message(STATUS "Not using LZ4.")
set(LZ4_LIB "")
endif()
Here the complete error output:
[ 0%] Performing update step for 'lz4'
Current branch dev is up to date.
[ 1%] Performing configure step for 'lz4'
CMake Error: The source directory "/****/build/lz4" does not appear to contain CMakeLists.txt.
Specify --help for usage, or press the help button on the CMake GUI.
CMakeFiles/lz4.dir/build.make:105: recipe for target 'lz4-prefix/src/lz4-stamp/lz4-configure' failed
make[2]: *** [lz4-prefix/src/lz4-stamp/lz4-configure] Error 1
CMakeFiles/Makefile2:72: recipe for target 'CMakeFiles/lz4.dir/all' failed
make[1]: *** [CMakeFiles/lz4.dir/all] Error 2
Makefile:94: recipe for target 'all' failed
make: *** [all] Error 2
I tried adding the path 'contrib/cmake_unofficial' to the CMAKE_ARGS variable (as seen in the module above), but it does not work (seems to be ignored?).
I also tried using PATCH_COMMAND to copy the CMakeLists.txt to the root before the build starts, but the relative paths of the file get messed up.
In other words, i need the cmake command to be called to build the library to be : cmake contrib/cmake_unofficial.
I also tried using CONFIGURE_COMMAND for this, but keep getting a file not found error for some reason (even though the path is correct).
The module has some other issues too, but I'm only interested in the non-root CMakeLists.
Thanks in advance!
ExternalProject separates download and source directories:
DOWNLOAD_DIR - a directory where downloading step is performed
SOURCE_DIR - a directory used as a source one when configuration step is performed
When use git for extract the project, note that git clone is called from the download directory, where it creates new directory with a project sources.
set(LZ4_DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}/lz4)
# Set a source dir based on the download one.
set(LZ4_SOURCE_DIR ${LZ4_DOWNLOAD_DIR}/lz4/contrib/cmake_unofficial)
set(LZ4_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib)
# Download and install lz4
ExternalProject_Add(
lz4
DOWNLOAD_DIR ${LZ4_DOWNLOAD_DIR} # Set download directory explicitely
GIT_REPOSITORY https://github.com/lz4/lz4.git
GIT_TAG dev
SOURCE_DIR ${LZ4_SOURCE_DIR}
BINARY_DIR ${LZ4_BINARY_DIR}
...
)
Probably not the correct way to do it, but this seems to work for me.
I used the CONFIGURE_COMMAND to call cmake on the correct directory.
Then use BUILD_COMMAND to call make
So essentially, it breaks down to this:
ExternalProject_Add(
lz4
GIT_REPOSITORY https://github.com/lz4/lz4.git
GIT_TAG dev
SOURCE_DIR ${LZ4_SOURCE_DIR}
BINARY_DIR ${LZ4_BINARY_DIR}
CONFIGURE_COMMAND cmake ${LZ4_SOURCE_DIR}/contrib/cmake_unofficial
BUILD_COMMAND make
INSTALL_COMMAND ""
CMAKE_ARGS
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
)

Using cmake as the configure tool (CONFIGURE_COMMAND) in CMakes "Add_External_Project" function

I am having an issue with CMakes Add_External_Project functionality (more of an annoyance than anything else). Specifically, I do not understand the keys CONFIGURE_COMMAND, BUILD_COMMAND and INSTALL_COMMAND.
In the following (working) example, which downloads Google's test library, the two files at the end of the question will ensure that the third party libraries are downloaded and built (not installed).
However, when I tried to add configure and build commands as "CONFIGURE_COMMAND" and "BUILD_COMMAND" (cmake . and cmake --build) instead of having to do execute_process CMake craps out with the error message:
[ 55%] Performing configure step for 'googletest'
/bin/sh: 1: cmake .: not found
Am I trying to do something that is obviously not within the scope of the Add_External_Project functionality?
Example Files:
CMakeLists.txt
cmake_minimum_required (VERSION 3.0)
project (Test VERSION 0.1.0.0 LANGUAGES CXX)
# Download and unpack googletest at configure time
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.in" "${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt" #ONLY)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" )
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download")
add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src" "${CMAKE_BINARY_DIR}/googletest-build")
CMakeLists.txt.in
cmake_minimum_required(VERSION 3.0)
project(third-party NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
SOURCE_DIR "#CMAKE_BINARY_DIR#/googletest-src"
BINARY_DIR "#CMAKE_BINARY_DIR#/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
If you don't specify CONFIGURE_COMMAND at all, it will assume a CMake project and run the appropriate cmake command for you (by appropriate, I mean it will use the same CMake generator as your main build, etc.). Similarly, if you leave out BUILD_COMMAND, it will also assume a CMake project and do cmake --build for you. So in your case, just leave out those two lines and ExternalProject_Add() should do exactly what you want.
The main reason you might specify these two options as empty strings is to prevent those steps from doing anything at all. This can be useful, for example, to use ExternalProject_Add() simply for its download and unpacking functionality. This exact situation is used in a technique described here for downloading the source of GoogleTest so it can be added to your project via add_subdirectory(), making it part of your build (see also this answer and other answers to that question for some related material). I suspect this might be where your code is derived from, as the structure looks similar.
For completeness, if you find yourself in a situation where you do need to specify a CMake command, don't use a bare cmake to refer to the command to run. Instead, always use ${CMAKE_COMMAND}, which is provided by CMake as the location of the CMake executable currently being used to process the file. Using this variable means cmake doesn't have to be on the user's PATH and also ensures that if the developer chooses to run a different version of CMake other than the one on the PATH, that same cmake will still be used for the command you are adding.
You can use PATCH_COMMAND like this:
option(WITH_MBEDTLS "Build with mbedtls" OFF)
if(WITH_MBEDTLS)
ExternalProject_Add(external-mbedtls
URL https://github.com/ARMmbed/mbedtls/archive/mbedtls-2.16.1.tar.gz
UPDATE_COMMAND ""
PATCH_COMMAND ./scripts/config.pl set MBEDTLS_THREADING_C &&
./scripts/config.pl set MBEDTLS_THREADING_PTHREAD
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${PROJECT_BINARY_DIR}/third_party/mbedtls
-DCMAKE_TOOLCHAIN_FILE:PATH=${TOOLCHAIN_FILE}
-DCMAKE_BUILD_TYPE:STRING=Debug
-DENABLE_TESTING:BOOL=OFF
-DENABLE_PROGRAMS:BOOL=ON
TEST_COMMAND ""
)
set(MBEDTLS_PREFIX ${PROJECT_BINARY_DIR}/third_party/mbedtls PARENT_SCOPE)
endif(WITH_MBEDTLS)

cmake's ExternalProject_Add: how to set source, build and install directories

I want to use ExternalProject_Add function to install cmake enabled projects. I need to control the building and installation processes. More specifically, I want that cmake build and install in specific directories.
There is an option to select a path where to put the source directory: SOURCE_DIR. Is there something equivalent for BUILD_DIR and INSTALL_DIR? I did not see anything alike.
There is a PREFIX option:
Root dir for entire project
What does it mean exactly? An how does cmake's ExternalProject_Add do the installation?
Edit:
This work, but I have no control over source directory and it does not install the library:
ExternalProject_Add(
wjelement-project
GIT_REPOSITORY "https://github.com/netmail-open/wjelement.git"
GIT_TAG "v1.2"
UPDATE_COMMAND ""
PATCH_COMMAND ""
TEST_COMMAND ""
INSTALL_COMMAND ""
#SOURCE_DIR "${MY_SOURCE_ROOT}/wjelement"
BINARY_DIR "${MY_BUILD_DIR}/wjelement"
INSTALL_DIR "${CCT_INSTALL_DIR}/wjelement"
)
If I uncomment SOURCE_DIR, it does not clone from GIT_CLONE but try to get source from SOURCE_DIR (and it fails because that's not what I expected...)
If I comment INSTALL_COMMAND "", then it try to install in C:/Program Files/wjelement, the default (apparently) and not in INSTALL_DIR
default path for source files seem to be ${CMAKE_BINARY_DIR}/wjelement-project-prefix/src
Another Stackoverflow question report problem (or at least non intuitive behavior) with INSTALL_DIR.

How to reuse extracted source with CMake's ExternalProject_Add?

I use the following CMake file to automatically download and compile the Boost libraries. After generating project files for my compiler, I run cmake --build . --config Debug and cmake --build . --config Release to build both variants. So I envoke the build process twice. Even though CMake is clever enough to not download the archive again if the checksum still matches, it extracts it into the source directory twice. Extraction takes a couple of minutes for the large Boost libraries, so I'd like to prevent CMake from doing it for the second build.
ExternalProject_Add(Boost
PREFIX ${BOOST_PREFIX}
TMP_DIR ${BOOST_PREFIX}/temp
STAMP_DIR ${BOOST_PREFIX}/stamp
#--Download step--------------
DOWNLOAD_DIR ${BOOST_PREFIX}/download
URL http://downloads.sourceforge.net/project/boost/boost/1.56.0/boost_1_56_0.tar.gz
URL_MD5 8c54705c424513fa2be0042696a3a162
#--Update/Patch step----------
UPDATE_COMMAND ""
#--Configure step-------------
SOURCE_DIR ${BOOST_PREFIX}/source
CONFIGURE_COMMAND ${BOOST_CONFIGURE_COMMAND}
#--Build step-----------------
BUILD_COMMAND ${BOOST_BUILD_EXECUTABLE} install
--build-dir=${BOOST_PREFIX}/build
--prefix=${BOOST_PREFIX}/install
variant=${BOOST_VARIANT}
link=${BOOST_LINK}
threading=multi
address-model=32
toolset=${BOOST_TOOLSET}
runtime-link=${BOOST_RUNTIME_LINK}
BUILD_IN_SOURCE 1
#--Install step---------------
INSTALL_COMMAND ""
)
In this particually example it would possible to just use variant=debug,release, but there are some other external dependencies in my project that I build this way. How can I make CMake only extract the archive if it downloaded a new one?
Create two projects: Foo-Release and Foo-Debug. Leave DOWNLOAD_COMMAND empty for the second project and make it depends on the first. Set SOURCE_DIR explicilty for both projects (point to the same directory). I.e.:
set(source_dir "${CMAKE_CURRENT_BINARY_DIR}/3rdParty/Foo/Source")
ExternalProject_Add(
Foo-Release
URL "..."
URL_HASH SHA1=...
SOURCE_DIR "${source_dir}"
...
)
ExternalProject_Add(
Foo-Debug
DOWNLOAD_COMMAND
""
SOURCE_DIR "${source_dir}"
...
)
add_dependencies(Foo-Debug Foo-Release)
Example