My C++ projects includes the source code of a third-party library (currently as a git submodule).
This library is added to the project by our main CMakelists through the use of add_subdirectory
, and then the library is linked with the main target.
Here is a reduced version of my current Cmake file :
add_subdirectory(foo)
set(FOO_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/libfoo/libfoo.so)
add_executable(target main.cpp)
add_dependencies(target foo)
target_link_libraries(target ${FOO_LIBRARY})
This library takes a long time to build and, since I don't change its code I need it built only once (per build configuration). But when I clean and rebuild my code it also cleans the library files and recompile them.
I have tried to set the property CLEAN_NO_CUSTOM
in the library's directory, but according to the documentation it only works for custom command targets.
Is there a mechanism in CMake through which it is possible to specify that this library target needs to be generated only once, or alternatively not cleaned by make clean
?
Based on the excellent answer by @Hikke, I wrote two macros to simplify using external projects.
Code
include(ExternalProject)
#
# Add external project.
#
# \param name Name of external project
# \param path Path to source directory
# \param external Name of the external target
#
macro(add_external_project name path)
# Create external project
set(${name}_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${path})
set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${path})
ExternalProject_Add(${name}
SOURCE_DIR "${${name}_SOURCE_DIR}"
BINARY_DIR "${${name}_BINARY_DIR}"
CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
# These are only useful if you're cross-compiling.
# They, however, will not hurt regardless.
"-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}"
"-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}"
"-DCMAKE_AR=${CMAKE_AR}"
"-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_RC_COMPILER=${CMAKE_RC_COMPILER}"
"-DCMAKE_COMPILER_PREFIX=${CMAKE_COMPILER_PREFIX}"
"-DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}"
INSTALL_COMMAND ""
)
endmacro(add_external_project)
#
# Add external target to external project.
#
# \param name Name of external project
# \param includedir Path to include directory
# \param libdir Path to library directory
# \param build_type Build type {STATIC, SHARED}
# \param external Name of the external target
#
macro(add_external_target name includedir libdir build_type external)
# Configurations
set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${libdir})
# Create external library
add_library(${name} ${build_type} IMPORTED)
set(${name}_LIBRARY "${${name}_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_${build_type}_LIBRARY_PREFIX}${name}${CMAKE_${build_type}_LIBRARY_SUFFIX}")
# Find paths and set dependencies
add_dependencies(${name} ${external})
set(${name}_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${includedir}")
# Set interface properties
set_target_properties(${name} PROPERTIES IMPORTED_LOCATION ${${name}_LIBRARY})
set_target_properties(${name} PROPERTIES INCLUDE_DIRECTORIES ${${name}_INCLUDE_DIR})
endmacro(add_external_target)
Explanation
The first macro creates the external project, which does the entire external build step, while the second step sets the necessary dependencies and defines the interface. Separating the two is important, because most projects have more than one interface/library.
Example
Say I have GoogleTest as a submodule in my project, located in the googletest
subfolder. I can use the following interface to define the gtest
and gtest_main
macros, very similar to how Googletest itself does it.
add_external_project(googletest_external googletest)
add_external_target(gtest googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external)
add_external_target(gtest_main googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external)
I can then link my target to googletest much like before:
target_link_libraries(target_tests
gtest
gtest_main
# The CMAKE_THREAD_LIBS_INIT can be found from `find_package(Threads)`
# and is required for all but MinGW builds.
${CMAKE_THREAD_LIBS_INIT}
)
This should provide sufficient boilerplate to simplify the actual external build process, even with CMake-driven projects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With