2016-06-30 60 views
6

我的C++项目包含第三方库(当前作为git子模块)的源代码。用CMake构建外部库一次

我们的主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进行清理?

+2

由于您没有使用第三方库的*内部*目标,所以'ExternalProject_Add'方法好于'add_subdirectory'。由于'ExternalProject_Add'没有指定清晰的规则,所以CMake不会尝试清理该库。 – Tsyvarev

回答

4

由于@Tsyvarev说,在你的情况下,ExternalProject_Addadd_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}) 
3

基于@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子文件夹。我可以使用以下接口来定义gtestgtest_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驱动的项目。