From 92a127e9f96598b86a4327b70783b9769eaa94fe Mon Sep 17 00:00:00 2001
From: Ron Yorston <rmy@pobox.com>
Date: Tue, 18 Oct 2022 15:00:36 +0100
Subject: make: support $+ and $^ as POSIX 202X features

Austin Group defect reports 514 and 1520 have both been accepted.
Together these introduce the internal macros $+ and $^:

- $+ lists all prerequisites, with duplicates retained;

- $^ lists all prerequisites, with duplicates removed.

$^ had already been implemented as a non-POSIX extension, it now
becomes a POSIX 202X extension.  $+ has been added as a POSIX
202X extension.

Neither of the above defect reports mentions how $? should handle
duplicate prerequisites.  In POSIX mode duplicates are retained.
Removal of duplicates is implemented as a non-POSIX extension to
match existing practice in other versions of make.
---
 miscutils/make.c     | 33 +++++++++++++++++++--------------
 testsuite/make.tests | 12 ++++++++++++
 2 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/miscutils/make.c b/miscutils/make.c
index 21d0ede51..8e3778461 100644
--- a/miscutils/make.c
+++ b/miscutils/make.c
@@ -1927,15 +1927,17 @@ touch(struct name *np)
 
 static int
 make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc,
-		struct name *implicit)
+		char *dedup, struct name *implicit)
 {
 	int estat = 0;	// 0 exit status is success
 	char *name, *member = NULL, *base;
 
 	name = splitlib(np->n_name, &member);
 	setmacro("?", oodate, 0 | M_VALID);
-	if (!posix)
-		setmacro("^", allsrc, 0 | M_VALID);
+	if (!posix) {
+		setmacro("+", allsrc, 0 | M_VALID);
+		setmacro("^", dedup, 0 | M_VALID);
+	}
 	setmacro("%", member, 0 | M_VALID);
 	setmacro("@", name, 0 | M_VALID);
 	if (implicit) {
@@ -1993,6 +1995,7 @@ make(struct name *np, int level)
 	struct cmd *sc_cmd = NULL;	// commands for single-colon rule
 	char *oodate = NULL;
 	char *allsrc = NULL;
+	char *dedup = NULL;
 	struct timespec dtim = {1, 0};
 	bool didsomething = 0;
 	bool estat = 0;	// 0 exit status is success
@@ -2080,17 +2083,17 @@ make(struct name *np, int level)
 			// Make prerequisite
 			estat |= make(dp->d_name, level + 1);
 
-			// Make strings of out-of-date prerequisites (for $?)
-			// and all prerequisites (for $^).  But not if we were
-			// invoked with -q.
-			if (!quest
-					// Skip duplicate entries.
-					&& (posix || !(dp->d_name->n_flag & N_MARK))
-			) {
+			// Make strings of out-of-date prerequisites (for $?),
+			// all prerequisites (for $+) and deduplicated prerequisites
+			// (for $^).  But not if we were invoked with -q.
+			if (!quest) {
 				if (timespec_le(&np->n_tim, &dp->d_name->n_tim)) {
-					oodate = xappendword(oodate, dp->d_name->n_name);
+					if (posix || !(dp->d_name->n_flag & N_MARK))
+						oodate = xappendword(oodate, dp->d_name->n_name);
 				}
 				allsrc = xappendword(allsrc, dp->d_name->n_name);
+				if (!(dp->d_name->n_flag & N_MARK))
+					dedup = xappendword(dedup, dp->d_name->n_name);
 				dp->d_name->n_flag |= N_MARK;
 			}
 			dtim = *timespec_max(&dtim, &dp->d_name->n_tim);
@@ -2099,7 +2102,7 @@ make(struct name *np, int level)
 			if (!quest && ((np->n_flag & N_PHONY) ||
 							timespec_le(&np->n_tim, &dtim))) {
 				if (estat == 0) {
-					estat = make1(np, rp->r_cmd, oodate, allsrc, locdep);
+					estat = make1(np, rp->r_cmd, oodate, allsrc, dedup, locdep);
 					dtim = (struct timespec){1, 0};
 					didsomething = 1;
 				}
@@ -2107,7 +2110,8 @@ make(struct name *np, int level)
 				oodate = NULL;
 			}
 			free(allsrc);
-			allsrc = NULL;
+			free(dedup);
+			allsrc = dedup = NULL;
 			if (locdep) {
 				rp->r_dep = rp->r_dep->d_next;
 				rp->r_cmd = NULL;
@@ -2132,7 +2136,7 @@ make(struct name *np, int level)
 				if (!doinclude)
 					bb_error_msg("nothing to be done for %s", np->n_name);
 			} else {
-				estat = make1(np, sc_cmd, oodate, allsrc, impdep);
+				estat = make1(np, sc_cmd, oodate, allsrc, dedup, impdep);
 				clock_gettime(CLOCK_REALTIME, &np->n_tim);
 			}
 		} else if (!doinclude) {
@@ -2145,6 +2149,7 @@ make(struct name *np, int level)
 		printf("%s: '%s' is up to date\n", applet_name, np->n_name);
 	}
 	free(allsrc);
+	free(dedup);
 	return estat;
 }
 
diff --git a/testsuite/make.tests b/testsuite/make.tests
index 3d0d14dd0..67617f8a2 100755
--- a/testsuite/make.tests
+++ b/testsuite/make.tests
@@ -255,6 +255,18 @@ mk:
 -include mk
 '
 
+# $^ skips duplicate prerequisites, $+ doesn't
+mkdir make.tempdir && cd make.tempdir || exit 1
+touch file1 file2 file3
+testing "make skip duplicate entries in \$^ but not \$+" \
+	"make -f -" \
+	"file1 file2 file3\nfile1 file2 file2 file3 file3\n" "" '
+target: file1 file2 file2 file3 file3
+	@echo $^
+	@echo $+
+'
+cd .. || exit 1; rm -rf make.tempdir 2>/dev/null
+
 # Nested macro expansion is allowed.  This should be compatible
 # with other implementations.
 testing "make nested macro expansion" \
-- 
cgit v1.2.3-55-g6feb