我是问题中引用的作者,该引用来自之前的回答 https://stackoverflow.com/questions/222827/how-do-you-organize-your-version-control-repository#304036.
杰森对我这样的简短陈述表示怀疑,并要求做出解释,这是正确的。当然,如果我完全解释了该答案中的所有内容,我就需要写一本书了。
迈克也正确地指出了问题之一svn:external
类似的功能是,目标源中的更改可能会破坏您自己的源,特别是如果该目标源位于您不拥有的存储库中。
在进一步解释我的评论时,让我首先说有“安全”的方法来使用svn:external
-类似功能,就像任何其他工具或功能一样。然而,我将其称为反模式 http://c2.com/cgi/wiki?AntiPattern因为该功能更有可能被滥用。根据我的经验,它一直被滥用,我发现自己不太可能以这种安全的方式使用它,也永远不会推荐这种使用。请进一步注意,我并没有贬低 Subversion 团队的意思——我喜欢 Subversion,尽管我计划转到 Bazaar。
此功能的主要问题是它鼓励并且通常用于将一个构建的源(“项目”)直接链接到另一个构建的源,或者将项目链接到二进制文件(DLL、JAR 等)。这取决于什么。这些用法都不明智,而且它们构成了反模式。
正如我在其他答案中所说,我相信软件构建的一个基本原则是每个项目都构建一个二进制或主要可交付成果。这可以被认为是原理的应用关注点分离 http://c2.com/cgi/wiki?SeparationOfConcerns到构建过程。对于一个项目直接引用另一个项目的来源尤其如此,这也违反了原则封装 http://c2.com/cgi/wiki?EncapsulationDefinition。这种违规的另一种形式是尝试创建构建层次结构,以通过递归调用子构建来构建整个系统或子系统。 Maven 强烈鼓励/强制执行这种行为,这是我不推荐它的众多原因之一。
最后,我发现有各种实际问题使得这个功能不受欢迎。其一,svn:external
有一些有趣的行为特征(但我暂时没有了解细节)。另一方面,我总是发现我需要这样的依赖项对我的项目(构建过程)显式可见,而不是作为某些源代码控制元数据隐藏。
那么,使用此功能的“安全”方式是什么?我认为这是仅由一个人临时使用的情况,例如“配置”工作环境的一种方式。我可以看到程序员可以在存储库中创建自己的文件夹(或为每个程序员一个),并在其中进行配置svn:external
链接到他们当前正在处理的存储库的各个其他部分。然后,签出该文件夹将创建所有当前项目的工作副本。当添加或完成项目时,svn:external
可以调整定义并适当更新工作副本。但是,我更喜欢一种不依赖于特定源代码控制系统的方法,例如使用调用签出的脚本来执行此操作。
根据记录,我最近一次接触到这个问题是在 2008 年夏天,当时一家咨询客户正在使用svn:external
在大规模上——一切都交叉链接以产生单一的主工作副本。他们基于 Ant 和 Jython(针对 WebLogic)的构建脚本是在此主工作副本之上构建的。最终结果是:没有任何东西可以独立构建,实际上有数十个子项目,但没有一个可以安全地单独检查/工作。因此,该系统上的任何工作首先需要检出/更新超过 2 GB 的文件(他们也将二进制文件放入存储库中)。完成任何事情都是徒劳的,我在尝试了三个月后离开了(还存在许多其他反模式)。
编辑:阐述递归构建 -
多年来(特别是过去十年),我为财富 500 强公司和大型政府机构构建了庞大的系统,涉及数十个子项目,这些子项目排列在多层目录层次结构中。我已经使用 Microsoft Visual Studio 项目/解决方案来组织基于 .NET 的系统,使用 Ant 或 Maven 2 来组织基于 Java 的系统,并且我已经开始使用 distutils 和 setuptools (easyinstall) 来组织基于 Python 的系统。这些系统还包括通常位于 Oracle 或 Microsoft SQL Server 中的大型数据库。
我在设计这些大型构建时取得了巨大的成功,以实现易用性和可重复性。我的设计标准是,新开发人员可以在第一天出现,获得一个新工作站(可能直接来自戴尔,只需安装典型的操作系统),获得一份简单的安装文档(通常只有一页安装说明),并能够在无人监督、无人协助的情况下,在半天或更短的时间内完全设置工作站并从源头构建完整的系统。调用构建本身涉及打开命令外壳,更改为源树的根目录,并发出一行命令来构建所有内容。
尽管取得了成功,但构建如此庞大的构建系统需要非常小心并严格遵守可靠的设计原则,就像构建大型业务关键型应用程序/系统一样。我发现一个关键部分是每个项目(生成单个工件/可交付成果)必须有一个构建脚本,该脚本必须有一个定义良好的接口(用于调用构建过程的部分的命令),并且它必须站立独立于所有其他(子)项目。从历史上看,构建整个系统很容易,但仅构建一个系统就很难/不可能。直到最近,我才学会仔细确保每个项目真正独立。
实际上,这意味着必须至少有两层构建脚本。最底层是生成每个可交付成果/工件的项目构建脚本。每个这样的脚本都驻留在其项目源代码树的根目录中(实际上,该脚本定义了其项目源代码树),这些脚本对源代码控制一无所知,它们期望从命令行运行,它们引用项目中相关的所有内容到构建脚本,它们根据一些可配置设置(环境变量、配置文件等)引用其外部依赖项(工具或二进制工件,没有其他源项目)。
第二层构建脚本也旨在从命令行调用,但它们了解源代码控制。事实上,第二层通常是使用项目名称和版本调用的单个脚本,然后它将指定项目的源检查到新的临时目录(可能在命令行上指定)并调用其构建脚本。
可能需要更多的变化来适应持续集成服务器、多个平台和各种发布场景。
有时需要第三层脚本来调用第二层脚本(它调用第一层),以便构建整个项目集的特定子集。例如,每个开发人员可能都有自己的脚本来构建他们今天正在处理的项目。可能有一个脚本来构建所有内容,以便生成主文档或计算指标。
无论如何,我发现尝试将系统视为项目层次结构会适得其反。它将项目相互联系起来,以便它们不能单独自由构建,也不能在任意位置(持续集成服务器上的临时目录)或以任意顺序构建(假设满足依赖关系)。通常,尝试强制建立层次结构会破坏可能尝试的任何 IDE 集成。
最后,构建庞大的项目层次结构可能会导致性能过于密集。例如,在 2007 年春天,我尝试使用 Ant 构建一个适度的源层次结构(Java 加 Oracle),但最终失败了,因为构建总是因 Java OutOfMemoryException 而中止。这是在具有 3.5 GB 交换空间的 2 GB RAM 工作站上,我已将 JVM 调整为能够使用所有可用内存。就代码量而言,应用程序/系统相对微不足道,但递归构建调用最终会耗尽内存,无论我给它多少内存。当然,它也需要很长时间才能执行(30-60 分钟很常见,然后才会中止)。我知道如何很好地进行调整,但最终我只是超出了工具的限制(在本例中为 Java/Ant)。
因此,帮自己一个忙,将构建构建为独立项目,然后将它们组合成一个完整的系统。保持轻便灵活。享受。
编辑:有关反模式的更多信息
严格来说,反模式是一种常见的解决方案,看起来它解决了问题,但实际上并没有,因为它留下了重要的空白,或者因为它引入了额外的问题(通常比原始问题更糟糕)。解决方案必然涉及一个或多个工具以及将它们应用于当前问题的技术。因此,将工具或工具的特定功能称为反模式是一种延伸,而且人们似乎正在检测这种延伸并做出反应——这很公平。
另一方面,由于关注工具而不是技术似乎是我们行业的常见做法,因此受到关注的是工具/功能(在 StackOverflow 上对问题进行的随意调查似乎很容易说明这一点)。我的评论以及这个问题本身都反映了这种做法。
然而,有时进行这种延伸似乎特别合理,例如在本例中。有些工具似乎“引导”用户使用特定的技术来应用它们,以至于有些人认为工具塑造思想 http://c2.com/cgi/wiki?ProgrammingLanguagesShapeThoughts(稍微改写)。我主要是本着这种精神建议svn:external
是一个反模式。
为了更严格地说明问题,反模式是设计一个构建解决方案,包括在源代码级别将项目捆绑在一起,或者隐式版本化项目之间的依赖关系,或者允许此类依赖关系隐式更改,因为这些都会调用非常负面的结果。的性质svn:external
类似的特征使得避免这些负面后果变得非常困难。
正确处理项目之间的依赖关系涉及解决这些动态以及基本问题,并且工具和技术会走上不同的道路。应该考虑的一个例子是Ivy http://ant.apache.org/ivy/,它以类似于 Maven 的方式提供帮助,但没有许多缺点。我正在研究 Ivy 和 Ant,作为我解决 Java 构建问题的短期解决方案。从长远来看,我希望将核心概念和功能融入到一个开源工具中,以促进多平台解决方案。