【问题标题】:CMake and finding other projects and their dependenciesCMake 并查找其他项目及其依赖项
【发布时间】:2013-04-30 05:16:27
【问题描述】:

想象以下场景:项目 A 是一个共享库,它具有多个依赖项(LibA、LibB 和 LibC)。项目 B 是一个依赖于项目 A 的可执行文件,因此也需要项目 A 的所有依赖项才能构建。

此外,这两个项目都是使用 CMake 构建的,不需要安装项目 A(通过“安装”目标)以便项目 B 使用它,因为这可能会给开发人员带来麻烦。

使用 CMake 解决这些依赖关系的最佳方法是什么?理想的解决方案应该尽可能简单(尽管并不简单)并且需要最少的维护。

【问题讨论】:

  • 为了未来的繁荣:cmake.org/Wiki/CMake/Tutorials/…
  • 该教程似乎没有解释如何处理导出外部库依赖项。唯一链接的库是项目构建的库。我需要知道如何告诉项目 B 项目 A 需要各种外部库,因此这些需要添加到项目 B 的链接步骤中。
  • 其实如果你是一个PC人,你应该尝试Linux或Linux子系统。这个平台最好的一点是 Linux 将为您安装所有依赖项。或者更好的是,它会建议您缺少哪些依赖项,并提供 Sudo apt-get install mydependencies,如何安装。真的很容易。
  • @Juniar,这确实简化和优化了很多事情,我同意。但是使软件的部署成为一场噩梦。我宁愿将我的软件全部放在一个包中并将它们全部部署在一起(甚至部分复制一些库)。更不用说维修问题了。每个盒子都有一组独特的库(在某种程度上)。
  • @OpalApps,依赖项可能安装在不同的路径和目录上,但是您仍然可以在编译时添加这些依赖项,或者配置/包含外部路径。它们不会全部安装在一个路径上 True,但是“sudo apt-get install”确实安装在特定目录上,只需切换它们即可。

标签: cmake


【解决方案1】:

简单。这是我脑海中的例子:

顶级CMakeLists.txt

cmake_minimum_required(VERSION 2.8.10)

# You can tweak some common (for all subprojects) stuff here. For example:

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES  ON)

if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
  message(SEND_ERROR "In-source builds are not allowed.")
endif ()

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_MAKEFILE   ON)

# Remove 'lib' prefix for shared libraries on Windows
if (WIN32)
  set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()

# When done tweaking common stuff, configure the components (subprojects).
# NOTE: The order matters! The most independent ones should go first.
add_subdirectory(components/B) # B is a static library (depends on Boost)
add_subdirectory(components/C) # C is a shared library (depends on B and external XXX)
add_subdirectory(components/A) # A is a shared library (depends on C and B)

add_subdirectory(components/Executable) # Executable (depends on A and C)

CMakeLists.txt in components/B:

cmake_minimum_required(VERSION 2.8.10)

project(B C CXX)

find_package(Boost
             1.50.0
             REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

include_directories(${Boost_INCLUDE_DIRS})

add_library(${PROJECT_NAME} STATIC ${CPP_FILES})

# Required on Unix OS family to be able to be linked into shared libraries.
set_target_properties(${PROJECT_NAME}
                      PROPERTIES POSITION_INDEPENDENT_CODE ON)

target_link_libraries(${PROJECT_NAME})

# Expose B's public includes (including Boost transitively) to other
# subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${Boost_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txt in components/C:

cmake_minimum_required(VERSION 2.8.10)

project(C C CXX)

find_package(XXX REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${XXX_DEFINITIONS})

# NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS.
include_directories(${B_INCLUDE_DIRS}
                    ${XXX_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} B
                                      ${XXX_LIBRARIES})

# Expose C's definitions (in this case only the ones of XXX transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose C's public includes (including the ones of C's dependencies transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${B_INCLUDE_DIRS}
                                 ${XXX_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txt in components/A:

cmake_minimum_required(VERSION 2.8.10)

project(A C CXX)

file(GLOB CPP_FILES source/*.cpp)

# XXX's definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})

# NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS.
include_directories(${C_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

# You could need `${XXX_LIBRARIES}` here too, in case if the dependency 
# of A on C is not purely transitive in terms of XXX, but A explicitly requires
# some additional symbols from XXX. However, in this example, I assumed that 
# this is not the case, therefore A is only linked against B and C.
target_link_libraries(${PROJECT_NAME} B
                                      C)

# Expose A's definitions (in this case only the ones of C transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose A's public includes (including the ones of A's dependencies
# transitively) to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${C_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txt in components/Executable:

cmake_minimum_required(VERSION 2.8.10)

project(Executable C CXX)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${A_DEFINITIONS})

include_directories(${A_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} A C)

为了清楚起见,这里是对应的源码树结构:

Root of the project
├───components
│   ├───Executable
│   │   ├───resource
│   │   │   └───icons
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───A
│   │   ├───include
│   │   │   └───A
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───B
│   │   ├───include
│   │   │   └───B
│   │   ├───source
|   |   └───CMakeLists.txt
│   └───C
│       ├───include
│       │   └───C
│       ├───source
|       └───CMakeLists.txt
└───CMakeLists.txt

有很多地方可以调整/定制或更改以满足某些需求,但这至少应该让您开始。

注意:我已经在几个大中型项目中成功使用了这种结构。

【讨论】:

  • 你是个 F**** 明星!你真的让我摆脱了持续近一天的严重头痛。非常感谢 1
  • 我很好奇,如果我从“可执行”目录调用 Cmake,它会编译吗?还是应该一直从项目的根目录编译?
  • 我认为它确实有一个小缺点,您在每个项目中定义了两次包含目录(一次用于set(...INCLUDE_DIRS,一次用于include_directories()),我发现很难维护(总是记住在 两个 位置添加新的包含依赖项)。您可以使用get_property(...PROPERTY INCLUDE_DIRECTORIES) 查询它们。
  • 这确实很棒,但是当A、B和C是完全独立的项目时,怎么能做到同样的事情呢? IE。我想构建A并将其导出以拥有自己的ProjectConfig.cmake文件,然后在B中使用find_package在系统上查找A,以某种方式获取A所依赖的所有库的列表,以便在构建时可以链接它们B.
  • @Ben Farmer,这确实是一个更复杂的话题,值得一篇大文章来正确解释。我从来没有足够的耐心在这里概述它。事实上,这就是我管理项目的方式,因为这实际上是 CMake 的最终(专业)用途和意图。为了管理所有这些,我有自己的 CMake 框架,它在幕后处理了很多事情。例如,您可以尝试构建我的 C++ HacksC++ Firewall 玩具项目。
【解决方案2】:

Alexander Shukaev 的开端很好,但还有很多事情可以做得更好:

  1. 不要使用包含目录。至少,使用target_include_directories。但是,如果您使用导入的目标,您可能甚至不需要这样做。
  2. 使用导入的目标。 Boost 示例:

    find_package(Boost 1.56 REQUIRED COMPONENTS
                 date_time filesystem iostreams)
    add_executable(foo foo.cc)
    target_link_libraries(foo
      PRIVATE
        Boost::date_time
        Boost::filesystem
        Boost::iostreams
    )
    

    这会处理包含目录、库等。如果您在 B 中的标头中使用了 Boost,那么请使用 PUBLIC 而不是 PRIVATE,这些依赖项将被传递到依赖于 B 的任何内容中。

  3. 不要使用文件通配符(除非您使用 3.12)。直到最近,文件 globbing 仅在配置期间有效,因此如果您添加文件并构建,它无法检测到更改,直到您明确重新生成项目。但是,如果您直接列出文件并尝试构建,它应该会识别出配置已过期并在构建步骤中自动重新生成。

这里有很好的谈话(YouTube):C++Now 2017: Daniel Pfeifer “Effective CMake"

其中涵盖了一个包管理器的想法,它允许您的根级别 CMake 与 find_packagesubdirectory 一起工作,不过,我一直在尝试采用这种思想,并且在使用 find_package 时遇到了很大的问题一切都像你的目录结构。

【讨论】:

    【解决方案3】:

    这也可以使用 CMake Cache 机制来实现相同(即共享项目特定变量):

    set(VAR "值" CACHE INTERNAL "")

    请参阅堆栈溢出问题How to share variables between different CMake files

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-13
      • 2014-07-01
      • 2021-12-25
      • 2021-08-19
      相关资源
      最近更新 更多