如何在我的CMake项目中使用需要先安装的库?

时间:2015-07-31 22:03:44

标签: cmake build-system

我的CMake构建系统有问题。有CMakeLists.txt个文件定义运行时或库,或使用ExternalProjects_Add()下载和构建外部代码。由于依赖性,这些项目必须找到彼此。现在我希望在顶层有一个CMakeLists.txt,可以同时构建所有这些。为了找到一个项目,必须安装。但是在CMake中已经在配置时完成了项目。

repository
├─project
│ ├─game (Depends on engine, uses EngineConfig.cmake once installed)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ ├─src
│ │ └─textures
│ ├─engine (Depends on boost, uses built-in FindBoost.cmake)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ └─src
│ ├─boost (Not the source code, just an ExternalProject_Add call)
│ : └─CMakeLists.txt
│
├─build
│ ├─game
│ ├─engine
│ ├─boost (Source will be downloaded and built here)
│ : ├─download
│   ├─source
│   :
│
├─install
│ ├─game
│ │ ├─bin
│ │ └─textures
│ ├─engine
│ │ ├─include
│ │ │ └─engine
│ │ │   ├─EngineConfig.cmake (Needed to find the library)
│ │ │   :
│ │ │
│ │ └─lib
│ ├─boost (Layout is up to the external library)
│ : └─ ...
│
└─CMakeLists.txt (Calls add_subdirectory for all inside the project folder)

为每个项目运行CMake流程:使用execute_process(${CMAKE_COMMAND} ...),我可以在配置时配置和构建每个项目。但是,这意味着我总是必须在编辑代码后运行CMake,并且无法在IDE中为我生成的项目文件进行编译。

链接到CMake目标:为所有外部库运行CMake流程是可以的,因为我不会对它们进行操作。可以通过使用目标名称调用target_link_libraries()来使用我自己的库。但是,链接还不够。我的库包括外部库的目录。这些也必须提供给使用项目。

如何在我的CMake项目中使用需要先安装的库?

3 个答案:

答案 0 :(得分:5)

您可以将项目分为三组:

  1. 您未在此超级项目中使用的外部依赖项
  2. 您正在处理的项目但过于复杂而无法将其添加为子目录,例如目标太多或其他原因。 (您的示例中似乎没有这样的项目。)
  3. 您正在处理的项目:这些项目将作为超级项目的子目录添加。
  4. 在配置超级项目之前,您需要在组#1和#2中配置,构建和安装项目:

    • 您可以在运行超级项目的CMakeLists.txt之前执行此操作,例如,从shell脚本
    • 或者,正如您所提到的,使用CMakeLists.txt在超级项目的execute_process(${CMAKE_COMMAND} ...)内。您可以使用适当的find_package(... QUIET)命令的结果有条件地执行此操作。

    您需要确定组#3中的项目(如engine)是否仅用于将它们用作子目录的项目中,或者您打算将它们用作独立库,这些库是在自己的构建树中构建的。

    另外,您提到过:“我的库包含外部库的目录”。让我们介绍engine可以依赖的所有可能的库:

    • 说,LIB1LIB2engine的私有和公共外部依赖关系及其配置模块导出旧学校LIB1_*LIB2_*变量< / LI>
    • LIB3LIB4engine的私有和公共外部依赖项,其配置模块导出LIB3LIB4 导入 libraries

    公共和私有依赖关系是指在engine的接口上是否使用特定库。

    现在,如果engine仅用作子目录,则engine/CMakeLists.txt的相关部分为:

    add_library(engine ...)
    target_include_directories(engine
        PRIVATE
            ${LIB1_INCLUDE_DIRS}
        PUBLIC
            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
            ${LIB2_INCLUDE_DIRS})
    target_compiled_definitions(engine
        PRIVATE ${LIB1_DEFINITIONS}
        PUBLIC ${LIB2_DEFINITIONS})
    target_link_libraries(engine
        PRIVATE LIB3
        PUBLIC LIB4)
    
    repository/CMakeLists.txt中的

    add_subdirectory(engine)
    add_subdirectory(game)
    
    game/CMakeLists.txt中的

    add_executable(game ...)
    target_link_libraries(game engine)
    

    引擎及其公共依赖项的include dirs将正确转发到game

    如果engine也将在自己的构建树中构建(在另一个项目中),则需要将导出代码添加到engine/CMakeLists.txt,并且可能是调用find_package的自定义配置模块(或find_dependency)的依赖关系。有关详细信息,请参阅How to use CMake to find and link to a library using install-export and find_package?。该答案中未讨论的一个问题是在库的配置模块中找到库的依赖关系:

    引用的SO答案只是安装<lib>-targets.cmake命令生成的install(EXPORT ...)脚本作为配置模块:

    install(EXPORT engine-targets
        FILE engine-config.cmake
        DESTINATION lib/cmake/engine)
    

    engine没有进一步的依赖关系时,此解决方案很好。如果是,则需要在配置模块的开头找到它们,这应该手动编写。

    engine/engine-config.cmake:

    include(CMakeFindDependencyMacro)
    find_dependency(some-dep-of-engine)
    include(${CMAKE_CURRENT_LIST_DIR}/engine-targets.cmake)
    

    engine/CMakeLists.txt

    install(EXPORT engine-targets
        FILE engine-targets.cmake
        DESTINATION lib/cmake/engine)
    install(FILES engine-config.cmake
        DESTINATION lib/cmake/engine)
    

    注意:CMake 3.0中引入了CMakeFindDependencyMacro。使用较旧的CMake,您可以使用find_package而不是find_dependency(处理QUIET和REQUIRED选项不会转发到相关性)。

答案 1 :(得分:4)

engine项目导出库时,您需要指定其包含目录。以下代码是http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages提供的示例的简化。对路径进行调整以使用安装前缀install/engine进行构建和安装engine组件。

发动机/的CMakeLists.txt:

...
install(TARGETS engine EXPORT engineTargets
    DESTINATION lib
    INCLUDES DESTINATION include
)

set(ConfigPackageLocation lib/cmake/engine)

install(EXPORT engineTargets
    FILE EngineTargets.cmake
    DESTINATION ${ConfigPackageLocation}
)

install(FILES cmake/EngineConfig.cmake
    DESTINATION ${ConfigPackageLocation}
)

发动机/ cmake的/ EngineConfig.cmake:

include("${CMAKE_CURRENT_LIST_DIR}/EngineTargets.cmake")

这提供了导出目标的接口。因此,当它将通过可执行文件链接时,可执行文件将获得正确的INCLUDE_DIRECTORIES属性:

的CMakeLists.txt:

# Need for `find_package` to find `EngineConfig.cmake`.
set(CMAKE_PREFIX_PATH <path-pointed-to-install/engine>)

游戏/的CMakeLists.txt:

find_package(Engine)
add_executable(game ...)
target_link_libraries(game engine)

答案 2 :(得分:0)

感谢@Tsyvarev@tamas.kenez您的两个好答案。我最终使用了super-build pattern。顶级项目在配置时没有做太多工作。在构建时,它运行外部CMake进程来配置,构建和安装项目。

通常,这是使用ExternalProject_Add()而不是add_subdirectory()来实现的,以添加项目。我发现add_custom_command()工作得更好,因为它不会在后台执行其他任务,比如创建戳记文件等等。

# add_project(<project> [DEPENDS project...])
function(add_project PROJECT)
    cmake_parse_arguments(PARAM "" "" "DEPENDS" ${ARGN})
    add_custom_target(${PROJECT} ALL DEPENDS ${PARAM_DEPENDS})
    # Paths for this project
    set(SOURCE_DIR  ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT})
    set(BUILD_DIR   ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT})
    set(INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/${PROJECT})
    # Configure
    escape_list(CMAKE_MODULE_PATH)
    escape_list(CMAKE_PREFIX_PATH)
    add_custom_command(TARGET ${TARGET}
        COMMAND ${CMAKE_COMMAND}
            --no-warn-unused-cli
            "-DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH_ESCAPED}"
            "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH_ESCAPED}"
            -DCMAKE_BINARY_DIR=${BUILD_DIR}
            -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
            -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
            ${SOURCE_DIR}
        WORKING_DIRECTORY ${BUILD_DIR})
    # Build
    add_custom_command(TARGET ${TARGET}
        COMMAND ${CMAKE_COMMAND}
            --build .
            --target install
        WORKING_DIRECTORY ${BUILD_DIR})
    # Help later find_package() calls
    append_global(CMAKE_PREFIX_PATH ${INSTALL_DIR})
endfunction()

以下是两个辅助函数。我花了很长时间才找出将列表参数传递给其他CMake进程的正确方法,而不会将它们解释为多个参数。

# escape_list(<list-name>)
function(escape_list LIST_NAME)
    string(REPLACE ";" "\;" ${LIST_NAME}_ESCAPED "${${LIST_NAME}}")
    set(${LIST_NAME}_ESCAPED "${${LIST_NAME}_ESCAPED}" PARENT_SCOPE)
endfunction()

# append_global(<name> value...)
function(append_global NAME)
    set(COMBINED "${${NAME}}" "${ARGN}")
    list(REMOVE_DUPLICATES COMBINED)
    set(${NAME} "${COMBINED}" CACHE INTERNAL "" FORCE)
endfunction()

唯一的缺点是每个项目都需要有一个安装目标。因此,您需要向没有安装命令的项目添加虚拟安装命令,如install(CODE ""),例如,那些只是致电ExternalProject_Add的人。