【问题标题】:How to handle a transitive dependency conflict using Git submodules and CMake?如何使用 Git 子模块和 CMake 处理传递依赖冲突?
【发布时间】:2017-08-16 03:31:59
【问题描述】:

我们有许多 Git 存储库,其中一些包含我们自己的代码,一些包含稍微修改的第三方库代码。一个简化的依赖图如下所示:

  executable_A
    |     |
    |     v
    |  library_B
    |     |
    v     v
   library_C

所以可执行文件对library_C 有两个依赖项,一个是直接的,一个是传递的。我希望使用 Git 子模块和 CMake 将这一切联系在一起,因此简化的目录结构如下所示:

executable_A/
  CMakeListst.txt
  library_B/
    CMakeLists.txt
    library_C/
      CMakeLists.txt
  library_C/
    CMakeLists.txt

如您所见,library_C 存储库作为子模块包含了两次。让我们假设两个子模块都指向同一个提交(任何关于如何强制执行的想法都会受到欢迎,但不是这个问题的主题)。

我们使用add_subdirectorytarget_link_librariestarget_include_directories 来管理这些相互依赖关系。很标准。

问题是如果你两次创建同名目标,CMake 不喜欢它,所以它会报错:

library_C/CMakeLists.txt:13 (add_library) 处的 CMake 错误:
add_library 无法创建目标“library_C”,因为另一个目标 同名的已经存在。现有目标是静态的 在源目录“.../library_B/library_C”中创建的库。
有关详细信息,请参阅政策 CMP0002 的文档。

我宁愿不删除executable_Alibrary_C 的直接依赖,因为它通过library_B 拉入的事实是library_B 的一个实现细节,不应依赖它。此外,一旦我们添加另一个依赖项,例如 executable_A --> library_D --> library_C,这种方法就会失效。

This question 是我能找到的最接近的,但更笼统一些,而且仍然没有答案。)

【问题讨论】:

  • 常用的方法是在使用add_subdirectory()进入项目之前检查是否存在某些特定于项目的目标(if(TARGET library_C))。
  • @Tsyvarev if(NOT TARGET library_c) 然后,当然。听起来是一种可行的方法。愿意将其发布为答案吗?

标签: c++ cmake git-submodules


【解决方案1】:

现在支持 CMake 3.10 及更高版本:

include_guard(GLOBAL)

类似于 C++ 中的#pragma once。

Craig Scott 的“Professional CMAKE, A Practical Guide”是一本关于 cmake 的好书,它对我解释了很多。仅在线提供: [https://crascit.com/professional-cmake/][1]

我不是 Craig,但他的书对于像我这样的新人来说是一项很棒的服务。

【讨论】:

  • 很高兴知道它的存在!我不认为它会解决这个特殊问题,因为子模块在源代码树中出现了两次。 CMAKE_CURRENT_LIST_FILE 对于每个化身都会有所不同。
【解决方案2】:

有几种方法可以检测和丢弃项目的包含,这些方法已经包含在主项目的其他部分中。

检查项目的目标是否存在

单个包含子项目的最简单模式是检查某个子项目的目标是否存在:

# When include 'C' subproject
if(NOT TARGET library_C)
    add_subdirectory(C)
endif()

(这里我们假设项目C定义了目标library_C。)

在这种有条件的包含之后,所有子项目的目标和功能将立即可供调用者使用保证

最好在所有地方使用这种模式(在executable_Alibrary_B)。这种在executable_A 中更改library_Blibrary_C 顺序的方式不会破坏正确性。

此模式可以重新设计以供子项目本身使用:

# At the beginning of 'C' project
cmake_minimum_required(...)
if(TARGET library_C)
    return() # The project has already been built.
endif()

project(C)
...

检查项目是否存在

在创建项目时,CMake 会为其定义多个变量,<PROJECT-NAME>_BINARY_DIR 就是其中之一。注意,这个变量是缓存的,所以当cmake被第二次调用时(例如,如果CMakeLists.txt的某些部分被改变了),这个变量在一开始就存在。

# When include 'C' subproject
if(NOT C_BINARY_DIR # Check that the subproject has never been included
    OR C_BINARY_DIR STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/C" # Or has been included by us.
)
    add_subdirectory(C)
endif()

此模式可以重新设计以供子项目本身使用:

# At the beginning of 'C' project
cmake_minimum_required(...)
if(NOT C_BINARY_DIR # Check that the project has never been created
    OR C_BINARY_DIR STREQUAL "${CMAKE_CURRENT_BINARY_DIR}" # Or has been created by us.
    project(C)
else()
    return() # The project has already been built
endif()

【讨论】:

  • 嘿 Tsyvarev,是否也可以制作多个目标。这种方法的问题是版本控制。如果项目 A 依赖于库 C 的 1.0.0 版本,而库 B 依赖于库 C 的 1.0.1 版本会发生什么。我的意思是你总是可以选择最高版本,但理想情况下,每个项目都应该需要指定的版本。你知道这怎么可能吗?
  • 不太可能在构建树中拥有相同的项目但版本不同。项目发展时目标名称很少更改,因此项目的两个版本将具有相同的目标名称,CMake 无法处理。为避免目标名称冲突,您可能拥有外部项目的 预构建 版本,或使用 ExternalProject_Add 构建它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 1970-01-01
  • 2010-10-24
  • 2021-12-25
  • 1970-01-01
  • 2018-08-14
  • 2016-08-21
相关资源
最近更新 更多