如何使用 Git 子模块和 CMake 处理传递依赖冲突?

2021-12-26 00:00:00 git-submodules cmake c++

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

We have a number of Git repositories, some containing our own code and some containing slightly modified third-party library code. A simplified dependency graph looks like this:

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

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

So the executable has two dependencies on library_C, one direct and one transitive. I am hoping to tie this all together using Git submodules and CMake, so a simplified directory structure looks like this:

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

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

As you can see, the library_C repository is included as a submodule twice. Let's assume that both submodules are pointing at the same commit (any ideas about how to enforce that would be welcome, but are not the topic of this question).

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

We're using add_subdirectory, target_link_libraries and target_include_directories to manage these interdependencies. Pretty standard.

问题是CMake不喜欢你创建一个同名的目标两次,所以它会抱怨:

The problem is that CMake doesn't like it if you create a target with the same name twice, so it complains:

library_C/CMakeLists.txt 中的 CMake 错误:13 (add_library):
add_library 无法创建目标library_C",因为另一个目标同名已经存在.现有目标是静态的在源目录.../library_B/library_C"中创建的库.
如需了解详情,请参阅政策 CMP0002 的文档.

CMake Error at library_C/CMakeLists.txt:13 (add_library):
add_library cannot create target "library_C" because another target with the same name already exists. The existing target is a static library created in source directory ".../library_B/library_C".
See documentation for policy CMP0002 for more details.

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

I'd rather not remove the direct dependency of executable_A on library_C, because the fact that it is pulled in via library_B is an implementation detail of library_B that should not be relied on. Moreover, this approach will break down as soon as we add another dependency like executable_A --> library_D --> library_C.

(这个问题 是我能找到的最接近的,但更笼统,无论如何仍然没有答案.)

(This question is the closest I could find, but is a bit more general and remains unanswered anyway.)

推荐答案

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

There are several approaches for detect and discard inclusion of the project, which has already be included in some other parts of the main project.

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

The simplest pattern for single inclusion of subproject is checking existence of some subproject's target:

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

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

(Here we assume that project C defines target library_C.)

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

After such conditional inclusion all subproject's targets and functions will be immediately available for the caller with garantee.

最好在所有地方(在executable_Alibrary_B 中)使用此模式.这种改变executable_Alibrary_Blibrary_C的顺序的方式不会破坏正确性.

It is better to use this pattern in all places (in executable_A and library_B). Such a way changing order of library_B and library_C in executable_A doesn't break correctness.

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

This pattern can be reworked for use by subproject itself:

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

project(C)
...

<小时>

检查项目是否存在

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


Check project existence

When a project is created, CMake defines several variables for it, and <PROJECT-NAME>_BINARY_DIR is among them. Note, that this variable is cached, so when cmake is called the second time (e.g. if some of CMakeLists.txt has been changed), the variable exists at the very beginning.

# 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()

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

This pattern can be reworked for use by subproject itself:

# 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()

相关文章