Suspend master CMake when using ExternalProject - cmake

I use ExternalProject in my CMakeLists.txt as follows:
include(ExternalProject)
ExternalProject_Add(eigen_build
SOURCE_DIR ${PROJECT_SOURCE_DIR}/${EIGEN_DIR}
# CONFIGURE_COMMAND cmake
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CATKIN_DEVEL_PREFIX}
-DCMAKE_BUILD_TYPE:STRING=Release
)
This works fine when building on a PC, but when building on a resource-limited target with cmake -j8, having two active make instances with 8 tasks each causes the build to often fail with out of memory conditions.
One workaround I have tried is:
# Find out host architecture
execute_process(COMMAND
dpkg-architecture
-qDEB_HOST_ARCH
OUTPUT_VARIABLE
CMAKE_DEB_HOST_ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# If we are building on arm64 then limit subprocesses to 2
if(${CMAKE_DEB_HOST_ARCH} MATCHES "arm64")
set(NUM_SUB_PROCESSES 2)
else()
set(NUM_SUB_PROCESSES 8)
endif()
message("Building library with ${NUM_SUB_PROCESSES} subprocess(es)")
include(ExternalProject)
ExternalProject_Add(eigen_build
SOURCE_DIR ${PROJECT_SOURCE_DIR}/${EIGEN_DIR}
# CONFIGURE_COMMAND cmake
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CATKIN_DEVEL_PREFIX}
-DCMAKE_BUILD_TYPE:STRING=Release
BUILD_COMMAND make -j${NUM_SUB_PROCESSES}
INSTALL_COMMAND make install -j${NUM_SUB_PROCESSES}
)
This appears to work, but there is still perhaps a risk of occasional failure, plus it makes the sub-build really slow resulting in other tasks wait on completion; building the target separately is of course a manual workaround. However, ideally I would like some option to suspend all parent tasks while this ExternalProject is building; is there some way to do this? (Note, I cannot easily fiddle with dependencies as this is just one small part of a 100+ module build system)

When using a Makefile generator you should try to use the load option -l to limit the number of jobs instead of using the jobs number -j (RTFM ;) )
ref: https://www.gnu.org/software/make/manual/make.html#Parallel

Related

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 ""
)

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)

What is INSTALL_DIR useful for in ExternalProject_Add command?

I don't understand the usage of INSTALL_DIR in ExternalProject_Add command. I try to use it but it does not seem to work. Here is an example of a CMakeLists.txt, using Eigen library which compiles quickly:
cmake_minimum_required (VERSION 2.6)
project (example CXX)
include(ExternalProject)
include(ProcessorCount)
set(CMAKE_VERBOSE_MAKEFILE ON)
ProcessorCount(N)
if(NOT N EQUAL 0)
set(CMAKE_BUILD_FLAGS -j${N})
endif()
ExternalProject_Add
(
mylib
PREFIX myprefix
DOWNLOAD_COMMAND wget http://bitbucket.org/eigen/eigen/get/3.2.4.tar.gz && tar xvzf 3.2.4.tar.gz -C mylib --strip-components=1
)
I chose the following project hierarchy:
project
CMakeLists.txt
build/
From build repository, I type:
cmake ..
make
The installation process fails with the following message:
file cannot create directory: /usr/local/include/eigen3.
Maybe need administrative privileges.
As far as I understand, it means that I need to define a "prefix" during the configuration step:
cmake -D CMAKE_INSTALL_PREFIX=$INSTALL_DIR ..
But, the INSTALL_DIR variable is already defined in the ExternalProject_Add command. However, I get the same error when I modify the value of INSTALL_DIR by adding
INSTALL_DIR myprefix/src/install
in the ExternalProject_Add command.
So, what is INSTALL_DIR useful for?
What am I doing wrong?
Of course, I know how to provide my own configuration command to add a prefix and solve the problem. But it is not my question. My question is: if I have to do that, what is the purpose of INSTALL_DIR?
From what I found in this discussion https://www.mail-archive.com/cmake#cmake.org/msg51663.html (scroll to the end of the page to navigate through the thread messages) it is indeed pretty common thing to use CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/contrib
Furthermore, lurking through the ExternalProject.cmake module I found out that the only effect setting this directory has is that CMake will create directory specified in INSTALL_DIR before doing anything else.
Also, it will set the property that you can gather through ExternalProject_Get_Property(${project_name} install_dir) command.
And that's pretty much it.
// As of CMake version 3.2.2

How can I distribute/install 3rd-party libraries with CMake?

I'm building and distributing an executable which relies on several 3rd-party libraries, most of which are built outside of CMake's buildsystem (though, if it helps, I can add and build them as custom targets in my CMake file).
Here's how I include the libs:
target_link_libraries(MyApp
Lib1_x64
Lib2_x64
etc.}
I'd like to include a CMake directive which installs my executable along with its dependencies. Is there a better way to do this other than calling the install command for every single dependency?
install(DIRECTORY ${DEV_PATH}/Release/ DESTINATION lib FILES_MATCHING PATTERN "libLib1*" PATTERN "*.svn" EXCLUDE PATTERN "*-obj" EXCLUDE)
Is there maybe a way to do this via the add_library command?
This is possible in nowadays with CMake 3.21, which allows you to get a set of dependent libraries via parameter RUNTIME_DEPENDENCY_SET in the install command. Keep in mind, that it will gather all dependent libraries including system, you will need to filter them.
cmake_minimum_required(VERSION 3.21)
# Install your app or library
install(
TARGETS ${PROJECT_NAME}
RUNTIME_DEPENDENCY_SET runtime_deps
)
# Install third-party libraries (exclude system libraries Windows/Unix)
LIST(APPEND pre_exclude_regexes "api-ms-.*")
LIST(APPEND pre_exclude_regexes "ext-ms-.*")
LIST(APPEND post_exclude_regexes ".*WINDOWS[\\/]system32.*")
LIST(APPEND post_exclude_regexes "^/lib" "^/usr" "^/bin")
install(RUNTIME_DEPENDENCY_SET runtime_deps
PRE_EXCLUDE_REGEXES ${pre_exclude_regexes}
POST_EXCLUDE_REGEXES ${post_exclude_regexes}
)
Sorry, no. The add_library command serves the purpose of creating a target in your makefile to build a library. target_link_dependencies just passes the various -L ... -l ... flags to the compiler for a target.
The normal CMake approach is to test for the presence of other libraries or executables on which yours depends, and fail if they are not available.
Handling dependencies is normally left to a package manage such as apt, rpm, etc. It is unwise to build your dependent components as part of your own makefile for a number of reasons:
Your clients might want to use a slightly different version
Any change in how those dependencies build requires you to modify your own build scripts
Your program may work with multiple versions of a dependency, all of which build in different ways.
As someone who uses CMake to build large parts of an entire embedded Linux distribution (see Open webOS), I advise you to keep your scripts focused very tightly on building just one thing.
I am also finding a way to deal this problem today.
and I found :
include(ExternalProject)
ExternalProject_Add()
does it!
here is my sample CMakeLists.txt:
cmake_minimum_required(VERSION 3.13)
project(third-party-build)
include(ExternalProject)
ExternalProject_Add(libuuid
PREFIX libuuid-prefix
# DOWNLOAD_DIR libuuid-download
# SOURCE_DIR libuuid-source
# DOWNLOAD_COMMAND wget xxx.com/xxx.tar.gz && tar zxf xxx.tar.gz
DOWNLOAD_COMMAND tar zxf ${PROJECT_SOURCE_DIR}/libuuid-1.0.3.tar.gz
CONFIGURE_COMMAND <DOWNLOAD_DIR>/libuuid-1.0.3/configure
BUILD_COMMAND ${MAKE}
BUILD_IN_SOURCE 1
INSTALL_COMMAND mkdir -p ${PROJECT_SOURCE_DIR}/lib/ && cp <SOURCE_DIR>/.libs/libuuid.a <SOURCE_DIR>/.libs/libuuid.so <SOURCE_DIR>/.libs/libuuid.so.1 <SOURCE_DIR>/.libs/libuuid.so.1.0.0 ${PROJECT_SOURCE_DIR}/lib/
LOG_DOWNLOAD 1
LOG_BUILD 1
# LOG_INSTALL 1
)

CMake setup advice for complicated build process (including externalProject)

I have an application with a mildly complicated build process, and as a bit of a newb to CMake, I was wondering if anyone could provide me with any pointers.
At preset, the application consists of a single executable, built from a source tree provided in the src and include folders.
It requires a few libraries to work, the big ones being Boost and Python. Python is embedded in the application, and Boost requires knowledge of the custom python install at compile time. I also use Qt, but I'm just linking against the system Qt for this.
What I'd like to have at the end is a stage folder, containing the compiled executable, and a lib folder with the required boost and python libraries.
At present, I have a single CMakeLists.txt file, and I am using ExternalProject to build Boost and Python from bzipped tarballs of their source. It gets a little messy where I copy out the compiled libs from the prefixed install directories.
Things are working, but I have a feeling I'm doing things very backwards. I sometimes see multiple CMakeLists in nested subdirectories but don't know how they would relate to my project. Would anyone who has worked on similarly scoped projects be able to weigh in and give me some pointers?
I should add that I hope to include Windows as a platform in the near future, and that things are currently running on Linux.
Note: This is my current CMakeLists.txt, I realise that boost isn't configured and that things aren't fully moved to the stage folder. I have been doing this manually, but I wanted to ask before I dig myself much deeper :)
Thanks!
CmakeList.txt
cmake_minimum_required(VERSION 2.6)
set(CMAKE_BUILD_TYPE Debug)
set(PROJ_NAME "mwave")
project(${PROJ_NAME})
include_directories("include")
include(ExternalProject)
# Add cmake dir to cmake module path so custom find modules will work
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
#Build Python via External Project
ExternalProject_Add(
Python
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/external/python
URL ${CMAKE_CURRENT_SOURCE_DIR}/extern/Python-3.3.0.tar.bz2
URL_MD5 2dbff60afed2b5f66adf6f77dac9e139
UPDATE_COMMAND ""
CONFIGURE_COMMAND ./configure -q --prefix=${CMAKE_CURRENT_BINARY_DIR}/external/python --enable-shared
BUILD_COMMAND make
BUILD_IN_SOURCE 1
INSTALL_COMMAND make install
)
# Manually copy the compiled python files and dirs to our stage folder
add_custom_command(TARGET Python PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_BINARY_DIR}/external/python/lib/pkgconfig
${CMAKE_CURRENT_BINARY_DIR}/stage/lib/pkgconfig)
add_custom_command(TARGET Python PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_BINARY_DIR}/external/python/lib/python3.3
${CMAKE_CURRENT_BINARY_DIR}/stage/lib/python3.3)
add_custom_command(TARGET Python PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/external/python/lib/libpython3.so
${CMAKE_CURRENT_BINARY_DIR}/stage/lib/libpython3.so)
add_custom_command(TARGET Python PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/external/python/lib/libpython3.3m.so.1.0
${CMAKE_CURRENT_BINARY_DIR}/stage/lib/libpython3.3m.so.1.0)
add_custom_command(TARGET Python PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
libpython3.3m.so.1.0
${CMAKE_CURRENT_BINARY_DIR}/stage/lib/libpython3.3m.so)
#Python
set(PYTHON_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/external/python/include/python3.3m")
set(PYTHON_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/external/python/lib/libpython3.3m.so" "pthread" "m" "util" "readline")
#Build boost via External Project
ExternalProject_Add(
Boost
DEPENDS Python
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/external/boost
URL ${CMAKE_CURRENT_SOURCE_DIR}/extern/boost_1_51_0_mwave.tar.bz2
URL_MD5 fe203a243e451b4dd4754c7b283b1db9
UPDATE_COMMAND ./bootstrap.sh --with-libraries=python,system,thread,program_options
CONFIGURE_COMMAND ""
BUILD_COMMAND ./b2
BUILD_IN_SOURCE 1
INSTALL_COMMAND ""
)
#Boost (workaround until external project is working)
set(Boost_INCLUDE_DIRS "/opt/mwave/include")
set(Boost_LIBRARIES "/opt/mwave/lib/libboost_python3.so" "/opt/mwave/lib/libboost_program_options.so")
#OpenImageIO
set(OIIO_PATH "/opt/mwave/oiio/dist/linux64.debug")
find_package(OIIO REQUIRED)
#Qt4
find_package(Qt4 REQUIRED)
set(QT_USE_QTOPENGL TRUE)
include(${QT_USE_FILE})
add_definitions(${QT_DEFINITIONS})
#OpenGL
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
# Mwave app
set(HEADERS
"include/Application.h"
"include/ImageChannel.h"
"include/CompDag.h"
"include/Dag.h"
"include/Gui/DagView.h"
"include/Gui/DagScene.h"
"include/Gui/MainWindow.h"
"include/Gui/GLViewer.h"
"include/Gui/Nodes/GNodeEdge.h"
"include/Gui/Nodes/GNodeLabel.h"
"include/Gui/Nodes/GNodeCacheStatus.h"
"include/Gui/Nodes/GNode.h"
"include/Gui/Nodes/GRead.h"
"include/Gui/Nodes/GViewer.h"
"include/MwaveException.h"
"include/Nodes/Node.h"
"include/Nodes/Read.h"
"include/Nodes/Viewer.h"
"include/mwave.h"
"include/main.h"
"include/shaders.h"
)
set(QOBJECT_HEADERS
"include/Gui/QCompDag.h"
"include/Gui/QPythonEditor.h"
"include/Gui/ViewerWidget.h"
)
set(SOURCES
"src/Application.cpp"
"src/CompDag.cpp"
"src/main.cpp"
"src/mwave.cpp"
"src/Dag.cpp"
"src/Gui/DagView.cpp"
"src/Gui/DagScene.cpp"
"src/Gui/MainWindow.cpp"
"src/Gui/QPythonEditor.cpp"
"src/Gui/GLViewer.cpp"
"src/Gui/ViewerWidget.cpp"
"src/Gui/Nodes/GNode.cpp"
"src/Gui/Nodes/GNodeEdge.cpp"
"src/Gui/QCompDag.cpp"
"src/Nodes/Node.cpp"
"src/Nodes/Read.cpp"
"src/Nodes/Viewer.cpp"
)
QT4_WRAP_CPP(HEADERS_MOC ${QOBJECT_HEADERS})
## Compiler flags
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-O2") ## Optimize
set(CMAKE_CXX_FLAGS "-O3") ## Optimize More
endif()
include_directories(${PYTHON_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
${GLEW_INCLUDE_PATH}
${OPENGL_INCLUDE_DIR}
${OIIO_INCLUDE_DIR}
)
add_executable(mwave WIN32 ${HEADERS} ${HEADERS_MOC} ${SOURCES})
set_target_properties(mwave PROPERTIES OUTPUT_NAME mwave.bin)
target_link_libraries( mwave
${PYTHON_LIBRARIES}
${Boost_LIBRARIES}
${OIIO_LIBRARIES}
${GLEW_LIBRARY}
${OPENGL_LIBRARIES}
${QT_LIBRARIES})
cmake 2.6, which you are using, does not support ExternalProjects.
ExternalProjects are supported in cmake 2.8 series. Please move to cmake 2.8.
The below link clarifies
http://www.cmake.org/pipermail/cmake/2011-June/044993.html