Here's an updated patch for this issue.  The difference relative to my
previous patch is that, rather than taking an environment variable to
disable timestamp output altogether, this takes an environment variable
to force a specific time: this is intended to be used by build systems
to e.g. ensure that all documents produced by a given build have a
creation date corresponding to the changelog date of the overall
package, rather than whatever time the build system happened to run.

Notwithstanding Werner's comments, I think that an environment variable
is a clearly better approach to this, because it's much easier to
arrange for it to be passed through build systems.  But I hope that
switching to the timestamp-based variable means that it's unlikely to be
set permanently in users' environments.  Also, SOURCE_DATE_EPOCH is an
emerging standard which is supported in an increasing number of other
packages, which means that build systems only need to set one variable
rather than kludging around things in a dozen different places.

The reason why filtering PDF output and the like is an inferior
solution, even though it's certainly possible (strip-nondeterminism
etc.; https://reproducible-builds.org/docs/timestamps/), is that it's
much more complicated and less transparent.  With a reproducible build
toolchain it is possible to build an entire package multiple times on
entirely different systems and get a bitwise-identical .deb; if you're
relying on postprocessing then it's much less obvious that nothing else
has sneaked in and you need a complicated pile of stuff to unpack two
packages and do a deep (possibly recursive!) comparison of files within
them.  Yes, we did without this for 20+ years, and it's not absolutely
the end of the world not to have it, but that doesn't mean it isn't
useful to work on it now.

More details on SOURCE_DATE_EPOCH (and there are many other useful
things on the same site):

  https://reproducible-builds.org/specs/source-date-epoch/

-- 
Colin Watson                                       [cjwat...@debian.org]
>From c111bb75c4793bbde55d173969492828d83496db Mon Sep 17 00:00:00 2001
From: Colin Watson <cjwat...@debian.org>
Date: Wed, 27 Aug 2014 09:06:26 +0100
Subject: [PATCH] Implement `SOURCE_DATE_EPOCH' for reproducible builds.

* src/include/curtime.h: New file.
* src/libs/libgroff/curtime.cpp: New file.
* src/libs/libgroff/libgroff.am (libgroff_a_SOURCES): Add
src/libs/libgroff/curtime.cpp.

* src/roff/troff/input.cpp (init_registers): Use `current_time'
instead of `time(0)'.
* src/devices/grohtml/post-html.cpp
(html_printer::do_file_components): Likewise.
(html_printer::~html_printer): Likewise.
* src/devices/grops/ps.cpp (ps_printer::~ps_printer): Likewise.
* src/devices/gropdf/gropdf.pl: Use `$ENV{SOURCE_DATE_EPOCH}` if
available in preference to `time`.

* doc/groff.texi (Environment): Document `SOURCE_DATE_EPOCH'.
* src/devices/grohtml/grohtml.1.man (ENVIRONMENT): Likewise.
* src/devices/gropdf/gropdf.1.man (ENVIRONMENT): Likewise.
* src/devices/grops/grops.1.man (ENVIRONMENT): Likewise.
---
 ChangeLog                         | 23 ++++++++++++++++++
 doc/groff.texi                    |  6 +++++
 src/devices/grohtml/grohtml.1.man |  7 ++++++
 src/devices/grohtml/post-html.cpp |  5 ++--
 src/devices/gropdf/gropdf.1.man   |  7 ++++++
 src/devices/gropdf/gropdf.pl      |  3 ++-
 src/devices/grops/grops.1.man     |  7 ++++++
 src/devices/grops/ps.cpp          |  3 ++-
 src/include/curtime.h             | 23 ++++++++++++++++++
 src/libs/libgroff/curtime.cpp     | 51 +++++++++++++++++++++++++++++++++++++++
 src/libs/libgroff/libgroff.am     |  1 +
 src/roff/troff/input.cpp          |  3 ++-
 12 files changed, 134 insertions(+), 5 deletions(-)
 create mode 100644 src/include/curtime.h
 create mode 100644 src/libs/libgroff/curtime.cpp

diff --git a/ChangeLog b/ChangeLog
index 2333859..98eac45 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2015-11-05  Colin Watson  <cjwat...@debian.org>
+
+	Implement `SOURCE_DATE_EPOCH' for reproducible builds.
+
+	* src/include/curtime.h: New file.
+	* src/libs/libgroff/curtime.cpp: New file.
+	* src/libs/libgroff/libgroff.am (libgroff_a_SOURCES): Add
+	src/libs/libgroff/curtime.cpp.
+
+	* src/roff/troff/input.cpp (init_registers): Use `current_time'
+	instead of `time(0)'.
+	* src/devices/grohtml/post-html.cpp
+	(html_printer::do_file_components): Likewise.
+	(html_printer::~html_printer): Likewise.
+	* src/devices/grops/ps.cpp (ps_printer::~ps_printer): Likewise.
+	* src/devices/gropdf/gropdf.pl: Use `$ENV{SOURCE_DATE_EPOCH}` if
+	available in preference to `time`.
+
+	* doc/groff.texi (Environment): Document `SOURCE_DATE_EPOCH'.
+	* src/devices/grohtml/grohtml.1.man (ENVIRONMENT): Likewise.
+	* src/devices/gropdf/gropdf.1.man (ENVIRONMENT): Likewise.
+	* src/devices/grops/grops.1.man (ENVIRONMENT): Likewise.
+
 2015-10-27  Deri James  <d...@chuzzlewit.myzen.co.uk>
 
 	gropdf was choking on -I flag passed by groff, now uses
diff --git a/doc/groff.texi b/doc/groff.texi
index 00a0f6d..9dc63eb 100644
--- a/doc/groff.texi
+++ b/doc/groff.texi
@@ -1453,6 +1453,12 @@ default directory (on Unix and GNU/Linux systems, this is usually
 @item GROFF_TYPESETTER
 @tindex GROFF_TYPESETTER@r{, environment variable}
 The default output device.
+
+@item SOURCE_DATE_EPOCH
+@tindex SOURCE_DATE_EPOCH@r{, environment variable}
+A timestamp (expressed as seconds since the Unix epoch) to use in place of
+the current time when initializing time-based built-in registers such as
+@code{\n[seconds]}.
 @end table
 
 Note that MS-DOS and MS-Windows ports of @code{groff} use semi-colons,
diff --git a/src/devices/grohtml/grohtml.1.man b/src/devices/grohtml/grohtml.1.man
index 2efdd81..f8c45dd 100644
--- a/src/devices/grohtml/grohtml.1.man
+++ b/src/devices/grohtml/grohtml.1.man
@@ -419,6 +419,13 @@ and
 for more details.
 .
 .
+.TP
+.SM
+.B SOURCE_DATE_EPOCH
+A timestamp (expressed as seconds since the Unix epoch) to use as the
+creation timestamp in place of the current time.
+.
+.
 .\" --------------------------------------------------------------------
 .SH BUGS
 .\" --------------------------------------------------------------------
diff --git a/src/devices/grohtml/post-html.cpp b/src/devices/grohtml/post-html.cpp
index fefbf01..b5fc516 100644
--- a/src/devices/grohtml/post-html.cpp
+++ b/src/devices/grohtml/post-html.cpp
@@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */
 #include "html.h"
 #include "html-text.h"
 #include "html-table.h"
+#include "curtime.h"
 
 #include <time.h>
 
@@ -5013,7 +5014,7 @@ void html_printer::do_file_components (void)
 	.put_string(Version_string)
 	.end_comment();
 
-      t = time(0);
+      t = current_time();
       html.begin_comment("CreationDate: ")
 	.put_string(ctime(&t), strlen(ctime(&t))-1)
 	.end_comment();
@@ -5126,7 +5127,7 @@ html_printer::~html_printer()
     .put_string(Version_string)
     .end_comment();
 
-  t = time(0);
+  t = current_time();
   html.begin_comment("CreationDate: ")
     .put_string(ctime(&t), strlen(ctime(&t))-1)
     .end_comment();
diff --git a/src/devices/gropdf/gropdf.1.man b/src/devices/gropdf/gropdf.1.man
index 25287a6..4a06c0f 100644
--- a/src/devices/gropdf/gropdf.1.man
+++ b/src/devices/gropdf/gropdf.1.man
@@ -1024,6 +1024,13 @@ and
 for more details.
 .
 .
+.TP
+.SM
+.B SOURCE_DATE_EPOCH
+A timestamp (expressed as seconds since the Unix epoch) to use as the
+creation timestamp in place of the current time.
+.
+.
 .\" --------------------------------------------------------------------
 .SH FILES
 .\" --------------------------------------------------------------------
diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl
index 0744378..b95169e 100644
--- a/src/devices/gropdf/gropdf.pl
+++ b/src/devices/gropdf/gropdf.pl
@@ -259,13 +259,14 @@ elsif (exists($ppsz{$papersz}))
     @defaultmb=@mediabox=(0,0,$ppsz{$papersz}->[0],$ppsz{$papersz}->[1]);
 }
 
-my (@dt)=localtime(time);
+my (@dt)=localtime($ENV{SOURCE_DATE_EPOCH} || time);
 my $dt=PDFDate(\@dt);
 
 my %info=('Creator' => "(groff version $cfg{GROFF_VERSION})",
 				'Producer' => "(gropdf version $cfg{GROFF_VERSION})",
 				'ModDate' => "($dt)",
 				'CreationDate' => "($dt)");
+
 while (<>)
 {
     chomp;
diff --git a/src/devices/grops/grops.1.man b/src/devices/grops/grops.1.man
index e7064d2..55b26f7 100644
--- a/src/devices/grops/grops.1.man
+++ b/src/devices/grops/grops.1.man
@@ -1423,6 +1423,13 @@ and
 for more details.
 .
 .
+.TP
+.SM
+.B SOURCE_DATE_EPOCH
+A timestamp (expressed as seconds since the Unix epoch) to use as the
+creation timestamp in place of the current time.
+.
+.
 .\" --------------------------------------------------------------------
 .SH FILES
 .\" --------------------------------------------------------------------
diff --git a/src/devices/grops/ps.cpp b/src/devices/grops/ps.cpp
index 745a503..03e6537 100644
--- a/src/devices/grops/ps.cpp
+++ b/src/devices/grops/ps.cpp
@@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */
 #include "cset.h"
 #include "nonposix.h"
 #include "paper.h"
+#include "curtime.h"
 
 #include "ps.h"
 #include <time.h>
@@ -1390,7 +1391,7 @@ ps_printer::~ps_printer()
 #else
     time_t
 #endif
-    t = time(0);
+    t = current_time();
     fputs(ctime(&t), out.get_file());
   }
   for (font_pointer_list *f = font_list; f; f = f->next) {
diff --git a/src/include/curtime.h b/src/include/curtime.h
new file mode 100644
index 0000000..a410519
--- /dev/null
+++ b/src/include/curtime.h
@@ -0,0 +1,23 @@
+/* Copyright (C) 2015  Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#ifdef LONG_FOR_TIME_T
+long
+#else
+time_t
+#endif
+current_time();
diff --git a/src/libs/libgroff/curtime.cpp b/src/libs/libgroff/curtime.cpp
new file mode 100644
index 0000000..00821b7
--- /dev/null
+++ b/src/libs/libgroff/curtime.cpp
@@ -0,0 +1,51 @@
+/* Copyright (C) 2015  Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "errarg.h"
+#include "error.h"
+
+#ifdef LONG_FOR_TIME_T
+long
+#else
+time_t
+#endif
+current_time()
+{
+  char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
+
+  if (source_date_epoch) {
+    errno = 0;
+    char *endptr;
+    long epoch = strtol(source_date_epoch, &endptr, 10);
+
+    if ((errno == ERANGE && (epoch == LONG_MAX || epoch == LONG_MIN)) ||
+	(errno != 0 && epoch == 0))
+      fatal("$SOURCE_DATE_EPOCH: strtol: %1", strerror(errno));
+    if (endptr == source_date_epoch)
+      fatal("$SOURCE_DATE_EPOCH: no digits found: %1", endptr);
+    if (*endptr != '\0')
+      fatal("$SOURCE_DATE_EPOCH: trailing garbage: %1", endptr);
+    return epoch;
+  } else
+    return time(0);
+}
diff --git a/src/libs/libgroff/libgroff.am b/src/libs/libgroff/libgroff.am
index 78e3394..e215f2e 100644
--- a/src/libs/libgroff/libgroff.am
+++ b/src/libs/libgroff/libgroff.am
@@ -33,6 +33,7 @@ libgroff_a_SOURCES = \
   src/libs/libgroff/cmap.cpp \
   src/libs/libgroff/color.cpp \
   src/libs/libgroff/cset.cpp\
+  src/libs/libgroff/curtime.cpp \
   src/libs/libgroff/device.cpp \
   src/libs/libgroff/errarg.cpp \
   src/libs/libgroff/error.cpp \
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
index 9594f07..f7d2c18 100644
--- a/src/roff/troff/input.cpp
+++ b/src/roff/troff/input.cpp
@@ -36,6 +36,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */
 #include "input.h"
 #include "defs.h"
 #include "unicode.h"
+#include "curtime.h"
 
 // Needed for getpid() and isatty()
 #include "posix.h"
@@ -8138,7 +8139,7 @@ static void init_registers()
 #else /* not LONG_FOR_TIME_T */
   time_t
 #endif /* not LONG_FOR_TIME_T */
-    t = time(0);
+    t = current_time();
   // Use struct here to work around misfeature in old versions of g++.
   struct tm *tt = localtime(&t);
   set_number_reg("seconds", int(tt->tm_sec));
-- 
2.6.2

Reply via email to