From: Zack Weinberg Date: Tue, 24 Feb 2026 21:00:03 +0000 (-0500) Subject: autoheader: preserve hand-written template files X-Git-Tag: v2.73~10 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=daea200ec4fabb4a0918b72129db70229da9abd7;p=thirdparty%2Fautoconf.git autoheader: preserve hand-written template files 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 . 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. --- diff --git a/NEWS b/NEWS index 7bbf0b152..479a80f79 100644 --- 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. diff --git a/bin/autoheader.in b/bin/autoheader.in index 537a16b64..a7fc17cb2 100644 --- a/bin/autoheader.in +++ b/bin/autoheader.in @@ -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; diff --git a/bin/autoreconf.in b/bin/autoreconf.in index 56011f72d..aec1d9f29 100644 --- a/bin/autoreconf.in +++ b/bin/autoreconf.in @@ -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. # diff --git a/doc/autoconf.texi b/doc/autoconf.texi index 5b4f106a4..d4cdb9472 100644 --- a/doc/autoconf.texi +++ b/doc/autoconf.texi @@ -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. diff --git a/tests/tools.at b/tests/tools.at index 8c08abbb4..8cb259472 100644 --- a/tests/tools.at +++ b/tests/tools.at @@ -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 ## ------------ ##