diff --git a/archive/release.sh b/archive/release.sh index 64bc10911e..e76becab17 100755 --- a/archive/release.sh +++ b/archive/release.sh @@ -127,6 +127,15 @@ type tar || pacman -Sy --noconfirm tar || die "Could not install tar" +# ---- Reproducible: pin file mtimes to $SOURCE_DATE_EPOCH (if set) ---- +# tar archives embed file timestamps; pinning mtimes makes repeated builds of +# the same version byte-identical. Skipped when SOURCE_DATE_EPOCH is unset. +if test -n "$SOURCE_DATE_EPOCH" +then + # shellcheck disable=SC2086 + "$SCRIPT_PATH/../pin-mtimes.sh" --root="$SCRIPT_PATH/root" $LIST +fi + echo "Creating .tar.bz2 archive" && if ! tar -c -j -f "$TARGET" --directory=/ --exclude=etc/post-install/* $LIST --directory=$SCRIPT_PATH/root bin dev etc tmp $MSYSTEM_LOWER && test $? = 1 then diff --git a/installer/release.sh b/installer/release.sh index 6f5cfdde20..41a1823def 100755 --- a/installer/release.sh +++ b/installer/release.sh @@ -361,6 +361,36 @@ signtool= test -z "$(git config alias.signtool)" || signtool="//Ssigntool=\"git signtool \\\$f\" //DSIGNTOOL" +# ---- Reproducible: pin the mtime of every file to be packaged ---- +# Inno Setup writes file timestamps into the installer; any mtime difference +# leads to different installer bytes. SOURCE_DATE_EPOCH is expected to be set +# by the build environment (e.g. CI); skipped for local builds without it. +# Sources are listed in file-list.iss and install.iss, including the +# generated files (ReleaseNotes.html, package-versions.txt, ...). +if test -n "$SOURCE_DATE_EPOCH" +then + files= + rel_pwd="${PWD#/}" + for iss in file-list.iss install.iss + do + while IFS= read -r s + do + case "$s" in + \{#SourcePath\}*) + # resolve {#SourcePath} against the installer directory + s="${s#\{#SourcePath\}}" + s="${s#\\}"; s="${s#/}" + s="$rel_pwd/${s//\\//}" + ;; + *) s="${s//\\//}" ;; + esac + test -e "/$s" && files="$files $s" + done < <(perl -ne 'if (/^Source: (?:"([^"]+)"|([^;]+));/) { print(defined $1 ? $1 : $2, "\n") }' "$iss") + done + # shellcheck disable=SC2086 + "$PWD/../pin-mtimes.sh" $files +fi + echo "Launching Inno Setup compiler ..." && eval ./InnoSetup/ISCC.exe "$signtool" install.iss >install.log || die "Could not make installer" diff --git a/mingit/release.sh b/mingit/release.sh index 37319aa10f..2534b7edae 100755 --- a/mingit/release.sh +++ b/mingit/release.sh @@ -177,6 +177,15 @@ esac test ! -f "$TARGET" || rm "$TARGET" || die "Could not remove $TARGET" +# ---- Reproducible: pin file mtimes to $SOURCE_DATE_EPOCH (if set) ---- +# ZIP archives embed file timestamps; pinning mtimes makes repeated builds of +# the same version byte-identical. Skipped when SOURCE_DATE_EPOCH is unset. +if test -n "$SOURCE_DATE_EPOCH" +then + # shellcheck disable=SC2086 + "$SCRIPT_PATH/../pin-mtimes.sh" --root="$SCRIPT_PATH/root" $LIST +fi + echo "Creating .zip archive" && -(cd / && 7z a -mx9 "$TARGET" $LIST "$SCRIPT_PATH"/root/*) && +(cd / && 7z a -mx9 -mta- -mtc- "$TARGET" $LIST "$SCRIPT_PATH"/root/*) && echo "Success! You will find the new MinGit at \"$TARGET\"." diff --git a/pin-mtimes.sh b/pin-mtimes.sh new file mode 100644 index 0000000000..f210f6cd13 --- /dev/null +++ b/pin-mtimes.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# +# pin-mtimes.sh - pin file mtimes for reproducible builds +# +# Usage: pin-mtimes.sh [--root=] ... +# +# Pin the mtime of every given file, and of every parent directory up to the +# filesystem root, to $SOURCE_DATE_EPOCH. When --root= is given, every +# file and directory under that overlay directory is pinned as well. +# +# The files must be given as paths relative to the filesystem root (e.g. +# "usr/bin/git.exe", as produced by make-file-list.sh). Archives (tar/zip/7z) +# and the installer embed file and directory timestamps, so pinning mtimes +# makes repeated builds of the same version byte-identical on the same SDK +# snapshot. This is a no-op (and exits 0) when SOURCE_DATE_EPOCH is unset. + +test -n "$SOURCE_DATE_EPOCH" || exit 0 + +root= +case "$1" in +--root=*) + root="${1#--root=}" + shift + ;; +esac + +echo "==> pinning file mtimes (SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH)" + +tmp="$(mktemp)" || exit 1 + +# Pin the files themselves; remember those that could not be pinned. +for f in "$@" +do + test -n "$f" || continue + touch -h -d "@$SOURCE_DATE_EPOCH" "/$f" 2>/dev/null || echo "$f" >>"$tmp" +done + +# make-file-list.sh only lists files, so pin the parent chain of every file +# (deduplicated) as well. +{ + for f in "$@" + do + test -n "$f" || continue + d="/${f%/*}" + while test -n "$d" && test "$d" != "/" + do + printf '%s\n' "$d" + d="${d%/*}" + done + done +} | sort -u | +while IFS= read -r d +do + test -d "$d" && + touch -h -d "@$SOURCE_DATE_EPOCH" "$d" 2>/dev/null +done +touch -h -d "@$SOURCE_DATE_EPOCH" "/" 2>/dev/null || true + +# Pin every file and directory in the root overlay, if any. +test -z "$root" || +find "$root" -exec touch -h -d "@$SOURCE_DATE_EPOCH" {} + 2>/dev/null || true +test -z "$root" || +find "$root" -type d -exec touch -h -d "@$SOURCE_DATE_EPOCH" {} + 2>/dev/null || true + +if test -s "$tmp" +then + echo "==> WARNING: could not pin mtime of $(wc -l <"$tmp") file(s):" >&2 + sed -n '1,20p' "$tmp" >&2 +fi +rm -f "$tmp" diff --git a/portable/release.sh b/portable/release.sh index 7acfa2b8aa..e7ac83333a 100755 --- a/portable/release.sh +++ b/portable/release.sh @@ -74,7 +74,7 @@ MSYSTEM_LOWER=${MSYSTEM,,} VERSION=$1 shift TARGET="$output_directory"/PortableGit-"$VERSION"-"$ARTIFACT_SUFFIX".7z.exe -OPTS7="-m0=lzma -mqs -mlc=8 -mx=9 -md=$MD_ARG -mfb=273 -ms=256M " +OPTS7="-m0=lzma -mqs -mlc=8 -mx=9 -md=$MD_ARG -mfb=273 -ms=256M -mta- -mtc- " TMPPACK=/tmp.7z SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)" @@ -170,6 +170,15 @@ type 7z || pacman -Sy --noconfirm $MINGW_PREFIX-7zip || die "Could not install 7-Zip" +# ---- Reproducible: pin file mtimes to $SOURCE_DATE_EPOCH (if set) ---- +# 7z archives embed file timestamps; pinning mtimes makes repeated builds of +# the same version byte-identical. Skipped when SOURCE_DATE_EPOCH is unset. +if test -n "$SOURCE_DATE_EPOCH" +then + # shellcheck disable=SC2086 + "$SCRIPT_PATH/../pin-mtimes.sh" --root="$SCRIPT_PATH/root" $LIST +fi + echo "Creating archive" && echo $LIST | tr ' ' '\n' >$TMPPACK.list && # 7-Zip will strip absolute paths completely... therefore, we can add another