Java 8 编译器能否被强制创建可重复的类文件?

7

我的雇主有一个需求,需要使Java构建能够完全一致。我知道由于归档顺序和时间戳等原因,制作可复制的JAR文件存在困难,但现在我只是讨论类文件。

我在Mac和Linux上使用Java 8u65编译了相同代码的版本。这两个版本的类文件二进制上是不同的。这两个类都反编译回了相同的源代码;要看到差异需要使用javap反汇编器。

源代码似乎是:

final TrustStrategy acceptingTrustStrategy =
              (X509Certificate[] chain, String authType) -> true;

在一个构建中,结果是:

private static boolean lambda$restTemplate$38(java.security.cert.X509Certificate[], java.lang.String) throws java.security.cert.CertificateException;
        Code:
           0: iconst_1
           1: ireturn
     

另一方面,它是:

private static boolean lambda$restTemplate$15(java.security.cert.X509Certificate[], java.lang.String) throws java.security.cert.CertificateException;
        Code:
           0: iconst_1
           1: ireturn

匿名的lambda表达式在其中被赋予了不同的数字名称(如lambda$restTemplate$15lambda$restTemplate$38)。
看起来当我在同一台主机上重新构建时,我会得到相同的字节。当主机不同时,数字会改变;两个Linux主机生成了不同的字节。
是什么决定了这些数字呢?是否有一种方法来强制每次编译在这个位置使用相同的数字,从而产生相同的类文件?还是Java 8类文件编译是不确定的?

在同一位置看到不同的计数器值表示Lambda表达式的编译顺序不同。这里保留了顺序:http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java#l1118 - Koshinae
上面的“不同计数器值”注释几乎是一个答案。计数器值是否可以以任何方式进行控制?它是针对每个源文件还是每个javac调用进行重置? - Robert Mandeville
@RobertMandeville:如果不知道您如何构建代码,诊断可能有点困难,但是有一些建议可以检查。大多数文件系统以任意顺序返回目录列表,这通常取决于磁盘上的文件/目录条目的物理组织方式。当您在文件夹中列出文件时,Posix系统不定义文件的排序方式,因此您必须进行排序/请求特定的排序方式(例如按字母数字顺序)。 - Lie Ryan
3个回答

3

我没有深入研究,但这篇文章谈到了Java中的可再生构建,并且reproducible-builds提供了一些工具来帮助实现可再生构建(和类)。

您可能正在寻找的链接是Reproducible Build Maven Plugin,专门为Java开发,旨在尝试“从生成的构件中删除不可再生的数据”。


我看到了这个网站和这个工具。不幸的是,我没有看到任何关于可重复类的内容。将类文件制作成可重复的JARs/WARs是一个已知且可以解决的问题,也是该插件明确解决的问题。自从Java 8引入Lambda表达式以来,互联网上对于可重复类构建的讨论变得异常安静;它们曾经是理所当然的。 - Robert Mandeville

3
编译器负责统计lambda表达式的数量,并在遇到其他lambda表达式时增加计数。
如果编译器以相同顺序读取文件,则应该生成相同的编译类。
在任何情况下,由于您自己构建代码,因此可以将lambda表达式更改为匿名类声明。
编辑:我刚刚注意到您指出类是在两个不同的操作系统上构建的。这可能会导致代码编译阶段的差异。为了进行可重复构建,必须在相同体系结构上执行构建。您为什么不能部署在一个体系结构上构建的构件(MacOS或Linux)?

我可能能够强制编译器按照特定顺序读取文件。但我怀疑我无法让我的开发人员将所有的lambda表达式转换为类定义。就架构而言,我比你领先很多。不幸的是,我的问题并不依赖于平台;我在两个使用相同Ubuntu版本和JDK版本的主机上都遇到了这个问题。 - Robert Mandeville

2
如Major所回答并在DZone文章中提到的,对于Gradle,你只需要以下内容即可:

Original Answer

翻译成"最初的回答"
tasks.withType(AbstractArchiveTask) {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}

在将这段代码添加到build.gradle后,同一系统上构建时.jar文件的md5sum保持稳定。但由于我询问的每个人都使用不同的编译器版本,因此无法在其他系统上进行测试,这会导致构建结果不同。"最初的回答"

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