使用CMake只构建一次外部库

Lou*_*uen 7 c++ build 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})
Run Code Online (Sandbox Code Playgroud)

这个库需要很长时间才能构建,因为我没有更改它的代码,所以我只需要构建一次(每个构建配置).但是当我清理并重建我的代码时,它还会清理库文件并重新编译它们.

我试图CLEAN_NO_CUSTOM在库的目录中设置属性,但根据文档,它只适用于自定义命令目标.

在CMake中是否存在一种机制,通过该机制可以指定此库目标只需要生成一次,或者不需要清除make clean

vat*_*mat 5

正如@Tsyvarev所说,您的情况ExternalProject_Add要好于add_subdirectoryadd_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})
Run Code Online (Sandbox Code Playgroud)


Ale*_*agh 5

根据@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)
Run Code Online (Sandbox Code Playgroud)

解释

第一个宏创建外部项目,它执行整个外部构建步骤,而第二个步骤设置必要的依赖项并定义接口。将两者分开很重要,因为大多数项目都有多个接口/库。

例子

假设我将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)
Run Code Online (Sandbox Code Playgroud)

然后我可以像以前一样将我的目标链接到 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}
)
Run Code Online (Sandbox Code Playgroud)

这应该提供足够的样板来简化实际的外部构建过程,即使是 CMake 驱动的项目。