Dear Recipient,

The issue that you reported, bug#13940, has been resolved. For your convenience, the patch has been forwarded. Thank you for reporting.

-------- Forwarded Message --------
Subject: [automake-commit] branch master updated: rules: accept overlapping conditional target declarations (bug#13940)
Date: Mon, 19 Oct 2026 15:04:28 -0400
From: Kamila Szewczyk <[email protected]>
Reply-To: [email protected], Kamila Szewczyk <[email protected]>
To: [email protected]

This is an automated email from the git hooks/post-receive script.

iczelia pushed a commit to branch master
in repository automake.

View the commit online:
https://git.savannah.gnu.org/gitweb/?p=automake.git;a=commitdiff;h=d163f0d56b11f294510c356443d1d993bf35608b

The following commit(s) were added to refs/heads/master by this push:
new d163f0d56 rules: accept overlapping conditional target declarations (bug#13940)
d163f0d56 is described below

commit d163f0d56b11f294510c356443d1d993bf35608b
Author: Kamila Szewczyk <[email protected]>
AuthorDate: Mon Oct 19 21:04:13 2026 +0200

    rules: accept overlapping conditional target declarations (bug#13940)
---
 NEWS                                   |   4 +
 bin/automake.in                        |  69 ++++++++++++++-
 lib/Automake/Rule.pm                   |  35 ++++++--
 t/list-of-tests.mk                     |   2 +-
 t/override-conditional-pr13940-link.sh |  78 +++++++++++++++++
t/override-conditional-pr13940.sh | 155 ++++++++++++++++++++++++++++++---
 6 files changed, 318 insertions(+), 25 deletions(-)

diff --git a/NEWS b/NEWS
index 094b08e79..d837c5daa 100644
--- a/NEWS
+++ b/NEWS
@@ -19,6 +19,10 @@ New in 1.20 (????-??-??):
  * Bugs fixed
 +  - Repeated target declarations in overlapping Automake conditionals no
+ longer produce spurious redefinition warnings when adding prerequisites.
+    (bug#13940)
+
   - Non-recursive Vala builds now pass distcheck when generated Vala
     sources trigger recompilation in the build directory.  Generated C
     files are moved into their subdirectories together, and build-tree
diff --git a/bin/automake.in b/bin/automake.in
index 0d4b24525..6fb9cba36 100644
--- a/bin/automake.in
+++ b/bin/automake.in
@@ -116,6 +116,7 @@ sub handle_tests ();
 sub handle_tests_dejagnu ();
 sub handle_texinfo ();
 sub handle_user_recursion ();
+sub has_inline_recipe ($);
 sub initialize_per_input ();
 sub lang_lex_finish ();
 sub lang_sub_obj ();
@@ -6588,6 +6589,38 @@ sub check_trailing_slash ($\$)
 }
  +sub has_inline_recipe ($)
+{
+  my ($text) = @_;
+  my @closing;
+  while (length $text)
+    {
+      if ($text =~ s/^\\.//s || $text =~ s/^\$\$//)
+        { next; }
+      if ($text =~ s/^\$([({])//)
+        {
+          push @closing, $1 eq '(' ? ')' : '}';
+          next;
+        }
+      next if $text =~ s/^\$.//s;
+      my $char = substr ($text, 0, 1, '');
+      if (@closing)
+        {
+          if ($char eq $closing[-1])
+            { pop @closing; }
+          elsif (($char eq '(' && $closing[-1] eq ')')
+                 || ($char eq '{' && $closing[-1] eq '}'))
+            { push @closing, $closing[-1]; }
+        }
+      else
+        {
+          return 0 if $char eq '#';
+          return 1 if $char eq ';';
+        }
+    }
+  return 0;
+}
+
 # read_am_file ($AMFILE, $WHERE, $RELDIR)
 # ---------------------------------------
 # Read $AMFILE file name which is located in $RELDIR, and set up
@@ -6599,7 +6632,8 @@ sub check_trailing_slash ($\$)
 # come after our generated stuff.
 sub read_am_file
 {
-    my ($amfile, $where, $reldir) = @_;
+    my ($amfile, $where, $reldir, $recipe) = @_;
+    $recipe ||= {};
     my $canon_reldir = &canonicalize ($reldir);
      my $am_file = new Automake::XFile ("< $amfile");
@@ -6724,6 +6758,17 @@ sub read_am_file
        {
            if ($prev_state == IN_RULE_DEF)
            {
+              if (exists $recipe->{'targets'} && !$recipe->{'commands'})
+                {
+                  $recipe->{'prerequisites'} .= $_;
+                  if (has_inline_recipe ($recipe->{'prerequisites'}))
+                    {
+                      Automake::Rule::register_recipe
+                        ($recipe->{'targets'}, $cond, $recipe->{'where'},
+                         $recipe->{'where'});
+                      $recipe->{'commands'} = 1;
+                    }
+                }
              my $cond = new Automake::Condition @cond_stack;
              $output_trailer .= $cond->subst_string;
              $output_trailer .= $_;
@@ -6779,7 +6824,17 @@ sub read_am_file
            # For now we have to output all definitions of user rules
            # and can't diagnose duplicates (see the comment in
            # Automake::Rule::define). So we go on and ignore the return value.
-           Automake::Rule::define ($1, $amfile, RULE_USER, $cond, $where);
+            my ($targets, $prerequisites) = ($1, $2);
+            %$recipe = (targets => $targets,
+                        prerequisites => $prerequisites . "\n",
+                        where => $where->clone);
+ Automake::Rule::define ($targets, $amfile, RULE_USER, $cond, $where);
+            if (has_inline_recipe ($prerequisites))
+              {
+                Automake::Rule::register_recipe
+                  ($targets, $cond, $where, $recipe->{'where'});
+                $recipe->{'commands'} = 1;
+              }
            check_variable_expansions ($_, $where);
 @@ -6792,6 +6847,7 @@ sub read_am_file
        elsif (/$ASSIGNMENT_PATTERN/o)
        {
            # Found a macro definition.
+            %$recipe = ();
            $prev_state = IN_VAR_DEF;
            $last_var_name = $1;
            $last_var_type = $2;
@@ -6860,11 +6916,18 @@ sub read_am_file
            my $new_reldir = File::Spec->abs2rel ($path, $relative_dir);
            $new_reldir = '.' if $new_reldir !~ s,/[^/]*$,,;
            $where->push_context ("'$path' included from here");
-           read_am_file ($path, $where, $new_reldir);
+           read_am_file ($path, $where, $new_reldir, $recipe);
            $where->pop_context;
        }
        else
        {
+            if (/^\t/ && exists $recipe->{'targets'})
+              {
+                Automake::Rule::register_recipe
+                  ($recipe->{'targets'}, $cond, $recipe->{'where'},
+                   $recipe->{'where'});
+                $recipe->{'commands'} = 1;
+              }
            # This isn't an error; it is probably a continued rule.
            # In fact, this is what we assume.
            $prev_state = IN_RULE_DEF;
diff --git a/lib/Automake/Rule.pm b/lib/Automake/Rule.pm
index 90de27b42..00d5e77bb 100644
--- a/lib/Automake/Rule.pm
+++ b/lib/Automake/Rule.pm
@@ -681,6 +681,32 @@ sub _maybe_warn_about_duplicated_target ($$$$$$)
     }
 }
 +sub register_recipe ($$$$)
+{
+  my ($target, $cond, $where, $declaration) = @_;
+  return if $cond == FALSE;
+
+  my $rule = _crule $target;
+  my $recipes = $rule->{'recipes'} ||= [];
+  return if grep { $_->[0] == $cond && $_->[2] == $declaration } @$recipes;
+  for my $old (@$recipes)
+    {
+      my ($oldcond, $oldwhere, $olddeclaration) = @$old;
+      next if $olddeclaration == $declaration;
+
+      next if $oldcond == $cond || $target =~ /^[^%]*%[^%]*$/;
+      my ($message) =
+ (new Automake::DisjConditions ($oldcond))->ambiguous_p ($target, $cond);
+      if ($message)
+        {
+          msg 'syntax', $where, "$message ...", partial => 1;
+          msg 'syntax', $oldwhere, "... '$target' previously defined here";
+          last;
+        }
+    }
+  push @$recipes, [$cond, $where->clone, $declaration];
+}
+
 # Return the list of conditionals in which the rule was defined.  In case
 # an ambiguous conditional definition is detected, return the empty list.
 sub _conditionals_for_rule ($$$$)
@@ -695,14 +721,7 @@ sub _conditionals_for_rule ($$$$)
# Don't coalesce the several pattern rules from footer.am into a single one.
   return $cond if $target eq "%:" && $where->get =~ /\/am\/footer\.am$/;
 -  if ($owner == RULE_USER)
-    {
-      # For user rules, just diagnose the ambiguity.
-      msg 'syntax', $where, "$message ...", partial => 1;
-      msg_cond_rule ('syntax', $ambig_cond, $target,
-                     "... '$target' previously defined here");
-      return ();
-    }
+  return () if $owner == RULE_USER;
    # FIXME: for Automake rules, we can't diagnose ambiguities yet.
   # The point is that Automake doesn't propagate conditions
diff --git a/t/list-of-tests.mk b/t/list-of-tests.mk
index c6cacdc55..d369ddb91 100644
--- a/t/list-of-tests.mk
+++ b/t/list-of-tests.mk
@@ -36,7 +36,6 @@ t/all.sh \
 t/cond17.sh \
 t/gcj6.sh \
 t/override-conditional-2.sh \
-t/override-conditional-pr13940.sh \
 t/dist-pr109765.sh \
 t/instdir-cond2.sh \
 t/instdir-ltlib.sh \
@@ -800,6 +799,7 @@ t/output9.sh \
 t/override-conditional-1.sh \
 t/override-conditional-2.sh \
 t/override-conditional-pr13940.sh \
+t/override-conditional-pr13940-link.sh \
 t/override-html.sh \
 t/override-suggest-local.sh \
 t/parallel-am.sh \
diff --git a/t/override-conditional-pr13940-link.sh b/t/override-conditional-pr13940-link.sh
new file mode 100644
index 000000000..3712f137c
--- /dev/null
+++ b/t/override-conditional-pr13940-link.sh
@@ -0,0 +1,78 @@
+#! /bin/sh
+# Copyright (C) 2013-2026 Free Software Foundation, Inc.
+#
+# This program 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, or (at your option)
+# any later version.
+#
+# This program 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# Prerequisite-only redeclarations must not change the selection of
+# Automake's generated link rules (bug#13940).
+
+required=cc
+. test-init.sh
+
+cat >> configure.ac << 'END'
+AC_PROG_CC
+AM_CONDITIONAL([FOO], [test "$foo" = yes])
+AC_OUTPUT
+END
+
+cat > Makefile.am << 'END'
+bin_PROGRAMS = prog
+prog_SOURCES = main.c
+if FOO
+prog$(EXEEXT): conditional
+endif
+prog$(EXEEXT): always
+always conditional:
+       :
+END
+
+cat > main.c << 'END'
+int main (void) { return 0; }
+END
+
+$ACLOCAL
+$AUTOMAKE -a -Werror
+$AUTOCONF
+./configure foo=no
+$MAKE
+./prog
+$MAKE clean
+$MAKE prog
+./prog
+
+# A conditional custom recipe must still override the generated one only
+# in that condition; adding unconditional prerequisites must not broaden it.
+cat > Makefile.am << 'END'
+bin_PROGRAMS = prog
+prog_SOURCES = main.c
+if FOO
+prog$(EXEEXT):
+       @echo custom > custom-seen
+endif
+prog$(EXEEXT): always
+always:
+       :
+END
+$AUTOMAKE -Werror
+./configure foo=no
+$MAKE clean
+$MAKE prog
+./prog
+test ! -e custom-seen
+./configure foo=yes
+$MAKE clean
+$MAKE prog
+test -f custom-seen
+
+:
diff --git a/t/override-conditional-pr13940.sh b/t/override-conditional-pr13940.sh
index 0ec41577c..45e5c9719 100644
--- a/t/override-conditional-pr13940.sh
+++ b/t/override-conditional-pr13940.sh
@@ -14,31 +14,160 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 -# Expose automake bug#1394: automake erroneously think that a .PHONY
-# target's rule is overridden only because we declare dependencies
-# to such targets twice: one in an Automake conditional, and once
-# unconditionally.
+# Repeated target declarations may add prerequisites in overlapping
+# conditions, in either order (bug#13940).
  . test-init.sh
  cat >> configure.ac << 'END'
-AM_CONDITIONAL([FOO], [true])
+AM_CONDITIONAL([FOO], [test "$foo" = yes])
+AM_CONDITIONAL([BAR], [test "$bar" = yes])
 AC_OUTPUT
 END
  cat > Makefile.am << 'END'
-# The conditional here is important: Automake only appears to warn
-# when one of the all-local rules is inside a conditional and the
-# other is not.
 if FOO
-all-local: bar
-bar: ; @echo bar
+all-local: conditional
+forward: conditional
+.PHONY: conditional
+if BAR
+nested: both
 endif
-all-local: baz
-baz: ; @echo baz
+endif
+all-local: always forward reverse nested overlap
+forward: always
+reverse: always
+nested: always
+overlap: always
+.PHONY: always forward reverse nested overlap both
+if FOO
+reverse: conditional
+overlap: conditional
+endif
+if BAR
+overlap: other
+endif
+always conditional both other:
+       @echo $@ >> seen
 END
  $ACLOCAL
-$AUTOMAKE -a
+$AUTOMAKE -a -Werror
+$AUTOCONF
+
+for foo in yes no; do
+  for bar in yes no; do
+    ./configure foo=$foo bar=$bar
+    rm -f seen
+    $MAKE all
+    echo always > expected
+    if test "$foo" = yes; then
+      echo conditional >> expected
+      if test "$bar" = yes; then
+        echo both >> expected
+      fi
+    fi
+    if test "$bar" = yes; then
+      echo other >> expected
+    fi
+    sort seen > seen.sorted
+    sort expected > expected.sorted
+    diff expected.sorted seen.sorted
+  done
+done
+
+# Adding prerequisites before or after a recipe is also legitimate.
+cat > Makefile.am << 'END'
+if FOO
+first: conditional
+last:
+       @echo last >> seen
+endif
+first:
+       @echo first >> seen
+last: always
+all-local: first last
+always conditional:
+       @echo $@ >> seen
+.PHONY: first last always conditional
+END
+
+$AUTOMAKE -Werror
+for foo in yes no; do
+  ./configure foo=$foo bar=no
+  rm -f seen
+  $MAKE all
+  echo always > expected
+  echo first >> expected
+  if test "$foo" = yes; then
+    echo conditional >> expected
+    echo last >> expected
+  fi
+  sort seen > seen.sorted
+  sort expected > expected.sorted
+  diff expected.sorted seen.sorted
+done
+
+# Only actual recipes conflict.  A semicolon in a comment, an escaped
+# filename, or a variable reference must not turn prerequisites into one.
+cat > Makefile.am << 'END'
+if FOO
+comment: dep # ; not a recipe
+escaped: dep\;name
+reference: $(subst ;,x,dep)
+short: $;
+continued: \
+  dep
+endif
+comment: ; :
+escaped: ; :
+reference: ; :
+short: ; :
+continued: ; :
+END
+$AUTOMAKE -Werror -Wno-portability
+
+# Diagnose real conditional recipe overrides in either declaration order.
+# Include empty inline recipes and recipes on continued dependency lines.
+for style in tab inline empty continued included; do
+  case $style in
+    tab) printf 'clash:\n\t:\n\t:\n' > recipe ;;
+    inline) echo 'clash: ; :' > recipe ;;
+    empty) echo 'clash: ;' > recipe ;;
+    continued) printf 'clash: \\\n; :\n' > recipe ;;
+    included)
+      printf 'clash:\ninclude commands.am\n' > recipe
+      printf '\t:\n' > commands.am
+      ;;
+  esac
+  for order in before after; do
+    echo 'clash: ; :' > unconditional
+    {
+      if test "$order" = before; then
+        cat unconditional
+      fi
+      echo 'if FOO'
+      cat recipe
+      echo endif
+      if test "$order" = after; then
+        cat unconditional
+      fi
+    } > Makefile.am
+    AUTOMAKE_fails
+    test $(grep -c 'clash was already defined in condition' stderr) -eq 1
+    grep "'clash' previously defined here" stderr
+  done
+done
+
+# Separate conditional pieces of the same recipe are not overrides.
+cat > Makefile.am << 'END'
+whole:
+       :
+if FOO
+       :
+endif
+       :
+END
+$AUTOMAKE -Werror
  :

Attachment: OpenPGP_0xC868F0B6DE38409D.asc
Description: OpenPGP public key

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to