aboutsummaryrefslogtreecommitdiff
path: root/gnu/packages
diff options
context:
space:
mode:
authorDanny Milosavljevic <dannym@scratchpost.org>2023-07-31 20:09:13 +0200
committerLudovic Courtès <ludo@gnu.org>2024-08-31 10:44:39 +0200
commit2f1daa35746c896cd59882383c236ee97a3cbad7 (patch)
treeed0d1a9b208f9aee1e41e1fee20ad93dda6eec59 /gnu/packages
parent10d45f6a0ead9243e4a807dd99d5d4501e20d345 (diff)
downloadguix-2f1daa35746c896cd59882383c236ee97a3cbad7.tar
guix-2f1daa35746c896cd59882383c236ee97a3cbad7.tar.gz
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
Diffstat (limited to 'gnu/packages')
-rw-r--r--gnu/packages/java.scm293
-rw-r--r--gnu/packages/patches/openjdk-9-classlist-reproducibility.patch31
-rw-r--r--gnu/packages/patches/openjdk-9-idlj-reproducibility.patch37
-rw-r--r--gnu/packages/patches/openjdk-9-jar-reproducibility.patch107
-rw-r--r--gnu/packages/patches/openjdk-9-module-reproducibility.patch297
-rw-r--r--gnu/packages/patches/openjdk-9-module2-reproducibility.patch125
-rw-r--r--gnu/packages/patches/openjdk-9-module3-reproducibility.patch36
7 files changed, 807 insertions, 119 deletions
diff --git a/gnu/packages/java.scm b/gnu/packages/java.scm
index 9dc28858bf..ace06977c9 100644
--- a/gnu/packages/java.scm
+++ b/gnu/packages/java.scm
@@ -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
diff --git a/gnu/packages/patches/openjdk-9-classlist-reproducibility.patch b/gnu/packages/patches/openjdk-9-classlist-reproducibility.patch
new file mode 100644
index 0000000000..d0ce7bc4d6
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-classlist-reproducibility.patch
@@ -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
diff --git a/gnu/packages/patches/openjdk-9-idlj-reproducibility.patch b/gnu/packages/patches/openjdk-9-idlj-reproducibility.patch
new file mode 100644
index 0000000000..30dcdf2496
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-idlj-reproducibility.patch
@@ -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 {
diff --git a/gnu/packages/patches/openjdk-9-jar-reproducibility.patch b/gnu/packages/patches/openjdk-9-jar-reproducibility.patch
new file mode 100644
index 0000000000..130eacd867
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-jar-reproducibility.patch
@@ -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);
+ }
diff --git a/gnu/packages/patches/openjdk-9-module-reproducibility.patch b/gnu/packages/patches/openjdk-9-module-reproducibility.patch
new file mode 100644
index 0000000000..320bb7806a
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-module-reproducibility.patch
@@ -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);
+ }
+
diff --git a/gnu/packages/patches/openjdk-9-module2-reproducibility.patch b/gnu/packages/patches/openjdk-9-module2-reproducibility.patch
new file mode 100644
index 0000000000..f167d94dc8
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-module2-reproducibility.patch
@@ -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);
+ }
+ }
diff --git a/gnu/packages/patches/openjdk-9-module3-reproducibility.patch b/gnu/packages/patches/openjdk-9-module3-reproducibility.patch
new file mode 100644
index 0000000000..9db54f5531
--- /dev/null
+++ b/gnu/packages/patches/openjdk-9-module3-reproducibility.patch
@@ -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,