【问题标题】:How to use libraries within my CMake project that need to be installed first?如何在我的 CMake 项目中使用需要首先安装的库?
【发布时间】:2015-10-23 16:55:45
【问题描述】:

我的 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 项目中使用需要先安装的库?

【问题讨论】:

  • My own libraries could be used by calling target_link_libraries() with their target names. However, linking isn't enough... - 您能否根据您提供的项目层次结构添加该用法的示例?
  • 当然,例如game要使用engineproject/engine/CMakeListst.txt 使用 add_library(engine ${SOURCE_FILES}) 声明库。然后,project/game/CMakeListst.txtadd_runtime(game ${SOURCE_FILES}) 声明它的可执行文件,并用target_link_libraries(game engine) 将库链接到它。是这个意思吗?

标签: cmake build-system


【解决方案1】:

您可以将您的项目分为三组:

  1. 您未在此超级项目中处理的外部依赖项
  2. 您正在处理的项目过于复杂,无法将它们添加为子目录,例如目标过多或其他原因。 (您的示例中似乎没有这样的项目。)
  3. 您正在处理的项目:这些将被添加为超级项目的子目录。

在配置超级项目之前,您需要在 #1 和 #2 组中配置、构建和安装项目:

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

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

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

  • 比如说,LIB1LIB2engine 的私有和公共外部依赖项,它们的配置模块导出老式的 LIB1_*LIB2_* 变量
  • LIB3LIB4engine 的私有和公共外部依赖项,它们的配置模块导出 LIB3LIB4 导入

公共和私有依赖是指在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)

引擎及其公共依赖项的包含目录将正确转发到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 答案只是将由install(EXPORT ...) 命令生成的&lt;lib&gt;-targets.cmake 脚本安装为配置模块:

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

engine 没有其他依赖项时,此解决方案很好。如果是,则需要在config模块的开头找到它们,应该手动编写。

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)

注意:CMakeFindDependencyMacro 已在 CMake 3.0 中引入。对于较旧的 CMake,您可以使用 find_package 而不是 find_dependency(对 QUIET 和 REQUIRED 选项的处理不会转发给依赖项)。

【讨论】:

  • 您好,感谢您的回答。在上周研究我的构建系统之后,我对您的答案有了更好的理解。你能详细说明你的最后一段吗? engine 是我项目的主库,game 只是一个测试功能的演示。其目的是让其他人可以将engine 用于真实游戏。
  • 您好,感谢您的回答。在上周研究我的构建系统之后,我对您的答案有了更好的理解。你能详细说明你的最后一段吗? engine 是我项目的主库,game 只是一个测试功能的演示。其目的是让其他人可以将engine 用于真实游戏。另外,target_ 函数是编写查找和配置脚本的新方法吗?
  • @danijar:我扩展了我的答案,我引用了另一个讨论这个问题的答案。如果您需要任何进一步的信息,请告诉我。 target_* 命令是指定目标要求(包括目录、编译选项、链接库)的新方法(它们已有 2 年历史)。 install(EXPORT ...) 命令可用于编写配置模块,引入新的target_* 命令更加方便。
  • 有没有办法不在配置文件中手动写入find_dependency?是否可以通过 CMakeLists 自动完成?
  • 不,没有自动的方法可以做到这一点。
【解决方案2】:

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}
)

engine/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)

【讨论】:

  • 能否请您演示如何在game/CMakeLists.txt 中导入它?
  • 我已将导入和导出代码添加到我的答案中。实际上,它基于cmake.org/cmake/help/v3.0/manual/… 的示例。
  • 谢谢。我想我了解这些步骤,并将在今天晚些时候尝试。但是,将顶级项目配置为EngineConfig.cmake 仅在构建项目后可用时,find_package 会不会出错?
  • 您使用execute_process 安装engine,是吗?如果是这样,EngineConfig.cmake 将在该命令之后立即可用。
  • 你写“我的库包括外部库的目录。”在你的问题。如果您使用add_subdirectory,则根本不需要导出,也不需要find_package() 调用。将engine/CMakeLists.txt 中的包含目录设置为target_include_directories(engine PUBLIC include) 并将game/CMakeLists.txt 中的链接设置为target_link_libraries(game engine) 自动包含目录engine/include 用于游戏。
【解决方案3】:

感谢@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的人。

【讨论】:

    猜你喜欢
    • 2021-04-13
    • 2018-12-30
    • 2020-03-05
    • 1970-01-01
    • 1970-01-01
    • 2019-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多