diff --git a/pkgs/build-support/java/canonicalize-jar.nix b/pkgs/build-support/java/canonicalize-jar.nix new file mode 100644 index 00000000000..1edd9a6e0d2 --- /dev/null +++ b/pkgs/build-support/java/canonicalize-jar.nix @@ -0,0 +1,9 @@ +{ substituteAll, unzip, zip }: + +substituteAll { + name = "canonicalize-jar"; + src = ./canonicalize-jar.sh; + + unzip = "${unzip}/bin/unzip"; + zip = "${zip}/bin/zip"; +} diff --git a/pkgs/build-support/java/canonicalize-jar.sh b/pkgs/build-support/java/canonicalize-jar.sh new file mode 100644 index 00000000000..af010bcd2b2 --- /dev/null +++ b/pkgs/build-support/java/canonicalize-jar.sh @@ -0,0 +1,29 @@ +# Canonicalize the manifest & repack with deterministic timestamps. +canonicalizeJar() { + local input='' outer='' + input="$(realpath -sm -- "$1")" + outer="$(pwd)" + # -qq: even quieter + @unzip@ -qq "$input" -d "$input-tmp" + canonicalizeJarManifest "$input-tmp/META-INF/MANIFEST.MF" + # Sets all timestamps to Jan 1 1980, the earliest mtime zips support. + find -- "$input-tmp" -exec touch -t 198001010000.00 {} + + rm "$input" + pushd "$input-tmp" 2>/dev/null + # -q|--quiet, -r|--recurse-paths + # -o|--latest-time: canonicalizes overall archive mtime + # -X|--no-extra: don't store platform-specific extra file attribute fields + @zip@ -qroX "$outer/tmp-out.jar" . 2> /dev/null + popd 2>/dev/null + rm -rf "$input-tmp" + mv "$outer/tmp-out.jar" "$input" +} + +# See also the Java specification's JAR requirements: +# https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Notes_on_Manifest_and_Signature_Files +canonicalizeJarManifest() { + local input='' + input="$(realpath -sm -- "$1")" + (head -n 1 "$input" && tail -n +2 "$input" | sort | grep -v '^\s*$') > "$input-tmp" + mv "$input-tmp" "$input" +} diff --git a/pkgs/build-support/setup-hooks/canonicalize-jars.sh b/pkgs/build-support/setup-hooks/canonicalize-jars.sh new file mode 100644 index 00000000000..8c55810748e --- /dev/null +++ b/pkgs/build-support/setup-hooks/canonicalize-jars.sh @@ -0,0 +1,17 @@ +# This setup hook causes the fixup phase to repack all JAR files in a +# canonical & deterministic fashion, e.g. resetting mtimes (like with normal +# store files) and avoiding impure metadata. + +fixupOutputHooks+=('if [ -z "$dontCanonicalizeJars" -a -e "$prefix" ]; then canonicalizeJarsIn "$prefix"; fi') + +canonicalizeJarsIn() { + local dir="$1" + header "canonicalizing jars in $dir" + dir="$(realpath -sm -- "$dir")" + while IFS= read -rd '' f; do + canonicalizeJar "$f" + done < <(find -- "$dir" -type f -name '*.jar' -print0) + stopNest +} + +source @canonicalize_jar@ diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 65f6d227388..66e0023475a 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -153,6 +153,12 @@ with pkgs; appindicator-sharp = callPackage ../development/libraries/appindicator-sharp { }; + canonicalize-jar = callPackage ../build-support/java/canonicalize-jar.nix { }; + canonicalize-jars-hook = makeSetupHook { + name = "canonicalize-jars-hook"; + substitutions = { canonicalize_jar = canonicalize-jar; }; + } ../build-support/setup-hooks/canonicalize-jars.sh; + ensureNewerSourcesHook = { year }: makeSetupHook {} (writeScript "ensure-newer-sources-hook.sh" '' postUnpackHooks+=(_ensureNewerSources)