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