目前,我正在从旧版版本控制系统迁移,并将我的团队的项目迁移到 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)?