]> git.ipfire.org Git - thirdparty/autoconf.git/commitdiff
autoheader: preserve hand-written template files
authorZack Weinberg <zack@owlfolio.org>
Tue, 24 Feb 2026 21:00:03 +0000 (16:00 -0500)
committerZack Weinberg <zack@owlfolio.org>
Tue, 24 Feb 2026 21:10:47 +0000 (16:10 -0500)
Consistent with the existing behavior of autoreconf regarding
hand-maintained aclocal.m4, make both autoheader and autoreconf
check for the /* Generated from configure.ac by autoheader */
line that autoheader emits at the beginning of its generated
configuration header templates.  If the file exists but the
marker line is not there, they will now leave the file alone
unless given the new command line option --replace-handwritten.

Fixes <https://savannah.gnu.org/support/index.php?111388>.

I’m looking for feedback regarding whether the new option should
also cause autoreconf to clobber a hand-written aclocal.m4, or any
other files that _could_ be generated but aren’t always.  (aclocal.m4
in particular may need coordination with Automake, since the ‘aclocal’
tool belongs to them.)

* bin/autoheader.in: Add option --replace-handwritten.  If the
  primary template file already exists and doesn’t begin with a
  marker comment indicating it was generated by autoheader, don’t
  modify it unless --replace-handwritten was given.
* bin/autoreconf.in: Similarly.
* doc/autoconf.texi: Document new option for autoheader and
  autoreconf.
* tests/tools.at: Test new behavior.

NEWS
bin/autoheader.in
bin/autoreconf.in
doc/autoconf.texi
tests/tools.at

diff --git a/NEWS b/NEWS
index 7bbf0b152418f51fcb6d17c065378b3f66de8c66..479a80f7967273b44cf8f66d9df2a4bf2affeb5e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,20 @@ GNU Autoconf NEWS - User visible changes.
 
 ** New features
 
+*** autoheader and autoreconf preserve hand-written template files
+  If ‘config.h.in’ (or whatever your primary configuration header
+  template’s name is) already exists, and does not begin with a
+  comment line indicating it was generated by autoheader, autoheader
+  and autoreconf will now leave it alone instead of replacing it.
+  You can force them to replace the template anyway with the new
+  command line option, --replace-handwritten.  (This is separate
+  from --force because --force is specifically about updating files
+  even if they don’t appear to be _out of date_.)
+
+  We invite feedback on whether the new option should also apply to
+  other existing files that might or might not be machine-generated,
+  most importantly aclocal.m4 and Makefile.in.
+
 *** Programs now recognize #elifdef and #elifndef.
   The autom4te, autoscan and ifnames programs now recognize the two
   preprocessor directives, which were introduced in C23 and C++23.
index 537a16b642d63f58dac605bf2949e46204e385e9..a7fc17cb26eb65ed341ff9f7fbc2b337d688c3d3 100644 (file)
@@ -59,6 +59,7 @@ my $config_h_in;
 my @prepend_include;
 my @include;
 my @warnings;
+my $replace_handwritten = 0;
 
 # $HELP
 # -----
@@ -73,6 +74,9 @@ or else 'configure.in'.
   -v, --verbose            verbosely report processing
   -d, --debug              don\'t remove temporary files
   -f, --force              consider all files obsolete
+  -R, --replace-handwritten
+                           overwrite an existing template file that appears
+                           not to have been generated by autoheader
   -W, --warnings=CATEGORY  report the warnings falling in CATEGORY
                            (comma-separated list accepted)
 
@@ -115,9 +119,10 @@ sub parse_args ()
 {
   my $srcdir;
 
-  getopt ('I|include=s'         => \@include,
-         'B|prepend-include=s' => \@prepend_include,
-         'W|warnings=s'        => \@warnings);
+  getopt ('I|include=s'           => \@include,
+          'B|prepend-include=s'   => \@prepend_include,
+          'W|warnings=s'          => \@warnings,
+          'R|replace-handwritten' => \$replace_handwritten);
 
   parse_WARNINGS;
   parse_warnings @warnings;
@@ -218,6 +223,31 @@ my @config_templates = map(templates_for_header($_), split(' ', $config_h));
 $config_h_in = shift(@config_templates);
 $config_h =~ s/[ :].*//;
 
+# If $config_h_in already exists and doesn't appear to have been generated
+# by autoheader, stop, unless --replace-handwritten was given on the command
+# line.  This is not considered an error condition.
+if (open (my $fh, "<", $config_h_in))
+  {
+    my $firstline = readline($fh);
+    # If the file is empty, $firstline will be undef.  We _do_ want to
+    # go ahead and update it in that case.
+    if (defined $firstline
+        && $firstline !~ /^\/\* .*\.  Generated from .* by autoheader\.  \*\//
+        && !$replace_handwritten)
+      {
+        msg 'override', "${config_h_in} was not generated by autoheader";
+        msg 'override',
+          "leaving it alone (force update with --replace-handwritten)";
+        exit 0;
+      }
+  }
+else
+  {
+    # If we can't read $config_h_in for any reason besides it not existing
+    # yet, we probably can't update it either.
+    fatal "$config_h_in: $!" unless $!{ENOENT};
+  }
+
 # %SYMBOL might contain things like 'F77_FUNC(name,NAME)', but we keep
 # only the name of the macro.
 %symbol = map { s/\(.*//; $_ => 1 } keys %symbol;
index 56011f72daa9fed7135f4844af9a6cd2ff0036fb..aec1d9f2913582a50095ae518e7cbf679e517f93 100644 (file)
@@ -80,6 +80,10 @@ Operation modes:
   -v, --verbose            verbosely report processing
   -d, --debug              don't remove temporary files
   -f, --force              consider all generated and standard files obsolete
+  -R, --replace-handwritten
+                           replace files that *could* be generated but appear
+                           to have been written by hand (only config.h.in at
+                           present)
   -i, --install            copy missing standard auxiliary files
       --no-recursive       don't rebuild sub-packages
   -s, --symlink            with -i, install symbolic links instead of copies
@@ -142,6 +146,9 @@ my $run_make = 0;
 # Recurse into subpackages
 my $recursive = 1;
 
+# Replace handwritten files (only config.h.in right now)
+my $replace_handwritten = 0;
+
 ## ---------- ##
 ## Routines.  ##
 ## ---------- ##
@@ -157,13 +164,14 @@ sub parse_args ()
   # List of command line warning requests.
   my @warning;
 
-  getopt ("W|warnings=s"         => \@warning,
-         'I|include=s'          => \@include,
-         'B|prepend-include=s'  => \@prepend_include,
-         'i|install'            => \$install,
-         's|symlink'            => \$symlink,
-         'm|make'               => \$run_make,
-         'recursive!'           => \$recursive);
+  getopt ("W|warnings=s"          => \@warning,
+          'I|include=s'           => \@include,
+          'B|prepend-include=s'   => \@prepend_include,
+          'i|install'             => \$install,
+          'R|replace-handwritten' => \$replace_handwritten,
+          's|symlink'             => \$symlink,
+          'm|make'                => \$run_make,
+          'recursive!'            => \$recursive);
 
   # Split the warnings as a list of elements instead of a list of
   # lists.
@@ -224,6 +232,12 @@ sub parse_args ()
     {
       $automake .= ' --no-force';
     }
+  # --replace-handwritten (could also be applied to aclocal but that
+  # requires changes in automake as well)
+  if ($replace_handwritten)
+    {
+      $autoheader .= ' --replace-handwritten';
+    }
   # --verbose --verbose or --debug;
   if ($verbose > 1 || $debug)
     {
@@ -641,9 +655,9 @@ sub autoreconf_current_directory ($)
   my $uses_intltool;
   my $uses_gtkdoc;
   my $uses_libltdl;
-  my $uses_autoheader;
   my $uses_automake;
   my @subdir;
+  my @header_templates;
   my $traces;
   verb "$configure_ac: tracing";
   {
@@ -705,10 +719,11 @@ sub autoreconf_current_directory ($)
                                        || $macro eq "AM_PROG_LIBTOOL"
                                        || $macro eq "LT_INIT";
       $uses_libltdl = 1             if $macro eq "LT_CONFIG_LTDL_DIR";
-      $uses_autoheader = 1          if $macro eq "AC_CONFIG_HEADERS";
       $uses_automake = 1            if $macro eq "AM_INIT_AUTOMAKE";
       $uses_intltool = 1            if $macro eq "IT_PROG_INTLTOOL";
       $uses_gtkdoc = 1              if $macro eq "GTK_DOC_CHECK";
+      push @header_templates, split(' ', $args[0] || '')
+                                    if $macro eq "AC_CONFIG_HEADERS";
       push @subdir, split (' ', $args[0] || '')
                                     if $macro eq "AC_CONFIG_SUBDIRS"
                                        && $recursive;
@@ -935,15 +950,51 @@ sub autoreconf_current_directory ($)
   #
   # Run it before automake, since the latter checks the presence of
   # config.h.in when it sees an AC_CONFIG_HEADERS.
-  if (!$uses_autoheader)
+  #
+  # If config.h.in exists and was not created by autoheader, don't run
+  # autoheader, unless --replace-handwritten was given.  Autoheader will
+  # only update the first template file mentioned in AC_CONFIG_HEADERS.
+  if (!@header_templates)
     {
-      verb "$configure_ac: not using Autoheader";
+      verb "$configure_ac: not running autoheader, no config headers"
     }
   else
     {
-      xsystem ($autoheader);
-    }
+      my $need_autoheader = 1;
 
+      my ($header, @templates) = split(':', $header_templates[0]);
+      my $tmpl1 = (scalar @templates) ? $templates[0] : "${header}.in";
+
+      if (open (my $fh, "<", $tmpl1))
+        {
+          my $line = readline($fh);
+          if (defined $line
+              && $line !~ /^\/\* .*\.  Generated from .* by autoheader\.  \*\//)
+            {
+              if ($replace_handwritten)
+                {
+                  verb "$configure_ac: replacing handwritten $tmpl1";
+                }
+              else
+                {
+                  verb "$configure_ac: $tmpl1 was not generated by autoheader";
+                  verb "force update of $tmpl1 with --replace-handwritten";
+                  $need_autoheader = 0;
+                }
+            }
+        }
+      else
+        {
+          # If we can't read $tmpl1 for any reason besides it
+          # not existing yet, we probably can't update it either.
+          fatal "$tmpl1: $!" unless $!{ENOENT};
+        }
+
+      if ($need_autoheader)
+        {
+          xsystem ($autoheader);
+        }
+    }
 
   # ------------------ #
   # Running automake.  #
index 5b4f106a46cb64c8f0ff62984772abb3da9ff667..d4cdb9472b8b8555ee668b1e1c77efefc50dad8e 100644 (file)
@@ -1747,6 +1747,14 @@ to @command{autoreconf} will in turn undo any customizations to standard
 files.  Note that the macro @code{AM_INIT_AUTOMAKE} has some options
 which change the set of files considered to be standard.
 
+@item --replace-handwritten
+@itemx -R
+Replace files that @emph{could} be generated but appear to have been
+written by hand.  Currently this only affects the treatment of the
+primary configuration header template (usually @file{config.h.in};
+@pxref{Header Templates}).  In the future it may also affect
+@file{aclocal.m4} and others.
+
 @item --install
 @itemx -i
 Install any missing standard auxiliary files in the package.  By
@@ -3579,6 +3587,11 @@ Don't remove the temporary files.
 @itemx -f
 Remake the template file even if newer than its input files.
 
+@item --replace-handwritten
+@itemx -R
+Remake the template file even if it appears not to have been generated
+by @command{autoheader}.
+
 @item --include=@var{dir}
 @itemx -I @var{dir}
 Append @var{dir} to the include path.  Multiple invocations accumulate.
index 8c08abbb41cb88e097b44e43e05d796600e3293d..8cb2594725c1235a972ab49cb12eb8f42faed4ed 100644 (file)
@@ -959,6 +959,75 @@ AT_CHECK([grep SEAN config.h.in], [0], [ignore], [ignore])
 
 AT_CLEANUP
 
+# autoheader should not clobber a hand-written config header template,
+# even if it is the only one.
+AT_SETUP([autoheader preserves a hand-written template])
+
+AT_DATA([config.h.in],
+[[/* Define this to whatever you want. */
+#undef HANNA
+]])
+
+AT_DATA([configure.ac],
+[[AC_INIT
+AC_CONFIG_HEADERS([config.h])
+AC_DEFINE([HANNA], ["Hanna"], [Define this however you want])
+AC_DEFINE([SEAN], ["Sean"], [Sean's name])
+AC_OUTPUT
+]])
+
+AT_CHECK_AUTOCONF
+AT_CHECK([grep HANNA configure], [0], [ignore], [ignore])
+AT_CHECK([grep SEAN configure],  [0], [ignore], [ignore])
+
+AT_CHECK_AUTOHEADER([-Wall], [ignore], [0], [],
+[autoheader: warning: config.h.in was not generated by autoheader
+autoheader: warning: leaving it alone (force update with --replace-handwritten)
+])
+
+# autoreconf should not run autoheader either
+AT_CHECK([autoreconf])
+
+AT_CHECK([grep Generated config.h.in], [1], [ignore], [ignore])
+AT_CHECK([grep however config.h.in],   [1], [ignore], [ignore])
+AT_CHECK([grep whatever config.h.in],  [0], [ignore], [ignore])
+
+AT_CHECK([grep HANNA config.h.in],     [0], [ignore], [ignore])
+AT_CHECK([grep SEAN config.h.in],      [1], [ignore], [ignore])
+
+AT_CHECK_AUTOHEADER([-Wall --replace-handwritten],
+                    [HANNA SEAN], [0], [], [])
+
+AT_CHECK([grep Generated config.h.in], [0], [ignore], [ignore])
+AT_CHECK([grep however config.h.in],   [0], [ignore], [ignore])
+AT_CHECK([grep whatever config.h.in],  [1], [ignore], [ignore])
+
+AT_CLEANUP
+
+
+# autoheader _should_ update an empty template file.
+AT_SETUP([autoheader updates an empty template])
+
+AT_DATA([config.h.in], [[]])
+
+AT_DATA([configure.ac],
+[[AC_INIT
+AC_CONFIG_HEADERS([config.h])
+AC_DEFINE([HANNA], ["Hanna"], [Define this however you want])
+AC_DEFINE([SEAN], ["Sean"], [Sean's name])
+AC_OUTPUT
+]])
+
+AT_CHECK_AUTOCONF
+AT_CHECK([grep HANNA configure], [0], [ignore], [ignore])
+AT_CHECK([grep SEAN configure],  [0], [ignore], [ignore])
+
+AT_CHECK_AUTOHEADER([-Wall], [HANNA SEAN], [0], [], [])
+
+AT_CHECK([grep Generated config.h.in], [0], [ignore], [ignore])
+AT_CHECK([grep however config.h.in],   [0], [ignore], [ignore])
+
+AT_CLEANUP
 
 
 ## ------------ ##