为什么重新编译时二进制输出不相等?

36

我正在使用一个构建脚本来编译多个C#项目。二进制输出被复制到一个结果文件夹中,覆盖先前版本的文件,然后添加/提交到Subversion。

我注意到即使在没有任何源代码或环境变化的情况下,编译的二进制输出也是不同的。这怎么可能?难道相同的输入不应该得到完全相同的二进制结果吗?

我没有故意在任何地方使用任何特殊的时间戳,但是编译器(Microsoft,包含在.NET 4.0中的那个)是否可能会自己添加时间戳?

我问这个问题的原因是我正在将输出提交给Subversion,由于我们的构建服务器的工作方式,检入的更改会触发重建,导致再次修改的二进制文件循环地被检入。


3
在我看来,对源代码和二进制文件同时进行Subversion控制似乎是多余的。如果您不仅仅只在Subversion下保存源代码,那会更好一些。您可以通过方案(solutions)按需聚合组件,并避免版本化构建输出(我在SourceSafe环境下也采用类似方法)。 - Alex
1
@alex 由于项目规模庞大,以及我们团队的工作方式,对我来说这并不容易,但我一定会尝试朝那个方向努力。 - mafu
1
我创建了一个向微软的请求,请点赞:https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/15167409-only-build-if-referenced-libraries-signatures-chan - Mr. TA
1
Alex Nolasco的回答包含了关于确定性构建的文档链接。你还需要什么? - Simon Mourier
4个回答

33

更新:

自2015年以来,编译器团队一直在努力清除编译工具链中的非确定性源,使得相同的输入确实产生相同的输出。请参见Roslyn github上的“概念-确定性”标签以获取更多详细信息。


更新: 这个问题是我在2012年5月的博客主题。感谢提出这个好问题!


这怎么可能?

非常容易。

难道二进制结果不应该对于相同的输入完全相等吗?

绝对不是这样的。事实恰恰相反。每次运行编译器都应该得到不同的输出。否则你怎么知道你已经重新编译了呢?

C#编译器在每次编译时嵌入一个新生成的GUID到一个程序集中,从而保证没有两个编译产生完全相同的结果。

此外--即使没有GUID,编译器也不会保证两个“相同”的编译会产生相同的结果。

特别是,元数据表填充的顺序高度依赖于文件系统的细节;C#编译器开始按照给定文件的顺序生成元数据,并且这可以被多种因素微妙地改变。

由于我们的构建服务器工作方式的原因,提交的更改会触发重新编译,导致修改后的二进制文件再次在循环中被检入。

如果我是你,我会修复这个问题。


2
我记得gcc生成的二进制文件是相同的(不确定是否有保证),所以.NET的行为让我感到惊讶。不过这是有道理的。 - mafu
16
人们有时确实会对此感到惊讶。例如,审查赌博机代码的政府机构期望能够从供应商那里获得源代码和二进制文件,并重新编译源代码以获取相同的二进制文件作为证明二进制文件与源代码匹配。不幸的是,证明二进制文件与其源代码匹配并不是C#团队曾经声称提供的服务,因此他们正在寻找另一种解决方案。 - Eric Lippert
1
@EricLippert:非常有趣。我在网上搜索中没有找到关于你的例子的任何信息。是否有在线文章介绍这个问题? - Brian
2
我偶尔会想知道编译器是否保留了每个“Type”所发出的GUID属性到元数据中...我见过GUID在重新编译时更改的情况,也见过保持不变的情况。 - LBushkin
4
有了/deterministic开关的可用性,此答案现在已经过时。 - Shiv
显示剩余5条评论

13

是的,编译器会包含一个时间戳。此外,在某些情况下,编译器会自动递增程序集版本号。我没有看到任何保证二进制结果应该是相同的。

(请注意,如果源代码已经在Subversion中,通常我会避免在其中添加二进制文件。我通常只包括第三方库的发布版本。不过这取决于你具体在做什么。)


有没有简单的方法来避免这种情况? - mafu
二进制文件被用作同一代码库中不同项目(实际上是很多项目)的输入。 - mafu
你可以在输出目录之外的其他地方复制二进制文件,并链接到该副本,在版本控制中放置二进制文件的副本(但不包括输出目录本身)。当然,如果你这样做,你必须决定谁负责更新那个二进制文件夹。如果两个项目非常相关,你也可以将项目本身添加到解决方案文件中,然后添加对该项目的引用。我认为VS可能会智能处理该场景。 - Brian

9

1
我看到这个在VS2015的MSBuild中可用。这也可以在devenv.com上完成吗?我似乎找不到一个开关。我问的原因是MSBuild不支持像Installer Projects这样的扩展,但devenv命令行却支持。 - Shiv
3
根据https://gist.github.com/aelij/b20271f4bd0ab1298e49068b388b54ae,您可以通过向.csproj添加一个属性组来实现。 true - Shiv

2
据我所知,只有微软的二进制文件在每次编译后才会有所不同。20年前并非如此。假设源代码相同,在每次编译后,微软的二进制文件都是一样的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接