]> granicus.if.org Git - nethack/commitdiff
REPRODUCIBLE_BUILD
authorPatR <rankin@nethack.org>
Fri, 1 Apr 2016 00:56:11 +0000 (17:56 -0700)
committerPatR <rankin@nethack.org>
Fri, 1 Apr 2016 00:56:11 +0000 (17:56 -0700)
Take the 4-5 line Debian patch and turn it into six dozen lines of
new code.  The submitted patch introduces use of several C library
routines that aren't presently in use, so would need testing by all
functional or nearly-functional ports to verify that it wouldn't
break anything.  It also switched the formatted build date+time
from localtime to UTC.  This makes the code conditional so it can
be ignored by anybody and avoid the risk of breakage.  And a lot of
the increase in size is comments attempting to explain what the new
conditional is for:  when REPRODUCIBLE_BUILD is defined, makedefs
will use getenv("SOURCE_DATE_EPOCH") (whose value is an integer
representing seconds since 1-Jan-1970) instead of current date+time
when generating date.h.  The purpose is to be able to rebuild at a
later date and produce an identical program, which doesn't happen
when compile time gets incorporated into the binary.

I've added some sanity checking to try to make sure the getenv()
value obtained isn't bogus.  And the version string put into date.h
will be slightly different, allowing someone who sees date.h or 'v'
output to tell whether SOURCE_DATE_EPOCH was involved:  showing
"<port> NetHack <version> last revision <date>" instead of the
usual "... last build <date>".

To test, checkout a new branch for building, make any local edits
to unixconf.h and config.h, including enabling REPRODUCIBLE_BUILD,
git add+commit them, then use
  SOURCE_DATE_EPOCH=`git log -1 --pretty=%ct` make install
Other ports will need a bit more work to set up the environment,
but can still use git to track file dates and supply the latest.
Building with alternate configurations could be accomplished by
using tags instead of 'log -1' or by using distinct build branches
where nothing is commited/merged/rebased after completed build.

Unresolved issue:  BUILD_DATE, VERSION_ID, and COPYRIGHT_BANNER_C
contain formatted date+time but omit timezone.  SOURCE_DATE_EPOCH
is assumed to be UTC but the formatted values don't say so, so it
might appear to be incorrect when compared with local time.  We
definitely don't want to start mucking about with timezones within
nethack, so I think we just live with this.  It's not an issue for
default configruation where REPRODUCIBLE_BUILD is left disabled.

include/config.h
src/version.c
util/makedefs.c

index 121ce54079e671bfaa3bf690be1e140681592a78..47b2951cc9f316f2001c02cf30d296f812cb991e 100644 (file)
@@ -11,6 +11,7 @@
  *              For "UNIX" select BSD, ULTRIX, SYSV, or HPUX in unixconf.h.
  *              A "VMS" option is not needed since the VMS C-compilers
  *              provide it (no need to change sec#1, vmsconf.h handles it).
+ *              MacOSX uses the UNIX configruation, not the old MAC one.
  */
 
 #define UNIX /* delete if no fork(), exec() available */
  */
 /* #define DLB */ /* not supported on all platforms */
 
+/*
+ *      Defining REPRODUCIBLE_BUILD causes 'util/makedefs -v' to construct
+ *      date+time in include/date.h (to be shown by nethack's 'v' command)
+ *      from SOURCE_DATE_EPOCH in the build environment rather than use
+ *      current date+time when makedefs is run.
+ *
+ *      [The version string will show "last revision <date><time>" instead
+ *      of "last build <date><time>" if SOURCE_DATE_EPOCH has a value
+ *      which seems valid at the time date.h is generated.  The person
+ *      building the program is responsible for setting it correctly,
+ *      and the value should be in UTC rather than local time.  NetHack
+ *      normally uses local time and doesn't display timezone so toggling
+ *      REPRODUCIBLE_BUILD on or off might yield a date+time that appears
+ *      to be incorrect relative to what the other setting produced.]
+ *
+ *      Intent is to be able to rebuild the program with the same value
+ *      and obtain an identical copy as was produced by a previous build.
+ *      Not necessary for normal game play....
+ */
+/* #define REPRODUCIBLE_BUILD */ /* use getenv("SOURCE_DATE_EPOCH") instead
+                                    of current time when creating date.h */
+
 /*
  *      Defining INSURANCE slows down level changes, but allows games that
  *      died due to program or system crashes to be resumed from the point
index becefd8f766ae23b4eda9241a1186c1c4f4a6d56..7456117930a3b4e98d2b1ecd7863a48b847e16c0 100644 (file)
@@ -200,7 +200,9 @@ boolean
 comp_times(filetime)
 long filetime;
 {
-    return (boolean) (filetime < BUILD_TIME);
+    /* BUILD_TIME is constant but might have L suffix rather than UL;
+       'filetime' is historically signed but ought to have been unsigned */
+    return (boolean) ((unsigned long) filetime < (unsigned long) BUILD_TIME);
 }
 #endif
 
index 53cb4d865aff1fd9e1bf8be855e278873aee767d..11b27916826acce395fd9c6e50cbadd026da92b8 100644 (file)
@@ -1165,6 +1165,9 @@ make_version()
     return;
 }
 
+/* REPRODUCIBLE_BUILD will change this to TRUE */
+static boolean date_via_env = FALSE;
+
 static char *
 version_string(outbuf, delim)
 char *outbuf;
@@ -1194,8 +1197,9 @@ const char *build_date;
     Strcat(subbuf, " Beta");
 #endif
 
-    Sprintf(outbuf, "%s NetHack%s Version %s - last build %s.", PORT_ID,
-            subbuf, version_string(versbuf, "."), build_date);
+    Sprintf(outbuf, "%s NetHack%s Version %s - last %s %s.", PORT_ID,
+            subbuf, version_string(versbuf, "."),
+            date_via_env ? "revision" : "build", build_date);
     return outbuf;
 }
 
@@ -1215,8 +1219,9 @@ const char *build_date;
     Strcat(subbuf, " Beta");
 #endif
 
-    Sprintf(outbuf, "         Version %s %s%s, built %s.",
-            version_string(versbuf, "."), PORT_ID, subbuf, &build_date[4]);
+    Sprintf(outbuf, "         Version %s %s%s, %s %s.",
+            version_string(versbuf, "."), PORT_ID, subbuf,
+            date_via_env ? "revised" : "built", &build_date[4]);
 #if 0
     Sprintf(outbuf, "%s NetHack%s %s Copyright 1985-%s (built %s)",
             PORT_ID, subbuf, version_string(versbuf,"."), RELEASE_YEAR,
@@ -1255,20 +1260,96 @@ do_date()
     Fprintf(ofp, "%s", Dont_Edit_Code);
 
     (void) time(&clocktim);
+#ifdef REPRODUCIBLE_BUILD
+    {
+        /*
+         * Use date+time of latest source file revision (set up in
+         * our environment rather than derived by scanning sources)
+         * instead of current date+time, so that later rebuilds of
+         * the same sources specifying the same configuration will
+         * produce the same result.
+         *
+         * Changing the configuration should be done by modifying
+         * config.h or <port>conf.h and setting SOURCE_DATE_EPOCH
+         * based on whichever changed most recently, not by using
+         *   make CFLAGS='-Dthis -Dthat'
+         * to make alterations on the fly.
+         *
+         * Limited validation is performed to prevent dates in the
+         * future (beyond a leeway of 24 hours) or distant past.
+         *
+         * Assumes the value of time_t is in seconds, which is
+         * fundamental for Unix and mandated by POSIX.  For any ports
+         * where that isn't true, leaving REPRODUCIBLE_BUILD disabled
+         * is probably preferrable to hacking this code....
+         */
+        static struct tm nh360; /* static init should yield UTC timezone */
+        unsigned long sd_num, sd_earliest, sd_latest;
+        const char *sd_str = getenv("SOURCE_DATE_EPOCH");
+
+        if (sd_str) {
+            sd_num = strtoul(sd_str, (char **) 0, 10);
+            /*
+             * Note:  this does not need to be updated for future
+             * releases.  It serves as a sanity check for potentially
+             * mis-set environment, not a hard baseline for when the
+             * current version could have first been built.
+             */
+            /* oldest date we'll accept: 7-Dec-2015 (release of 3.6.0) */
+            nh360.tm_mday = 7;
+            nh360.tm_mon  = 12 - 1;
+            nh360.tm_year = 2015 - 1900;
+            sd_earliest = (unsigned long) mktime(&nh360);
+            /* 'youngest' date we'll accept: 24 hours in the future */
+            sd_latest = (unsigned long) clocktim + 24L * 60L * 60L;
+
+            if (sd_num >= sd_earliest && sd_num <= sd_latest) {
+                /* use SOURCE_DATE_EPOCH value */
+                clocktim = (time_t) sd_num;
+                date_via_env = TRUE;
+            } else {
+                Fprintf(stderr, "? Invalid value for SOURCE_DATE_EPOCH (%lu)",
+                        sd_num);
+                if (sd_num > 0L && sd_num < sd_earliest)
+                    Fprintf(stderr, ", older than %lu", sd_earliest);
+                else if (sd_num > sd_latest)
+                    Fprintf(stderr, ", newer than %lu", sd_latest);
+                Fprintf(stderr, ".\n");
+                Fprintf(stderr, ": Reverting to current date+time (%lu).\n",
+                        (unsigned long) clocktim);
+                (void) fflush(stderr);
+            }
+        } else {
+            /* REPRODUCIBLE_BUILD enabled but SOURCE_DATE_EPOCH is missing */
+            Fprintf(stderr, "? No value for SOURCE_DATE_EPOCH.\n");
+            Fprintf(stderr, ": Using current date+time (%lu).\n",
+                    (unsigned long) clocktim);
+            (void) fflush(stderr);
+        }
+        Strcpy(cbuf, asctime(gmtime(&clocktim)));
+    }
+#else
+    /* ordinary build: use current date+time */
     Strcpy(cbuf, ctime(&clocktim));
+#endif
 
-    for (c = cbuf; *c; c++)
-        if (*c == '\n')
-            break;
-    *c = '\0'; /* strip off the '\n' */
-    Fprintf(ofp, "#define BUILD_DATE \"%s\"\n", cbuf);
-    Fprintf(ofp, "#define BUILD_TIME (%ldL)\n", (long) clocktim);
-    Fprintf(ofp, "\n");
+    if ((c = index(cbuf, '\n')) != 0)
+        *c = '\0'; /* strip off the '\n' */
 #ifdef NHSTDC
     ul_sfx = "UL";
 #else
     ul_sfx = "L";
 #endif
+    if (date_via_env)
+        Fprintf(ofp, "#define SOURCE_DATE_EPOCH (%lu%s) /* via getenv() */\n",
+                (unsigned long) clocktim, ul_sfx);
+    Fprintf(ofp, "#define BUILD_DATE \"%s\"\n", cbuf);
+    if (date_via_env)
+        Fprintf(ofp, "#define BUILD_TIME SOURCE_DATE_EPOCH\n");
+    else
+        Fprintf(ofp, "#define BUILD_TIME (%lu%s)\n",
+                (unsigned long) clocktim, ul_sfx);
+    Fprintf(ofp, "\n");
     Fprintf(ofp, "#define VERSION_NUMBER 0x%08lx%s\n", version.incarnation,
             ul_sfx);
     Fprintf(ofp, "#define VERSION_FEATURES 0x%08lx%s\n", version.feature_set,
@@ -1293,10 +1374,11 @@ do_date()
 #ifdef AMIGA
     {
         struct tm *tm = localtime((time_t *) &clocktim);
+
         Fprintf(ofp, "#define AMIGA_VERSION_STRING ");
         Fprintf(ofp, "\"\\0$VER: NetHack %d.%d.%d (%d.%d.%d)\"\n",
-                VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL, tm->tm_mday,
-                tm->tm_mon + 1, tm->tm_year + 1900);
+                VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL,
+                tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900);
     }
 #endif
     Fclose(ofp);