替代Subversion中的二进制文件

我的一些同事确信将构建工件提交到subversion存储库是个好主意。 这样的论点就是这样,测试机器上的安装和更新很容易 – 只需“svn up”!

我确信有很多反对这种不良做法的论据,但我能想到的只是“它占用更多空间”等蹩脚的行为。 没有做到这一点的最佳,杀手原因是什么? 还有什么其他方法呢?

这是针对Java代码的,如果这会产生影响。 一切都是从Eclipse编译的(没有自动PDE构建)。

当我说添加构建工件时,我的意思是提交看起来像这样:

"Added the new Whizbang feature" M src/foo/bar/Foo.java M bin/Foo.jar 

每个代码更改都有相应的生成的jar文件。

在我看来,代码存储库应该只包含源代码以及编译此源代码所需的第三方库(在构建过程中也可以使用某些依赖管理工具检索第三方库)。 生成的二进制文件不应与源代码一起签入。

我认为您的问题是您没有适当的构建脚本。 这就是为什么从源代码构建二进制文件涉及一些工作,如启动eclipse,导入项目,调整classpathes等…

如果有适当的构建脚本,可以使用如下命令来完成二进制文件:

 svn update; ant dist 

我认为不检查二进制文件和源代码的最重要原因是存储库的最终大小。 这将导致:

  • 版本控制系统服务器上的存储库更大,空间可能太少
  • 版本控制系统服务器和客户端之间的流量很大
  • 更新时间更长(想象一下你从互联网上做SVN更新……)

另一个原因可能是:

  • 源代码很容易比较,因此版本控制系统的许多function都很有意义。 但你不能轻易比较二进制文件……

此外,您的方法如上所述在我看来引入了很多开销。 如果开发人员忘记更新相应的jar文件怎么办?

首先,Subversion(以及现在所有其他人)不是源代码控制管理器(我一直认为SCM意味着软件配置管理),而是版本控制系统。 这意味着它们存储对您存储的内容的更改,它不必是源代码,它可以是图像文件,位图资源,配置文件(文本或xml),各种东西。 建立二进制文件不应被视为此列表的一部分只有一个原因,那是因为您可以重建它们。

但是,请想一想为什么要将已发布的二进制文件存储在那里。

首先,它是一个帮助您的系统,而不是告诉您应该如何构建您的应用程序。 让计算机为您工作,而不是对您。 那么如果存储二进制文件占用空间怎么办呢 – 你有数百GB的磁盘空间和超快的网络。 将二进制对象存储在那里并不是什么大问题(而十年前它可能是一个问题 – 这也许是人们将SCM中的二进制文件视为一种不良做法的原因)。

其次,作为开发人员,您可能习惯使用系统来重建任何版本的应用程序,但其他可能使用它的人(例如qa,test,support)可能不会。 这意味着您需要一个替代系统来存储二进制文件,实际上,您已经拥有了这样一个系统,它就是您的SCM! 利用它。

第三,您假设您可以从源重建。 显然,您将所有源代码存储在那里,但是您不存储编译器,库,sdks以及所需的所有其他相关位。 当有人出现时会发生什么,并问“你可以建立我2年前发布的版本,客户对该版本有问题”。 2年是如今的永恒,你甚至拥有当时使用的相同编译器吗? 当您检查所有源代码时发现新更新的sdk与您的源代码不兼容并且失败并出现错误时会发生什么? 您是否擦除了开发框并重新安装所有依赖项以构建此应用程序? 你能记住所有的依赖关系吗?!

最后一点是最重要的一点,为了节省几k的磁盘空间,如果不是数周的痛苦,你可能会花费几天时间。 (并且索德定律还说,无论你需要重建哪个应用程序都需要最晦涩,难以设置的依赖,你很高兴摆脱它)

因此,将二进制文件存储在SCM中,不要担心琐事。

PS。 我们将每个二进制文件都放在每个项目的“release”目录中,然后当我们想要更新一台机器时,我们使用一个特殊的“setup”项目,除了svn:externals之外什么都没有。 您导出安装项目并完成它,因为它获取正确的东西并将它们放入正确的目录结构中。

像Hudson这样的持续集成服务器可以存档构建工件。 它没有帮助你的论证“为什么不”,但至少它是另一种选择。

我敢肯定有一些重要的论据反对这种不良做法

您有错误的假设,即将“构建工件”提交给版本控制是一个坏主意(除非您错误地表达了您的问题)。 不是这样。

确实非常重要的是,在版本控制中保留你所谓的“构建工件”。 除此之外,您还应该保留编译器以及用于将源文件集转换为成品的任何其他内容。

从现在开始的五年内,您肯定会使用不同的编译器和不同的构建环境,无论出于何种原因,这些环境可能无法编译今天的项目版本。 修复遗留版本中的错误可能是一个简单的小改动,将变成将旧软件移植到当前编译器和构建工具的噩梦,只需重新编译具有单行更改的源文件。

所以,你没有理由害怕在版本控制中存储“构建工件”。 您可能想要做的是将它们放在不同的地方。

我建议将它们分开:

  ProjectName |--- /trunk | |--- /build | | |--- /bin <-- compilers go here | | |--- /lib <-- libraries (*.dll, *.jar) go here | | '--- /object <-- object files (*.class, *.jar) go here | '--- /source <-- sources (*.java) go here | |--- package1 <-- sources (*.java) go here | |--- package2 <-- sources (*.java) go here 

您必须配置IDE或构建脚本以将对象文件放在/ ProjectName / trunk / build / object中(甚至可以在... / source下重新创建目录结构)。

这样,您可以让用户选择签出/ ProjectName / trunk以获取完整的构建环境,或者/ ProjectName / trunk / source来获取应用程序的源。

在../build/bin和../build/lib中,您必须放置用于编译最终产品的编译器和库,用于将软件发送给用户的编译器和库。 在5年或10年内,您将拥有它们,可供您在某些情况下使用。

如果您知道原因, “将构建工件提交到subversion存储库”可能是一个好主意。

对于发布管理目的而言,这是一个好主意,更具体地说是:

1 /包装问题

如果构建工件不仅仅是exe(或dll或……),还包括:

  • 一些配置文件
  • 一些用于启动/停止/重启工件的脚本
  • 一些sql来更新你的数据库
  • 一些来源(压缩成文件)以方便调试
  • 一些文档(javadoc在文件中压缩)

那么将构建工件所有相关文件存储在VCS中是个好主意。
(因为它不再只是“重建”工件,而是“检索”所有那些将使该工件运行的额外文件)

2 /部署问题

假设您需要在不同的环境中部署许多工件(测试,认证,预生产,生产)。
如果:

  • 你会产生很多构建工件
  • 那些文物很难从头开始重新创建

然后在VCS中拥有这些工件是一个好主意,以避免重新创建它们。
您可以从环境到环境查询它们。

但你需要记住:

  • 1 /您无法存储您在VCS中创建的每个工件:您为持续集成目的构建的所有中间构建都不能存储在VCS中(或者您最终会得到一个包含许多无用版本的二进制文件的大型存储库)。
    只需要引用认证和生产目的所需的版本。
    对于中间构建,您需要一个外部存储库(maven或共享目录),以便快速发布/测试这些构建。

  • 2 /您不应该将它们存储在同一个Subversion存储库中,因为您的开发承诺(修订版号)比您的重要版本(认为值得认证和生产部署的版本)更频繁
    这意味着存储在第二个存储库中的工件必须具有标记(或属性)的命名约定 ,以便轻松检索构建它们的开发的修订版号。

根据我的经验,可以在SVN中存储Jars。
我认为最好将Jar文件保存在像Nexus这样的Maven-Repository中。
这也有好处,你可以使用像Maven或Ivy这样的依赖管理工具。

二进制文件,特别是您自己的二进制文件,也是第三方,在SVN等源代码控制工具中没有位置。

理想情况下,您应该有一个构建脚本来构建自己的二进制文件(然后可以使用许多精细的自动构建工具之一自动化,可以直接从SVN检查源代码)。

对于第三方二进制文件,您将需要一个依赖管理工具,如Maven2。 然后,您可以设置本地Maven存储库来处理所有第三方二进制文件(或仅依赖于公共文件)。 本地仓库也可以管理您自己的二进制文件。

将二进制文件放在主干或分支中肯定是矫枉过正。 除了像你提到的占用空间一样,它还会导致源和二进制文件之间的不一致 。 当您参考修订版1234时,您不想知道这是否意味着“修订版1234中源代码生成的内容”与“修订版1234中的二进制文件”。 避免不一致的相同规则适用于自动生成的代码。 您不应该对构建可以生成的内容进行版本控制。

OTOH我或多或少把二进制文件放在标签中 。 这样,其他项目很容易通过svn:externals使用其他项目的二进制文件,而无需构建所有这些依赖项。 它还使测试人员能够轻松地在标签之间切换,而无需完整的构建环境。

要在标记中获取二进制文件,可以使用以下过程:

  1. 看看干净的工作副本
  2. 运行构建脚本并评估任何测试结果
  3. 如果构建正常,则svn添加二进制文件
  4. 而不是提交到主干或分支,直接从您的工作副本标记,如下所示: svn copy myWorkingCopyFolder myTagURL
  5. 丢弃工作副本以避免意外将二进制文件提交到主干或分支

我们有一个tagbuild脚本来半自动化第3步和第4步。

一个很好的理由是快速获得在新机器上运行的可执行文件。 特别是如果构建环境需要一段时间来设置。 (加载编译器,第三方库和工具等)

在我的项目中,我通常使用服务器上的特殊工作副本构建后构建挂钩,即在从HTTP浏览器可到达的路径中。 这意味着,每次提交后,任何[可以阅读内部网络]的人都可以轻松下载相关的二进制文件。 没有一致性问题,即时更新+自动化测试的途径。

版本控制应该包含您需要做的所有事情:svn co然后构建。 它不应该有中间体或最终产品,因为这会破坏目的。 您可以在SVN中为结果创建一个新项目,并单独为二进制结果生成版本(如果需要,可以为版本和补丁)。

检查重要的二进制文件违反了源代码/ SVN的使用原则,即源代码控制中的文件应具有有意义的差异属性。

今天的源文件与昨天的源文件有很大的不同; 差异将产生一组对人类读者有意义的变化。 办公室前面的今天图片与昨天的办公室图片没有任何有意义的区别。

因为图像之类的东西不具备差异的概念,为什么要将它们存储在存在记录并存储文件之间差异的系统中?

基于版本的存储是关于存储文件更改的历史记录。 (例如)JPEG文件的数据没有任何变化的历史记录。 这些文件也完美地存储在目录中。

更实际的是,在SVN中存储大文件 – 构建输出文件 – 使得结帐速度变慢。 有可能滥用SVN作为通用二进制存储库。 一开始看起来一切都很好 – 因为二进制文件不多。 当然,文件数量会随着时间的推移而增加; 我见过需要几个小时才能查看的模块。

最好将大型关联二进制文件(和输出文件)存储在目录结构中,并从构建过程中引用它们。

您是说您在同一个存储库中拥有源代码和构建结果?

对于每日构建而言,这是一个很好的参数,版本化的构建脚本位于单独的存储库中。 存储库本身的二进制文件也不错,但源代码+构建结果对我来说很糟糕

如果你构建了几个二进制文件并且没有注意到某个地方的构建破坏,那么你最终会得到来自不同版本的二进制文件,并且你正准备自己进行一些微妙的bug追逐。

倡导每日单独版本的autobuild脚本,而不是仅仅针对二进制文件+代码

  • Subversion是源控制管理器 – >二进制文件不是源代码
  • 如果使用“svn up”命令更新生产,所有具有提交权限的开发人员都可以更新/修改/破坏生产?

替代方案:使用Hudson或Cruise Control等持续集成。

我认为,当二进制文件被输入VCS时,做坏事的感觉是由于资源经济和双重数据管理的缺点所致的基本思想,即永远不应该将多余的东西放在存档中。

这就是为什么:如果您可以从该特定版本的其他文件轻松地重建您的归档工作状态,例如简单的重新编译或安装标准设置,您就不应该提交此类二进制文件,而是提交类似README或INSTALL文件的内容。 如果未能重建的困难或风险太大,请做出承诺。