~ruther/guix-local

48ea1a2b3b8bc4c8b2247972401ab5a6f7f4ecc7 — Danny Milosavljevic 2 years ago 2f1daa3
gnu: openjdk10: Make more reproducible.

* gnu/packages/patches/openjdk-10-char-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-classlist-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-corba-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-jar-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-jtask-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-module-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-module3-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-10-module4-reproducibility.patch: New file.
* gnu/packages/java.scm (openjdk10)[source]: Add patches.
[arguments]<#:phases>[remove-timestamping]: New phase.
* gnu/local.mk (dist_patch_DATA): Add patches.

Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
M gnu/local.mk => gnu/local.mk +8 -0
@@ 1829,7 1829,15 @@ dist_patch_DATA =						\
  %D%/packages/patches/openjdk-9-module-reproducibility.patch	\
  %D%/packages/patches/openjdk-9-module2-reproducibility.patch	\
  %D%/packages/patches/openjdk-9-module3-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-char-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-classlist-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-corba-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-idlj-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-jar-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-jtask-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-module-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-module3-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-module4-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-pointer-comparison.patch      \
  %D%/packages/patches/openjdk-10-setsignalhandler.patch        \
  %D%/packages/patches/openjdk-15-xcursor-no-dynamic.patch	\

M gnu/packages/java.scm => gnu/packages/java.scm +13 -0
@@ 1215,7 1215,15 @@ new Date();"))
               (base32
                "0i47ar8lxzjrkkiwbzybfxs473390h4jq9ahm3xqdvy5zpchxy3y"))
              (patches (search-patches
                        "openjdk-10-char-reproducibility.patch"
                        "openjdk-10-classlist-reproducibility.patch"
                        "openjdk-10-corba-reproducibility.patch"
                        "openjdk-10-idlj-reproducibility.patch"
                        "openjdk-10-module-reproducibility.patch"
                        "openjdk-10-module3-reproducibility.patch"
                        "openjdk-10-module4-reproducibility.patch"
                        "openjdk-10-jar-reproducibility.patch"
                        "openjdk-10-jtask-reproducibility.patch"
                        "openjdk-10-pointer-comparison.patch"
                        "openjdk-10-setsignalhandler.patch"
                        "openjdk-currency-time-bomb2.patch"))))


@@ 1241,6 1249,11 @@ new Date();"))
               ;; this exact first line.
               (substitute* "make/data/blacklistedcertsconverter/blacklisted.certs.pem"
                 (("^#!.*") "#! java BlacklistedCertsConverter SHA-256\n"))))
           (add-after 'unpack 'remove-timestamping
             (lambda _
               (substitute* "./src/hotspot/share/runtime/vm_version.cpp"
                 (("__DATE__") "")
                 (("__TIME__") ""))))
           (replace 'configure
             (lambda* (#:key inputs outputs #:allow-other-keys)
               (invoke "bash" "./configure"

A gnu/packages/patches/openjdk-10-char-reproducibility.patch => gnu/packages/patches/openjdk-10-char-reproducibility.patch +12 -0
@@ 0,0 1,12 @@
Danny
--- orig/jdk-6fa770f9f8ab/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java	2022-04-13 19:24:10.211683257 +0200
+++ jdk-6fa770f9f8ab/make/jdk/src/classes/build/tools/generatecharacter/GenerateCharacter.java	2022-04-13 22:51:50.680487330 +0200
@@ -693,7 +693,7 @@
         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);
+            (System.getenv("SOURCE_DATE_EPOCH") == null ? new java.util.Date() : new java.util.Date(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")))) + commentEnd);
         int marklen = commandMarker.length();
         LOOP: while(true) {
             try {

A gnu/packages/patches/openjdk-10-classlist-reproducibility.patch => gnu/packages/patches/openjdk-10-classlist-reproducibility.patch +27 -0
@@ 0,0 1,27 @@
--- orig/jdk-6fa770f9f8ab/make/GenerateLinkOptData.gmk	2022-04-04 17:16:29.365930149 +0200
+++ jdk-6fa770f9f8ab/make/GenerateLinkOptData.gmk	2022-04-04 17:16:54.954624358 +0200
@@ -61,11 +61,12 @@
 	$(call MakeDir, $(LINK_OPT_DIR))
 	$(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $@))
 	$(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $(JLI_TRACE_FILE)))
-	$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@ \
+	$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.tmp \
 	    -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true \
 	    -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
 	    build.tools.classlist.HelloClasslist \
 	    $(LOG_DEBUG) 2>&1 > $(JLI_TRACE_FILE)
+	sort $@.tmp > $@
 
 # The jli trace is created by the same recipe as classlist. By declaring these
 # dependencies, make will correctly rebuild both jli trace and classlist
--- orig/jdk-6fa770f9f8ab/make/gendata/Gendata-jdk.compiler.gmk	2022-04-13 19:24:10.191682716 +0200
+++ jdk-6fa770f9f8ab/make/gendata/Gendata-jdk.compiler.gmk	2022-04-13 20:58:57.891368216 +0200
@@ -83,6 +83,8 @@
 	    $(CT_MODULESOURCEPATH) \
 	    $(CT_MODULES) \
 	    >$(@D)/A/system-modules
+	# Make files reproducible
+	find $(@D) -exec $(TOUCH) -h -c -t 197001010000.01 {} \;
 	$(TOUCH) $@
 
 # Can't generate ct.sym directly into modules libs as the SetupJarArchive macro

A gnu/packages/patches/openjdk-10-corba-reproducibility.patch => gnu/packages/patches/openjdk-10-corba-reproducibility.patch +12 -0
@@ 0,0 1,12 @@
Danny
--- orig/jdk-6fa770f9f8ab/make/corba/src/classes/build/tools/logutil/MC.java	2022-04-13 19:24:10.111680549 +0200
+++ jdk-6fa770f9f8ab/make/corba/src/classes/build/tools/logutil/MC.java	2022-04-13 22:51:13.399462259 +0200
@@ -154,7 +154,7 @@
                   groupName);
     pw.println("//");
     pw.printMsg("// Generated by MC.java version @, DO NOT EDIT BY HAND!", VERSION);
-    pw.printMsg("// Generated from input file @ on @", inFile, new Date());
+    pw.printMsg("// Generated from input file @ on @", inFile, System.getenv("SOURCE_DATE_EPOCH") == null ? new Date() : new Date(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH"))));
     pw.println();
   }
 

A gnu/packages/patches/openjdk-10-jar-reproducibility.patch => gnu/packages/patches/openjdk-10-jar-reproducibility.patch +103 -0
@@ 0,0 1,103 @@
diff -ru orig/jdk-6fa770f9f8ab/make/common/JarArchive.gmk jdk-6fa770f9f8ab/make/common/JarArchive.gmk
--- orig/jdk-6fa770f9f8ab/make/common/JarArchive.gmk	2022-04-13 19:24:10.107680441 +0200
+++ jdk-6fa770f9f8ab/make/common/JarArchive.gmk	2022-04-13 19:31:18.031271019 +0200
@@ -251,12 +251,14 @@
 	    $(ECHO) "Main-Class: $$(strip $$($1_JARMAIN))" >> $$($1_MANIFEST_FILE) $$(NEWLINE)) \
 	  $$(if $$($1_EXTRA_MANIFEST_ATTR), \
 	    $(PRINTF) "$$($1_EXTRA_MANIFEST_ATTR)\n" >> $$($1_MANIFEST_FILE) $$(NEWLINE)) \
+	  $(TOUCH) -h -c -t 197001010000.00 $$($1_MANIFEST_FILE) $$(NEWLINE) \
 	  $(ECHO) Creating $$($1_NAME) $$(NEWLINE) \
 	  $$($1_JAR_CMD) $$($1_JAR_CREATE_OPTIONS) $$@ $$($1_MANIFEST_FILE) $$(NEWLINE) \
 	  $$($1_SCAPTURE_CONTENTS) \
 	  $$($1_SCAPTURE_METAINF) \
 	  $$($1_SUPDATE_CONTENTS) \
-	  $$($1_JARINDEX) && true \
+	  $$($1_JARINDEX) && true $$(NEWLINE) \
+	  d="`mktemp -d`" && $(CP) -f $$@ "$$$$d/a.jar" && (cd "$$$$d" && unzip a.jar META-INF/MANIFEST.MF && $(TOUCH) -h -c -t 197001010000.00 META-INF &&  $(TOUCH) -h -c -t 197001010000.00 META-INF/MANIFEST.MF && (zip --symlinks -0 -X a.jar META-INF META-INF/MANIFEST.MF; zip --symlinks -0 -X a.jar META-INF META-INF/MANIFEST.MF)) && $(CP) -f "$$$$d/a.jar" $$@ \
 	, \
 	  $(ECHO) Modifying $$($1_NAME) $$(NEWLINE) \
 	  $$($1_CAPTURE_CONTENTS) \
diff -ru orig/jdk-6fa770f9f8ab/make/JrtfsJar.gmk jdk-6fa770f9f8ab/make/JrtfsJar.gmk
--- orig/jdk-6fa770f9f8ab/make/JrtfsJar.gmk	2022-04-13 19:24:10.091680007 +0200
+++ jdk-6fa770f9f8ab/make/JrtfsJar.gmk	2022-04-13 19:29:30.044346222 +0200
@@ -57,13 +57,18 @@
 # file will not be copied unless META-INF/services would also be added to the INCLUDES.
 # Adding META-INF/services would include all files in that directory when only the one
 # is needed, which is why this explicit copy is defined instead.
-$(eval $(call SetupCopyFiles, COPY_JIMAGE_SERVICE_PROVIDER, \
+$(eval $(call SetupCopyFiles, COPY_JIMAGE_SERVICE_PROVIDER, \
     SRC := $(TOPDIR)/src/java.base/share/classes, \
     DEST := $(SUPPORT_OUTPUTDIR)/jrtfs_classes, \
     FILES := META-INF/services/java.nio.file.spi.FileSystemProvider))
 
+.PHONY: $(COPY_JIMAGE_SERVICE_PROVIDER)_fix
+$(COPY_JIMAGE_SERVICE_PROVIDER)_fix: $(COPY_JIMAGE_SERVICE_PROVIDER)
+	find $(SUPPORT_OUTPUTDIR)/jrtfs_classes -exec $(TOUCH) -h -c -t 197001010000.00 {} \;
+	$(TOUCH) -h -c -t 197001010000.00 $(SUPPORT_OUTPUTDIR)/java-main-manifest.mf
+
 $(eval $(call SetupJarArchive,BUILD_JRTFS_JAR, \
-    DEPENDENCIES := $(BUILD_JRTFS) $(COPY_JIMAGE_SERVICE_PROVIDER), \
+    DEPENDENCIES := $(BUILD_JRTFS) $(COPY_JIMAGE_SERVICE_PROVIDER)_fix, \
     SRCS := $(SUPPORT_OUTPUTDIR)/jrtfs_classes, \
     JAR := $(SUPPORT_OUTPUTDIR)/modules_libs/java.base/jrt-fs.jar, \
     MANIFEST := $(SUPPORT_OUTPUTDIR)/java-main-manifest.mf, \
diff -ru orig/jdk-6fa770f9f8ab/src/jdk.jartool/share/classes/sun/tools/jar/Main.java jdk-6fa770f9f8ab/src/jdk.jartool/share/classes/sun/tools/jar/Main.java
--- orig/jdk-6fa770f9f8ab/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	2022-04-13 19:24:12.555746751 +0200
+++ jdk-6fa770f9f8ab/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	2022-04-13 19:25:34.117955999 +0200
@@ -849,12 +849,18 @@
                     output(getMsg("out.added.manifest"));
                 }
                 ZipEntry e = new ZipEntry(MANIFEST_DIR);
-                e.setTime(System.currentTimeMillis());
+                if (System.getenv("SOURCE_DATE_EPOCH") != null)
+                    e.setTime(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")));
+                else
+                    e.setTime(System.currentTimeMillis());
                 e.setSize(0);
                 e.setCrc(0);
                 zos.putNextEntry(e);
                 e = new ZipEntry(MANIFEST_NAME);
-                e.setTime(System.currentTimeMillis());
+                if (System.getenv("SOURCE_DATE_EPOCH") != null)
+                    e.setTime(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")));
+                else
+                    e.setTime(System.currentTimeMillis());
                 if (flag0) {
                     crc32Manifest(e, manifest);
                 }
@@ -1021,7 +1027,10 @@
         throws IOException
     {
         ZipEntry e = new ZipEntry(INDEX_NAME);
-        e.setTime(System.currentTimeMillis());
+        if (System.getenv("SOURCE_DATE_EPOCH") != null)
+            e.setTime(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")));
+        else
+            e.setTime(System.currentTimeMillis());
         if (flag0) {
             CRC32OutputStream os = new CRC32OutputStream();
             index.write(os);
@@ -1040,7 +1049,10 @@
             String name = mi.getKey();
             byte[] bytes = mi.getValue();
             ZipEntry e = new ZipEntry(name);
-            e.setTime(System.currentTimeMillis());
+            if (System.getenv("SOURCE_DATE_EPOCH") != null)
+                e.setTime(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")));
+            else
+                e.setTime(System.currentTimeMillis());
             if (flag0) {
                 crc32ModuleInfo(e, bytes);
             }
@@ -1065,7 +1077,10 @@
             addMultiRelease(m);
         }
         ZipEntry e = new ZipEntry(MANIFEST_NAME);
-        e.setTime(System.currentTimeMillis());
+        if (System.getenv("SOURCE_DATE_EPOCH") != null)
+            e.setTime(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")));
+        else
+            e.setTime(System.currentTimeMillis());
         if (flag0) {
             crc32Manifest(e, m);
         }

A gnu/packages/patches/openjdk-10-jtask-reproducibility.patch => gnu/packages/patches/openjdk-10-jtask-reproducibility.patch +53 -0
@@ 0,0 1,53 @@
--- jdk-10/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java.orig	2022-04-04 11:18:52.760626467 +0200
+++ jdk-10/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java	2022-04-04 12:03:40.645325687 +0200
@@ -105,6 +105,7 @@
 import jdk.internal.module.ModuleTarget;
 import jdk.internal.module.Resources;
 import jdk.tools.jlink.internal.Utils;
+import java.util.TreeSet;
 
 import static java.util.stream.Collectors.joining;
 
@@ -768,6 +769,7 @@
         void processSection(JmodOutputStream out, Section section, Path path)
             throws IOException
         {
+            TreeSet<Path> paths = new TreeSet<>();
             Files.walkFileTree(path, Set.of(FileVisitOption.FOLLOW_LINKS),
                 Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
                     @Override
@@ -781,20 +783,24 @@
 
                         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;
-                            }
+                            paths.add(file);
                         }
                         return FileVisitResult.CONTINUE;
                     }
                 });
+            for (Path file : paths) {
+                Path relPath = path.relativize(file);
+                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) {

A gnu/packages/patches/openjdk-10-module-reproducibility.patch => gnu/packages/patches/openjdk-10-module-reproducibility.patch +305 -0
@@ 0,0 1,305 @@
From a52c4ef44c0553a399a8a47e528db92e3bf51c6c Mon Sep 17 00:00:00 2001
From: Alan Bateman <alanb@openjdk.org>
Date: Wed, 29 Apr 2020 08:38:28 +0100
Subject: [PATCH] 8243666: ModuleHashes attribute generated for JMOD and JAR
 files depends on timestamps

Reviewed-by: mchung
---

--- orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java	2022-04-13 19:24:10.655695284 +0200
+++ jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java	2022-04-14 02:43:48.610326492 +0200
@@ -27,9 +27,8 @@
 
 import java.io.PrintStream;
 import java.lang.module.Configuration;
+import java.lang.module.ModuleReference;
 import java.lang.module.ResolvedModule;
-import java.net.URI;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
 import java.util.Collections;
@@ -39,8 +38,8 @@
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Stream;
 import static java.util.stream.Collectors.*;
 
@@ -101,7 +100,7 @@
         // the modules to record the hashes - it is the first matching
         // module and has not been hashed during the traversal.
         Set<String> mods = new HashSet<>();
-        Map<String, ModuleHashes> hashes = new HashMap<>();
+        Map<String, ModuleHashes> hashes = new TreeMap<>();
         builder.build()
                .orderedNodes()
                .filter(mn -> roots.contains(mn) && !mods.contains(mn))
@@ -116,27 +115,17 @@
                    mods.addAll(ns);
 
                    if (!ns.isEmpty()) {
-                       Map<String, Path> moduleToPath = ns.stream()
-                           .collect(toMap(Function.identity(), this::moduleToPath));
-                       hashes.put(mn, ModuleHashes.generate(moduleToPath, "SHA-256"));
+                       Set<ModuleReference> mrefs = ns.stream()
+                               .map(name -> configuration.findModule(name)
+                                                         .orElseThrow(InternalError::new))
+                               .map(ResolvedModule::reference)
+                               .collect(toSet());
+                       hashes.put(mn, ModuleHashes.generate(mrefs, "SHA-256"));
                    }
                });
         return hashes;
     }
 
-    private Path moduleToPath(String name) {
-        ResolvedModule rm = configuration.findModule(name).orElseThrow(
-            () -> new InternalError("Selected module " + name + " not on module path"));
-
-        URI uri = rm.reference().location().get();
-        Path path = Paths.get(uri);
-        String fn = path.getFileName().toString();
-        if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) {
-            throw new UnsupportedOperationException(path + " is not a modular JAR or jmod file");
-        }
-        return path;
-    }
-
     /*
      * Utility class
      */diff -ru orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java
--- orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java	2022-04-12 16:58:05.639985936 +0200
@@ -26,17 +26,21 @@
 package jdk.internal.module;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.Path;
+import java.lang.module.ModuleReader;
+import java.lang.module.ModuleReference;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Supplier;
 
 /**
  * The result of hashing the contents of a number of module artifacts.
@@ -60,8 +64,8 @@
      * @param algorithm   the algorithm used to create the hashes
      * @param nameToHash  the map of module name to hash value
      */
-    public ModuleHashes(String algorithm, Map<String, byte[]> nameToHash) {
-        this.algorithm = algorithm;
+    ModuleHashes(String algorithm, Map<String, byte[]> nameToHash) {
+        this.algorithm = Objects.requireNonNull(algorithm);
         this.nameToHash = Collections.unmodifiableMap(nameToHash);
     }
 
@@ -95,54 +99,125 @@
     }
 
     /**
-     * Computes the hash for the given file with the given message digest
-     * algorithm.
+     * Computes a hash from the names and content of a module.
      *
+     * @param reader the module reader to access the module content
+     * @param algorithm the name of the message digest algorithm to use
+     * @return the hash
+     * @throws IllegalArgumentException if digest algorithm is not supported
      * @throws UncheckedIOException if an I/O error occurs
      * @throws RuntimeException if the algorithm is not available
      */
-    public static byte[] computeHash(Path file, String algorithm) {
+    private static byte[] computeHash(ModuleReader reader, String algorithm) {
+        MessageDigest md;
         try {
-            MessageDigest md = MessageDigest.getInstance(algorithm);
-
-            // Ideally we would just mmap the file but this consumes too much
-            // memory when jlink is running concurrently on very large jmods
-            try (FileChannel fc = FileChannel.open(file)) {
-                ByteBuffer bb = ByteBuffer.allocate(32*1024);
-                while (fc.read(bb) > 0) {
-                    bb.flip();
-                    md.update(bb);
-                    assert bb.remaining() == 0;
-                    bb.clear();
-                }
-            }
-
-            return md.digest();
+            md = MessageDigest.getInstance(algorithm);
         } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(e);
+            throw new IllegalArgumentException(e);
+        }
+        try {
+            byte[] buf = new byte[32*1024];
+            reader.list().sorted().forEach(rn -> {
+                md.update(rn.getBytes(StandardCharsets.UTF_8));
+                try (InputStream in = reader.open(rn).orElseThrow(java.util.NoSuchElementException::new)) {
+                    int n;
+                    while ((n = in.read(buf)) > 0) {
+                        md.update(buf, 0, n);
+                    }
+                } catch (IOException ioe) {
+                    throw new UncheckedIOException(ioe);
+                }
+            });
         } catch (IOException ioe) {
             throw new UncheckedIOException(ioe);
         }
+        return md.digest();
     }
 
     /**
-     * Computes the hash for every entry in the given map, returning a
-     * {@code ModuleHashes} to encapsulate the result. The map key is
-     * the entry name, typically the module name. The map value is the file
-     * path to the entry (module artifact).
+     * Computes a hash from the names and content of a module.
      *
+     * @param supplier supplies the module reader to access the module content
+     * @param algorithm the name of the message digest algorithm to use
+     * @return the hash
+     * @throws IllegalArgumentException if digest algorithm is not supported
+     * @throws UncheckedIOException if an I/O error occurs
+     */
+    static byte[] computeHash(Supplier<ModuleReader> supplier, String algorithm) {
+        try (ModuleReader reader = supplier.get()) {
+            return computeHash(reader, algorithm);
+        } catch (IOException ioe) {
+            throw new UncheckedIOException(ioe);
+        }
+    }
+
+    /**
+     * Computes the hash from the names and content of a set of modules. Returns
+     * a {@code ModuleHashes} to encapsulate the result.
+     * @param mrefs the set of modules
+     * @param algorithm the name of the message digest algorithm to use
      * @return ModuleHashes that encapsulates the hashes
+     * @throws IllegalArgumentException if digest algorithm is not supported
+     * @throws UncheckedIOException if an I/O error occurs
      */
-    public static ModuleHashes generate(Map<String, Path> map, String algorithm) {
+    static ModuleHashes generate(Set<ModuleReference> mrefs, String algorithm) {
         Map<String, byte[]> nameToHash = new HashMap<>();
-        for (Map.Entry<String, Path> entry: map.entrySet()) {
-            String name = entry.getKey();
-            Path path = entry.getValue();
-            nameToHash.put(name, computeHash(path, algorithm));
+        for (ModuleReference mref : mrefs) {
+            try (ModuleReader reader = mref.open()) {
+                byte[] hash = computeHash(reader, algorithm);
+                nameToHash.put(mref.descriptor().name(), hash);
+            } catch (IOException ioe) {
+                throw new UncheckedIOException(ioe);
+            }
         }
         return new ModuleHashes(algorithm, nameToHash);
     }
 
+    @Override
+    public int hashCode() {
+        int h = algorithm.hashCode();
+        for (Map.Entry<String, byte[]> e : nameToHash.entrySet()) {
+            h = h * 31 + e.getKey().hashCode();
+            h = h * 31 + Arrays.hashCode(e.getValue());
+        }
+        return h;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ModuleHashes))
+            return false;
+        ModuleHashes other = (ModuleHashes) obj;
+        if (!algorithm.equals(other.algorithm)
+                || nameToHash.size() != other.nameToHash.size())
+            return false;
+        for (Map.Entry<String, byte[]> e : nameToHash.entrySet()) {
+            String name = e.getKey();
+            byte[] hash = e.getValue();
+            if (!Arrays.equals(hash, other.nameToHash.get(name)))
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(algorithm);
+        sb.append(" ");
+        nameToHash.entrySet()
+                .stream()
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> {
+                    sb.append(e.getKey());
+                    sb.append("=");
+                    byte[] ba = e.getValue();
+                    for (byte b : ba) {
+                        sb.append(String.format("%02x", b & 0xff));
+                    }
+                });
+        return sb.toString();
+    }
+
     /**
      * This is used by jdk.internal.module.SystemModules class
      * generated at link time.
diff -ru orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java
--- orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java	2022-04-12 16:43:12.967868689 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020, 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
diff -ru orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
--- orig/jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java	2022-04-12 16:43:12.971868797 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, 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
@@ -95,7 +95,7 @@
                                         Path file) {
         URI uri = file.toUri();
         Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri);
-        HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
+        HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
         return newModule(attrs, uri, supplier, patcher, hasher);
     }
 
@@ -105,7 +105,7 @@
     static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
         URI uri = file.toUri();
         Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
-        HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
+        HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
         return newModule(attrs, uri, supplier, null, hasher);
     }
 

A gnu/packages/patches/openjdk-10-module3-reproducibility.patch => gnu/packages/patches/openjdk-10-module3-reproducibility.patch +34 -0
@@ 0,0 1,34 @@
Danny wrote.

--- orig/jdk-3cc80be736f2/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	2022-04-13 17:30:37.242775977 +0200
@@ -43,6 +43,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -2155,9 +2156,9 @@
          * @return The module descriptor
          */
         public ModuleDescriptor build() {
-            Set<Requires> requires = new HashSet<>(this.requires.values());
-            Set<Exports> exports = new HashSet<>(this.exports.values());
-            Set<Opens> opens = new HashSet<>(this.opens.values());
+            Set<Requires> requires = new TreeSet<>(this.requires.values());
+            Set<Exports> exports = new TreeSet<>(this.exports.values());
+            Set<Opens> opens = new TreeSet<>(this.opens.values());
 
             // add dependency on java.base
             if (strict
@@ -2169,7 +2170,7 @@
                                           null));
             }
 
-            Set<Provides> provides = new HashSet<>(this.provides.values());
+            Set<Provides> provides = new TreeSet<>(this.provides.values());
 
             return new ModuleDescriptor(name,
                                         version,

A gnu/packages/patches/openjdk-10-module4-reproducibility.patch => gnu/packages/patches/openjdk-10-module4-reproducibility.patch +14 -0
@@ 0,0 1,14 @@
Danny wrote it

--- orig/jdk-6fa770f9f8ab/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java	2022-04-13 19:24:12.655749459 +0200
+++ jdk-6fa770f9f8ab/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java	2022-04-14 01:53:23.555465018 +0200
@@ -861,7 +861,8 @@
          */
         private void genModuleReads(ClassWriter cw, Configuration cf) {
             // module name -> names of modules that it reads
-            Map<String, Set<String>> map = cf.modules().stream()
+            Map<String, Set<String>> map = cf.modules().stream()
+                    .sorted(java.util.Comparator.comparing(ResolvedModule::name))
                     .collect(Collectors.toMap(
                             ResolvedModule::name,
                             m -> m.reads().stream()