From fccac7b14027def7cc40529a77a85c6f3df81f74 Mon Sep 17 00:00:00 2001
From: Todd Lowe <toddlowe@cisco.com>
Date: Mon, 10 May 2021 18:59:35 -0400
Subject: [PATCH] [SV60077] add grouped targets automatic variables

$(@<) $(@^) $(@<F) $(@^F) $(@<D) $(@^D)
https://savannah.gnu.org/bugs/?60077

Note: this commit reverses the stored order
of the 'also_makes' list and adds $@ to also_makes
for pattern rules.
---
 src/commands.c                         | 45 +++++++++++++++++++++-
 src/implicit.c                         |  9 ++---
 src/read.c                             | 17 +++++++--
 src/variable.c                         | 10 ++++-
 tests/scripts/features/grouped_targets | 27 +++++++++++++
 tests/scripts/variables/automatic      | 52 ++++++++++++++++++++++++--
 6 files changed, 145 insertions(+), 15 deletions(-)

diff --git a/src/commands.c b/src/commands.c
index dd179998..744255b3 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -1,5 +1,5 @@
 /* Command processing for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2021 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -64,7 +64,8 @@ void
 set_file_variables (struct file *file)
 {
   struct dep *d;
-  const char *at, *percent, *star, *less;
+  const char *at, *percent, *star, *less, *at_less;
+  static char *at_caret;
 
 #ifndef NO_ARCHIVES
   /* If the target is an archive member 'lib(member)',
@@ -94,6 +95,44 @@ set_file_variables (struct file *file)
       percent = "";
     }
 
+    /* Setup $(@<) and $(@^) for grouped-targets
+       $(@<) is the first specified target, which is the last in the list.
+       $(@^) is the list of targets */
+    at_less = "";
+    if (file->also_make)
+      {
+        static size_t at_caret_max;
+        size_t at_caret_len=0, len;
+        char *acp;
+        char const *name;
+
+        at_less = dep_name(file->also_make);
+
+        for (d = file->also_make; d != 0; d = d->next)
+          at_caret_len += strlen (dep_name (d)) + 1;
+
+        if (at_caret_len > at_caret_max)
+          at_caret = xrealloc (at_caret, at_caret_max = at_caret_len);
+
+        acp = at_caret;
+        for (d = file->also_make; d != 0; d = d->next)
+          {
+            name = dep_name (d);
+            len = strlen (name);
+            memcpy (acp, name, len);
+            acp += len;
+            *acp++ = FILE_LIST_SEPARATOR;
+          }
+
+        /* kill the trailing space */
+        acp[acp > at_caret ? -1 : 0] = '\0';
+      }
+    else
+      {
+        at_less = at;
+      }
+
+
   /* $* is the stem from an implicit or static pattern rule.  */
   if (file->stem == 0)
     {
@@ -154,6 +193,8 @@ set_file_variables (struct file *file)
   DEFINE_VARIABLE ("*", 1, star);
   DEFINE_VARIABLE ("@", 1, at);
   DEFINE_VARIABLE ("%", 1, percent);
+  DEFINE_VARIABLE ("@<", 2, at_less);
+  DEFINE_VARIABLE ("@^", 2, at_caret?at_caret:at);
 
   /* Compute the values for $^, $+, $?, and $|.  */
 
diff --git a/src/implicit.c b/src/implicit.c
index 5bbd6b79..34906b4c 100644
--- a/src/implicit.c
+++ b/src/implicit.c
@@ -1,5 +1,5 @@
 /* Implicit rule searching for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2021 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -983,14 +983,13 @@ pattern_search (struct file *file, int archive,
       file->precious = 1;
   }
 
-  /* If this rule builds other targets, too, put the others into FILE's
+  /* If this rule builds other targets, too, put all targets into FILE's
      'also_make' member.  */
 
   if (rule->num > 1)
-    for (ri = 0; ri < rule->num; ++ri)
-      if (ri != tryrules[foundrule].matches)
+    for (ri = rule->num; ri > 0;)
         {
-          char *nm = alloca (rule->lens[ri] + fullstemlen + 1);
+          char *nm = alloca (rule->lens[--ri] + fullstemlen + 1);
           char *p = nm;
           struct file *f;
           struct dep *new = alloc_dep ();
diff --git a/src/read.c b/src/read.c
index c0e3315f..d016b832 100644
--- a/src/read.c
+++ b/src/read.c
@@ -1,5 +1,5 @@
 /* Reading and parsing of makefiles for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2021 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -2190,8 +2190,19 @@ record_files (struct nameseq *filenames, int are_also_makes,
           struct dep *also = alloc_dep();
           also->name = f->name;
           also->file = f;
-          also->next = also_make;
-          also_make = also;
+          also->next = NULL;
+
+          if (also_make == 0)
+            also_make = also;
+          else
+          {
+            struct dep *am = also_make;
+            /* add to the end to maintain specified order */
+            while (am->next != 0)
+                am = am->next;
+
+              am->next = also;
+          }
         }
 
       f->is_target = 1;
diff --git a/src/variable.c b/src/variable.c
index 92582625..bdded2be 100644
--- a/src/variable.c
+++ b/src/variable.c
@@ -1,5 +1,5 @@
 /* Internals of variables for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2021 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -960,6 +960,10 @@ define_automatic_variables (void)
                          o_automatic, 1);
   define_variable_cname ("+D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $+)))",
                          o_automatic, 1);
+  define_variable_cname ("<@D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $(@<))))",
+                         o_automatic, 1);
+  define_variable_cname ("^@D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $(@^))))",
+                         o_automatic, 1);
 #else  /* not __MSDOS__, not WINDOWS32 */
   define_variable_cname ("@D", "$(patsubst %/,%,$(dir $@))", o_automatic, 1);
   define_variable_cname ("%D", "$(patsubst %/,%,$(dir $%))", o_automatic, 1);
@@ -968,6 +972,8 @@ define_automatic_variables (void)
   define_variable_cname ("?D", "$(patsubst %/,%,$(dir $?))", o_automatic, 1);
   define_variable_cname ("^D", "$(patsubst %/,%,$(dir $^))", o_automatic, 1);
   define_variable_cname ("+D", "$(patsubst %/,%,$(dir $+))", o_automatic, 1);
+  define_variable_cname ("@<D", "$(patsubst %/,%,$(dir $(@<)))", o_automatic, 1);
+  define_variable_cname ("@^D", "$(patsubst %/,%,$(dir $(@^)))", o_automatic, 1);
 #endif
   define_variable_cname ("@F", "$(notdir $@)", o_automatic, 1);
   define_variable_cname ("%F", "$(notdir $%)", o_automatic, 1);
@@ -976,6 +982,8 @@ define_automatic_variables (void)
   define_variable_cname ("?F", "$(notdir $?)", o_automatic, 1);
   define_variable_cname ("^F", "$(notdir $^)", o_automatic, 1);
   define_variable_cname ("+F", "$(notdir $+)", o_automatic, 1);
+  define_variable_cname ("@<F", "$(notdir $(@<))", o_automatic, 1);
+  define_variable_cname ("@^F", "$(notdir $(@^))", o_automatic, 1);
 }
 
 int export_all_variables;
diff --git a/tests/scripts/features/grouped_targets b/tests/scripts/features/grouped_targets
index 131b8c0b..4f075c9a 100644
--- a/tests/scripts/features/grouped_targets
+++ b/tests/scripts/features/grouped_targets
@@ -49,6 +49,33 @@ g1 g2 g3 g4&: ; @echo $@
 '',
 "t1\nt2\nt3\nt4\ng4");
 
+# Same as previous targets come from m1 phony rather than 
+# from the command line.  We don't see "Nothing to
+# be done for" messages. Also, note reversed order g4 g3 ...
+# The auto variable $(@<) is "g1" when that rule fires despite
+# "g4" triggering it.
+run_make_test(q{
+.PHONY: m1 t1 t2 t3 t4 g1 g2 g3 g4
+m1: t1 t2 t3 t4 g4 g3 g2 g1
+t1 t2 t3 t4: ; @echo $@
+g1 g2 g3 g4&: ; @echo $@ $(@<)
+},
+'',
+"t1\nt2\nt3\nt4\ng4 g1");
+
+# Same as previous targets come from m1 phony rather than 
+# from the command line.  Despite reversed dependency order g4 g3 ...
+# the auto variable $(@^) is in defined order of "g1 g2 g3 g4"
+# when that rule fires.
+run_make_test(q{
+.PHONY: m1 t1 t2 t3 t4 g1 g2 g3 g4
+m1: t1 t2 t3 t4 g4 g3 g2 g1
+t1 t2 t3 t4: ; @echo $@
+g1 g2 g3 g4&: ; @echo $(@^) 
+},
+'',
+"t1\nt2\nt3\nt4\ng1 g2 g3 g4");
+
 # Set a grouped target recipe for existing targets
 run_make_test(q{
 .PHONY: M a b
diff --git a/tests/scripts/variables/automatic b/tests/scripts/variables/automatic
index efd039c8..e70e0856 100644
--- a/tests/scripts/variables/automatic
+++ b/tests/scripts/variables/automatic
@@ -16,6 +16,8 @@ print MAKEFILE <<'EOF';
 .SUFFIXES: .x .y .z
 $(dir)/foo.x : baz.z $(dir)/bar.y baz.z
 	@echo '$$@ = $@, $$(@D) = $(@D), $$(@F) = $(@F)'
+	@echo '$$(@<) = $(@<), $$(@<D) = $(@<D), $$(@<F) = $(@<F)'
+	@echo '$$(@^) = $(@^), $$(@^D) = $(@^D), $$(@^F) = $(@^F)'
 	@echo '$$* = $*, $$(*D) = $(*D), $$(*F) = $(*F)'
 	@echo '$$< = $<, $$(<D) = $(<D), $$(<F) = $(<F)'
 	@echo '$$^ = $^, $$(^D) = $(^D), $$(^F) = $(^F)'
@@ -36,6 +38,8 @@ close(MAKEFILE);
 &run_make_with_options($makefile, "", &get_logfile);
 $answer = "touch $dir/bar.y
 \$\@ = $dir/foo.x, \$(\@D) = $dir, \$(\@F) = foo.x
+\$(\@<) = $dir/foo.x, \$(\@<D) = $dir, \$(\@<F) = foo.x
+\$(\@^) = $dir/foo.x, \$(\@^D) = $dir, \$(\@^F) = foo.x
 \$* = $dir/foo, \$(*D) = $dir, \$(*F) = foo
 \$< = baz.z, \$(<D) = ., \$(<F) = baz.z
 \$^ = baz.z $dir/bar.y, \$(^D) = . $dir, \$(^F) = baz.z bar.y
@@ -46,7 +50,47 @@ touch $dir/foo.x\n";
 
 unlink(qw(foo.x bar.y baz.z));
 
-# TEST #1 -- test the SysV emulation of $$@ etc.
+# TEST #1 -- simple test with grouped-targets
+# -------
+
+$makefile1 = &get_tmpfile;
+open(MAKEFILE, "> $makefile1");
+print MAKEFILE "dir = $dir\n";
+print MAKEFILE <<'EOF';
+.SUFFIXES:
+.SUFFIXES: .y .z .g1 .g2 .g3
+$(dir)/foo.g1 foo.g2 $(dir)/foo.g3 &: baz.z $(dir)/bar.y baz.z
+	@echo '$$@ = $@, $$(@D) = $(@D), $$(@F) = $(@F)'
+	@echo '$$(@<) = $(@<), $$(@<D) = $(@<D), $$(@<F) = $(@<F)'
+	@echo '$$(@^) = $(@^), $$(@^D) = $(@^D), $$(@^F) = $(@^F)'
+	@echo '$$< = $<, $$(<D) = $(<D), $$(<F) = $(<F)'
+	@echo '$$^ = $^, $$(^D) = $(^D), $$(^F) = $(^F)'
+	@echo '$$+ = $+, $$(+D) = $(+D), $$(+F) = $(+F)'
+	@echo '$$? = $?, $$(?D) = $(?D), $$(?F) = $(?F)'
+	touch $(@^)
+
+$(dir)/bar.y baz.z : ; touch $@
+EOF
+close(MAKEFILE);
+
+# Touch these into the past
+&utouch(-10, qw(foo.g2 baz.z));
+
+&run_make_with_options($makefile1, "foo.g2", &get_logfile);
+$answer = "touch $dir/bar.y
+\$\@ = foo.g2, \$(\@D) = ., \$(\@F) = foo.g2
+\$(\@<) = $dir/foo.g1, \$(\@<D) = $dir, \$(\@<F) = foo.g1
+\$(\@^) = $dir/foo.g1 foo.g2 $dir/foo.g3, \$(\@^D) = $dir . $dir, \$(\@^F) = foo.g1 foo.g2 foo.g3
+\$< = baz.z, \$(<D) = ., \$(<F) = baz.z
+\$^ = baz.z $dir/bar.y, \$(^D) = . $dir, \$(^F) = baz.z bar.y
+\$+ = baz.z $dir/bar.y baz.z, \$(+D) = . $dir ., \$(+F) = baz.z bar.y baz.z
+\$? = $dir/bar.y, \$(?D) = $dir, \$(?F) = bar.y
+touch $dir/foo.g1 foo.g2 $dir/foo.g3\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink(qw(foo.g1 foo.g2 foo.g3 bar.y baz.z));
+
+# TEST #2 -- test the SysV emulation of $$@ etc.
 # -------
 
 $makefile2 = &get_tmpfile;
@@ -79,7 +123,7 @@ $answer = ".x\n$dir/x.z.x\nx\n\$@.x\n$dir.x\nx.z.x\n.y\n$dir/y.z.y\ny\n\$@.y\n$d
 $answer = "$dir/biz.x\n$dir.x\nbiz.x\n";
 &compare_output($answer, &get_logfile(1));
 
-# TEST #2 -- test for Savannah bug #12320.
+# TEST #3 -- test for Savannah bug #12320.
 #
 run_make_test('
 .SUFFIXES: .b .src
@@ -91,7 +135,7 @@ mbr.src: ; @:',
               '',
               'mbr');
 
-# TEST #3 -- test for Savannah bug #8154
+# TEST #4 -- test for Savannah bug #8154
 # Make sure that nonexistent prerequisites are listed in $?, since they are
 # considered reasons for the target to be rebuilt.
 #
@@ -107,7 +151,7 @@ bar: ;',
 
 unlink('foo');
 
-# TEST #4: ensure prereq ordering is correct when the command target has none
+# TEST #5: ensure prereq ordering is correct when the command target has none
 # See Savannah bug #21198
 
 run_make_test('
-- 
2.26.2.Cisco

