使用 CMake 只构建一次外部库
Posted
技术标签:
【中文标题】使用 CMake 只构建一次外部库【英文标题】:Build external library only once with CMake 【发布时间】:2016-11-02 14:22:23 【问题描述】:我的 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
清理?
【问题讨论】:
由于您不使用第三方库的internal 目标,因此使用ExternalProject_Add
的方法似乎比add_subdirectory
更好。由于ExternalProject_Add
没有指定清理规则,CMake 不会尝试清理库。
【参考方案1】:
正如@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_PREFIXfoo$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)
【讨论】:
【参考方案2】:基于@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 驱动的项目。
【讨论】:
以上是关于使用 CMake 只构建一次外部库的主要内容,如果未能解决你的问题,请参考以下文章