Subject: Re: bug#59099: 3-rd party aux files.

From: Van de Bugger <[email-masked]>
Date: Sat, 01 Mar 2025 20:06:21 +0300

Hi,

> Since the purpose of the new feature is to support per-project helper
> files (right?)…

No exactly. Per-project helper files can be easily added to the project
source with no problem. The problem if several projects share the same
helper files. Currently such files should be managed manually: if a
helper file changed in one project, it should be manually copied to
other projects that use the same helper file.

My idea is put all helper files I use in my projects to a library.
Every project pulls required files from the library to  the project aux
directory by running 

$ autoreconf -i

> What comes to mind follows your typo above :)

That wasn't a typo. I was going to introduce AUTOMAKE_LIBDIRS
environment variable.

⁂

Okay, my initial patch for automake 1.16.5 is attached. Patched
automake looks for files in:

1. Directory specified by --libdir option. If more than one --libdir
option is present in the command line, directories are searched in
left-to-right order.
2. Directory specified by AUTOMAKE_LIBDIR environment variable.
3. Directories specified by AUTOMAKE_LIBDIRS environment variable. The
variable contains a list of directories separated with colon (semicolon
in case of Windows OS).
4. System directories $datadir/$PACKAGE and $datadir/$PACKAGE-
$APIVERSION.

⁂

Implementation details:

Instead of $libdir variable containing a single library directory,
@libdir array is maintained.

Unused FileUtils::find_file function is modified: (1) The current
directory is not searched implicitly; (2) File optionality is specified
by the last argument, not by question sign in the end of the file name.

find_file is used for file search.

Arguments of --libdir options are pushed to the @libdirs. 

When command line parsing finished, content of environment variables
AUTOMAKE_LIBDIR and AUTOMAKE_LIBDIRS is appended, as well as the system
directories (however, system directories are not appended if automake
is not installed). These actions are performed in finalize_libdirs
function, because the function is called twice.

--print-libdir option is renamed to --print-libdirs. The colon-
separated (semicolon in case of Windows OS) list of directories is
printed. Old option, --print-libdir, is still recognized as
abbreviation of the full name.

⁂

I built the patched automake in Fedora Copr (I have used official
Fedora rpm spec and added my patch). Build result is:

https://copr.fedorainfracloud.org/coprs/vandebugger/testing/build/8714811/
https://download.copr.fedorainfracloud.org/results/vandebugger/testing/fedora-41-x86_64/08714811-automake/builder-live.log.gz

Testsuite summary:

# TOTAL: 2980
# PASS:  2882
# SKIP:  60
# XFAIL: 38
# FAIL:  0
# XPASS: 0
# ERROR: 0

You see, all the tests passed. 

⁂

Incompatibilities with the previous implementation are minor:

1. Subsequent --libdir option does not override the previous --libdir
option.
2. --print-libdir option prints list of directories, not a single
directory.

⁂

The patched automake solves my problem. Now I can put all the helper
scripts to a directory, set AUTOMAKE_LIBDIRS, and put few lines to
autoconf.ac:

AC_REQUIRE_AUX_FILE([clean-dir.sh])
AC_REQUIRE_AUX_FILE([check-doc-hunspell.sh])
AC_REQUIRE_AUX_FILE([check-html-tidy.sh])
...

---

On Mon, 2022-11-07 at 16:37 -0700, Karl Berry wrote:
> Hi Van - first, I've copied your messages to the bug tracker, so
please  
> use [[email-masked]](mailto:[email-masked]) and the given
subject line so things stay in  
> the bug going forward.  
[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=59099](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=59099)
 
> (Except my resend of your reply didn't get attached. I don't know why
> not. Will try to figure that out. For the record, your msg is here:  
>
[https://lists.gnu.org/archive/html/automake/2022-11/msg00001.html](https://lists.gnu.org/archive/html/automake/2022-11/msg00001.html)
 
> )
> 
>     I am not sure which interface to choose for the new feature. I
see  
>     few possible approaches:
> 
> Your summary is admirable :).  I think we should have Jim and/or
other  
> more experienced Automakers weigh in before you start coding. But
here  
> are my comments.
> 
>     Disadvantage is that both directories are  
>     not writable by non-privileged user.
> 
> Since the purpose of the new feature is to support per-project helper
> files (right?), a single system directory doesn't seem right. Let's
not  
> mess around with system directories if we can help it.
> 
>     2. Let automake maintain the *list* of directories to be searched
for  
>     aux files.
> 
> Sounds right.
> 
>     In such case, the --libdir  option (and the AUTOMAKE_LIBDIRS  
>     environment variable) will insert the given directory to the
beginning  
>     (or to the end?) of the list. Multiple --libdir options are
allowed.
> 
> I like the idea in general.
> 
>     Is there any existing code relying on the fact that automake
ignores  
>     standard directory if the --libdir option is specified?
> 
> The answer to the question "does anything depend on X", for any
Automake  
> (or Autoconf) behavior X, is yes.  So I think we must not change  
> existing behavior of --libdir or AUTOMAKE_LIBDIR. Instead, we can add
to it.
> 
> What comes to mind follows your typo above :) ... have a new  
> option/envvar --libdirs/AUTOMAKE_LIBDIRS which maintains an
independent  
> list of directories to search. I think the new dirs should be
searched  
> after the libdir/AUTOMAKE_LIBDIR directory.
> 
> I think the list of dirs should be searched in the order specified:  
> --libdirs foo --libdirs bar would search foo then bar.
> 
> As with --libdir/AUTOMAKE_LIBDIR, if --libdirs is specified, it would
> override (completely) AUTOMAKE_LIBDIRS. No such "extra" directories  
> would be searched by default.
> 
> Does that reasonably solve your original request?
> 
>     Also, it is not clear to me, should it affect searching for *.am
>     files or not.
> 
> For consistency, I think the new searching should look for the same  
> files as the existing searching. Presumably that will be  
> easiest/cleanest to implement, too.
> 
> In general, the closer the new behavior is to the existing, the
easier I  
> think it will be to understand (and implement and document).
> 
> Wdyt? --thanks, karl.

diff -ur a/bin/automake.in b/bin/automake.in
--- a/bin/automake.in	2021-09-20 03:53:14.000000000 +0300
+++ b/bin/automake.in	2025-03-01 17:28:50.656106540 +0300
@@ -87,6 +87,7 @@
 sub define_verbose_libtool ();
 sub define_verbose_texinfo ();
 sub do_check_merge_target ();
+sub finalize_libdirs ();
 sub get_number_of_threads ();
 sub handle_compile ();
 sub handle_data ();
@@ -283,6 +284,8 @@
 # TRUE if we should always update files that we know about.
 my $force_missing = 0;
 
+# List of directories to look for files.
+my @libdirs;
 
 ## ---------------------------------------- ##
 ## Variables filled during files scanning.  ##
@@ -2512,7 +2515,7 @@
     }
 
     my ($coms, $vars, $rules) =
-      file_contents_internal (1, "$libdir/am/compile.am",
+      file_contents_internal (1, find_file ("am/compile.am", @libdirs),
 			      new Automake::Location,
 			      'DEFAULT_INCLUDES' => $default_includes,
 			      'MOSTLYRMS' => join ("\n", @mostly_rms),
@@ -6739,7 +6742,7 @@
 {
   my $saved_output_vars = $output_vars;
   my ($comments, undef, $rules) =
-    file_contents_internal (1, "$libdir/am/header-vars.am",
+    file_contents_internal (1, find_file ("am/header-vars.am", @libdirs),
 			    new Automake::Location);
 
   foreach my $var (sort keys %configure_vars)
@@ -6963,7 +6966,7 @@
 # ($COMMENT, $VARIABLES, $RULES)
 # file_contents_internal ($IS_AM, $FILE, $WHERE, [%TRANSFORM])
 # ------------------------------------------------------------
-# Return contents of a file from $libdir/am, automatically skipping
+# Return contents of a file from libdir/am, automatically skipping
 # macros or rules which are already known. $IS_AM iff the caller is
 # reading an Automake file (as opposed to the user's Makefile.am).
 sub file_contents_internal
@@ -7017,7 +7020,7 @@
 	{
 	    if ($cond != FALSE)
 	      {
-		my $file = ($is_am ? "$libdir/am/" : '') . $1;
+		my $file = $is_am ? find_file ("am/$1", @libdirs) : $1;
 		$where->push_context ("'$file' included from here");
 		# N-ary '.=' fails.
 		my ($com, $vars, $rules)
@@ -7156,13 +7159,13 @@
 # $CONTENTS
 # file_contents ($BASENAME, $WHERE, [%TRANSFORM])
 # -----------------------------------------------
-# Return contents of a file from $libdir/am, automatically skipping
+# Return contents of a file from libdir/am, automatically skipping
 # macros or rules which are already known.
 sub file_contents
 {
     my ($basename, $where, %transform) = @_;
     my ($comments, $variables, $rules) =
-      file_contents_internal (1, "$libdir/am/$basename.am", $where,
+      file_contents_internal (1, find_file ("am/$basename.am", @libdirs), $where,
 			      %transform);
     return "$comments$variables$rules";
 }
@@ -7655,7 +7658,7 @@
   my $message = "required file '$fullfile' not found";
   if ($add_missing)
     {
-      if (-f "$libdir/$file")
+      if (my $libfile = find_file ($file, @libdirs, {optional=>1}))
         {
           $suppress = 1;
 
@@ -7679,14 +7682,14 @@
           unlink ($fullfile) if -f $fullfile;
           if ($symlink_exists && ! $copy_missing)
             {
-              if (! symlink ("$libdir/$file", $fullfile)
+              if (! symlink ($libfile, $fullfile)
                   || ! -e $fullfile)
                 {
                   $suppress = 0;
                   $trailer = "; error while making link: $!";
                 }
             }
-          elsif (system ('cp', "$libdir/$file", $fullfile))
+          elsif (system ('cp', $libfile, $fullfile))
             {
               $suppress = 0;
               $trailer = "\n    error while copying";
@@ -7697,7 +7700,7 @@
   else
     {
       $trailer = "\n  'automake --add-missing' can install '$file'"
-        if -f "$libdir/$file";
+        if find_file ($file, @libdirs, {optional=>1});
     }
 
   # If --force-missing was specified, and we have
@@ -8252,8 +8255,9 @@
     (
      'version' => \&version,
      'help'    => \&usage,
-     'libdir=s'	=> \$libdir,
-     'print-libdir'     => sub { print "$libdir\n"; exit 0; },
+     'libdir=s'	=> \@libdirs,
+     'print-libdirs'    => sub { finalize_libdirs (); local $" = $path_sep;
+       print "@libdirs\n"; exit 0; },
      'gnu'		=> sub { $strict = 'gnu'; },
      'gnits'		=> sub { $strict = 'gnits'; },
      'foreign'		=> sub { $strict = 'foreign'; },
@@ -8270,6 +8274,8 @@
   use Automake::Getopt ();
   Automake::Getopt::parse_options %cli_options;
 
+  finalize_libdirs ();
+
   set_strictness ($strict);
   my $cli_where = new Automake::Location;
   set_global_option ('no-dependencies', $cli_where) if $ignore_deps;
@@ -8302,6 +8308,14 @@
     if $errspec && ! @input_files;
 }
 
+sub finalize_libdirs ()
+{
+  push(@libdirs, $ENV{AUTOMAKE_LIBDIR} // '');
+  push(@libdirs, split(/\Q$path_sep\E/, $ENV{AUTOMAKE_LIBDIRS} // ''));
+  push(@libdirs, "$datadir/$PACKAGE", "$datadir/$PACKAGE-$APIVERSION")
+    unless $ENV{AUTOMAKE_UNINSTALLED};
+  @libdirs = grep({$_ ne ''} @libdirs);
+}
 
 # handle_makefile ($MAKEFILE)
 # ---------------------------
diff -ur a/lib/Automake/Config.in b/lib/Automake/Config.in
--- a/lib/Automake/Config.in	2021-07-12 05:41:13.000000000 +0300
+++ b/lib/Automake/Config.in	2025-03-01 17:27:29.143383542 +0300
@@ -22,10 +22,11 @@
 use warnings FATAL => 'all';
 
 use Exporter;
+use Config;
 
 our @ISA = qw (Exporter);
 our @EXPORT = qw ($APIVERSION $PACKAGE $PACKAGE_BUGREPORT $VERSION
-                  $RELEASE_YEAR $libdir $perl_threads);
+                  $RELEASE_YEAR $datadir $path_sep $perl_threads);
 
 # Parameters set by configure.  Not to be changed.  NOTE: assign
 # VERSION as string so that e.g. version 0.30 will print correctly.
@@ -34,13 +35,13 @@
 our $PACKAGE_BUGREPORT = '@PACKAGE_BUGREPORT@';
 our $VERSION = '@VERSION@';
 our $RELEASE_YEAR = '@RELEASE_YEAR@';
-our $libdir = $ENV{"AUTOMAKE_LIBDIR"} || '@datadir@/@PACKAGE@-@APIVERSION@';
+our $datadir = '@datadir@';
+our $path_sep = $Config{path_sep};
 
 our $perl_threads = 0;
 # We need at least this version for CLONE support.
 if (eval { require 5.007_002; })
   {
-    use Config;
     $perl_threads = $Config{useithreads};
   }
 
diff -ur a/lib/Automake/FileUtils.pm b/lib/Automake/FileUtils.pm
--- a/lib/Automake/FileUtils.pm	2021-07-12 05:41:13.000000000 +0300
+++ b/lib/Automake/FileUtils.pm	2025-03-01 00:24:20.689020670 +0300
@@ -55,46 +55,47 @@
 
 =over 4
 
-=item C<find_file ($file_name, @include)>
+=item C<find_file ($file_name, @include, {optional => $bool})>
 
 Return the first path for a C<$file_name> in the C<include>s.
 
-We match exactly the behavior of GNU M4: first look in the current
-directory (which includes the case of absolute file names), and then,
-if the file name is not absolute, look in C<@include>.
+The function does not treat the current directory specially:
+if you want to look for the file in the current directory,
+include it to the @include list.
 
-If the file is flagged as optional (ends with C<?>), then return undef
+If the file is flagged as optional, then return undef
 if absent, otherwise exit with error.
 
 =cut
 
 # $FILE_NAME
-# find_file ($FILE_NAME, @INCLUDE)
+# find_file ($FILE_NAME, @INCLUDE, {optional => $BOOL})
 # --------------------------------
 sub find_file ($@)
 {
   use File::Spec;
 
+  # If the last argument is hashref, use it as options.
+  my %opts = @_ > 1 && ref($_[-1]) eq 'HASH' ? %{pop(@_)} : ();
   my ($file_name, @include) = @_;
-  my $optional = 0;
 
-  $optional = 1
-    if $file_name =~ s/\?$//;
-
-  return File::Spec->canonpath ($file_name)
-    if -e $file_name;
-
-  if (!File::Spec->file_name_is_absolute ($file_name))
+  if (File::Spec->file_name_is_absolute ($file_name))
+    {
+      return File::Spec->canonpath ($file_name)
+        if -e $file_name;
+    }
+  else
     {
-      foreach my $path (@include)
+      foreach my $dir (@include)
 	{
-	  return File::Spec->canonpath (File::Spec->catfile ($path, $file_name))
-	    if -e File::Spec->catfile ($path, $file_name)
+          my $path = File::Spec->catfile ($dir, $file_name);
+	  return File::Spec->canonpath ($path)
+	    if -e $path;
 	}
     }
 
   fatal "$file_name: no such file or directory"
-    unless $optional;
+    unless $opts{optional};
   return undef;
 }