From b78b70612b83632d9f1256e4fa4c69c33f81a5f8 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 20 May 2025 09:17:04 -0700 Subject: [PATCH] mdate-sh: support $SOURCE_DATE_EPOCH. From https://bugs.gnu.org/77805. * lib/mdate-sh: if $SOURCE_DATE_EPOCH is set, use it instead of the mtime of any file. Debian was removing @value{UPDATED}, etc., from manuals because of varying output. Try date --date, then date -r, then perl gmtime, then fall back to the file's mtime. Also, warn if more than one file argument is given. (mon_to_month): factor out conversion of Jan to January. * t/mdate5.sh: test it. * doc/automake.texi (Texinfo): mention it. * NEWS: mention it. --- NEWS | 7 ++- doc/automake.texi | 21 +++++++-- lib/mdate-sh | 118 +++++++++++++++++++++++++++++++++------------- t/mdate5.sh | 16 ++++++- 4 files changed, 121 insertions(+), 41 deletions(-) diff --git a/NEWS b/NEWS index 30c0cd2c7..fdff666cb 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,9 @@ New in 1.17.92: the tar-v7 and other explicit options to force a particular tar format are unchanged and still override the default. (bug#74847) + - mdate-sh uses SOURCE_DATE_EPOCH, if set, instead of a file's mtime. + (bug#77805) + - New option dist-bzip3 for bzip3 compression of distributions. (bug#73795) - New option --stderr-prefix for tap-driver.sh, to prefix each line of @@ -34,7 +37,7 @@ New in 1.17.92: is set to "false", do nothing but exit unsuccessfully, also to match previous behavior. (bug#74434) - - The no-dist-built-sources Automake option hopefully now operates as + - The no-dist-built-sources Automake option now operates (hopefully) as intended, i.e., omits the dependency on $(BUILT_SOURCES) for the distdir target. (bug#69908) @@ -42,7 +45,7 @@ New in 1.17.92: error. (bug#19964) - The compile script is more robust to Windows configurations; - specifically, avoiding double-path translation on MSYS. (bug#75939) + specifically, avoids double-path translation on MSYS. (bug#75939) - The test infrastructure sets the CONFIG_SITE environment variable to /dev/null, to avoid the local system's Autoconf site defaults from diff --git a/doc/automake.texi b/doc/automake.texi index 3cbbff250..608c0a8d6 100644 --- a/doc/automake.texi +++ b/doc/automake.texi @@ -8327,7 +8327,7 @@ All of these targets can be extended using @samp{-local} rules @cindex @code{EDITION} Texinfo flag @cindex @code{UPDATED-MONTH} Texinfo flag -@cindex @file{mdate-sh} +@cindex @file{mdate-sh}, Automake helper script If a @file{.texi} file @code{@@include}s @file{version.texi} (actually any file named @file{vers...texi}, then that file will be @@ -8336,18 +8336,20 @@ Texinfo flags you can reference using @code{@@value@{EDITION@}}, @code{@@value@{VERSION@}}, @code{@@value@{UPDATED@}}, and @code{@@value@{UPDATED-MONTH@}}. +@vindex SOURCE_DATE_EPOCH@r{, and @command{mdate-sh}} @table @code @item EDITION @itemx VERSION Both of these flags hold the version number of your program. They are -kept separate for clarity. +kept separate for historical reasons. @item UPDATED -This holds the date the primary @file{.texi} file was last modified. +This holds the date the primary @file{.texi} file was last modified, +or the date of the @env{SOURCE_DATE_EPOCH} variable (see below). @item UPDATED-MONTH This holds the name of the month in which the primary @file{.texi} file -was last modified. +was last modified, or the month of @env{SOURCE_DATE_EPOCH}. @end table The @file{version.texi} support requires the @command{mdate-sh} @@ -8355,6 +8357,17 @@ script; this script is supplied with Automake and automatically included when @command{automake} is invoked with the @option{--add-missing} option. +@cindex reproducible builds +@cindex epoch, and reproducible builds +If the @env{SOURCE_DATE_EPOCH} environment variable is set, +@command{mdate-sh} uses its value, instead of the modification time of +any file. This variable is typically set globally for the sake of +creating a reproducible build across a distribution. Its value is an +integer, the number of seconds since the Unix epoch (January 1, 1970, +00:00:00 UTC). If for any reason the @env{SOURCE_DATE_EPOCH} value +cannot be converted into the human-readable date strings, +@command{mdate-sh} falls back to using the given file's mtime. + If you have multiple Texinfo files, and you want to use the @file{version.texi} feature, then you have to have a separate version file for each Texinfo file. Automake will treat any include in a diff --git a/lib/mdate-sh b/lib/mdate-sh index 51fb44bcf..9c195dbd0 100755 --- a/lib/mdate-sh +++ b/lib/mdate-sh @@ -1,7 +1,8 @@ #!/bin/sh -# Get modification time of a file or directory and pretty-print it. +# Get modification time of a file or directory, or value of +# $SOURCE_DATE_EPOCH, and pretty-print it, formatted like 1 January 2000. -scriptversion=2024-12-03.03; # UTC +scriptversion=2025-05-21.01; # UTC # Copyright (C) 1995-2025 Free Software Foundation, Inc. # written by Ulrich Drepper , June 1995 @@ -49,6 +50,10 @@ Usage: mdate-sh [--help] [--version] FILE Pretty-print the modification day of FILE, in the format: 1 January 1970 +If the environment variable SOURCE_DATE_EPOCH is set, use its value (in +epoch-seconds) for the date instead of any FILE mtime. The FILE +argument is still required in this case, but ignored. + Report bugs to . GNU Automake home page: . General help using GNU software: . @@ -61,12 +66,35 @@ EOF ;; esac +# Warn if more than one file given. +if test $# -ne 1; then + echo "$0: warning: multiple files given, using first: $*" >&2 +fi + error () { echo "$0: $1" >&2 exit 1 } +# set $month ("January") and $nummonth (1) given arg MON ("Jan"). +mon_to_month () +{ + case $1 in + Jan) month=January; nummonth=1;; + Feb) month=February; nummonth=2;; + Mar) month=March; nummonth=3;; + Apr) month=April; nummonth=4;; + May) month=May; nummonth=5;; + Jun) month=June; nummonth=6;; + Jul) month=July; nummonth=7;; + Aug) month=August; nummonth=8;; + Sep) month=September; nummonth=9;; + Oct) month=October; nummonth=10;; + Nov) month=November; nummonth=11;; + Dec) month=December; nummonth=12;; + esac +} # Prevent date giving response in another language. LANG=C @@ -80,6 +108,56 @@ export LC_TIME TZ=UTC0 export TZ +# +# https://reproducible-builds.org/docs/source-date-epoch/ +if test -n "$SOURCE_DATE_EPOCH"; then + epoch_ok=true # be optimistic + date_fmt="+%d %B %Y" + result=`date -u --date="@$SOURCE_DATE_EPOCH" "$date_fmt" 2>/dev/null` + if test -z "$result"; then + result=`date -u -r "$SOURCE_DATE_EPOCH" "$date_fmt" 2>/dev/null` + if test -z "$result"; then + # The date command on Solaris 10 and 11 doesn't support any way + # to do this. Fall back to Perl. + # + perlout=`perl -e 'print scalar gmtime($SOURCE_DATE_EPOCH)' 2>/dev/null` + # Output is, e.g., Thu Jan 1 00:00:00 1970. Split it apart, + # since we need to convert "Jan" to "January". + # (We could use cut, but surely if a system has perl, it has awk?) + day=`echo $perlout | awk '{print $3}'` + mon=`echo $perlout | awk '{print $2}'` + mon_to_month $mon # sets $month + year=`echo $perlout | awk '{print $5}'` + result="$day $month $year" + # + if test -z "$result"; then + echo "$0: warning: SOURCE_DATE_EPOCH was set, but can't convert, ignoring: $SOURCE_DATE_EPOCH" >&2 + epoch_ok=false + fi + fi + fi + # + if $epoch_ok; then + # Remove leading spaces and zeros. We don't want to get into the + # various date options to control this. (Not quoting $result here + # isn't important, just another way to omit leading spaces.) + result=`echo $result | sed 's/^[ 0]*//'` + if test -z "$result"; then + echo "$0: SOURCE_DATE_EPOCH was set, but converted to empty: $SOURCE_DATE_EPOCH" >&2 + epoch_ok=false + fi + fi + if $epoch_ok; then + echo $result + exit 0 + else + echo "$0: SOURCE_DATE_EPOCH failed, falling back to using mtime on: $1" >&2 + fi +fi +# end of SOURCE_DATE_EPOCH support, rest is about the normal case of +# using the mtime of the specified file. + +# # GNU ls changes its time format in response to the TIME_STYLE # variable. Since we cannot assume 'unset' works, revert this # variable to its documented default. @@ -125,20 +203,7 @@ do shift # Add another shift to the command. command="$command shift;" - case $1 in - Jan) month=January; nummonth=1;; - Feb) month=February; nummonth=2;; - Mar) month=March; nummonth=3;; - Apr) month=April; nummonth=4;; - May) month=May; nummonth=5;; - Jun) month=June; nummonth=6;; - Jul) month=July; nummonth=7;; - Aug) month=August; nummonth=8;; - Sep) month=September; nummonth=9;; - Oct) month=October; nummonth=10;; - Nov) month=November; nummonth=11;; - Dec) month=December; nummonth=12;; - esac + mon_to_month $1 done test -n "$month" || error "failed parsing '$ls_command /' output" @@ -152,7 +217,6 @@ eval $command # Because of the dummy argument above, month is in $2. # # On a POSIX system, we should have -# # $# = 5 # $1 = file size # $2 = month @@ -161,7 +225,6 @@ eval $command # $5 = filename # # On Darwin 7.7.0 and 7.6.0, we have -# # $# = 4 # $1 = day # $2 = month @@ -169,20 +232,7 @@ eval $command # $4 = filename # Get the month. -case $2 in - Jan) month=January; nummonth=1;; - Feb) month=February; nummonth=2;; - Mar) month=March; nummonth=3;; - Apr) month=April; nummonth=4;; - May) month=May; nummonth=5;; - Jun) month=June; nummonth=6;; - Jul) month=July; nummonth=7;; - Aug) month=August; nummonth=8;; - Sep) month=September; nummonth=9;; - Oct) month=October; nummonth=10;; - Nov) month=November; nummonth=11;; - Dec) month=December; nummonth=12;; -esac +mon_to_month $2 case $3 in ???*) day=$1;; @@ -207,9 +257,9 @@ case $3 in Nov) nummonthtod=11;; Dec) nummonthtod=12;; esac - # For the first six month of the year the time notation can also + # For the first six months of the year the time notation can also # be used for files modified in the last year. - if (expr $nummonth \> $nummonthtod) > /dev/null; + if (expr $nummonth \> $nummonthtod) >/dev/null; then year=`expr $year - 1` fi;; diff --git a/t/mdate5.sh b/t/mdate5.sh index 45270c039..11be50f13 100644 --- a/t/mdate5.sh +++ b/t/mdate5.sh @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# Test to make sure mdate-sh works correctly. +# Test that mdate-sh works correctly. am_create_testdir=empty . test-init.sh @@ -42,4 +42,18 @@ if year=$(date +%Y) && test $year -gt 2010; then test $year = $3 || exit 1 fi +# +# Also check that mdate-sh respects SOURCE_DATE_EPOCH. +SOURCE_DATE_EPOCH=123456 # into January 2, 1970, for no particular reason. +export SOURCE_DATE_EPOCH +set x $(./mdate-sh mdate-sh) +shift +echo "$*" # For debugging. + +# Check that mdate output is the expected day (1 January 1970): +test $# = 3 +test x$1 = x2 +test x$2 = xJanuary +test x$3 = x1970 + : -- 2.47.3