我应该将生成的代码存储在源代码pipe理中
这是我正在参与的辩论。我想获得更多意见和观点。
我们有一些在编译时生成的类来处理数据库操作(在这种情况下,使用SubSonic,但我不认为这是非常重要的问题)。 在Visual Studio中将生成设置为预生成步骤。 所以每当开发人员(或官方构build过程)运行构build时,都会生成这些类,然后编译到项目中。
现在有些人声称,将这些类保存在源代码控制中可能会导致混淆,以防您获得的代码与您自己的环境中生成的代码不匹配。
我想有一个方法来追溯代码的历史,即使它通常被视为一个黑匣子。
任何论据或反驳论点?
更新:我问这个问题,因为我真的相信有一个明确的答案。 纵观所有的答案,我可以高度肯定地说,没有这样的答案。 决定应该基于多个参数。 阅读下面的答案可以为您在决定这个问题时应该问自己的问题types提供非常好的指导。
由于上述原因,我不会在这一点上select一个被接受的答案。
将它保存在源代码控制中比它的价值更麻烦。
每次构build它时,都必须进行一次提交才能获得任何价值。
通常我们把生成的代码(idl,jaxb stuff等)放在源代码控制之外的地方,而且从来都不是问题
每次我想在自己的个人回购中显示源树的更改时,所有“生成的文件”都将显示为已更改并需要进行合作。
我宁愿有一个更清晰的修改列表,其中只包括已执行的实际更新,而不是自动生成的更改。
离开它们,然后在构build之后,为每个生成的文件添加一个“忽略”。
把它放在源代码控制。 拥有所有可用于未来开发人员的历史logging的优势超过了同步之后偶尔重build的轻微痛苦。
看看这种方式:你检查你的目标文件到源代码pipe理? 生成的源文件就像对象文件,库和可执行文件一样是构build工件。 他们应该被对待相同。 大多数人会争辩说,你不应该将生成的目标文件和可执行文件检查到源代码pipe理中。 相同的参数适用于生成的源。
如果您需要查看生成文件的历史版本,则可以将其同步到源的历史版本并重build。
检查生成的任何types的文件到源代码控制类似于数据库非规范化。 偶尔有这样做的原因(通常是为了性能),但是这应该非常小心,因为一旦数据非规范化,要保持正确性和一致性就变得困难了。
我会说,你应该避免将任何生成的代码(或其他工件)添加到源代码pipe理。 如果生成的代码对于给定的input是相同的,那么你可以检查出你想要比较的版本并生成代码进行比较。
我称DRY原则。 如果您在版本库中已经有用于生成这些代码文件的“源文件”,则不需要“两次”提交相同的代码。
另外,如果例如代码生成有一天会中断,你可能会以这种方式避免一些问题。
我真的不认为你应该检查他们。
当然,生成的代码中的任何更改都会变成噪声 – 环境之间发生变化,或者由于其他原因而发生更改 – 例如更改数据库。 如果你的数据库的创build脚本(或任何其他依赖项)在源代码控制,那么你为什么还需要生成的脚本?
不,有三个原因。
-
源代码是一切必要和足够的重现您的应用程序的快照,作为一些当前或以前的时间点 – 仅此而已。 这意味着有人负责检查所有内容。一般来说,我很乐意为我编写的代码负责,而不是因为我写的代码而产生的代码。
-
我不希望有人试图通过使用可能或不可能是最新的中间代码来尝试从主要来源快速构build构build(更重要的是,我不想承担责任)。诱惑一些人陷入一个没有意义的过程中,在基于部分构build的中间代码中debugging冲突。
-
一旦它在源代码控制,我接受一个责任。 它在那里,B。 它是当前的,和c。 它可靠地与其他所有内容集成在一起。 这包括当我不再使用它时将其删除。 责任越less越好。
一般的规则是否定的 ,但是如果需要时间来生成代码(因为数据库访问,Web服务等),那么你可能想要在源代码控制中保存一个caching的版本,并保存所有人的痛苦。
您的工具也需要了解这一点,并在需要时处理来自源代码pipe理的检查,太多的工具决定从源代码pipe理中检查出来,而没有任何理由。
一个好的工具将使用caching的版本,而不会触及它(也不会修改文件的时间步骤)。
另外你还需要在生成的代码里面加上大的警告,让人们不要修改文件,最上面的警告是不够的,你必须每十行重复一遍。
我们不存储生成的数据库代码,因为它是生成的,你可以从任何给定的版本从源文件中随意获取它。 存储它将像存储字节码等。
现在,您需要确保在给定版本中使用的代码生成器可用! 较新的版本可以生成不同的代码…
离开它。
如果你正在检查生成的文件,你做错了什么。 有什么不对,可能是你的构build过程效率低下,或者别的什么,但是我看不到它是一个好主意。 历史应该与源文件关联,而不是生成的。
对于那些最终试图解决差异的人来说,这只会让人头疼,find不再由构build生成的文件,然后删除它们等。
一个痛苦的世界正在等待那些签入生成文件的人!
在一些项目中,我将生成的代码添加到源代码控制,但这取决于。 我的基本原则是如果生成的代码是编译器的内在部分,那么我不会添加它。 如果生成的代码来自外部工具,如SubSonic在这种情况下,那么我会添加,如果源代码pipe理。 如果您定期升级组件,那么我想知道生成的源中的变化,以防出现错误或问题。
至于生成的代码需要检入,最坏的情况是手动区分文件并在必要时还原文件。 如果你正在使用svn,你可以在svn中添加一个pre-commit钩子来拒绝提交,如果这个文件没有真正改变的话。
到达有点晚…反正…
你会把编译器的中间文件放到源代码版本控制中吗? 在代码生成的情况下,根据定义,源代码是生成器的input,而生成的代码可以被认为是“真实”源和构build的应用程序之间的中间文件。
所以我会说:不要把生成的代码放在版本控制下,而是把生成器和它的input。
具体来说,我使用了一个我写的代码生成器:我从来不必在版本控制下维护生成的源代码。 我甚至会说,由于生成器达到了一定的成熟度,所以尽pipeinput(例如模型描述)发生了变化,我并不需要观察生成的代码的内容。
这真的取决于。 最终,目标是能够复制你有需要的东西。 如果你能够正确地重新生成你的二进制文件,就不需要存储它们。 但是您需要记住,为了重新创build您的东西,您可能首先需要使用您的确切configuration,这不仅意味着您的源代码,还包括您的构build环境,IDE,甚至其他库,发电机或东西,在你使用的确切configuration(版本)。
我在项目中遇到了麻烦,因为我们将构build环境升级到更新的版本,甚至升级到其他厂商,我们无法重新创build我们以前的确切的二进制文件。 这是一个真正的痛苦,当二进制文件被deplyed取决于一种散列,特别是在安全的环境中,重新创build的文件由于编译器升级或其他原因而有所不同。
所以,你会存储生成的代码:我会说不。 所发布的二进制文件或可交付成果,包括您用它们复制的工具将被存储。 然后,无需将其存储在源代码pipe理中,只需对这些文件进行备份即可。
有一种特殊情况,你想检查你生成的文件:当你可能需要build立在用于生成其他文件的工具不可用的系统上时。 Lex和Yacc代码就是这样一个典型的例子,我和其中一个合作。 因为我们开发的运行时系统必须在各种各样的平台和体系结构上构build和运行,所以我们只能依靠目标系统来拥有C和C ++编译器,而不是为我们的接口定义生成lexing / parsing代码所必需的工具翻译。 因此,当我们更改语法时,我们检查生成的代码来parsing它。
我会争辩的。 如果您正在使用持续集成过程来检查代码,修改内部版本号,构build软件并对其进行testing,那么只需将该代码作为存储库的一部分即可更简单易行。
此外,它是您对软件存储库的每个“快照”的一部分。 如果它是软件的一部分,那么它应该是存储库的一部分。
我会说是的,你想把它放在源代码控制之下。 从configurationpipe理的angular度来看,用于生成软件构build的所有东西都需要进行控制,以便可以重新创build。 我明白,生成的代码可以很容易地重新创build,但可以作出这样的说法,因为两个版本之间的date/时间戳是不同的。 在一些地方,如政府,他们需要很多次这是做了什么。
通常情况下,生成的代码不需要存储在源代码pipe理中,因为代码的修订历史logging可以通过生成代码的修订历史来追踪!
但是,这听起来OP是使用生成的代码作为应用程序的数据访问层,而不是手动编写一个。 在这种情况下,我会更改构build过程,并将代码提交到源代码pipe理,因为它是运行时代码的关键组件。 如果开发者需要为不同的分支使用不同版本的工具,这也可以消除构build过程中对代码生成工具的依赖。
看来代码只需要生成一次,而不是每个生成。 当开发人员需要添加/删除/更改对象访问数据库的方式时,应该再次生成代码,就像进行手动修改一样。 这加快了构build过程,允许对数据访问层进行手动优化,并且以简单的方式保留数据访问层的历史。
我(遗憾地)把很多派生的源头放在源代码控制之下,因为我远程工作的人谁不能build立一个适当的build设环境,或谁没有设置的技能,以便派生的来源是完全正确的。 (当谈到Gnu autotools时,我自己就是其中之一!我不能使用三种不同的系统,每种系统都与不同版本的自动工具一起工作 – 只有那个版本。)
这种困难可能更多地适用于兼职,志愿者,开放源代码项目,而不是付费项目,付费人员可以坚持统一的构build环境。
当你这样做时,你基本上只是在一个站点上构build派生文件,或者只在正确configuration的站点上构build派生文件。 你的Makefiles(或其他)应该被设置为注意他们正在运行的地方,并且应该拒绝重新派生源,除非他们知道他们正在一个安全的build站点运行。
在源代码pipe理中绝对有生成的代码,原因很多。 我重申了很多人已经说过的话,但是我想要做的一些原因是
- 通过源代码pipe理中的代码文件,您将可以在不使用Visual Studio预生成步骤的情况下编译代码。
- 当你在两个版本之间进行完整的比较时,最好知道生成的代码是否在这两个标签之间改变,而不必手动检查。
- 如果代码生成器本身发生更改,那么您需要确保生成的代码的更改正确更改。 即,如果您的生成器发生了更改,但输出不应该更改,那么当您提交代码时,之前生成的代码与生成的代码之间将没有区别。
configurationpipe理(其中版本控制只是其中一部分)的工作是能够执行以下操作:
- 知道哪些更改和错误修复已经进入每个交付的版本。
- 能够从原始源代码开始重现任何已发布的版本。 无论语言如何,自动生成的代码都不会被视为“源代码”。
第一个是确保当你告诉客户端或者最终用户“上周你报告的bug已经被修正,新的特性已经被添加了”时,他们不会在两个小时后回来,并且说“不”。 这也确保他们不会说“为什么这样做X?我们从来没有要求X”。
第二个意思是当客户端或最终用户在一年前发布的某个版本中报告错误时,您可以回到该版本,重现错误,修复错误,并且certificate您的修复已经消除了错误,而不是一些扰乱编译器和其他修复。
这意味着你的编译器,库等也需要成为CM的一部分。
所以现在要回答你的问题:如果你能做到以上所有的事情,那么你不需要logging任何中间表示,因为无论如何你都可以得到相同的答案。 如果你不能做所有的事情,那么所有的投注都将被closures,因为你永远不能保证两次做同样的事情并得到相同的答案。 所以你不妨把所有的.o文件放在版本控制之下。
在这里提出和反对都有很好的论据。 为了logging,我在Visual Studio中构build了T4生成系统,我们默认的开箱即用选项会导致生成的代码被检入。如果您不想签入,则必须努力工作。
对我来说,最重要的考虑因素是在input或发生器本身更新时区分生成的输出。
如果您没有检入输出,那么在升级生成器或修改input之前,必须复制所有生成的代码,以便将其与新版本的输出进行比较。 我认为这是一个相当乏味的过程,但是通过检查输出,将新输出与存储库区分开是一件简单的事情。
在这一点上,问“为什么你关心生成代码的变化?”是合理的。 (特别是与目标代码相比)。我相信有几个关键的原因归结于当前的技术水平,而不是任何固有的问题。
-
您制作与生成的代码紧密啮合的手写代码。 目前,obj文件并非如此。 当生成的代码发生变化时,很可悲的是一些手写代码需要更改以匹配。 在生成的代码中,人们通常不会看到与扩展点的高度向后兼容性。
-
生成的代码只是改变它的行为。 编译器不会容忍这种情况,但公平地说,应用程序级别的代码生成器正在针对不同领域的问题提供更广泛的可接受解决scheme。 看看你对以前的行为所做的假设现在是否被打破是很重要的。
-
你只是不能100%信任你的发电机从发布到发布的输出。 即使不是用编译器厂商的严格构build和维护,发生器工具也有很多价值。 版本1.0可能对你的应用程序来说是非常稳定的,但是现在可能1.1版本有一些小故障。 或者,你改变input值,发现你正在锻炼一个你以前没用过的发生器的新部件 – 可能你会对结果感到惊讶。
实质上,所有这些都归结为工具成熟度 – 大多数业务应用程序代码生成器与编译器甚至lex / yacc级别的工具已经有多年的关系。
我会将生成的文件从源树中移出 ,但将其放在单独的构build树中。
例如工作stream程
- 正常签入/退出/修改/合并源(没有任何生成的文件)
- 在适当的情况下,检查出源树到一个干净的构build树
- 在构build之后,检查所有必须出于审计/监pipe目的而出现的“重要”文件(“真实”源文件,可执行文件+生成的源文件)。 这给你一个所有适当的生成代码+可执行文件+任何时间的历史logging,与发布/testing快照等相关的时间增量,并从日常开发中分离出来。
在Subversion / Mercurial / Git / etc中可能有很好的方法将两个地方的真实源文件的历史联系在一起。
如果它是源代码的一部分,那么它应该放在源代码pipe理中,而不pipe它是由谁来生成的。 您希望您的源代码pipe理能够反映系统的当前状态,而不必重新生成它。
看起来双方都有非常强烈的说服力。 我会build议阅读所有最高投票答案,然后决定什么参数适用于您的具体情况。
更新:我问这个问题,因为我真的相信有一个明确的答案。 纵观所有的答案,我可以高度肯定地说,没有这样的答案。 决定应该基于多个参数。 阅读其他答案可以为您在决定这个问题时应该问自己的问题提供一个很好的指导。
双方都有合理的论点,很难就共同点达成一致。 版本控制系统(VCS)跟踪开发人员投入的文件,并假设VCS内部的文件是由开发人员手工制作的,开发人员对任何文件修订之间的历史和更改感兴趣。 这个假设使两个概念相等,“当我结账的时候,我想得到这个文件”。 和“我对这个文件的改变感兴趣”。
现在,双方的争论可以这样重述:
- “我想结账的时候,我想得到所有这些生成的文件,因为我没有在这台机器上生成它们的工具。”
- “我不应该把它们放到VCS中,因为我对这个文件的改变不感兴趣。”
幸运的是,这两个要求似乎并没有从根本上相冲突。 随着目前的VCSs的一些扩展,应该有可能有两个。 换句话说,这是一个虚假的困境。 如果我们思考一段时间,不难发现问题源于VCS的假设。 VCS应该将由开发者手工制作的文件与开发者手工制作的文件区分开来,但恰好在这个VCS中。 对于第一类文件,我们称之为源文件(代码)通常,VCS现在已经做得很好了。 就后一类而言,据我所知,VCSs还没有这样的概念。
概要
我将以git为例来说明我的意思。
-
git status
不应该默认显示生成的文件。 -
git commit
应该包含生成的文件作为快照。 -
git diff
不应该显示默认生成的文件。
PS
Git钩子可以作为一个解决方法,但是如果git本身支持,那将会很好。 gitignore
不符合我们的要求,因为忽略的文件不会进入VCSs。 enter code here
正确答案是“It Depends”。 这取决于客户的需求。 如果您可以将代码回滚到特定的版本,并在没有它的情况下支持任何外部审计,那么您仍然没有坚实的基础。 作为开发者,我们不仅要考虑“噪音”,痛苦和磁盘空间,还要考虑到我们负责产生知识产权的任务,这可能会产生法律上的后果。 你能否向法官certificate你能够重新build立一个网站,就像客户两年前看到的一样?
我不build议你保存或不保存gen'd文件,无论哪种方式,你决定如果你不涉及主题专家的决定,你可能是错的。
我的两分钱