autoconf-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v2, committed] autom4te: report subsecond timestamp support in --


From: Zack Weinberg
Subject: [PATCH v2, committed] autom4te: report subsecond timestamp support in --version
Date: Wed, 06 Dec 2023 16:58:56 -0500
User-agent: Cyrus-JMAP/3.9.0-alpha0-1178-geeaf0069a7-fm-20231114.001-geeaf0069

The Automake test suite wants this in order to know if it’s safe to
reduce the length of various delays for the purpose of ensuring files
in autom4te.cache are newer than the corresponding source files.  We
can also take advantage of this to speed up a couple of tests in our
own testsuite.

* lib/Autom4te/FileUtils.pm: Provide (but do not export) a flag
  $subsecond_mtime, indicating whether the ‘mtime’ function reports
  modification time with precision greater than one second.
  Reorganize commentary and import logic for clarity.  Add
  configuration for emacs’ perl-mode to the bottom of the file.
* bin/autom4te.in ($version): If $Autom4te::FileUtils::subsecond_mtime
  is true, print “Features: subsecond-mtime” as the second line
  of --version output.

* lib/autotest/general.m4: Move definitions of AS_MESSAGE_LOG_FD,
  AT_JOB_FIFO_IN_FD, and AT_JOB_FIFO_OUT_FD to top level and change
  the latter two to be defined using _AT_DEFINE_INIT.  This enables
  use of AS_MESSAGE_LOG_FD in AT_TESTS_PREPARE code.

* tests/local.at (AT_MTIME_DELAY): New utility that delays a test for
  an appropriate time to ensure all files created before its use are
  considered older than all files created afterward.  The delay will
  be as short as possible.
  (AT_TESTS_PREPARE): Calculate and log the delay used by AT_MTIME_DELAY.
  Only use a delay shorter than one second if the build filesystem,
  our autom4te, and the automake on the PATH all support this.
  (AT_CMP): En passant removal of unnecessary blank lines.

* tests/tools.at: Use AT_MTIME_DELAY, instead of sleeping for a
  hardcoded interval, everywhere the delay was to control file
  timestamps.
---
 bin/autom4te.in           |  6 ++-
 lib/Autom4te/FileUtils.pm | 71 +++++++++++++++++++++++---------
 lib/autotest/general.m4   | 20 ++++++---
 tests/local.at            | 86 +++++++++++++++++++++++++++++++++++++--
 tests/tools.at            | 26 +++++-------
 5 files changed, 164 insertions(+), 45 deletions(-)

diff --git a/bin/autom4te.in b/bin/autom4te.in
index 38a61ac9..8957a6c9 100644
--- a/bin/autom4te.in
+++ b/bin/autom4te.in
@@ -207,8 +207,10 @@ General help using GNU software: 
<https://www.gnu.org/gethelp/>.
 
 # $VERSION
 # --------
-$version = "autom4te (@PACKAGE_NAME@) @VERSION@
-Copyright (C) @RELEASE_YEAR@ Free Software Foundation, Inc.
+$version = "autom4te (@PACKAGE_NAME@) @VERSION@\n"
+  . ($Autom4te::FileUtils::subsecond_mtime
+     ? "Features: subsecond-mtime\n" : "")
+  . "\nCopyright (C) @RELEASE_YEAR@ Free Software Foundation, Inc.
 License GPLv3+/Autoconf: GNU GPL version 3 or later
 <https://gnu.org/licenses/gpl.html>, <https://gnu.org/licenses/exceptions.html>
 This is free software: you are free to change and redistribute it.
diff --git a/lib/Autom4te/FileUtils.pm b/lib/Autom4te/FileUtils.pm
index c1e8e8c3..06f87c31 100644
--- a/lib/Autom4te/FileUtils.pm
+++ b/lib/Autom4te/FileUtils.pm
@@ -38,24 +38,45 @@ use 5.006;
 use strict;
 use warnings FATAL => 'all';
 
-use Exporter;
+BEGIN
+{
+  require Exporter;
+  our @ISA = qw (Exporter);
+  our @EXPORT = qw (&contents
+                   &find_file &mtime
+                   &update_file
+                   &xsystem &xsystem_hint &xqx
+                   &dir_has_case_matching_file &reset_dir_cache
+                   &set_dir_cache_file);
+}
+
+# Use sub-second resolution file timestamps if available, carry on
+# with one-second resolution timestamps if Time::HiRes is not available.
+#
+# Unfortunately, even if Time::HiRes is available, we don't get
+# timestamps to the full precision recorded by the operating system,
+# because Time::HiRes converts timestamps to floating-point, and the
+# rounding error is hundreds of nanoseconds for circa-2023 timestamps
+# in IEEE double precision.  But this is the best we can do without
+# dropping down to C.
+#
+# $subsecond_mtime is not exported, but is intended for external
+# consumption, as $Autom4te::FileUtils::subsecond_mtime.
+BEGIN
+{
+  our $subsecond_mtime = 0;
+  eval
+    {
+      require Time::HiRes;
+      import Time::HiRes qw(stat);
+      $subsecond_mtime = 1;
+    }
+}
+
 use IO::File;
-
-# use sub-second resolution timestamps if available,
-# carry on with one-second resolution timestamps if that is all we have
-BEGIN { eval { require Time::HiRes; import Time::HiRes qw(stat) } }
-
 use Autom4te::Channels;
 use Autom4te::ChannelDefs;
 
-our @ISA = qw (Exporter);
-our @EXPORT = qw (&contents
-                 &find_file &mtime
-                 &update_file
-                 &xsystem &xsystem_hint &xqx
-                 &dir_has_case_matching_file &reset_dir_cache
-                 &set_dir_cache_file);
-
 =over 4
 
 =item C<find_file ($file_name, @include)>
@@ -122,11 +143,6 @@ sub mtime ($)
     $atime,$mtime,$ctime,$blksize,$blocks) = stat ($file)
     or fatal "cannot stat $file: $!";
 
-  # Unfortunately Time::HiRes converts timestamps to floating-point, and the
-  # rounding error can be hundreds of nanoseconds for circa-2023 timestamps.
-  # Perhaps some day Perl will support accurate file timestamps.
-  # For now, do the best we can without going outside Perl.
-
   return $mtime;
 }
 
@@ -394,3 +410,20 @@ sub set_dir_cache_file ($$)
 =cut
 
 1; # for require
+
+### Setup "GNU" style for perl-mode and cperl-mode.
+## Local Variables:
+## perl-indent-level: 2
+## perl-continued-statement-offset: 2
+## perl-continued-brace-offset: 0
+## perl-brace-offset: 0
+## perl-brace-imaginary-offset: 0
+## perl-label-offset: -2
+## cperl-indent-level: 2
+## cperl-brace-offset: 0
+## cperl-continued-brace-offset: 0
+## cperl-label-offset: -2
+## cperl-extra-newline-before-brace: t
+## cperl-merge-trailing-else: nil
+## cperl-continued-statement-offset: 2
+## End:
diff --git a/lib/autotest/general.m4 b/lib/autotest/general.m4
index 1babc3ca..bf18866e 100644
--- a/lib/autotest/general.m4
+++ b/lib/autotest/general.m4
@@ -186,6 +186,21 @@ m4_define([_AT_DEFINE_SETUP],
 [m4_define([$1], [m4_ifndef([AT_ingroup],
  [m4_fatal([$1: missing AT_SETUP detected])])$2])])
 
+# AS_MESSAGE_LOG_FD
+# -----------------
+# File descriptor open on the log file.  This needs to be defined
+# unconditionally, so that AT_TESTS_PREPARE code can use it.  Usage
+# prior to the point where the log file is opened will crash the
+# testsuite.
+m4_define([AS_MESSAGE_LOG_FD], [5])
+
+# AT_JOB_FIFO_{IN,OUT}_FD
+# -----------------
+# Used by the parallel test driver.
+# The parent needs two fds to the same fifo, otherwise, there is a race
+# where the parent can read the fifo before a child opens it for writing
+_AT_DEFINE_INIT([AT_JOB_FIFO_IN_FD], [6])
+_AT_DEFINE_INIT([AT_JOB_FIFO_OUT_FD], [7])
 
 # AT_INIT([TESTSUITE-NAME])
 # -------------------------
@@ -943,11 +958,6 @@ PATH=$at_new_path
 export PATH
 
 # Setting up the FDs.
-m4_define([AS_MESSAGE_LOG_FD], [5])
-dnl The parent needs two fds to the same fifo, otherwise, there is a race
-dnl where the parent can read the fifo before a child opens it for writing
-m4_define([AT_JOB_FIFO_IN_FD], [6])
-m4_define([AT_JOB_FIFO_OUT_FD], [7])
 [#] AS_MESSAGE_LOG_FD is the log file.  Not to be overwritten if '-d'.
 if $at_debug_p; then
   at_suite_log=/dev/null
diff --git a/tests/local.at b/tests/local.at
index 7265a090..a5de5e14 100644
--- a/tests/local.at
+++ b/tests/local.at
@@ -48,6 +48,81 @@ export CONFIG_SITE
 # our command line.
 : "${MAKE=make}"
 export MAKE
+
+# Determine how long we need to delay in between operations that might
+# modify autom4te.cache.  This depends on three factors: whether the
+# 'sleep' utility supports fractional seconds in its argument; what
+# the resolution of last-modification timestamps is on the filesystem
+# hosting the build; and whether autom4te and automake can both make
+# use of high-resolution file timestamps (this is not entirely under
+# our control because it depends on the capabilities of the Perl
+# installation).
+#
+# This series of tests mostly cribbed from automake/m4/sanity.m4.
+# We cannot rely on the execution of that code inside autoconf's own
+# configure script, because (depending on what version of automake was
+# used to generate the configure script) it might not be there at all,
+# or might be buggy.  Also, even if it's present and correct, it didn't
+# probe the autom4te we just built, which is the one we care about.
+#
+# The coarsest filesystem we know of is FAT, with a resolution
+# of only two seconds, even with the most recent "exFAT" extensions.
+# The finest (e.g. ext4 with large inodes, XFS, ZFS) is one
+# nanosecond, matching clock_gettime.  However, it is probably not
+# possible to delay execution of a shell script for less than one
+# millisecond, due to process creation overhead and scheduling
+# granularity, so we don't check for anything finer than that.
+
+# Default to the coarsest case.
+at_ts_resolution=2
+
+# Only try to go finer than 1s if sleep, autom4te, and automake
+# can all handle it.
+at_try_resolutions=1
+if sleep 0.001 2>/dev/null &&
+   autom4te --version 2>&1 |
+     grep 'Features:.*subsecond-mtime' > /dev/null 2>&1 &&
+   automake --version 2>&1 |
+     grep 'Features:.*subsecond-mtime' > /dev/null 2>&1
+then
+  at_try_resolutions="0.001 0.01 0.1 $at_try_resolutions"
+fi
+
+# In order to catch current-generation FAT out, we must *modify* files
+# that already exist; the *creation* timestamp is finer.  Use names
+# that make ls -t sort them differently when they have equal
+# timestamps than when they have distinct timestamps, keeping
+# in mind that ls -t prints the *newest* file first.
+rm -f conftest.ts?
+: > conftest.ts1
+: > conftest.ts2
+: > conftest.ts3
+
+for at_try_res in $at_try_resolutions; do
+  # Any one fine-grained sleep might happen to cross the boundary
+  # between two values of a coarser actual resolution, but if we do
+  # two fine-grained sleeps in a row, at least one of them will fall
+  # entirely within a coarse interval.
+  echo alpha > conftest.ts1
+  sleep $at_try_res
+  echo beta > conftest.ts2
+  sleep $at_try_res
+  echo gamma > conftest.ts3
+
+  # We assume that 'ls -t' will make use of high-resolution
+  # timestamps if the operating system supports them at all.
+  set X `ls -t conftest.ts?`
+  if test "$[]2" = conftest.ts3 &&
+     test "$[]3" = conftest.ts2 &&
+     test "$[]4" = conftest.ts1; then
+    at_ts_resolution=$at_try_res
+    break
+  fi
+done
+rm -f conftest.ts?
+
+AS_ECHO(["$at_srcdir/AT_LINE: using ${at_ts_resolution}s as timestamp 
resolution
+"]) >&AS_MESSAGE_LOG_FD
 ])
 
 
@@ -60,10 +135,15 @@ export MAKE
 # ----------------------
 # Check FILE-1 and FILE-2 for equality, like 'cmp FILE-1 FILE-2'.
 m4_define([AT_CMP],
-[m4_ifval([$2],, [m4_fatal([AT_CMP takes two arguments.])])[]dnl
-AT_CHECK([$at_diff "$1" "$2"])
-])# AT_CMP
+[m4_ifval([$2],, [m4_fatal([AT_CMP takes two arguments.])])]dnl
+[AT_CHECK([$at_diff "$1" "$2"])])
 
+# AT_MTIME_DELAY
+# --------------
+# Wait for a short time, to ensure that files created before this
+# command are considered to be older than files created afterward.
+m4_define([AT_MTIME_DELAY],
+[sleep $at_ts_resolution])
 
 ## ---------------- ##
 ## Testing syntax.  ##
diff --git a/tests/tools.at b/tests/tools.at
index ee28c05b..5dcf5f29 100644
--- a/tests/tools.at
+++ b/tests/tools.at
@@ -460,9 +460,7 @@ args: --cache=autom4te.cache
 end-language: "Autoconf-without-aclocal-m4"
 ]])
 
-# Delay to make sure the cache files generated by the next command are
-# considered newer than configure.ac.
-sleep 2
+AT_MTIME_DELAY
 
 AT_CHECK_AUTOCONF([], 1, [],
 [[trailer.m4: warning: AC_INIT was never used
@@ -476,9 +474,7 @@ configure.ac:5: error: possibly undefined macro: _AS@&t@_BAR
 configure.ac:6: error: possibly undefined macro: d@&t@nl
 ]])
 
-# Delay to make sure the cache files generated by the previous command
-# are considered to have been created in the past.
-sleep 2
+AT_MTIME_DELAY
 
 # A second run (without --force) should succeed and yield only the
 # warnings about AC_INIT and AC_OUTPUT.
@@ -684,7 +680,7 @@ AC_OUTPUT
 AT_CHECK_AUTOCONF
 
 cp configure configure.1
-sleep 1
+AT_MTIME_DELAY
 printf '%s\n' 'AC_LIBSOURCES([foo.c])dn@&t@l' >> configure.ac
 
 # This step must not use --force.
@@ -696,7 +692,7 @@ AT_CHECK([cmp configure configure.1])
 AT_CHECK([test configure -nt configure.1])
 
 cp configure configure.2
-sleep 1
+AT_MTIME_DELAY
 printf '%s\n' \
   'AC_DEFUN([unused_MACRO], [# bob was there too' \
   '])' >> aclocal.m4
@@ -1499,10 +1495,6 @@ AT_CLEANUP
 # -----------------------------
 
 AT_SETUP([autom4te preselections])
-: ${sleep='sleep 1'}   # Command to force different timestamps.
-# If this test should run on FAT file systems and older w32,
-# then setting $sleep correctly needs to be revisited.
-
 # We use aclocal and automake.  Skip broken automake wrappers.
 AT_CHECK([automake --version || exit 77], [], [stdout], [ignore])
 AT_CHECK([[grep '[1-9]\.[0-9]' stdout || exit 77]], [], [ignore])
@@ -1525,7 +1517,7 @@ AT_DATA([Makefile.am],
 [[AUTOMAKE_OPTIONS = foreign
 ]])
 
-$sleep # 'aclocal.m4' should be strictly younger than its inputs
+AT_MTIME_DELAY
 
 # If Autoconf is too old, or the user has turned caching off, skip:
 AT_CHECK([$ACLOCAL || { ret=$?; test $ret -eq 63 && ret=77; exit $ret; }],
@@ -1537,8 +1529,9 @@ AT_CHECK([autoconf])
 # comparing the old and new requests is a good place to start debugging:
 sort autom4te.cache/requests >old-requests
 echo newer >newer
-$sleep # if 'configure' is regenerated, we want it to be strictly newer,
-       # to catch the error consistently.
+# if 'configure' is regenerated, we want it to be strictly newer,
+# to catch the error consistently.
+AT_MTIME_DELAY
 AT_CHECK([$ACLOCAL], [],[], [ignore])
 AT_CHECK([automake --no-force --add-missing], [], [], [ignore])
 AT_CHECK([autoconf])
@@ -1627,7 +1620,8 @@ AT_XFAIL_IF([$PERL -I "$top_srcdir/lib" -e '
               '])
 
 # Cannot use AT_CHECK here, autotest internals could be messed up.
-
+# This sleep is not because of file timestamps, it's to make the
+# locks conflict.
 (echo AC_INIT; sleep 2; echo) \
   | (autom4te --language=autoconf -o configure -; echo $? >&2 ) 2>errlog &
 AT_CHECK([echo AC_INIT | autom4te --language=autoconf -o configure -])
-- 
2.41.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]