From 25fa181200291f2758cf5d1f8ac9b2fa109d51ca Mon Sep 17 00:00:00 2001 From: "Darryl L. Miles" Date: Thu, 23 Jul 2026 15:31:18 +0000 Subject: [PATCH] build: detect GNU/BSD date at configure time for SOURCE_DATE_EPOCH The SOURCE_DATE_EPOCH -> date conversion previously relied on a runtime `date -u -d @EPOCH 2>/dev/null || date -u -r EPOCH` fallback. That is unreliable on macOS/*BSD: BSD `date -d` reads -d as a DST flag and silently prints the wrong time (exit 0), so the `-r` fallback is never reached. Decide at configure time instead: prefer coreutils `gdate` (GNU date, e.g. installed on macOS), else a native `date` that really is GNU (verified by the epoch-0 output containing 1970), else BSD `date -r`. configure substitutes the chosen invocation as @SOURCE_DATE_CMD@ and defs.mak appends the epoch. Verified on Linux (no gdate -> GNU `date -u -d @`): a fixed SOURCE_DATE_EPOCH yields a byte-identical buildinfo.o across builds; the no-epoch path still stamps the current local time. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/configure | 53 ++++++++++++++++++++++++++++++++++++++++++++ scripts/configure.in | 17 ++++++++++++++ scripts/defs.mak.in | 10 +++++---- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/scripts/configure b/scripts/configure index 78de0bc1..3015e22e 100755 --- a/scripts/configure +++ b/scripts/configure @@ -693,6 +693,8 @@ GREP PERL CCACHE_PREFIX_MAP_FLAG CCACHE +SOURCE_DATE_CMD +GDATE SED RANLIB INSTALL_DATA @@ -4239,6 +4241,57 @@ $as_echo "$ac_cv_path_SED" >&6; } rm -f conftest.sed +for ac_prog in gdate +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_GDATE+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$GDATE"; then + ac_cv_prog_GDATE="$GDATE" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_GDATE="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +GDATE=$ac_cv_prog_GDATE +if test -n "$GDATE"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GDATE" >&5 +$as_echo "$GDATE" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$GDATE" && break +done + +if test -n "$GDATE"; then + SOURCE_DATE_CMD="$GDATE -u -d @" +elif date -u -d @0 2>/dev/null | grep -q 1970; then + SOURCE_DATE_CMD="date -u -d @" +else + SOURCE_DATE_CMD="date -u -r " +fi + + # Check whether --enable-ccache was given. if test "${enable_ccache+set}" = set; then : enableval=$enable_ccache; diff --git a/scripts/configure.in b/scripts/configure.in index 224067e2..e275ba52 100644 --- a/scripts/configure.in +++ b/scripts/configure.in @@ -29,6 +29,23 @@ AC_PROG_INSTALL AC_PROG_RANLIB AC_PROG_SED +dnl SOURCE_DATE_EPOCH -> UTC date, for reproducible builds. GNU date (Linux, or +dnl coreutils `gdate` on macOS) converts an epoch with `-d @EPOCH`; BSD date +dnl (native macOS/*BSD) uses `-r EPOCH`. Decide here, at configure time, because +dnl runtime probing is unreliable: BSD `date -d` silently mis-parses `@EPOCH` (it +dnl reads -d as a DST flag) and prints the wrong time rather than failing. Prefer +dnl `gdate`; else a `date` that really is GNU (verified by the epoch-0 output +dnl containing 1970); else fall back to BSD `date -r`. +AC_CHECK_PROGS([GDATE], [gdate]) +if test -n "$GDATE"; then + SOURCE_DATE_CMD="$GDATE -u -d @" +elif date -u -d @0 2>/dev/null | grep -q 1970; then + SOURCE_DATE_CMD="date -u -d @" +else + SOURCE_DATE_CMD="date -u -r " +fi +AC_SUBST([SOURCE_DATE_CMD]) + dnl Prefix the compilers with ccache when it is installed (autodetected; opt out dnl with --disable-ccache). Done after the AC_PROG_C* checks above so those dnl feature tests run with the plain compiler; only the substituted @CC@/@CXX@ diff --git a/scripts/defs.mak.in b/scripts/defs.mak.in index 29120274..e9563f63 100755 --- a/scripts/defs.mak.in +++ b/scripts/defs.mak.in @@ -131,11 +131,13 @@ MAGIC_BUILDDATE_DEFS = @MAGIC_BUILDDATE_DEFS@ # skipping it under --disable-magic-builddate avoids the parse-time date spawn. ifeq (${MAGIC_BUILDDATE_DEFS},) # Honor SOURCE_DATE_EPOCH (reproducible builds -- appimage/npm tarballs) when set, -# else the current time. The SOURCE_DATE_EPOCH branch uses UTC so a reproducible -# stamp does not depend on the builder's zone; the plain-`date` fallback is local. +# else the current time. @SOURCE_DATE_CMD@ is the platform's epoch->UTC-date +# invocation, chosen by configure (GNU `date -u -d @` / `gdate` vs BSD +# `date -u -r `); the epoch is appended here. The SOURCE_DATE_EPOCH branch is +# UTC so a reproducible stamp does not depend on the builder's zone; the plain +# `date` fallback is local. MAGIC_BUILDDATE ?= $(shell if [ -n "$$SOURCE_DATE_EPOCH" ]; then \ - date -u -d "@$$SOURCE_DATE_EPOCH" 2>/dev/null \ - || date -u -r "$$SOURCE_DATE_EPOCH" 2>/dev/null; \ + @SOURCE_DATE_CMD@$$SOURCE_DATE_EPOCH; \ else date; fi | tr -d '\r\n') endif