Create an external library only once with CMake

My C ++ projects include the source code of a third-party library (currently as a submodule of git).

This library is added to the project by our main CMakelists using add_subdirectory , and then the library is linked to the main purpose.

Here is the 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 lot of time, and since I do not change my code, I need it to be built only once (for each build configuration). But when I clean and rebuild my code, it also cleans up the library files and recompiles them.

I tried to set the CLEAN_NO_CUSTOM property in the library directory, but, according to the documentation, it works only for custom purposes of the command.

Is there a mechanism in CMake through which you can specify that this target library should be generated only once or alternatively not cleaned up with make clean ?

+6
source share
2 answers

As @ Tsyvarev said, in your case ExternalProject_Add better than add_subdirectory . add_subdirectory is good if you want the project to be an integral part of your build system, because the target you create can be used on the right side of the target_link_libraries() command, while the target created by ExternalProject_Add cannot.

This is the approach that I used in one of my projects. You are trying to find the right library and build it only if it is not found. I use the INTERFACE library to turn FOO_EXTERNAL into a target acceptable with target_link_libraries() .

 add_library(foo INTERFACE) find_package(foo ${FOO_VER}) if(NOT foo_FOUND) include(ExternalProject) include(GNUInstallDirs) ExternalProject_Add(FOO_EXTERNAL SOURCE_DIR "${FOO_SOURCE_DIR}" BINARY_DIR "${FOO_BINARY_DIR}" INSTALL_DIR "${FOO_INSTALL_DIR}" CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" "-DCMAKE_INSTALL_PREFIX=${FOO_INSTALL_DIR}" ) add_dependencies(foo FOO_EXTERNAL) set(foo_LIBRARY "${FOO_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}foo${CMAKE_STATIC_LIBRARY_SUFFIX}") set(foo_INCLUDE_DIR "${FOO_INSTALL_DIR}/include") endif() target_link_libraries(foo INTERFACE ${foo_LIBRARY}) target_include_directories(foo INTERFACE ${foo_INCLUDE_DIR}) 
+4
source

Based on @Hikke's excellent answer, I wrote two macros to make it easier to use external projects.

the 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 an external project that runs the entire external build step, and the second step sets up the necessary dependencies and defines the interface. Separation of the two is important because most projects have more than one interface / library.

Example

Let's say that I am GoogleTest as a submodule in my project, located in a subfolder of googletest . I can use the following interface to define the gtest and gtest_main , very similar to how Googletest 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) 

Then I can associate my goal with googletest in the same way as 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 enough template to simplify the actual external build process, even with projects with CMake support.

+3
source

All Articles