~ruther/guix-local

2f1daa35746c896cd59882383c236ee97a3cbad7 — Danny Milosavljevic 2 years ago 10d45f6
gnu: openjdk9: Make build reproducible.

* gnu/packages/patches/openjdk-9-classlist-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-9-jar-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-9-module-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-9-module2-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-9-module3-reproducibility.patch: New file.
* gnu/packages/patches/openjdk-9-idlj-reproducibility.patch: New file.
* gnu/packages/java.scm (openjdk9)[source]: Add patches.
[arguments]<#:phases>[strip-zip-timestamps]: Modify.
[native-inputs, inputs]: Use new-style syntax.
* gnu/local/mk (dist_patch_DATA): Add patches.

Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
Modified-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
Change-Id: Ifb7a87b0c11f3a7032597013ac904aefc9234db1
M gnu/local.mk => gnu/local.mk +6 -0
@@ 1823,6 1823,12 @@ dist_patch_DATA =						\
  %D%/packages/patches/openjdk-currency-time-bomb2.patch	\
  %D%/packages/patches/openjdk-9-pointer-comparison.patch       \
  %D%/packages/patches/openjdk-9-setsignalhandler.patch         \
  %D%/packages/patches/openjdk-9-classlist-reproducibility.patch	\
  %D%/packages/patches/openjdk-9-idlj-reproducibility.patch	\
  %D%/packages/patches/openjdk-9-jar-reproducibility.patch	\
  %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-idlj-reproducibility.patch	\
  %D%/packages/patches/openjdk-10-pointer-comparison.patch      \
  %D%/packages/patches/openjdk-10-setsignalhandler.patch        \

M gnu/packages/java.scm => gnu/packages/java.scm +174 -119
@@ 886,9 886,16 @@ new Date();"))
              (sha256
               (base32
                "1v92nzdqx07c35x945awzir4yk0fk22vky6fpp8mq9js930sxsz0"))
              (patches (search-patches "openjdk-9-pointer-comparison.patch"
                                       "openjdk-9-setsignalhandler.patch"
                                       "openjdk-currency-time-bomb.patch"))))
              (patches
               (search-patches "openjdk-9-pointer-comparison.patch"
                               "openjdk-9-classlist-reproducibility.patch"
                               "openjdk-currency-time-bomb.patch"
                               "openjdk-9-jar-reproducibility.patch"
                               "openjdk-9-module-reproducibility.patch"
                               "openjdk-9-module2-reproducibility.patch"
                               "openjdk-9-module3-reproducibility.patch"
                               "openjdk-9-idlj-reproducibility.patch"
                               "openjdk-9-setsignalhandler.patch"))))
    (build-system gnu-build-system)
    (outputs '("out" "jdk" "doc"))
    (arguments


@@ 899,7 906,7 @@ new Date();"))
       ((guix build utils)
        (guix build gnu-build-system)
        (ice-9 popen))
       #:tests? #f; require jtreg
       #:tests? #f                      ; require jtreg
       #:make-flags '("all")
       #:disallowed-references ,(list (gexp-input icedtea-8)
                                      (gexp-input icedtea-8 "jdk"))


@@ 988,80 995,80 @@ new Date();"))
                              file))))
                (find-files "."
                            "\\.c$|\\.h$")))))
           ;; By default OpenJDK only generates an empty keystore.  In order to
           ;; be able to use certificates in Java programs we need to generate a
           ;; keystore from a set of certificates.  For convenience we use the
           ;; certificates from the nss-certs package.
           (add-after 'install 'install-keystore
             (lambda* (#:key inputs outputs #:allow-other-keys)
               (use-modules (ice-9 rdelim))
               (let* ((keystore  "cacerts")
                      (certs-dir (search-input-directory inputs
                                                         "etc/ssl/certs"))
                      (keytool   (string-append (assoc-ref outputs "jdk")
                                                "/bin/keytool")))
                 (define (extract-cert file target)
                   (call-with-input-file file
                     (lambda (in)
                       (call-with-output-file target
                         (lambda (out)
                           (let loop ((line (read-line in 'concat))
                                      (copying? #f))
                             (cond
                              ((eof-object? line) #t)
                              ((string-prefix? "-----BEGIN" line)
                               (display line out)
                               (loop (read-line in 'concat) #t))
                              ((string-prefix? "-----END" line)
                               (display line out)
                               #t)
                              (else
                               (when copying? (display line out))
                               (loop (read-line in 'concat) copying?)))))))))
                 (define (import-cert cert)
                   (format #t "Importing certificate ~a\n" (basename cert))
                   (let ((temp "tmpcert"))
                     (extract-cert cert temp)
                     (let ((port (open-pipe* OPEN_WRITE keytool
                                             "-import"
                                             "-alias" (basename cert)
                                             "-keystore" keystore
                                             "-storepass" "changeit"
                                             "-file" temp)))
                       (display "yes\n" port)
                       (when (not (zero? (status:exit-val (close-pipe port))))
                         (format #t "failed to import ~a\n" cert)))
                     (delete-file temp)))

                 ;; This is necessary because the certificate directory contains
                 ;; files with non-ASCII characters in their names.
                 (setlocale LC_ALL "C.UTF-8")
                 (setenv "LC_ALL" "C.UTF-8")

                 (copy-file (string-append (assoc-ref outputs "out")
                                           "/lib/security/cacerts")
                            keystore)
                 (chmod keystore #o644)
                 (for-each import-cert (find-files certs-dir "\\.pem$"))
                 (mkdir-p (string-append (assoc-ref outputs "out")
                                         "/lib/security"))
                 (mkdir-p (string-append (assoc-ref outputs "jdk")
                                         "/lib/security"))

                 ;; The cacerts files we are going to overwrite are chmod'ed as
                 ;; read-only (444) in icedtea-8 (which derives from this
                 ;; package).  We have to change this so we can overwrite them.
                 (chmod (string-append (assoc-ref outputs "out")
                                       "/lib/security/" keystore) #o644)
                 (chmod (string-append (assoc-ref outputs "jdk")
                                       "/lib/security/" keystore) #o644)

                 (install-file keystore
                               (string-append (assoc-ref outputs "out")
                                              "/lib/security"))
                 (install-file keystore
                               (string-append (assoc-ref outputs "jdk")
                                              "/lib/security")))))
         ;; By default OpenJDK only generates an empty keystore.  In order to
         ;; be able to use certificates in Java programs we need to generate a
         ;; keystore from a set of certificates.  For convenience we use the
         ;; certificates from the nss-certs package.
         (add-after 'install 'install-keystore
           (lambda* (#:key inputs outputs #:allow-other-keys)
             (use-modules (ice-9 rdelim))
             (let* ((keystore  "cacerts")
                    (certs-dir (search-input-directory inputs
                                                       "etc/ssl/certs"))
                    (keytool   (string-append (assoc-ref outputs "jdk")
                                              "/bin/keytool")))
               (define (extract-cert file target)
                 (call-with-input-file file
                   (lambda (in)
                     (call-with-output-file target
                       (lambda (out)
                         (let loop ((line (read-line in 'concat))
                                    (copying? #f))
                           (cond
                            ((eof-object? line) #t)
                            ((string-prefix? "-----BEGIN" line)
                             (display line out)
                             (loop (read-line in 'concat) #t))
                            ((string-prefix? "-----END" line)
                             (display line out)
                             #t)
                            (else
                             (when copying? (display line out))
                             (loop (read-line in 'concat) copying?)))))))))
               (define (import-cert cert)
                 (format #t "Importing certificate ~a\n" (basename cert))
                 (let ((temp "tmpcert"))
                   (extract-cert cert temp)
                   (let ((port (open-pipe* OPEN_WRITE keytool
                                           "-import"
                                           "-alias" (basename cert)
                                           "-keystore" keystore
                                           "-storepass" "changeit"
                                           "-file" temp)))
                     (display "yes\n" port)
                     (when (not (zero? (status:exit-val (close-pipe port))))
                       (format #t "failed to import ~a\n" cert)))
                   (delete-file temp)))

               ;; This is necessary because the certificate directory contains
               ;; files with non-ASCII characters in their names.
               (setlocale LC_ALL "C.UTF-8")
               (setenv "LC_ALL" "C.UTF-8")

               (copy-file (string-append (assoc-ref outputs "out")
                                         "/lib/security/cacerts")
                          keystore)
               (chmod keystore #o644)
               (for-each import-cert (find-files certs-dir "\\.pem$"))
               (mkdir-p (string-append (assoc-ref outputs "out")
                                       "/lib/security"))
               (mkdir-p (string-append (assoc-ref outputs "jdk")
                                       "/lib/security"))

               ;; The cacerts files we are going to overwrite are chmod'ed as
               ;; read-only (444) in icedtea-8 (which derives from this
               ;; package).  We have to change this so we can overwrite them.
               (chmod (string-append (assoc-ref outputs "out")
                                     "/lib/security/" keystore) #o644)
               (chmod (string-append (assoc-ref outputs "jdk")
                                     "/lib/security/" keystore) #o644)

               (install-file keystore
                             (string-append (assoc-ref outputs "out")
                                            "/lib/security"))
               (install-file keystore
                             (string-append (assoc-ref outputs "jdk")
                                            "/lib/security")))))
         ;; Some of the libraries in the lib/ folder link to libjvm.so.
         ;; But that shared object is located in the server/ folder, so it
         ;; cannot be found.  This phase creates a symbolic link in the


@@ 1094,48 1101,96 @@ new Date();"))
               (copy-recursively (string-append images "/images/docs") doc))))
         (add-after 'install 'strip-zip-timestamps
           (lambda* (#:key outputs #:allow-other-keys)
             (for-each (lambda (zip)
                         (let ((dir (mkdtemp "zip-contents.XXXXXX")))
                           (with-directory-excursion dir
                             (invoke "unzip" zip))
                           (delete-file zip)
                           (for-each (lambda (file)
                                       (let ((s (lstat file)))
                                         (unless (eq? (stat:type s) 'symlink)
                                           (format #t "reset ~a~%" file)
                                           (utime file 0 0 0 0))))
                                     (find-files dir #:directories? #t))
                           (with-directory-excursion dir
                             (let ((files (find-files "." ".*" #:directories? #t)))
                               (apply invoke "zip" "-0" "-X" zip files)))))
                       (find-files (assoc-ref outputs "doc") ".*.zip$")))))))
    (inputs
     `(("alsa-lib" ,alsa-lib)
       ("cups" ,cups)
       ("fontconfig" ,fontconfig)
       ("freetype" ,freetype)
       ("giflib" ,giflib)
       ("lcms" ,lcms)
       ("libelf" ,libelf)
       ("libjpeg" ,libjpeg-turbo)
       ("libice" ,libice)
       ("libpng" ,libpng)
       ("libx11" ,libx11)
       ("libxcomposite" ,libxcomposite)
       ("libxi" ,libxi)
       ("libxinerama" ,libxinerama)
       ("libxrender" ,libxrender)
       ("libxt" ,libxt)
       ("libxtst" ,libxtst)))
             (for-each
              (lambda (zip)
                (let ((dir (mkdtemp "zip-contents.XXXXXX")))
                  (with-directory-excursion dir
                    ;; This is an exact copy of the implementation of invoke,
                    ;; but this accepts exit code 1 as OK.
                    (let ((code (system* "unzip" "--" zip)))
                      ;; jmod files are zip files with an extra header in
                      ;; front.  unzip will warn about that -- but otherwise
                      ;; work.
                      (when (> (status:exit-val code) 1) ; 1 is just a warning
                        (raise
                         (condition
                          (&invoke-error
                           (program "unzip")
                           (arguments (list "--" zip))
                           (exit-status (status:exit-val code))
                           (term-signal (status:term-sig code))
                           (stop-signal (status:stop-sig code))))))))
                  (delete-file zip)
                  (for-each (lambda (file)
                              (let ((s (lstat file)))
                                (format #t "reset ~a~%" file)
                                (utime file 1 1 0 0
                                       AT_SYMLINK_NOFOLLOW)))
                            (find-files dir #:directories? #t))
                  (with-directory-excursion dir
                    (let ((files (cons "./META-INF/MANIFEST.MF"
                                       (append
                                        (find-files "./META-INF" ".*")
                                        ;; for jmod:
                                        (list "./classes/module-info.class")
                                        (find-files "." ".*")))))
                      (apply invoke "zip" "--symlinks" "-0" "-X" zip files)
                      (when (string-suffix? ".jmod" zip)
                        (let ((new-zip (string-append zip "n"))
                              (contents (call-with-input-file zip
                                          (@ (ice-9 binary-ports)
                                             get-bytevector-all))))
                          (call-with-output-file new-zip
                            (lambda (output-port)
                              ((@ (ice-9 binary-ports) put-bytevector)
                               output-port
                               #vu8(#x4a #x4d #x01 #x00)) ; JM
                              ((@ (ice-9 binary-ports) put-bytevector)
                               output-port
                               contents)))
                          (rename-file new-zip zip)))))))
              (append (find-files (string-append
                                   (assoc-ref outputs "doc")
                                   "/api")
                                  "\\.zip$")
                      (find-files (assoc-ref outputs "doc") "src\\.zip$")
                      (find-files (assoc-ref outputs "jdk") "src\\.zip$")
                      (find-files (assoc-ref outputs "jdk") "\\.jmod$")
                      (find-files (assoc-ref outputs "jdk") "\\.diz$")
                      (find-files (assoc-ref outputs "out") "\\.diz$")

                      (list (string-append (assoc-ref outputs "jdk")
                                           "/lib/jrt-fs.jar"))
                      (find-files (string-append (assoc-ref outputs "jdk")
                                                 "/demo")
                                  "\\.jar$"))))))))
    (inputs
     (list alsa-lib
           cups
           fontconfig
           freetype
           giflib
           lcms
           libelf
           libjpeg-turbo
           libice
           libpng
           libx11
           libxcomposite
           libxi
           libxinerama
           libxrender
           libxt
           libxtst))
    (native-inputs
     `(("icedtea-8" ,icedtea-8)
       ("icedtea-8:jdk" ,icedtea-8 "jdk")
       ;; XXX: The build system fails with newer versions of GNU Make.
       ("make@4.2" ,gnu-make-4.2)
       ("nss-certs" ,nss-certs)
       ("unzip" ,unzip)
       ("which" ,which)
       ("zip" ,zip)))
     (list icedtea-8
           `(,icedtea-8 "jdk")
           ;; XXX: The build system fails with newer versions of GNU Make.
           gnu-make-4.2
           nss-certs
           unzip
           which
           zip))
    (home-page "https://openjdk.org/projects/jdk9/")
    (synopsis "Java development kit")
    (description

A gnu/packages/patches/openjdk-9-classlist-reproducibility.patch => gnu/packages/patches/openjdk-9-classlist-reproducibility.patch +31 -0
@@ 0,0 1,31 @@
From: Danny Milosavljevic <dannym@scratchpost.org>
Date: Wed, 18 Apr 2022 18:38:28 +0100
Subject: Make classlist reproducible

--- jdk-09/make/GenerateLinkOptData.gmk.orig	2022-04-05 10:05:35.892134188 +0200
+++ jdk-09/make/GenerateLinkOptData.gmk	2022-04-05 10:06:07.885003056 +0200
@@ -61,11 +61,12 @@
 	$(call MakeDir, $(LINK_OPT_DIR))
 	$(call LogInfo, Generating $(patsubst $(OUTPUT_ROOT)/%, %, $@))
 	$(call LogInfo, Generating $(patsubst $(OUTPUT_ROOT)/%, %, $(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
--- jdk-09/langtools/make/gendata/Gendata-jdk.compiler.gmk.orig	2022-04-08 22:04:05.784424812 +0200
+++ jdk-09/langtools/make/gendata/Gendata-jdk.compiler.gmk	2022-04-08 22:09:36.333575143 +0200
@@ -79,6 +79,8 @@
 	    $(CT_MODULESOURCEPATH) \
 	    $(CT_MODULES) \
 	    >$(@D)/9/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-9-idlj-reproducibility.patch => gnu/packages/patches/openjdk-9-idlj-reproducibility.patch +37 -0
@@ 0,0 1,37 @@
From: Danny Milosavljevic <dannym@scratchpost.org>
Date: Wed, 18 Apr 2022 19:28:00 +0100
Subject: Make IDL reproducible

--- jdk-09/corba/src/java.corba/share/classes/com/sun/tools/corba/se/idl/toJavaPortable/Util.java.orig	2022-04-05 02:46:26.805340292 +0200
+++ jdk-09/corba/src/java.corba/share/classes/com/sun/tools/corba/se/idl/toJavaPortable/Util.java	2022-04-05 02:48:23.152494213 +0200
@@ -1146,7 +1146,7 @@
     else
       formatter.setTimeZone (java.util.TimeZone.getDefault ());
 
-    stream.println ("* " + formatter.format (new Date ()));
+    stream.println ("* " + formatter.format (System.getenv("SOURCE_DATE_EPOCH") == null ? new Date () : new Date(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")))));
 
     // <daz>
     ///////////////
--- jdk-09/corba/make/src/classes/build/tools/logutil/MC.java.orig	2022-04-05 11:09:43.824720493 +0200
+++ jdk-09/corba/make/src/classes/build/tools/logutil/MC.java	2022-04-05 11:10:46.518435511 +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();
   }
 
--- jdk-09/jdk/make/src/classes/build/tools/generatecharacter/GenerateCharacter.java.orig	2022-04-05 11:14:29.228526408 +0200
+++ jdk-09/jdk/make/src/classes/build/tools/generatecharacter/GenerateCharacter.java	2022-04-05 11:15:32.658260748 +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-9-jar-reproducibility.patch => gnu/packages/patches/openjdk-9-jar-reproducibility.patch +107 -0
@@ 0,0 1,107 @@
From: Danny Milosavljevic <dannym@scratchpost.org>
Date: Wed, 18 Apr 2022 20:10:01 +0100
Subject: Make JARs reproducible

--- jdk-09/make/common/JarArchive.gmk.orig	2022-04-08 21:56:04.075111687 +0200
+++ jdk-09/make/common/JarArchive.gmk	2022-04-11 00:49:16.809140388 +0200
@@ -249,12 +249,16 @@
 	    $(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)) \
-	  $(ECHO) Creating $$($1_NAME) $$(NEWLINE) \
+	  $(TOUCH) -h -c -t 197001010000.00 $$($1_MANIFEST_FILE) $$(NEWLINE) \
+	  $(ECHO) XCreating $$($1_NAME) $(JAR) $$($1_JAR_CREATE_OPTIONS) $$@ $$($1_MANIFEST_FILE) $$(NEWLINE) \
 	  $(JAR) $$($1_JAR_CREATE_OPTIONS) $$@ $$($1_MANIFEST_FILE) $$(NEWLINE) \
 	  $$($1_SCAPTURE_CONTENTS) \
 	  $$($1_SCAPTURE_METAINF) \
 	  $$($1_SUPDATE_CONTENTS) \
-	  $$($1_JARINDEX) && true \
+	  $$($1_JARINDEX) && true $$(NEWLINE) \
+	  $(ECHO) Kreppel2 $$@ $$(NEWLINE) \
+	  unzip -v $$@ $$(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) \
--- jdk-09/make/JrtfsJar.gmk.orig	2022-04-10 13:48:57.385120008 +0200
+++ jdk-09/make/JrtfsJar.gmk	2022-04-10 13:58:04.688158538 +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_PROVIDER1, \
     SRC := $(JDK_TOPDIR)/src/java.base/share/classes, \
     DEST := $(SUPPORT_OUTPUTDIR)/jrtfs_classes, \
     FILES := META-INF/services/java.nio.file.spi.FileSystemProvider))
 
+.PHONY: jrtfsfixtimestamps47
+jrtfsfixtimestamps47: $(COPY_JIMAGE_SERVICE_PROVIDER1)
+	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) jrtfsfixtimestamps47, \
     SRCS := $(SUPPORT_OUTPUTDIR)/jrtfs_classes, \
     JAR := $(SUPPORT_OUTPUTDIR)/modules_libs/java.base/jrt-fs.jar, \
     MANIFEST := $(SUPPORT_OUTPUTDIR)/java-main-manifest.mf, \
--- jdk-09/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java.orig	2022-04-10 02:05:50.983247794 +0200
+++ jdk-09/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	2022-04-10 02:13:01.638960337 +0200
@@ -850,12 +850,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);
                 }
@@ -1022,7 +1028,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);
@@ -1041,7 +1050,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);
             }
@@ -1066,7 +1078,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-9-module-reproducibility.patch => gnu/packages/patches/openjdk-9-module-reproducibility.patch +297 -0
@@ 0,0 1,297 @@
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
See: https://bugs.openjdk.org/browse/JDK-8243666

Reviewed-by: mchung
---

diff -ru orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java
--- orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java	2022-04-12 16:47:15.690423653 +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;
@@ -40,7 +39,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Stream;
 import static java.util.stream.Collectors.*;
 
@@ -116,27 +114,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/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java
--- orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleHashes.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/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/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java
--- orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/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/jdk/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
--- orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/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-9-module2-reproducibility.patch => gnu/packages/patches/openjdk-9-module2-reproducibility.patch +125 -0
@@ 0,0 1,125 @@
Backport from openjdk 10

--- orig/jdk-3cc80be736f2/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModuleSorter.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModuleSorter.java	2022-04-12 20:48:04.474353305 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2017, 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
@@ -30,13 +30,16 @@
 import jdk.tools.jlink.plugin.ResourcePoolModuleView;
 
 import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Requires;
 import java.lang.module.ModuleDescriptor.Requires.Modifier;
 
 import java.nio.ByteBuffer;
-import java.util.Deque;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
@@ -45,9 +48,8 @@
  * Helper class to sort modules in topological order
  */
 public final class ModuleSorter {
-    private final Deque<ResourcePoolModule> nodes = new LinkedList<>();
-    private final Map<String, Set<ResourcePoolModule>> edges = new HashMap<>();
-    private final Deque<ResourcePoolModule> result = new LinkedList<>();
+    private final Map<ResourcePoolModule, Set<ResourcePoolModule>> graph = new HashMap<>();
+    private final List<ResourcePoolModule> result = new ArrayList<>();
 
     private final ResourcePoolModuleView moduleView;
 
@@ -69,11 +71,17 @@
 
     private ModuleSorter addModule(ResourcePoolModule module) {
         addNode(module);
-        readModuleDescriptor(module).requires().forEach(req -> {
+        // the module graph will be traversed in a stable order for
+        // the topological sort. So add the dependences in the module name order
+        readModuleDescriptor(module).requires()
+                                    .stream()
+                                    .sorted(Comparator.comparing(Requires::name))
+                                    .forEach(req ->
+        {
             ResourcePoolModule dep = moduleView.findModule(req.name()).orElse(null);
             if (dep != null) {
                 addNode(dep);
-                edges.get(module.name()).add(dep);
+                graph.get(module).add(dep);
             } else if (!req.modifiers().contains(Modifier.STATIC)) {
                 throw new PluginException(req.name() + " not found");
             }
@@ -82,22 +90,23 @@
     }
 
     private void addNode(ResourcePoolModule module) {
-        nodes.add(module);
-        edges.computeIfAbsent(module.name(), _n -> new HashSet<>());
+        graph.computeIfAbsent(module, _n -> new LinkedHashSet<>());
     }
 
+    /*
+     * The module graph will be traversed in a stable order
+     * (traversing the modules and their dependences in alphabetical order)
+     * so that it will produce the same result of a given module graph.
+     */
     private synchronized void build() {
-        if (!result.isEmpty() || nodes.isEmpty())
+        if (!result.isEmpty() || graph.isEmpty())
             return;
 
-        Deque<ResourcePoolModule> visited = new LinkedList<>();
-        Deque<ResourcePoolModule> done = new LinkedList<>();
-        ResourcePoolModule node;
-        while ((node = nodes.poll()) != null) {
-            if (!visited.contains(node)) {
-                visit(node, visited, done);
-            }
-        }
+        Set<ResourcePoolModule> visited = new HashSet<>();
+        Set<ResourcePoolModule> done = new HashSet<>();
+        graph.keySet().stream()
+             .sorted(Comparator.comparing(ResourcePoolModule::name))
+             .forEach(node -> visit(node, visited, done));
     }
 
     public Stream<ResourcePoolModule> sorted() {
@@ -106,19 +115,21 @@
     }
 
     private void visit(ResourcePoolModule node,
-                       Deque<ResourcePoolModule> visited,
-                       Deque<ResourcePoolModule> done) {
+                       Set<ResourcePoolModule> visited,
+                       Set<ResourcePoolModule> done) {
         if (visited.contains(node)) {
             if (!done.contains(node)) {
                 throw new IllegalArgumentException("Cyclic detected: " +
-                    node + " " + edges.get(node.name()));
+                    node + " " + graph.get(node));
             }
             return;
         }
+
+        // traverse the dependences of the given module which are
+        // also sorted in alphabetical order
         visited.add(node);
-        edges.get(node.name())
-             .forEach(x -> visit(x, visited, done));
+        graph.get(node).forEach(x -> visit(x, visited, done));
         done.add(node);
-        result.addLast(node);
+        result.add(node);
     }
 }

A gnu/packages/patches/openjdk-9-module3-reproducibility.patch => gnu/packages/patches/openjdk-9-module3-reproducibility.patch +36 -0
@@ 0,0 1,36 @@
From: Danny Milosavljevic <dannym@scratchpost.org>
Date: Wed, 18 Apr 2022 21:50:00 +0100
Subject: Make module descriptor reproducible

--- orig/jdk-3cc80be736f2/jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	1970-01-01 01:00:01.000000000 +0100
+++ jdk-3cc80be736f2/jdk/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,