使用CMake只构建一次外部库
Build external library only once with CMake
我的c++项目包含第三方库的源代码(目前作为git的子模块)。
该库由我们的主CMakelists通过使用add_subdirectory
添加到项目中,然后该库与主目标链接。
这是我当前的Cmake文件的简化版本:
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})
这个库需要很长时间来构建,因为我不改变它的代码,我只需要构建一次(每个构建配置)。但是当我清理和重建我的代码时,它也会清理库文件并重新编译它们。
我已经尝试在库的目录中设置属性CLEAN_NO_CUSTOM
,但是根据文档,它只适用于自定义命令目标。
在CMake中是否有一种机制,通过它可以指定该库目标只需要生成一次,或者不被make clean
清除?
正如@Tsyvarev所说,在你的情况下,ExternalProject_Add
比add_subdirectory
好。当您希望项目成为构建系统的重要组成部分时,add_subdirectory
是很好的,因为它创建的目标可以在target_link_libraries()
命令的右侧使用,而ExternalProject_Add
创建的目标则不能。
这是我在一个项目中使用的方法。您尝试查找所需的库,只有在没有找到时才构建它。我使用INTERFACE库将FOO_EXTERNAL转换为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})
根据@Hikke的精彩回答,我编写了两个宏来简化使用外部项目。
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)
第一个宏创建外部项目,它执行整个外部构建步骤,而第二个步骤设置必要的依赖项并定义接口。将两者分开是很重要的,因为大多数项目都有不止一个接口/库。
说我有GoogleTest作为我的项目中的子模块,位于googletest
子文件夹中。我可以使用下面的接口来定义gtest
和gtest_main
宏,非常类似于Googletest本身的做法。
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)
我可以把我的目标链接到googletest,就像以前一样:
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}
)
这应该提供足够的样板来简化实际的外部构建过程,即使使用cmake驱动的项目。
- 我是否需要在下一次转移时将所有权*转移回转移队列
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 在C++中一次将矢量值写入多个文件
- 循环中的条件:为什么每次都调用strlen(),而vector.size()只调用一次
- 为什么 zlib 放气初始化调用一次不起作用?
- 在一次迭代中从 txt 文件中读取多行
- 为什么无论你输入什么,这"while(cin.get(str,3))"只运行一次?
- 在头文件和 cpp 文件中使用一次 #pragma 时出现结构重定义错误
- 有没有办法一次声明相同类型的多个对象,并通过一个表达式立即使用相同的右值初始化它们?
- 高级选择排序 - 在一次迭代中搜索两个元素
- 具有相同特征的两个对象是否只在内存中存储一次?无论定义它们的函数是什么,都是不同的
- 如何仅使用一次固定<<设置精度(2)?或者至少恢复到默认行为?
- 什么是仅调用一次并调用参数的控制台应用
- 如何确保用户在一行上提示输入一次时输入名字和姓氏?
- (C++)虽然循环一次不起作用,但我引入了多个变量
- 为什么我的信号处理程序只执行一次?
- 如何使用C++一次读取整个二进制文件
- 为什么用于解析我读取的行的内部循环每隔一次迭代就会通过外部读取循环跳过
- 如何在没有外部标志的情况下只在循环中运行一次代码
- 使用CMake只构建一次外部库