在Mercurial中组织具有共享依赖关系的项目的好方法是什么?

目前,我正在从传统的版本控制系统转移到我的小组项目mercurial。 作为我正在移动的代码types的一个例子,我有一个25 +项目的Visual Studio解决scheme,包含几个独立的应用程序区域,所有这些都依赖于通用代码。 纵观堆栈溢出,我发现的最接近的问题是这一个 ,但它只是提到版本控制。 我正在寻找进一步的build议,使用Mercurial来pipe理这些依赖关系的具体实现技术。

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

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

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

我有几个目标,build立我的mercurial仓库:

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

还有一点就是我有一台服务器,我为每个项目都有独立的存储库。

我看到了一些将这些项目铺设的方法。

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

这将使用基于url的subrepos(例如,在.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存储库中的每个主文件夹将包含一个子项目,每个项目区域一个。 依赖关系是相对的:例如,因为应用程序4需要公共库2,它将简单地使用相对path来引用该公共库。

这种方法的优点:

  • 每个图书馆都被拉下来一次。
  • Mercurial的subreos将确保在所有项目中自动使用相同版本的库,因为项目中只有一个版本的subrepo。
  • 很容易find每个资源。

这种做法的缺点:

  • 我不能在一个应用程序独立工作。 例如,如果我在App 2上工作,并且需要对公共库进行更改,则所有其他应用程序都必须立即进行这些更改。
  • 如果我自己推出应用程序回购协议,那么如果要构build它,我必须亲自弄清(或知道)需要的其他依赖回购协议。
  • 依赖关系并没有很强的分离 – 在任何地方插入一个新的特性是很诱人的,因为很容易得到所有的特性。

2.有依赖的subrepos完全包含。

在这种方法中,每个应用程序将拥有自己的存储库(与以前一样),但是这次也包含子库:一个用于其自己的源,另一个用于每个相关的子库。 然后,整个存储库将包含每个这些项目存储库,并知道如何构build整个解决scheme。 这看起来像下面这样:

 +-----------------------------------------------------------------------+ | 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 | | | | | +--------------+ | | | +--------------------+ | +-----------------------------------------------------------------------+ 

优点:

  • 每个应用程序都可以独立构build,彼此独立。
  • 依赖版本的库可以按每个应用程序进行跟踪,而不是全局跟踪。 它需要在项目中插入一个subrepo来添加一个新的依赖。

缺点:

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

3.不要将依赖关系作为subrepos包含进来 – 作为构build的一部分。

这种方法看起来很像方法1,只是通用库只能作为构build的一部分。 每个应用程序都知道它需要什么回购,并把它们放在公共位置。

优点:

  • 每个应用程序可以自己build立。
  • 通用库只需要拉一次。

缺点:

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

4.还有什么?

还有另一种处理方法吗? 更好的方法? 你尝试过什么样的方法,并且成功了,你尝试过什么,但又恨过了什么? 我目前正在倾向于1,但是应用程序独立性的缺乏在什么时候应该能够真正困扰我。 有没有办法让方法2的好分离没有大规模的重复代码拉和依赖维护的噩梦,而不必编写脚本来处理它(如在选项3)?

在我看来,依赖pipe理是项目组织的一个重要方面。 根据Mercurial的subreposfunction,您可以详细了解各种解决scheme,我同意您提供的所有优点/缺点。

我认为SCM不太适合依赖pipe理。 我更喜欢有一个专门的工具(这将是你的解决schemen°3)。

我目前的项目是在Java中。 它是用Apache Ant构build的,我首先将Apache Ivy设置为依赖关系pipe理工具。 最后,设置包括一个共享目录中的一些Ivyconfiguration文件,以及一个XML文件,其中列出了项目中每个模块的依赖关系。 常春藤可以被Ant目标调用,所以我在每个模块中添加了两个新的操作:“解决依赖关系”和“部署构build的工件”。 部署将buid(称为工件)的结果添加到共享目录中。 依赖关系parsing意味着可以及时parsing模块的依赖关系,并将parsing后的构件复制到模块源文件的“lib”文件夹中。

此解决scheme适用于C ++项目,因为Ivy并不特定于pipe理Java依赖项:工件可以是任何东西。 在C ++中,由模块产生的工件将是:

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

这不是一个完美的解决scheme:常春藤不容易build立,你仍然必须告诉你的构build脚本使用哪些依赖关系,而且你不能直接访问依赖关系的源代码来进行debugging。 但是你最终会得到独立的SCM仓库。

在我们的项目中,我们将Ant + Ivy转换为Apache Maven ,它负责构build和依赖关系pipe理。 工件被部署在Apache Archiva中,而不是共享文件夹。 这是一个巨大的改进,但它只适用于Java项目。

你想要做的是把每个项目放在自己的目录中,就像(1)中的一样。 然后,标记工作版本的依赖关系,并将标记保存在某个文件中,以便构build,如下所示:

应用1 / .dependencies:
 CommonLib1标签-20100515
 CommonLib2标签-20100510

应用2 / .dependencies:
 CommonLib1标签-20100510
 CommonLib2标签-20100510

然后,使用构build脚本基于特定标记构build库,并将这些构build库作为应用程序的派生对象。 如果构build时间是个问题,那么可以使用这些库的标记版本预先构build并保存在某个地方。

注意(devise数据库模式,对象模型或产品构build的devise原则是相同的):

  • 不要链接到其他项目的代码(打破封装)
  • 在存储库中没有多个库的副本(模块化)

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

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

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

所以如果提供了一组新的dll,应用程序只会获得Common Lib的更新。

但是升级lib文件夹并不是微不足道的,因为依赖的子DLL必须匹配正确的版本。