From 31036e648f4a92ae0cce215eb3d60a1311a09c60 Mon Sep 17 00:00:00 2001
From: Paul Smith <psmith@gnu.org>
Date: Mon, 8 Jan 2024 23:14:57 -0500
Subject: [PATCH] [SV 64571] Add --print-targets option

Add an option to print a list of targets defined in the makefiles.
Don't print targets of implicit rules, or special targets.  To
support this remember which files are deemed suffix rule targets.

Add a missing warning for single-suffix targets with prerequisites.

Suggested by many.  Sample implementation by Tim <tdhutt@gmail.com>.

* NEWS: Announce the new option and single-suffix warning.
* doc/make.1: Add --print-targets to the man page.
* doc/make.texi: Add --print-targets to the documentation.  Clean up
the text around the definition of suffix rules.
* src/main.c (print_targets_flag): New variable for --print-targets.
(switches): Add a new long option --print-targets.
(main): If the option was provided call print_targets() and exit.
* src/filedef.h (struct file): Add a "suffix" boolean value.  Remove
print_prereqs() since it's static.  Add new print_targets().
* src/file.c (rehash_file): Merge the new suffix value.
(print_prereqs): Used only locally: change to static.
(print_target): Print targets which are not suffix rule targets and
are not special targets.
(print_targets): Call print_target() on each file.
* src/rule.c (convert_to_pattern): Make maxsuffix local; it doesn't
need to be static.  Emit ignoring prerequisites for single-suffix
rules as well as double-suffix rules.  Remember which files are
actually suffix rules.
* tests/scripts/features/suffixrules: Test single-suffix behavior.
* tests/scripts/options/print-targets: Add tests for --print-targets.
---
 NEWS                                |  9 ++++
 doc/make.1                          |  9 +++-
 doc/make.texi                       | 65 +++++++++++++++++------------
 src/file.c                          | 32 +++++++++++++-
 src/filedef.h                       |  3 +-
 src/main.c                          | 18 +++++++-
 src/rule.c                          | 33 ++++++++++-----
 tests/scripts/features/suffixrules  | 28 ++++++++++---
 tests/scripts/options/print-targets | 32 ++++++++++++++
 9 files changed, 181 insertions(+), 48 deletions(-)
 create mode 100644 tests/scripts/options/print-targets

diff --git a/NEWS b/NEWS
index 56358ec4..7d296ac7 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,10 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=111&se
   things like "ifeq ((foo,bar),)" are now syntax errors.  Use a variable to
   hide the comma if needed: "COMMA = ," / "ifeq ((foo$(COMMA)bar),)".
 
+* NOTE: Deprecated behavior.
+  The check in GNU Make 4.3 for suffix rules with prerequisites didn't check
+  single-suffix rules, only double-suffix rules.  Add the missing check.
+
 * New feature: Unload function for loaded objects
   When a loaded object needs to be unloaded by GNU Make, it will invoke an
   unload function (if one is defined) beforehand that allows the object to
@@ -56,6 +60,11 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=111&se
   invoked recursively, warnings can be controlled only for the current
   instance of make using the .WARNINGS variable.
 
+* New feature: Printing targets defined by the makefile
+  A new option "--print-targets" will print all explicit, non-special targets
+  defined in the makefiles, one per line, then exit with success.  No recipes
+  are invoked and no makefiles are re-built.
+
 * 'make --print-data-base' (or 'make -p') now outputs time of day
   using the same form as for file timestamps, e.g., "2023-05-10
   10:43:57.570558743".  Previously it used the form "Wed May 10
diff --git a/doc/make.1 b/doc/make.1
index 342807fe..bb394bde 100644
--- a/doc/make.1
+++ b/doc/make.1
@@ -270,10 +270,15 @@ reading the makefiles; then execute as usual or as otherwise
 specified.
 This also prints the version information given by the
 .B \-v
-switch (see below).
-To print the data base without trying to remake any files, use
+switch (see below).  To print the built-in data base only, use
 .IR "make \-p \-f/dev/null" .
 .TP 0.5i
+\fB\-\-print\-targets\fR
+Print each target defined as a result of reading the makefiles, one target per
+line, then exit with success.  Implicit rule targets are not printed, nor are
+special targets (target names that consist of "." followed by all upper-case
+letters).  No recipe commands are invoked and no makefiles are rebuilt.
+.TP 0.5i
 \fB\-q\fR, \fB\-\-question\fR
 ``Question mode''.
 Do not run any commands, or print anything; just return an exit status
diff --git a/doc/make.texi b/doc/make.texi
index 90ecb74e..21bee987 100644
--- a/doc/make.texi
+++ b/doc/make.texi
@@ -9677,15 +9677,28 @@ performed.  @xref{Parallel Output, ,Output During Parallel Execution}.
 @cindex @code{--print-data-base}
 @cindex data base of @code{make} rules
 @cindex predefined rules and variables, printing
-Print the data base (rules and variable values) that results from
-reading the makefiles; then execute as usual or as otherwise
-specified.  This also prints the version information given by the
-@samp{-v} switch (see below).  To print the data base without trying
-to remake any files, use @w{@samp{make -qp}}.  To print the data base
-of predefined rules and variables, use @w{@samp{make -p -f /dev/null}}.
-The data base output contains file name and line number information for
-recipe and variable definitions, so it can be a useful debugging tool
-in complex environments.
+Print the data base (rules and variable values) that results from reading the
+makefiles; then execute as usual or as otherwise specified.  This also prints
+the version information given by the @samp{-v} switch (see below).  To print
+the data base without trying to remake any files, use @w{@samp{make -qp}}.  To
+print the data base of predefined rules and variables, use @w{@samp{make -p -f
+/dev/null}}.  The data base output contains file name and line number
+information for recipe and variable definitions, so it can be a useful
+debugging tool in complex environments.
+
+@item --print-targets
+@cindex @code{--print-targets}
+@cindex print @file{makefile} targets
+@cindex targets, printing
+Print all the targets defined by reading the makefiles, one target per line,
+then exit immediately with success.  No implicit targets are printed.  No
+special targets (target names consisting of ``.'' followed by all upper-case
+letters) are printed.
+
+No commands are run, including commands that would rebuild makefiles
+(@pxref{Remaking Makefiles, ,How Makefiles Are Remade}); if makefiles need to
+be rebuilt then the targets listed might be outdated.  Also, @code{make} will
+not generate any errors or warnings for missing @code{include} files.
 
 @item -q
 @cindex @code{-q}
@@ -11237,26 +11250,26 @@ You can use a last-resort rule to override part of another makefile.
 @section Old-Fashioned Suffix Rules
 @cindex old-fashioned suffix rules
 @cindex suffix rule
+@cindex inference rule
 
-@dfn{Suffix rules} are the old-fashioned way of defining implicit rules for
-@code{make}.  Suffix rules are obsolete because pattern rules are more
-general and clearer.  They are supported in GNU @code{make} for
-compatibility with old makefiles.  They come in two kinds:
-@dfn{double-suffix} and @dfn{single-suffix}.
-
-A double-suffix rule is defined by a pair of suffixes: the target
-suffix and the source suffix.  It matches any file whose name ends
-with the target suffix.  The corresponding implicit prerequisite is
-made by replacing the target suffix with the source suffix in the file
-name.  A two-suffix rule @samp{.c.o} (whose target and source suffixes
-are @samp{.o} and @samp{.c}) is equivalent to the pattern rule
-@samp{%.o : %.c}.
+@dfn{Suffix rules} (called @dfn{inference rules} in POSIX) are an
+old-fashioned way of defining implicit rules for @code{make}.  Suffix rules
+are less powerful and harder to use than pattern rules (@pxref{Pattern Rules,
+,Defining and Redefining Pattern Rules}); they are supported by GNU Make
+primarily for POSIX compatibility.  They come in two flavors:
+@dfn{single-suffix} and @dfn{double-suffix}.
 
 A single-suffix rule is defined by a single suffix, which is the source
-suffix.  It matches any file name, and the corresponding implicit
-prerequisite name is made by appending the source suffix.  A single-suffix
-rule whose source suffix is @samp{.c} is equivalent to the pattern rule
-@samp{% : %.c}.
+suffix.  It matches any file name, and the corresponding implicit prerequisite
+name is made by appending the source suffix.  A single-suffix rule whose
+source suffix is @samp{.c} is equivalent to the pattern rule @samp{% : %.c}.
+
+A double-suffix rule is defined by a pair of suffixes: the source suffix and
+the target suffix.  It matches any file whose name ends with the target
+suffix.  The corresponding implicit prerequisite is made by replacing the
+target suffix with the source suffix in the file name.  A two-suffix rule
+@samp{.c.o} has a source suffix @samp{.c} and a target suffix @samp{.o}, and
+is equivalent to the pattern rule @samp{%.o : %.c}.
 
 Suffix rule definitions are recognized by comparing each rule's target
 against a defined list of known suffixes.  When @code{make} sees a rule
diff --git a/src/file.c b/src/file.c
index 6b20cb57..77c328ca 100644
--- a/src/file.c
+++ b/src/file.c
@@ -26,6 +26,7 @@ this program.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "debug.h"
 #include "hash.h"
 #include "shuffle.h"
+#include "rule.h"
 
 
 /* Remember whether snap_deps has been invoked: we need this to be sure we
@@ -334,6 +335,7 @@ rehash_file (struct file *from_file, const char *to_hname)
   MERGE (notintermediate);
   MERGE (ignore_vpath);
   MERGE (snapped);
+  MERGE (suffix);
 #undef MERGE
 
   to_file->builtin = 0;
@@ -1050,7 +1052,7 @@ file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts)
 
 /* Print the data base of files.  */
 
-void
+static void
 print_prereqs (const struct dep *deps)
 {
   const struct dep *ood = 0;
@@ -1201,6 +1203,34 @@ print_file_data_base (void)
   fputs (_("\n# files hash-table stats:\n# "), stdout);
   hash_print_stats (&files, stdout);
 }
+
+static void
+print_target (const void *item)
+{
+  const struct file *f = item;
+
+  if (!f->is_target || f->suffix)
+    return;
+
+  /* Ignore any special targets, as defined by POSIX. */
+  if (f->name[0] == '.' && isupper ((unsigned char)f->name[1]))
+    {
+      const char *cp = f->name + 1;
+      while (*(++cp) != '\0')
+        if (!isupper ((unsigned char)*cp))
+          break;
+      if (*cp == '\0')
+        return;
+    }
+
+  puts (f->name);
+}
+
+void
+print_targets (void)
+{
+  hash_map (&files, print_target);
+}
 
 /* Verify the integrity of the data base of files.  */
 
diff --git a/src/filedef.h b/src/filedef.h
index 019d420d..b2ef1a16 100644
--- a/src/filedef.h
+++ b/src/filedef.h
@@ -113,6 +113,7 @@ struct file
                                     --shuffle passes through the graph.  */
     unsigned int snapped:1;     /* True if the deps of this file have been
                                    secondary expanded.  */
+    unsigned int suffix:1;      /* True if this is a suffix rule. */
   };
 
 
@@ -134,8 +135,8 @@ void notice_finished_file (struct file *file);
 void init_hash_files (void);
 void verify_file_data_base (void);
 char *build_target_list (char *old_list);
-void print_prereqs (const struct dep *deps);
 void print_file_data_base (void);
+void print_targets (void);
 int try_implicit_rule (struct file *file, unsigned int depth);
 int stemlen_compare (const void *v1, const void *v2);
 
diff --git a/src/main.c b/src/main.c
index c9c64b54..d07f561a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -167,11 +167,16 @@ int env_overrides = 0;
 
 int ignore_errors_flag = 0;
 
-/* Nonzero means don't remake anything, just print the data base
-   that results from reading the makefile (-p).  */
+/* Nonzero means print the data base that results from reading the makefile.
+   (-p or --print-data-base).  */
 
 int print_data_base_flag = 0;
 
+/* Nonzero means don't remake anything, just print a list of targets defined
+   by reading the makefile (--print-targets).  */
+
+int print_targets_flag = 0;
+
 /* Nonzero means don't remake anything; just return a nonzero status
    if the specified targets are not up to date (-q).  */
 
@@ -509,6 +514,7 @@ static struct command_switch switches[] =
     { CHAR_MAX+11, string, &shuffle_mode, 1, 1, 0, 0, "random", 0, "shuffle", 0 },
     { CHAR_MAX+12, string, &jobserver_style, 1, 0, 0, 0, 0, 0, "jobserver-style", 0 },
     { WARN_OPT, strlist, &warn_flags, 1, 1, 0, 0, "warn", NULL, "warn", NULL },
+    { CHAR_MAX+14, flag, &print_targets_flag, 1, 1, 0, 0, 0, 0, "print-targets", 0 },
     { 0, 0, NULL, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
   };
 
@@ -2289,6 +2295,14 @@ main (int argc, char **argv, char **envp)
         }
     }
 
+  /* If the user wants to see a list of targets show it now then quit.
+     We do this before rebuilding makefiles to avoid extraneous output.  */
+  if (print_targets_flag)
+    {
+      print_targets ();
+      die (EXIT_SUCCESS);
+    }
+
   if (!restarts && new_files != 0)
     {
       const char **p;
diff --git a/src/rule.c b/src/rule.c
index f04dd9bf..8da27fcc 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -56,10 +56,6 @@ size_t max_pattern_dep_length;
 
 struct file *suffix_file;
 
-/* Maximum length of a suffix.  */
-
-static size_t maxsuffix;
-
 /* Return the rule definition: space separated rule targets, followed by
    either a colon or two colons in the case of a terminal rule, followed by
    space separated rule prerequisites, followed by a pipe, followed by
@@ -304,7 +300,7 @@ convert_to_pattern (void)
      suffixes in the .SUFFIXES target's dependencies and see if it exists.
      First find the longest of the suffixes.  */
 
-  maxsuffix = 0;
+  size_t maxsuffix = 0;
   for (d = suffix_file->deps; d != 0; d = d->next)
     {
       size_t l = strlen (dep_name (d));
@@ -317,6 +313,7 @@ convert_to_pattern (void)
 
   for (d = suffix_file->deps; d != 0; d = d->next)
     {
+      struct file *f;
       size_t slen;
 
       /* Make a rule that is just the suffix, with no deps or commands.
@@ -327,14 +324,26 @@ convert_to_pattern (void)
         /* Record a pattern for this suffix's null-suffix rule.  */
         convert_suffix_rule ("", dep_name (d), d->file->cmds);
 
+      slen = strlen (dep_name (d));
+      memcpy (rulename, dep_name (d), slen + 1);
+
+      f = lookup_file (rulename);
+      if (f && f->cmds)
+        {
+          if (!f->deps)
+            f->suffix = 1;
+          else if (!posix_pedantic)
+            {
+              O (error, &f->cmds->fileinfo,
+                 _("warning: ignoring prerequisites on suffix rule definition"));
+              f->suffix = 1;
+            }
+        }
+
       /* Add every other suffix to this one and see if it exists as a
          two-suffix rule.  */
-      slen = strlen (dep_name (d));
-      memcpy (rulename, dep_name (d), slen);
-
       for (d2 = suffix_file->deps; d2 != 0; d2 = d2->next)
         {
-          struct file *f;
           size_t s2len;
 
           s2len = strlen (dep_name (d2));
@@ -359,10 +368,12 @@ convert_to_pattern (void)
             {
               if (posix_pedantic)
                 continue;
-              error (&f->cmds->fileinfo, 0,
-                     _("warning: ignoring prerequisites on suffix rule definition"));
+              O (error, &f->cmds->fileinfo,
+                 _("warning: ignoring prerequisites on suffix rule definition"));
             }
 
+          f->suffix = 1;
+
           if (s2len == 2 && rulename[slen] == '.' && rulename[slen + 1] == 'a')
             /* A suffix rule '.X.a:' generates the pattern rule '(%.o): %.X'.
                It also generates a normal '%.a: %.X' rule below.  */
diff --git a/tests/scripts/features/suffixrules b/tests/scripts/features/suffixrules
index 5e969b2a..6c562efc 100644
--- a/tests/scripts/features/suffixrules
+++ b/tests/scripts/features/suffixrules
@@ -51,12 +51,30 @@ run_make_test(q!
 
 unlink('foo.baz');
 
-# SV 40657: Test #4: "Suffix rules" with deps are normal rules
+# SV 40657: "Suffix rules" with deps are normal rules
 
 my $prewarn = 'warning: ignoring prerequisites on suffix rule definition';
 
 touch('foo.bar');
 
+# Verify warnings for single-suffix rules
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .baz
+
+.baz: foo.bar ; @echo make $@ from $<
+
+$X.POSIX:
+!,
+              'X=1 .baz', "#MAKEFILE#:5: $prewarn\nmake .baz from foo.bar\n");
+
+# In POSIX mode we don't get a warning
+
+run_make_test(undef, 'X= .baz', "make .baz from foo.bar\n");
+
+# Test double-suffix rules
+
 run_make_test(q!
 .SUFFIXES:
 .SUFFIXES: .biz .baz
@@ -67,25 +85,25 @@ $X.POSIX:
 !,
               'X=1 .baz.biz', "#MAKEFILE#:7: $prewarn\nmake .baz.biz from foo.bar\n");
 
-# SV 40657: Test #5: In POSIX mode we don't get a warning
+# SV 40657: In POSIX mode we don't get a warning
 
 run_make_test(undef, 'X= .baz.biz', "make .baz.biz from foo.bar\n");
 
 unlink('foo.bar');
 
-# SV 40657: Test #6: In POSIX mode, no pattern rules should be created
+# SV 40657: In POSIX mode, no pattern rules should be created
 
 utouch(-20, 'foo.baz');
 
 run_make_test(undef,
               'X= foo.biz', "#MAKE#: *** No rule to make target 'foo.biz'.  Stop.\n", 512);
 
-# SV 40657: Test #7: In Non-POSIX mode, a pattern rule is created
+# SV 40657: In Non-POSIX mode, a pattern rule is created
 
 run_make_test(undef,
               'X=1 foo.biz', "#MAKEFILE#:7: $prewarn\nmake foo.biz from foo.baz\n");
 
-# SV 40657: Test #8: ... but any prerequisites are ignored
+# SV 40657: ... but any prerequisites are ignored
 
 utouch(-10, 'foo.biz');
 touch('foo.bar');
diff --git a/tests/scripts/options/print-targets b/tests/scripts/options/print-targets
new file mode 100644
index 00000000..d3f9e65e
--- /dev/null
+++ b/tests/scripts/options/print-targets
@@ -0,0 +1,32 @@
+#                                                                    -*-perl-*-
+
+$description = "Test the --print-targets option to GNU Make.";
+
+# Define various things and verify the output
+run_make_test(q!
+.PHONY: all
+all: ;@:
+
+# "special" target
+.BOGUS: ;@:
+
+# Check various forms of suffix rule
+.SUFFIXES: .q
+.q: ;@:
+.c.o: ;@:
+
+# Not a suffix rule
+.x.z: ;@:
+
+# Verify included files aren't built / don't fail
+
+include badfile
+include goodfile
+
+submake: ; $(MAKE) all
+always: ; +echo always
+goodfile: ; touch goodfile
+!,
+        "--print-targets", "submake\n.x.z\nalways\nall\ngoodfile\n");
+
+1;