Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package java-11-openjdk for openSUSE:Factory
checked in at 2026-08-25 13:19:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/java-11-openjdk (Old)
and /work/SRC/openSUSE:Factory/.java-11-openjdk.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "java-11-openjdk"
Tue Aug 25 13:19:46 2026 rev:89 rq:1373461 version:11.0.32.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/java-11-openjdk/java-11-openjdk.changes
2026-07-22 19:08:01.823068952 +0200
+++
/work/SRC/openSUSE:Factory/.java-11-openjdk.new.1258/java-11-openjdk.changes
2026-08-25 13:19:51.737477100 +0200
@@ -1,0 +2,28 @@
+Mon Aug 24 16:50:27 UTC 2026 - Fridrich Strba <[email protected]>
+
+- Upgrade to upstream tag jdk-11.0.32.1+1 (August 2026 CSPU)
+ * Changes:
+ + JDK-8385390: Update FreeType to 2.14.3
+ + JDK-8384708, bsc#1275778, CVE-2026-61308: Enhance HTTP
+ Connections
+ + JDK-8386205, bsc#1275764, CVE-2026-70907: Enhance TLS server
+ + JDK-8382471, CVE-2026-60589: Improve Resource Resolving
+ + JDK-8389476: Bump update version for OpenJDK: jdk-11.0.32.1
+- Added patch:
+ * tzdata-2026c.patch
+ + backport upcoming upgrade of timezone data (bsc#1275035)
+
+-------------------------------------------------------------------
+Wed Jul 29 09:37:47 UTC 2026 - Bernhard Wiedemann <[email protected]>
+
+- Add patches for reproducible builds:
+ * reproducible-classlist-sort.patch
+ * reproducible-cldr-shortids-order.patch
+ * reproducible-generated-source-dates.patch
+ * reproducible-jar-timestamps.patch
+ * reproducible-jmod-entry-order.patch
+ * reproducible-jmod-timestamps.patch
+ * reproducible-module-descriptor-hashcode.patch
+ * reproducible-source-date-epoch.patch
+
+-------------------------------------------------------------------
Old:
----
jdk-11.0.32+9.tar.gz
New:
----
jdk-11.0.32.1+1.tar.gz
reproducible-classlist-sort.patch
reproducible-cldr-shortids-order.patch
reproducible-generated-source-dates.patch
reproducible-jar-timestamps.patch
reproducible-jmod-entry-order.patch
reproducible-jmod-timestamps.patch
reproducible-module-descriptor-hashcode.patch
reproducible-source-date-epoch.patch
tzdata-2026c.patch
----------(New B)----------
New:- Add patches for reproducible builds:
* reproducible-classlist-sort.patch
* reproducible-cldr-shortids-order.patch
New: * reproducible-classlist-sort.patch
* reproducible-cldr-shortids-order.patch
* reproducible-generated-source-dates.patch
New: * reproducible-cldr-shortids-order.patch
* reproducible-generated-source-dates.patch
* reproducible-jar-timestamps.patch
New: * reproducible-generated-source-dates.patch
* reproducible-jar-timestamps.patch
* reproducible-jmod-entry-order.patch
New: * reproducible-jar-timestamps.patch
* reproducible-jmod-entry-order.patch
* reproducible-jmod-timestamps.patch
New: * reproducible-jmod-entry-order.patch
* reproducible-jmod-timestamps.patch
* reproducible-module-descriptor-hashcode.patch
New: * reproducible-jmod-timestamps.patch
* reproducible-module-descriptor-hashcode.patch
* reproducible-source-date-epoch.patch
New: * reproducible-module-descriptor-hashcode.patch
* reproducible-source-date-epoch.patch
New:- Added patch:
* tzdata-2026c.patch
+ backport upcoming upgrade of timezone data (bsc#1275035)
----------(New E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ java-11-openjdk.spec ++++++
--- /var/tmp/diff_new_pack.OIzu2Y/_old 2026-08-25 13:19:58.568717647 +0200
+++ /var/tmp/diff_new_pack.OIzu2Y/_new 2026-08-25 13:19:58.571717753 +0200
@@ -43,7 +43,8 @@
%global featurever 11
%global interimver 0
%global updatever 32
-%global buildver 9
+%global patchver 1
+%global buildver 1
%global openjdk_repo jdk11u
%global openjdk_tag
jdk-%{featurever}.%{interimver}.%{updatever}%{?patchver:.%{patchver}}+%{buildver}
%global openjdk_dir
%{openjdk_repo}-jdk-%{featurever}.%{interimver}.%{updatever}%{?patchver:.%{patchver}}-%{buildver}
@@ -233,9 +234,32 @@
Patch22: reproducible-jlink.patch
Patch23: reproducible-directory-mtime.patch
Patch24: reproducible-search-index-files.patch
+# Honour the SOURCE_DATE_EPOCH from the environment and derive the copyright
+# year from it (adapted from JDK-8282567, and from what JDK 17 already does)
+Patch25: reproducible-source-date-epoch.patch
+# Sort the classlist, its order otherwise depends on thread scheduling
+# (JDK-8273092, in JDK 18)
+Patch26: reproducible-classlist-sort.patch
+# Stamp jmod entries with SOURCE_DATE_EPOCH rather than the current time
+Patch27: reproducible-jmod-timestamps.patch
+# Use SOURCE_DATE_EPOCH for the dates the CLDR converter, GenerateCharacter
+# and EquivMapsGenerator write into the sources they generate
+Patch28: reproducible-generated-source-dates.patch
+# Write jmod entries in sorted order, not in file system order
+Patch29: reproducible-jmod-entry-order.patch
#
Patch30: JDK-8208602.patch
Patch31: bsc_1255446.patch
+# Stamp the entries of jrt-fs.jar and ct.sym with SOURCE_DATE_EPOCH, so that
+# java.base.jmod and jdk.compiler.jmod stop depending on the build time
+Patch32: reproducible-jar-timestamps.patch
+# Insert the ZoneId.SHORT_IDS entries into the generated TimeZoneNames bundles
+# in a fixed order, rather than in the per-JVM-run random order of Map.of
+Patch33: reproducible-cldr-shortids-order.patch
+# Derive ModuleDescriptor.hashCode from the modifier names rather than from
+# enum identity hash codes, which vary per JVM run (JDK-8275509, JDK-8290041).
+# jlink bakes that hash into the SystemModules classes in lib/modules.
+Patch34: reproducible-module-descriptor-hashcode.patch
#
# OpenJDK specific patches
#
@@ -247,6 +271,8 @@
#
Patch302: disable-doclint-by-default.patch
#
+Patch400: tzdata-2026c.patch
+#
Patch500: activation-module.patch
Patch501: annotation-module.patch
BuildRequires: alsa-lib-devel
@@ -277,6 +303,9 @@
BuildRequires: libxslt
BuildRequires: mozilla-nss-devel >= 3.53
BuildRequires: pkgconfig
+%if 0%{?suse_version} >= 1500
+BuildRequires: strip-nondeterminism
+%endif
BuildRequires: unzip
BuildRequires: xorg-x11-proto-devel
BuildRequires: xprop
@@ -540,9 +569,17 @@
%patch -P 22 -p1
%patch -P 23 -p1
%patch -P 24 -p1
+%patch -P 25 -p1
+%patch -P 26 -p1
+%patch -P 27 -p1
+%patch -P 28 -p1
+%patch -P 29 -p1
%patch -P 30 -p1
%patch -P 31 -p1
+%patch -P 32 -p1
+%patch -P 33 -p1
+%patch -P 34 -p1
# s390 build fixes
@@ -559,6 +596,8 @@
%patch -P 302 -p1
+%patch -P 400 -p1
+
%patch -P 500
%patch -P 501
@@ -983,6 +1022,9 @@
| sed 's|^|%doc |' \
>> %{name}-demo.files
+%if 0%?have_strip_nondeterminism
+strip-all-nondeterminism %{buildroot}/%{_jvmdir}
+%endif
# fdupes links the files from JDK to JRE, so it breaks a JRE
# use it carefully :))
%fdupes -s %{buildroot}/%{_jvmdir}/%{sdkdir}/
++++++ jdk-11.0.32+9.tar.gz -> jdk-11.0.32.1+1.tar.gz ++++++
/work/SRC/openSUSE:Factory/java-11-openjdk/jdk-11.0.32+9.tar.gz
/work/SRC/openSUSE:Factory/.java-11-openjdk.new.1258/jdk-11.0.32.1+1.tar.gz
differ: char 28, line 1
++++++ reproducible-classlist-sort.patch ++++++
# 8273092: Sort classlist in JDK image
#
# Reviewed-by: iklam, erikj
# (cherry picked from commit 1996f649a3a30b7ac4b547a762417f807f5fa414)
#
# Backported to make openSUSE java-11-openjdk reproducible. The classlist
# records the order in which classes happen to be loaded, and threads such
# as Finalizer$FinalizerThread load classes concurrently with the main
# thread, so the order varies between builds. Two openSUSE builds differed
# by jdk/internal/misc/VM moving three lines down in lib/classlist, which
# also changes java.base.jmod, since the classlist is copied into it.
#
# Sorting the classlist removes the ordering dependency entirely.
#
# Adapted for 11:
# - JDK 11 writes plain class names (classFileParser.cpp does
# classlist_file->print_cr("%s", ...)), with no "id:" suffixes and no
# @lambda-form-invoker lines, so the tool sorts one flat list. Its
# handling of "#" and "@" lines is inert here but is kept so the file
# stays identical to the one in 17u.
# - the JDK 11 recipe generates the classlist in a single java run, so
# the sorting step is appended to that one recipe.
#
# Note that the classlist is also passed to jlink --order-resources, so
# this changes the resource order inside lib/modules. JDK 17 and later
# sort the classlist and feed it to jlink the same way.
#
diff --git a/make/GenerateLinkOptData.gmk b/make/GenerateLinkOptData.gmk
index 7a98cb36a1d..445c1717856 100644
--- a/make/GenerateLinkOptData.gmk
+++ b/make/GenerateLinkOptData.gmk
@@ -66,7 +66,10 @@ $(CLASSLIST_FILE):
$(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST
-cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
build.tools.classlist.HelloClasslist \
$(LOG_DEBUG) 2>&1 > $(JLI_TRACE_FILE)
- $(GREP) -v HelloClasslist [email protected] > $@
+ $(GREP) -v HelloClasslist [email protected] > [email protected]
+ $(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java \
+ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
+ build.tools.classlist.SortClasslist [email protected] > $@
# The jli trace is created by the same recipe as classlist. By declaring these
# dependencies, make will correctly rebuild both jli trace and classlist
diff --git a/make/jdk/src/classes/build/tools/classlist/SortClasslist.java
b/make/jdk/src/classes/build/tools/classlist/SortClasslist.java
new file mode 100644
index 00000000000..872d00c6d06
--- /dev/null
+++ b/make/jdk/src/classes/build/tools/classlist/SortClasslist.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2021,2023 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * This application is meant to be run to create a classlist file representing
+ * common use.
+ *
+ * The classlist is produced by adding -XX:DumpLoadedClassList=classlist
+ */
+package build.tools.classlist;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.Scanner;
+
+/**
+ * The classlist generated by build.tools.classlist.HelloClasslist
+ * may have non-deterministic contents, affected by Java thread execution
order.
+ * SortClasslist sorts the file to make the JDK image's contents more
deterministic.
+ */
+public class SortClasslist {
+ public static void main(String args[]) throws FileNotFoundException {
+ ArrayList<String> classes = new ArrayList<>();
+ ArrayList<String> lambdas = new ArrayList<>();
+
+ FileInputStream fis = new FileInputStream(args[0]);
+ Scanner scanner = new Scanner(fis);
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (line.startsWith("#")) {
+ // Comments -- print them first without sorting. These appear
only at the top
+ // of the file.
+ System.out.println(line);
+ } else if (line.startsWith("@")) {
+ // @lambda-form-invoker, @lambda-proxy, etc.
+ lambdas.add(line);
+ } else {
+ // Class name line
+ classes.add(line);
+ }
+ }
+
+ Collections.sort(classes);
+ Collections.sort(lambdas);
+
+ for (String s : classes) {
+ System.out.println(s);
+ }
+ for (String s : lambdas) {
+ System.out.println(s);
+ }
+ }
+}
++++++ reproducible-cldr-shortids-order.patch ++++++
# Insert the ZoneId.SHORT_IDS compatibility entries in a fixed order
#
# ZoneId.SHORT_IDS is built with Map.ofEntries, and the immutable maps
# introduced in JDK 9 randomize their iteration order once per JVM run.
# CLDRConverter.extractZoneNames() collects into a HashMap, so that random
# order decides the relative order of any two short ids that hash to the
# same bucket, and ResourceBundleGenerator then writes the map out in
# iteration order.
#
# The result is that a generated TimeZoneNames bundle differs between two
# otherwise identical builds. TimeZoneNames_kea.java, for instance, has
# "PST" and "AET" in the same bucket and emits them in either order, which
# propagates into lib/src.zip, jdk.localedata.jmod, lib/modules, and - via
# the module hashes recorded in its module-info - java.base.jmod.
#
# Sort the entries by key before inserting them. Still present upstream.
#
diff --git a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java
b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java
index 068b8701d70..5954462573b 100644
--- a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java
+++ b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java
@@ -718,8 +718,14 @@ public class CLDRConverter {
names.put("UTC", META_EMPTY_ZONE_NAME);
}
- // Finally some compatibility stuff
+ // Finally some compatibility stuff.
+ // ZoneId.SHORT_IDS is an immutable map, whose iteration order is
+ // randomized per JVM run. Since "names" is a HashMap, that insertion
+ // order decides the order of any two of these ids that end up in the
+ // same bucket, and with it the order of the entries in the generated
+ // TimeZoneNames bundles. Insert them in a fixed order instead.
ZoneId.SHORT_IDS.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
.filter(e -> !names.containsKey(e.getKey()) &&
names.containsKey(e.getValue()))
.forEach(e -> {
names.put(e.getKey(), names.get(e.getValue()));
++++++ reproducible-generated-source-dates.patch ++++++
# Use SOURCE_DATE_EPOCH for dates written into generated sources
#
# Three build tools stamp the current date into the sources they generate,
# so the result depends on when the build runs. All three show up in the
# shipped lib/src.zip and in the jmods and lib/modules built from them.
#
# - CopyrightHeaders.getYear() in the CLDR converter used the current
# year, which put "Copyright (c) 2012, 2026" into one build and
# "2012, 2027" into the next, across more than a thousand generated
# locale sources.
#
# - GenerateCharacter wrote "This file was generated AUTOMATICALLY from
# a template file <full timestamp>" into the five CharacterData*.java
# files.
#
# - EquivMapsGenerator did the same as the CLDR converter for the
# copyright header of sun/util/locale/LocaleEquivalentMaps.java.
#
# Take the date from SOURCE_DATE_EPOCH when it is set, and keep the
# current time otherwise. The CLDR converter and EquivMapsGenerator keep
# computing the year in America/Los_Angeles, as before, so only the
# instant changes.
#
# Note that this is separate from the COPYRIGHT_YEAR that configure
# computes: that one already follows SOURCE_DATE_EPOCH and fixed the
# javadoc bundle, but these tools never consulted it.
#
diff --git
a/make/jdk/src/classes/build/tools/cldrconverter/CopyrightHeaders.java
b/make/jdk/src/classes/build/tools/cldrconverter/CopyrightHeaders.java
index 89c7e0f41aa..d714dd27292 100644
--- a/make/jdk/src/classes/build/tools/cldrconverter/CopyrightHeaders.java
+++ b/make/jdk/src/classes/build/tools/cldrconverter/CopyrightHeaders.java
@@ -150,8 +150,20 @@ class CopyrightHeaders {
}
private static int getYear() {
- return new
GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"),
- Locale.US).get(Calendar.YEAR);
+ GregorianCalendar cal =
+ new GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"),
+ Locale.US);
+ String sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH");
+ if (sourceDateEpoch != null && !sourceDateEpoch.isEmpty()) {
+ // Use the source date rather than the current time, so that the
+ // generated files do not depend on when the build is run.
+ try {
+ cal.setTimeInMillis(Long.parseLong(sourceDateEpoch.trim()) *
1000L);
+ } catch (NumberFormatException e) {
+ // Not a valid timestamp, keep the current time.
+ }
+ }
+ return cal.get(Calendar.YEAR);
}
// no instantiation
diff --git
a/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java
b/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java
index 88a8d82f893..fd553f35c42 100644
--- a/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java
+++ b/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java
@@ -686,6 +686,22 @@ OUTER: for (int i = 0; i < n; i += m) {
* @see GenerateCharacter#replaceCommand
*/
+ /**
+ * The date stamped into the generated files. Use SOURCE_DATE_EPOCH when it
+ * is set, so that the generated files do not depend on when the build
runs.
+ */
+ static java.util.Date generationDate() {
+ String sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH");
+ if (sourceDateEpoch != null && !sourceDateEpoch.isEmpty()) {
+ try {
+ return new
java.util.Date(Long.parseLong(sourceDateEpoch.trim()) * 1000L);
+ } catch (NumberFormatException e) {
+ // Not a valid timestamp, fall through to the current time.
+ }
+ }
+ return new java.util.Date();
+ }
+
static void generateCharacterClass(String theTemplateFileName,
String theOutputFileName)
throws FileNotFoundException, IOException {
@@ -693,7 +709,7 @@ OUTER: for (int i = 0; i < n; i += m) {
PrintWriter out = new PrintWriter(new BufferedWriter(new
FileWriter(theOutputFileName)));
out.println(commentStart +
" This file was generated AUTOMATICALLY from a template file " +
- new java.util.Date() + commentEnd);
+ generationDate() + commentEnd);
int marklen = commandMarker.length();
LOOP: while(true) {
try {
diff --git
a/make/jdk/src/classes/build/tools/generatelsrequivmaps/EquivMapsGenerator.java
b/make/jdk/src/classes/build/tools/generatelsrequivmaps/EquivMapsGenerator.java
index cffaed7186a..c8660c90c15 100644
---
a/make/jdk/src/classes/build/tools/generatelsrequivmaps/EquivMapsGenerator.java
+++
b/make/jdk/src/classes/build/tools/generatelsrequivmaps/EquivMapsGenerator.java
@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
@@ -266,9 +267,27 @@ public class EquivMapsGenerator {
+ "}";
private static String getOpenJDKCopyright() {
- int year = ZonedDateTime.now(ZoneId
- .of("America/Los_Angeles")).getYear();
- return String.format(Locale.US, COPYRIGHT, year);
+ return String.format(Locale.US, COPYRIGHT, getYear());
+ }
+
+ /**
+ * The copyright year written into the generated source file. Use
+ * SOURCE_DATE_EPOCH when it is set, so that the generated file does not
+ * depend on when the build is run.
+ */
+ private static int getYear() {
+ ZoneId zone = ZoneId.of("America/Los_Angeles");
+ String sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH");
+ if (sourceDateEpoch != null && !sourceDateEpoch.isEmpty()) {
+ try {
+ return ZonedDateTime.ofInstant(
+
Instant.ofEpochSecond(Long.parseLong(sourceDateEpoch.trim())),
+ zone).getYear();
+ } catch (NumberFormatException e) {
+ // Not a valid timestamp, fall back to the current time.
+ }
+ }
+ return ZonedDateTime.now(zone).getYear();
}
/**
++++++ reproducible-jar-timestamps.patch ++++++
# Stamp jrt-fs.jar and ct.sym entries with SOURCE_DATE_EPOCH
#
# The jar command of this release takes the entry times from the
# modification times of the input files, and stamps the entries it creates
# itself (META-INF/ and META-INF/MANIFEST.MF) with the current time, so
# every archive built by SetupJarArchive differs between builds. Upstream
# solved this in JDK 19 with the jar --date option (JDK-8276766), which the
# boot JDK used to build this release does not have.
#
# The two archives that matter here are lib/jrt-fs.jar and lib/ct.sym,
# because they are also packed into java.base.jmod and jdk.compiler.jmod -
# these were the last two files that still differed between two openSUSE
# builds. Normalising the installed image does not reach the copies inside
# the jmods.
#
# Add a build tool that rewrites the entry timestamps of a finished archive
# to SOURCE_DATE_EPOCH, leaving names, order, contents and compression
# method alone, and a REPRODUCIBLE_TIMESTAMPS argument to SetupJarArchive
# that runs it. It is opt-in because the tool lives in buildtools-jdk,
# which the buildtools-hotspot jars cannot depend on. When SOURCE_DATE_EPOCH
# is unset nothing changes.
#
diff --git a/make/JrtfsJar.gmk b/make/JrtfsJar.gmk
index e35c8220cd0..589eb0f90eb 100644
--- a/make/JrtfsJar.gmk
+++ b/make/JrtfsJar.gmk
@@ -67,6 +67,7 @@ $(eval $(call SetupJarArchive,BUILD_JRTFS_JAR, \
SRCS := $(SUPPORT_OUTPUTDIR)/jrtfs_classes, \
JAR := $(SUPPORT_OUTPUTDIR)/modules_libs/java.base/jrt-fs.jar, \
MANIFEST := $(SUPPORT_OUTPUTDIR)/java-main-manifest.mf, \
+ REPRODUCIBLE_TIMESTAMPS := true, \
))
all: $(BUILD_JRTFS_JAR)
diff --git a/make/Main.gmk b/make/Main.gmk
index 2c08532626a..66451a1e0fc 100644
--- a/make/Main.gmk
+++ b/make/Main.gmk
@@ -894,7 +894,8 @@ else
zip-source: gensrc rmic
- jrtfs-jar: interim-langtools
+ # buildtools-jdk is needed to make the jar entry timestamps reproducible.
+ jrtfs-jar: interim-langtools buildtools-jdk
ifeq ($(ENABLE_GENERATE_CLASSLIST), true)
ifeq ($(CREATE_BUILDJDK), true)
diff --git a/make/common/JarArchive.gmk b/make/common/JarArchive.gmk
index e1ec1427a88..0e39a377fdb 100644
--- a/make/common/JarArchive.gmk
+++ b/make/common/JarArchive.gmk
@@ -57,6 +57,9 @@ FALSE_FIND_PATTERN:=-name FILE_NAME_THAT_DOESNT_EXIST
# EXTRA_MANIFEST_ATTR:=Extra attribute to add to manifest.
# CHECK_COMPRESS_JAR Check the COMPRESS_JAR variable
# JAR_CMD:=Optionally override the jar command to use when creating the
archive.
+# REPRODUCIBLE_TIMESTAMPS:=true means rewrite the entry timestamps of the
+# finished archive to SOURCE_DATE_EPOCH. Requires the jdk build tools,
+# so the target must depend on buildtools-jdk.
SetupJarArchive = $(NamedParamsMacroTemplate)
define SetupJarArchiveBody
@@ -68,6 +71,20 @@ define SetupJarArchiveBody
$1_BIN:=$$(dir $$($1_JAR))
$$(call SetIfEmpty, $1_JAR_CMD, $$(JAR))
+ # The jar command takes the entry times from the input files, and stamps the
+ # entries it creates itself (META-INF/ and META-INF/MANIFEST.MF, and the
+ # index) with the current time, so its output differs between builds. Later
+ # releases pass jar --date, which the boot JDK used here does not support,
+ # so rewrite the timestamps once the archive is complete instead.
+ $1_FIXUP_TIMESTAMPS := true
+ ifeq ($$($1_REPRODUCIBLE_TIMESTAMPS), true)
+ ifneq ($(SOURCE_DATE_EPOCH), )
+ $1_FIXUP_TIMESTAMPS := $(JAVA_SMALL) \
+ -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
+ build.tools.jarreproducible.JarReproducible $$($1_JAR)
+ endif
+ endif
+
ifeq (,$$($1_SUFFIXES))
# No suffix was set, default to classes.
$1_SUFFIXES:=.class
@@ -230,7 +247,8 @@ define SetupJarArchiveBody
# Include all variables of significance in the vardeps file
$1_VARDEPS := $$($1_JAR_CMD) $$($1_JAR_CREATE_OPTIONS) $$($1_MANIFEST) \
$$($1_JARMAIN) $$($1_EXTRA_MANIFEST_ATTR) $$($1_ORIG_DEPS) $$($1_SRCS) \
- $$($1_INCLUDES) $$($1_EXCLUDES) $$($1_EXCLUDE_FILES) $$($1_EXTRA_FILES)
+ $$($1_INCLUDES) $$($1_EXCLUDES) $$($1_EXCLUDE_FILES) $$($1_EXTRA_FILES) \
+ $$($1_FIXUP_TIMESTAMPS)
$1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, $$(dir
$$($1_JAR))_the.$$($1_JARNAME).vardeps)
# Here is the rule that creates/updates the jar file.
@@ -270,6 +288,7 @@ define SetupJarArchiveBody
fi $$(NEWLINE) \
$$($1_UPDATE_CONTENTS) true $$(NEWLINE) \
$$($1_JARINDEX) && true )
+ $$($1_FIXUP_TIMESTAMPS)
# Add jar to target list
$1 += $$($1_JAR)
diff --git a/make/gendata/Gendata-jdk.compiler.gmk
b/make/gendata/Gendata-jdk.compiler.gmk
index 1d034828598..976ba758a7f 100644
--- a/make/gendata/Gendata-jdk.compiler.gmk
+++ b/make/gendata/Gendata-jdk.compiler.gmk
@@ -91,6 +91,7 @@ $(eval $(call SetupJarArchive, CREATE_CTSYM, \
SRCS := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym-files, \
SUFFIXES := .sig system-modules, \
JAR := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym, \
+ REPRODUCIBLE_TIMESTAMPS := true, \
))
# Copy ct.sym to the modules libs dir
diff --git
a/make/jdk/src/classes/build/tools/jarreproducible/JarReproducible.java
b/make/jdk/src/classes/build/tools/jarreproducible/JarReproducible.java
new file mode 100644
index 00000000000..87a39525abc
--- /dev/null
+++ b/make/jdk/src/classes/build/tools/jarreproducible/JarReproducible.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package build.tools.jarreproducible;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Rewrites the given jar files so that every entry is stamped with the time
+ * given by SOURCE_DATE_EPOCH instead of the time the build happened to run.
+ *
+ * The jar command of this release takes entry times from the modification
+ * times of the input files, and stamps the entries it generates itself
+ * (META-INF/ and META-INF/MANIFEST.MF) with the current time, so the archives
+ * it produces differ between builds. Later releases solve this with the
+ * jar --date option, which is not available in the boot JDK used here.
+ *
+ * Entry names, order, contents and compression method are left untouched, so
+ * this only removes the timestamps as a source of difference. If
+ * SOURCE_DATE_EPOCH is not set the files are left alone.
+ */
+public class JarReproducible {
+
+ public static void main(String[] args) throws IOException {
+ String sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH");
+ if (sourceDateEpoch == null || sourceDateEpoch.trim().isEmpty()) {
+ return;
+ }
+ long time;
+ try {
+ time = Long.parseLong(sourceDateEpoch.trim()) * 1000L;
+ } catch (NumberFormatException e) {
+ return;
+ }
+ for (String arg : args) {
+ normalize(Paths.get(arg), time);
+ }
+ }
+
+ private static void normalize(Path jar, long time) throws IOException {
+ Path tmp = jar.resolveSibling(jar.getFileName() + ".tmp");
+ try (ZipFile in = new ZipFile(jar.toFile());
+ ZipOutputStream out =
+ new ZipOutputStream(Files.newOutputStream(tmp))) {
+ Enumeration<? extends ZipEntry> entries = in.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ ZipEntry copy = new ZipEntry(entry.getName());
+ copy.setMethod(entry.getMethod());
+ copy.setTime(time);
+ copy.setComment(entry.getComment());
+ // The extra field is deliberately not carried over. The jar
+ // command does not add any, and the ones ZipFile hands out
+ // from the central directory (zip64, timestamps) would either
+ // be regenerated by ZipOutputStream or reintroduce a time.
+ if (entry.getMethod() == ZipEntry.STORED) {
+ copy.setSize(entry.getSize());
+ copy.setCompressedSize(entry.getCompressedSize());
+ copy.setCrc(entry.getCrc());
+ }
+ out.putNextEntry(copy);
+ try (InputStream is = in.getInputStream(entry)) {
+ is.transferTo(out);
+ }
+ out.closeEntry();
+ }
+ out.setComment(in.getComment());
+ }
+ Files.move(tmp, jar, StandardCopyOption.REPLACE_EXISTING);
+ }
+}
++++++ reproducible-jmod-entry-order.patch ++++++
# Write jmod entries in sorted order
#
# JmodTask walks the input directories with Files.walkFileTree and writes
# each file as it is visited, so the order of the entries in the resulting
# jmod is whatever order the file system returned the directory entries
# in. Eight of the shipped jmods differed between two openSUSE builds for
# this reason: the entry timestamps and the contents matched, but the
# entries appeared at different offsets.
#
# Collect the files first, sort them by their relative path, and then
# write them out.
#
# The jar files handled by processClasses keep their existing order, which
# comes from the jar itself and is already deterministic.
#
diff --git a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
index 0f6b488bf5e..68c9faf73d0 100644
--- a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
+++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
@@ -773,33 +773,40 @@ public class JmodTask {
void processSection(JmodOutputStream out, Section section, Path path)
throws IOException
{
+ // Collect the files first and sort them, so that the entry order
in
+ // the resulting jmod does not depend on the order in which the
file
+ // system happens to return directory entries.
+ List<Path> files = new ArrayList<>();
Files.walkFileTree(path, Set.of(FileVisitOption.FOLLOW_LINKS),
Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Override
- public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs)
- throws IOException
- {
- Path relPath = path.relativize(file);
- if (relPath.toString().equals(MODULE_INFO)
- && !Section.CLASSES.equals(section))
- warning("warn.ignore.entry", MODULE_INFO, section);
-
- if (!relPath.toString().equals(MODULE_INFO)
- && !matches(relPath, excludes)) {
- try (InputStream in = Files.newInputStream(file)) {
- out.writeEntry(in, section,
relPath.toString());
- } catch (IOException x) {
- if (x.getMessage().contains("duplicate
entry")) {
- warning("warn.ignore.duplicate.entry",
- relPath.toString(), section);
- return FileVisitResult.CONTINUE;
- }
- throw x;
- }
- }
+ public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) {
+ files.add(file);
return FileVisitResult.CONTINUE;
}
});
+ files.sort(Comparator.comparing(f ->
path.relativize(f).toString()));
+
+ for (Path file : files) {
+ Path relPath = path.relativize(file);
+ if (relPath.toString().equals(MODULE_INFO)
+ && !Section.CLASSES.equals(section))
+ warning("warn.ignore.entry", MODULE_INFO, section);
+
+ if (!relPath.toString().equals(MODULE_INFO)
+ && !matches(relPath, excludes)) {
+ try (InputStream in = Files.newInputStream(file)) {
+ out.writeEntry(in, section, relPath.toString());
+ } catch (IOException x) {
+ if (x.getMessage().contains("duplicate entry")) {
+ warning("warn.ignore.duplicate.entry",
+ relPath.toString(), section);
+ continue;
+ }
+ throw x;
+ }
+ }
+ }
}
boolean matches(Path path, List<PathMatcher> matchers) {
++++++ reproducible-jmod-timestamps.patch ++++++
# Stamp jmod entries with SOURCE_DATE_EPOCH instead of the current time
#
# JmodOutputStream.newEntry() returns a ZipEntry without a time, so
# ZipOutputStream.putNextEntry() fills in System.currentTimeMillis() for
# every entry. Each build therefore writes a different DOS timestamp into
# the local file header of every entry, which is why all of the roughly 70
# shipped jmods differ between two openSUSE builds while their contents are
# identical.
#
# Use SOURCE_DATE_EPOCH for the entry time when it is set in the
# environment, and keep the previous behaviour when it is not.
#
# JDK 19 solved this upstream with the jmod --date option (JDK-8276766),
# which also required jmod command line and ZipEntry.setTimeLocal()
# support that JDK 11 does not have, so this is an openSUSE specific
# change rather than a backport.
#
# Note that ZipEntry.setTime() converts to the DOS timestamp using the
# local time zone, so the build still has to run with a fixed TZ, which
# the openSUSE build service already guarantees.
#
--- a/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java
+++ b/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java
@@ -101,7 +101,11 @@ class JmodOutputStream extends OutputStream implements
AutoCloseable {
String prefix = section.jmodDir();
String name = Paths.get(prefix, path).toString()
.replace(File.separatorChar, '/');
- return new ZipEntry(name);
+ ZipEntry ze = new ZipEntry(name);
+ String sde = System.getenv("SOURCE_DATE_EPOCH");
+ if (sde != null)
+ ze.setTime(1000 * Long.parseLong(sde));
+ return ze;
}
@Override
++++++ reproducible-module-descriptor-hashcode.patch ++++++
# Backport of JDK-8275509 and its follow-up JDK-8290041:
# 8275509: ModuleDescriptor.hashCode isn't reproducible across builds
# 8290041: ModuleDescriptor.hashCode is inconsistent
#
# ModuleDescriptor.hashCode() and the hashCode() of Requires, Exports and
# Opens folded in the hash of their Set of modifiers. Those are sets of
# enums, and Enum.hashCode() is the identity hash code, which depends on
# the state of HotSpot's per-thread PRNG when the constant is first hashed
# - so it varies between JVM runs, and with it the descriptor hash.
#
# jlink bakes ModuleDescriptor.hashCode() into the SystemModules classes it
# generates (SystemModulesPlugin.putModuleDescriptor calls
# mv.visitLdcInsn(md.hashCode())), so this landed in lib/modules as an int
# constant that changed from build to build. It was the last remaining
# difference between two openSUSE builds, and it is sensitive to anything
# that shifts execution order in the jlink JVM - such as the number of
# build cores.
#
# Hash the modifier names instead of the enum instances. JDK-8290041 is the
# follow-up that makes the sum order independent again, which matters here
# because the modifier sets are Set.of instances whose iteration order is
# also randomized per JVM run - without it the first fix alone would leave
# the same symptom.
#
# Note that jlink runs from the JDK being built (BUILD_JDK defaults to
# JDK_OUTPUTDIR for a native build), so patching java.base takes effect for
# the image built in the same run.
#
diff --git a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
index fa359348f6c..f9ebfe1559d 100644
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
+++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
@@ -333,7 +333,7 @@ public class ModuleDescriptor
*/
@Override
public int hashCode() {
- int hash = name.hashCode() * 43 + mods.hashCode();
+ int hash = name.hashCode() * 43 + modsHashCode(mods);
if (compiledVersion != null)
hash = hash * 43 + compiledVersion.hashCode();
if (rawCompiledVersion != null)
@@ -514,7 +514,7 @@ public class ModuleDescriptor
*/
@Override
public int hashCode() {
- int hash = mods.hashCode();
+ int hash = modsHashCode(mods);
hash = hash * 43 + source.hashCode();
return hash * 43 + targets.hashCode();
}
@@ -721,7 +721,7 @@ public class ModuleDescriptor
*/
@Override
public int hashCode() {
- int hash = mods.hashCode();
+ int hash = modsHashCode(mods);
hash = hash * 43 + source.hashCode();
return hash * 43 + targets.hashCode();
}
@@ -2283,7 +2283,7 @@ public class ModuleDescriptor
int hc = hash;
if (hc == 0) {
hc = name.hashCode();
- hc = hc * 43 + Objects.hashCode(modifiers);
+ hc = hc * 43 + modsHashCode(modifiers);
hc = hc * 43 + requires.hashCode();
hc = hc * 43 + Objects.hashCode(packages);
hc = hc * 43 + exports.hashCode();
@@ -2568,6 +2568,18 @@ public class ModuleDescriptor
.collect(Collectors.joining(" "));
}
+ /**
+ * Generates and returns a hashcode for the enum instances. The returned
hashcode
+ * is a value based on the {@link Enum#name() name} of each enum instance.
+ */
+ private static int modsHashCode(Iterable<? extends Enum<?>> enums) {
+ int h = 0;
+ for (Enum<?> e : enums) {
+ h += e.name().hashCode();
+ }
+ return h;
+ }
+
private static <T extends Object & Comparable<? super T>>
int compare(T obj1, T obj2) {
if (obj1 != null) {
diff --git a/test/jdk/java/lang/module/ModuleDescriptorHashCodeTest.java
b/test/jdk/java/lang/module/ModuleDescriptorHashCodeTest.java
new file mode 100644
index 00000000000..c1269f28c48
--- /dev/null
+++ b/test/jdk/java/lang/module/ModuleDescriptorHashCodeTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Opens;
+import java.lang.module.ModuleDescriptor.Requires;
+import java.util.Set;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotSame;
+
+/**
+ * @test
+ * @bug 8275509 8290041
+ * @summary Tests the ModuleDescriptor.hashCode()
+ * @run testng ModuleDescriptorHashCodeTest
+ * @run testng/othervm -Xshare:off ModuleDescriptorHashCodeTest
+ */
+public class ModuleDescriptorHashCodeTest {
+
+ /**
+ * Verifies that the ModuleDescriptor.hashCode() returned by a boot layer
module is
+ * the same as that returned by a ModuleDescriptor constructed from the
ModuleDescriptor.Builder
+ * for the same module.
+ */
+ @Test
+ public void testBootModuleDescriptor() throws Exception {
+ Set<Module> bootModules = ModuleLayer.boot().modules();
+ for (Module bootModule : bootModules) {
+ System.out.println("Testing module descriptor of boot module " +
bootModule);
+ ModuleDescriptor bootMD = bootModule.getDescriptor();
+ ModuleDescriptor mdFromBuilder = fromModuleInfoClass(bootModule);
+ // verify that this object is indeed a different object instance
than the boot module descriptor
+ // to prevent any artificial passing of the test
+ assertNotSame(mdFromBuilder, bootMD, "ModuleDescriptor loaded from
boot layer and " +
+ "one created from module-info.class unexpectedly returned
the same instance");
+ assertEquals(mdFromBuilder.hashCode(), bootMD.hashCode(),
+ "Unexpected ModuleDescriptor.hashCode() for " +
mdFromBuilder);
+ assertEquals(mdFromBuilder.compareTo(bootMD), 0,
+ "Unexpected ModuleDescriptor.compareTo() for " +
mdFromBuilder);
+ }
+ }
+
+ /**
+ * Verifies that two "equal" module descriptors which only differ in the
order of
+ * {@link ModuleDescriptor.Opens.Modifier opens modifiers}, that were used
to construct the
+ * descriptors, have the same hashcode.
+ */
+ @Test
+ public void testOpensModifiersOrdering() throws Exception {
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Opens.Modifier> mods1 = Set.of(Opens.Modifier.SYNTHETIC,
Opens.Modifier.MANDATED);
+ final ModuleDescriptor desc1 = createModuleDescriptor(mods1, null,
null);
+
+ // create the same module descriptor again and this time just change
the order of the
+ // "opens" modifiers' Set.
+
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Opens.Modifier> mods2 = Set.of(Opens.Modifier.MANDATED,
Opens.Modifier.SYNTHETIC);
+ final ModuleDescriptor desc2 = createModuleDescriptor(mods2, null,
null);
+
+ // basic verification of the modifiers themselves before we check the
module descriptors
+ assertEquals(mods1, mods2, "Modifiers were expected to be equal");
+
+ // now verify the module descriptors
+ assertEquals(desc1, desc2, "Module descriptors were expected to be
equal");
+ assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to
return" +
+ " 0 for module descriptors that are equal");
+ System.out.println(desc1 + " hashcode = " + desc1.hashCode());
+ System.out.println(desc2 + " hashcode = " + desc2.hashCode());
+ assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor
hashcodes" +
+ " were expected to be equal");
+ }
+
+ /**
+ * Verifies that two "equal" module descriptors which only differ in the
order of
+ * {@link ModuleDescriptor.Exports.Modifier exports modifiers}, that were
used to construct the
+ * descriptors, have the same hashcode.
+ */
+ @Test
+ public void testExportsModifiersOrdering() throws Exception {
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Exports.Modifier> mods1 = Set.of(Exports.Modifier.SYNTHETIC,
Exports.Modifier.MANDATED);
+ final ModuleDescriptor desc1 = createModuleDescriptor(null, null,
mods1);
+
+ // create the same module descriptor again and this time just change
the order of the
+ // "exports" modifiers' Set.
+
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Exports.Modifier> mods2 = Set.of(Exports.Modifier.MANDATED,
Exports.Modifier.SYNTHETIC);
+ final ModuleDescriptor desc2 = createModuleDescriptor(null, null,
mods2);
+
+ // basic verification of the modifiers themselves before we check the
module descriptors
+ assertEquals(mods1, mods2, "Modifiers were expected to be equal");
+
+ // now verify the module descriptors
+ assertEquals(desc1, desc2, "Module descriptors were expected to be
equal");
+ assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to
return" +
+ " 0 for module descriptors that are equal");
+ System.out.println(desc1 + " hashcode = " + desc1.hashCode());
+ System.out.println(desc2 + " hashcode = " + desc2.hashCode());
+ assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor
hashcodes" +
+ " were expected to be equal");
+ }
+
+ /**
+ * Verifies that two "equal" module descriptors which only differ in the
order of
+ * {@link ModuleDescriptor.Requires.Modifier requires modifiers}, that
were used to construct the
+ * descriptors, have the same hashcode.
+ */
+ @Test
+ public void testRequiresModifiersOrdering() throws Exception {
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Requires.Modifier> mods1 =
Set.of(Requires.Modifier.SYNTHETIC, Requires.Modifier.MANDATED);
+ final ModuleDescriptor desc1 = createModuleDescriptor(null, mods1,
null);
+
+ // create the same module descriptor again and this time just change
the order of the
+ // "exports" modifiers' Set.
+
+ // important to use Set.of() (i.e. backed by immutable set) to
reproduce the issue
+ final Set<Requires.Modifier> mods2 =
Set.of(Requires.Modifier.MANDATED, Requires.Modifier.SYNTHETIC);
+ final ModuleDescriptor desc2 = createModuleDescriptor(null, mods2,
null);
+
+ // basic verification of the modifiers themselves before we check the
module descriptors
+ assertEquals(mods1, mods2, "Modifiers were expected to be equal");
+
+ // now verify the module descriptors
+ assertEquals(desc1, desc2, "Module descriptors were expected to be
equal");
+ assertEquals(desc1.compareTo(desc2), 0, "compareTo was expected to
return" +
+ " 0 for module descriptors that are equal");
+ System.out.println(desc1 + " hashcode = " + desc1.hashCode());
+ System.out.println(desc2 + " hashcode = " + desc2.hashCode());
+ assertEquals(desc1.hashCode(), desc2.hashCode(), "Module descriptor
hashcodes" +
+ " were expected to be equal");
+ }
+
+ // Returns a ModuleDescriptor parsed out of the module-info.class of the
passed Module
+ private static ModuleDescriptor fromModuleInfoClass(Module module) throws
IOException {
+ try (InputStream moduleInfo =
module.getResourceAsStream("module-info.class")) {
+ if (moduleInfo == null) {
+ throw new RuntimeException("Could not locate module-info.class
in " + module);
+ }
+ // internally calls ModuleDescriptor.Builder
+ return ModuleDescriptor.read(moduleInfo);
+ }
+ }
+
+ // creates a module descriptor with passed (optional)
opens/exports/requires modifiers
+ private static ModuleDescriptor createModuleDescriptor(
+ Set<Opens.Modifier> opensModifiers,
+ Set<Requires.Modifier> reqsModifiers,
+ Set<Exports.Modifier> expsModifiers) {
+
+ final ModuleDescriptor.Builder builder =
ModuleDescriptor.newModule("foobar");
+ if (opensModifiers != null) {
+ builder.opens(opensModifiers, "a.p1", Set.of("a.m1"));
+ }
+ if (reqsModifiers != null) {
+ builder.requires(reqsModifiers, "a.m2");
+ }
+ if (expsModifiers != null) {
+ builder.exports(expsModifiers, "a.b.c", Set.of("a.m3"));
+ }
+ return builder.build();
+ }
+}
++++++ reproducible-source-date-epoch.patch ++++++
# Derive the source date and the copyright year from SOURCE_DATE_EPOCH
#
# Adapted from JDK-8282567 ("Improve source-date handling in build system",
# JDK 20) and from the COPYRIGHT_YEAR handling that JDK 17 already has.
#
# Two independent sources of non-determinism in openSUSE java-11-openjdk:
#
# 1. configure ignored the SOURCE_DATE_EPOCH exported by the build service
# and left SOURCE_DATE at "updated", so SetupReproducibleBuild in
# make/InitSupport.gmk replaced the inherited SOURCE_DATE_EPOCH with the
# output of "date +%s" at make start. Since gcc derives __DATE__ and
# __TIME__ from SOURCE_DATE_EPOCH, every build embedded a different
# timestamp in libjvm.so ("built on Sep 11 2025 22:44:08" versus
# "built on Oct 18 2026 12:53:30" in two openSUSE builds).
#
# 2. COPYRIGHT_YEAR was taken from the wall clock, so builds run in
# different years differ in every generated file that carries a
# copyright header: the whole javadoc bundle and more than 1200
# generated CLDR sources in lib/src.zip.
#
# Honour SOURCE_DATE_EPOCH from the environment when --with-source-date is
# not given, and derive COPYRIGHT_YEAR from it, exactly like JDK 17.
#
# Note that setting with_source_date_present flips ENABLE_REPRODUCIBLE_BUILD
# to true, which on JDK 11 only affects the Microsoft toolchain
# (flags-cflags.m4 and flags-ldflags.m4 gate -pathmap and
# -experimental:deterministic on it), so this is a no-op elsewhere. Unlike
# JDK 17, JDK 11 has no HOTSPOT_BUILD_TIME and no jar/jmod --date, so those
# parts of JDK-8282567 have no counterpart here.
#
# JDK 11 also has no IS_GNU_DATE (make/InitSupport.gmk references it, but
# nothing ever sets it), so the epoch to year conversion tries the GNU
# syntax first and falls back to the BSD one.
#
diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
index cc45f3c0267..00e0ea22878 100644
--- a/make/autoconf/jdk-options.m4
+++ b/make/autoconf/jdk-options.m4
@@ -255,6 +255,17 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JDK_OPTIONS],
AC_MSG_ERROR([Copyright year must have a value])
elif test "x$with_copyright_year" != x; then
COPYRIGHT_YEAR="$with_copyright_year"
+ elif test "x$SOURCE_DATE_EPOCH" != x; then
+ # Derive the copyright year from SOURCE_DATE_EPOCH, so that it does not
+ # depend on when the build happens to run. JDK 17 and later do the same.
+ # JDK 11 has no IS_GNU_DATE, so try the GNU syntax and fall back to BSD.
+ COPYRIGHT_YEAR=`$DATE --utc --date=@$SOURCE_DATE_EPOCH +%Y 2> /dev/null`
+ if test "x$COPYRIGHT_YEAR" = x; then
+ COPYRIGHT_YEAR=`$DATE -u -j -f %s $SOURCE_DATE_EPOCH +%Y 2> /dev/null`
+ fi
+ if test "x$COPYRIGHT_YEAR" = x; then
+ AC_MSG_ERROR([Cannot convert SOURCE_DATE_EPOCH to a copyright year])
+ fi
else
COPYRIGHT_YEAR=`$DATE +'%Y'`
fi
@@ -659,7 +670,17 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_REPRODUCIBLE_BUILD],
if test "x$with_source_date" = xyes; then
AC_MSG_ERROR([--with-source-date must have a value])
- elif test "x$with_source_date" = xupdated || test "x$with_source_date" = x;
then
+ elif test "x$with_source_date" = x; then
+ if test "x$SOURCE_DATE_EPOCH" != x; then
+ SOURCE_DATE=$SOURCE_DATE_EPOCH
+ with_source_date_present=true
+ AC_MSG_RESULT([$SOURCE_DATE, from SOURCE_DATE_EPOCH])
+ else
+ # Tell the makefiles to update at each build
+ SOURCE_DATE=updated
+ AC_MSG_RESULT([determined at build time (default)])
+ fi
+ elif test "x$with_source_date" = xupdated; then
# Tell the makefiles to update at each build
SOURCE_DATE=updated
AC_MSG_RESULT([determined at build time, from 'updated'])
++++++ tzdata-2026c.patch ++++++
++++ 805 lines (skipped)