62

目前,我正在从旧版版本控制系统迁移,并将我的团队的项目迁移到 mercurial。作为我正在移动的代码类型的一个示例,我有一个 25 多个项目的 Visual Studio 解决方案,其中包含几个独立的应用程序区域,它们都依赖于公共代码。查看 Stack Overflow,我发现最接近的问题是这个,但它只提到了版本控制。我正在寻找更多关于使用 Mercurial 管理这些依赖项的具体实现技术的建议。

依赖项的简化视图如下所示。(这仅用于说明和示例;实际的依赖关系要复杂得多,但本质上相似。)

                     Common Lib 1
                    /      |      \
                ----       |       -----   
               /           |        \   \
            App 1    Common Lib 2    \ App 2
                       /   |   \      \
                -------    |    ------ |
               /           |          \|
             App 3       App 4      App 5

Common Lib 模块将是共享代码 - 这将是一个 DLL 或 SO 或其他将在所有应用程序之间同时使用的库 - 在编译和运行时。否则应用程序将能够彼此独立运行。

在设置我的 mercurial 存储库时,我有几个目标:

  • 为每个重要的应用程序或组件组提供自己的存储库。
  • 使每个存储库自包含。
  • 使项目的总和自包含。
  • 使一次构建整个代码库变得容易。(最终,所有这些程序和库最终都在一个安装程序中。)
  • 把事情简单化。

另一点是我设置了一个服务器,其中每个项目都有单独的存储库。

我看到了几种布置这些项目的方法。

1. 创建一个包含所有内容的“Shell”存储库。

这将使用基于 url 的子存储库(例如,在 .hgsub 中,我会做类似的事情。)布局后,它将如下所示:App1 = https://my.server/repo/app1

+---------------------------+
| Main Repository           |
| | +---------------------+ |
| +-| Build               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 1        | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 2        | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 1               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 2               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 3               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 4               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 5               | |
|   +---------------------+ |
+---------------------------+

shell 存储库中的每个主文件夹都将包含一个子存储库,每个项目区域都有一个子存储库。依赖关系是相对的:例如,由于 App 4 需要 Common Lib 2,它会简单地使用相对路径来引用该公共库。

这种方法的优点:

  • 每个库被拉下一次且仅一次。
  • Mercurial 的 subreos 将确保在所有项目中自动使用相同版本的库,因为项目中仅存在该 subrepo 的一个版本。
  • 很容易找到每个资源。

这种方法的缺点:

  • 我无法独立开发应用程序。例如,如果我在应用程序 2 上工作,并且它需要更改公共库,那么所有其他应用程序都必须立即进行这些更改。
  • 如果我自己提取一个 App 存储库,如果我想构建它,我必须手动弄清楚(或知道)它需要哪些其他依赖存储库。
  • 依赖关系没有被严格分离——在任何地方插入一个新特性是很诱人的,因为它很容易获得所有特性。

2. 完全包含依赖的子存储库。

在这种方法中,每个应用程序都有自己的存储库(如前所述),但这次也包含子存储库:一个用于自己的源,一个用于每个依赖的子存储库。然后,一个整体存储库将包含这些项目存储库中的每一个,并且知道如何构建整个解决方案。这将如下所示:

+-----------------------------------------------------------------------+
| Main Repository                                                       |
| +--------------------+ +--------------------+ +--------------------+  |
| | Build              | | Common Lib 1       | | Common Lib 2       |  |
| +--------------------+ | | +--------------+ | | | +--------------+ |  |
|                        | +-| Lib 1 Source | | | +-| Common Lib 1 | |  |
|                        |   +--------------+ | | | +--------------+ |  |
|                        |                    | | | +--------------+ |  |
|                        |                    | | +-| Lib 2 Source | |  |
|                        |                    | |   +--------------+ |  |
|                        +--------------------+ +--------------------+  |
| +--------------------+ +--------------------+ +---------------------+ |
| | App 1              | | App 2              | |  App 3              | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | +-| Common Lib 1 | | | +-| Common Lib 1 | | |  +-| Common Lib 2 | | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | +-| App 1 Source | | | +-| App 2 Source | | |  +-| App 3 Source | | |
| |   +--------------+ | |   +--------------+ | |    +--------------+ | |
| +--------------------+ +--------------------+ +---------------------+ |
| +--------------------+ +--------------------+                         |
| | App 4              | | App 5              |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | +-| Common Lib 2 | | | +-| Common Lib 1 | |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | +-| App 4 Source | | | +-| Common Lib 2 | |                         |
| |   +--------------+ | | | +--------------+ |                         |
| +--------------------+ + | +--------------+ |                         |
|                        | +-| App 5 Source | |                         |
|                        |   +--------------+ |                         |
|                        +--------------------+                         |
+-----------------------------------------------------------------------+

优点:

  • 每个应用程序都可以自己构建,彼此独立。
  • 可以按应用而不是全局跟踪库的依赖版本。它需要将子存储库插入项目以添加新依赖项的显式行为。

缺点:

  • 在进行最终构建时,每个应用程序可能使用不同版本的共享库。(可能需要编写工具来同步公共库子存储库。Eww。)
  • 如果我想构建整个源代码,我最终会多次拉下共享库。在 Common Lib 1 的情况下,我必须拉它八(!)次。

3. 根本不要将依赖项包含为子存储库——将它们作为构建的一部分引入。

这种方法看起来很像方法 1,除了公共库只会作为构建的一部分被提取。每个应用程序都会知道它需要什么存储库,并将它们放在公共位置。

优点:

  • 每个应用程序都可以自己构建。
  • 公共库只需要拉一次。

缺点:

  • 我们必须跟踪每个应用程序当前使用的库版本。这重复了 subrepo 功能。
  • 我们必须建立一个基础设施来支持这一点,这意味着更多的东西进入构建脚本。啊。

4. 还有什么?

有没有其他的处理方式?更好的方法?您尝试过哪些方法并成功了,您尝试过哪些方法但讨厌?我目前倾向于 1,但缺乏应用程序独立性,当它应该能够时,真的很困扰我。有没有办法在没有大量重复代码拉取和依赖维护噩梦的情况下很好地分离方法 2,而不必编写脚本来处理它(如选项 3)?

4

3 回答 3

6

在我看来,依赖管理是项目组织的一个重要方面。您基于 Mercurial 的 subrepos 功能详细介绍了各种解决方案,我同意您给出的所有优点/缺点。

我认为 SCM 不太适合依赖管理。我更喜欢为此使用专用工具(这将是您的解决方案 n°3)。

我目前的项目是Java。它是用Apache Ant构建的,我首先将Apache Ivy设置为依赖项管理工具。最后,设置由共享目录中的一些 Ivy 配置文件和一个列出项目每个模块的依赖关系的 XML 文件组成。Ant 目标可以调用 Ivy,因此我在每个模块中添加了两个新操作:“解决依赖关系”和“部署构建的工件”。部署将 buid 的结果(称为工件)添加到共享目录中。依赖关系解析意味着传递地解析模块的依赖关系,并将解析的工件复制到模块源的“lib”文件夹中。

该解决方案适用于 C++ 项目,因为 Ivy 并非专门用于管理 Java 依赖项:工件可以是任何东西。在 C++ 中,模块产生的工件将是:

  1. 运行时的 so/dll
  2. 编译时的头文件。

这不是一个完美的解决方案:Ivy 不容易设置,您仍然必须告诉构建脚本要使用哪些依赖项,并且您无法直接访问依赖项的源以进行调试。但是您最终会得到独立的 SCM 存储库。

在我们的项目中,我们从 Ant+Ivy 切换到Apache Maven,它负责构建和依赖项管理。工件部署在Apache Archiva而不是共享文件夹中。这是一个巨大的改进,但它只适用于 Java 项目。

于 2011-05-17T11:36:34.813 回答
2

What you want to do is have each project in its own directory like in (1). Then you tag working versions of your dependencies and save the tag in some file for build like:

App1/.dependencies:
CommonLib1 tag-20100515
CommonLib2 tag-20100510

App2/.dependencies:
CommonLib1 tag-20100510
CommonLib2 tag-20100510

Then you use your build scripts to build the libraries based on the specific tag and include those built libraries as derived objects for your applications. If build time is an issue, you can have the tagged version that are in use for those libraries pre-built and saved somewhere.

Note (design principles are same if designing database schema, object model or product build):

  • Do not link to the code in other projects (breaks encapsulation)
  • Do not have multiple copies of the libraries in your repository (modularity)
于 2011-05-17T08:15:24.173 回答
0

我们使用颠覆解决了类似的问题。

每个应用程序Common Lib都有自己的存储库。

每个应用程序都有一个Libs包含依赖 dll 的目录。

因此,该应用程序仅会更新Common Lib是否提供了一组新的 dll。

但是升级 lib 文件夹并非易事,因为依赖的子 dll 必须匹配正确的版本。

于 2011-05-18T12:13:36.637 回答