]> git.ipfire.org Git - thirdparty/make.git/commitdiff
Add support for .WARNINGS special variable
authorPaul Smith <psmith@gnu.org>
Sat, 18 Mar 2023 21:24:45 +0000 (17:24 -0400)
committerPaul Smith <psmith@gnu.org>
Sun, 2 Apr 2023 14:02:18 +0000 (10:02 -0400)
Create a new special variable, .WARNINGS, to allow per-makefile
control over warnings.  The command line settings will override
this.

Move the handling of warning flags to a new file: src/warning.c.
Allow the decode to work with generic strings, and call it from
decode_switches().

* Makefile.am: Add new file src/warning.c.
* build_w32.bat: Ditto.
* builddos.bat: Ditto.
* po/POTFILES.in: Ditto.
* src/makeint.h: #define for the .WARNINGS variable name.
* src/warning.h: Add declarations for methods moved from main.c.
Rename the enum warning_state to warning_action.
* src/warning.c: New file.  Move all warning encode/decode here
from main.c.
* src/main.c: Move methods into warning.c and call those methods
instead.
(main): Set .WARNINGS as a special variable.
* src/job.c (construct_command_argv): Rename to warning_action.
* src/read.c (tilde_expand): Ditto.
* src/variable.c (set_special_var): Update warnings when the
.WARNINGS special variable is set.
* tests/scripts/options/warn: Check invalid warning options.
* tests/scripts/variables/WARNINGS: Add tests for the .WARNINGS
special variable.

17 files changed:
Makefile.am
build_w32.bat
builddos.bat
doc/make.texi
po/POTFILES.in
src/expand.c
src/job.c
src/main.c
src/makeint.h
src/misc.c
src/output.c
src/read.c
src/variable.c
src/warning.c [new file with mode: 0644]
src/warning.h
tests/scripts/options/warn
tests/scripts/variables/WARNINGS [new file with mode: 0644]

index 8741b5627080289da555e30999fb44b2ea2834b4..05be34539759cf003b50d51bf5ee5b70388b8e54 100644 (file)
@@ -37,7 +37,7 @@ make_SRCS =   src/ar.c src/arscan.c src/commands.c src/commands.h \
                src/mkcustom.h src/os.h src/output.c src/output.h src/read.c \
                src/remake.c src/rule.c src/rule.h src/shuffle.h src/shuffle.c \
                src/signame.c src/strcache.c src/variable.c src/variable.h \
-               src/version.c src/vpath.c src/warning.h
+               src/version.c src/vpath.c src/warning.c src/warning.h
 
 w32_SRCS =     src/w32/pathstuff.c src/w32/w32os.c src/w32/compat/dirent.c \
                src/w32/compat/posixfcn.c src/w32/include/dirent.h \
index 3e0230022d2a3f91ad0b533612ac9ab4b5dcb03f..3abc43ed21f813bb611e290f344f56ec9d2bd9c0 100644 (file)
@@ -271,6 +271,7 @@ call :Compile src/strcache
 call :Compile src/variable\r
 call :Compile src/version\r
 call :Compile src/vpath\r
+call :Compile src/warning\r
 call :Compile src/w32/pathstuff\r
 call :Compile src/w32/w32os\r
 call :Compile src/w32/compat/posixfcn\r
index 3e540a98b2dcdf3c662a98ef0a90cfdc59c1c4be..c933c1f01d22c5a3e7bb3981afdc4eadbe6afd21 100644 (file)
@@ -56,6 +56,7 @@ gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/s
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/implicit.c -o implicit.o\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/default.c -o default.o\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/variable.c -o variable.o\r
+gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/warning.c -o warning.o\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/expand.c -o expand.o\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/function.c -o function.o\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/vpath.c -o vpath.o\r
@@ -74,7 +75,7 @@ gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/l
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/lib/fnmatch.c -o lib/fnmatch.o\r
 @echo off\r
 echo commands.o > respf.$$$\r
-for %%f in (job output dir file misc main read remake rule implicit default variable load) do echo %%f.o >> respf.$$$\r
+for %%f in (job output dir file misc main read remake rule implicit default variable warning load) do echo %%f.o >> respf.$$$\r
 for %%f in (expand function vpath hash strcache version ar arscan signame remote-stub getopt getopt1 shuffle) do echo %%f.o >> respf.$$$\r
 for %%f in (lib\glob lib\fnmatch) do echo %%f.o >> respf.$$$\r
 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/guile.c -o guile.o\r
index b3cc57a07c076fed43e993a1115fcc21f99be337..2dd6f1c971f85a2034518e59bee4a5e0067d8c63 100644 (file)
@@ -7043,6 +7043,10 @@ a target-specific value).  Note @code{make} is smart enough not to add
 a prerequisite listed in @code{.EXTRA_PREREQS} as a prerequisite to
 itself.
 
+@item .WARNINGS
+Changes the actions taken when @code{make} detects warning conditions in the
+makefile.  @xref{Warnings, ,Makefile Warnings}.
+
 @end table
 
 @node Conditionals, Functions, Using Variables, Top
@@ -9309,69 +9313,108 @@ correct them all before the next attempt to compile.  This is why Emacs'
 @section Makefile Warnings
 @cindex warnings
 
-GNU Make can detect some types of incorrect usage in makefiles and show
-warnings about them.  Currently these issues can be detected:
+GNU Make can detect some types of incorrect usage in makefiles.  When one of
+these incorrect usages is detected, GNU Make can perform one of these actions:
+
+@table @samp
+@item ignore
+@cindex warning action ignore
+@cindex ignore, warning action
+Ignore the usage.
+
+@item warn
+@cindex warning action warn
+@cindex warn, warning action
+Show a warning about the usage and continue processing the makefile.
+
+@item error
+@cindex warning action error
+@cindex error, warning action
+Show an error for the usage and immediately stop processing the makefile.
+@end table
+
+@noindent
+The types of warnings GNU Make can detect are:
 
 @table @samp
 @item invalid-var
+@findex invalid-var
+@cindex warning invalid variable
 Assigning to an invalid variable name (e.g., a name containing whitespace).
+The default action is @samp{warn}.
 
 @item invalid-ref
-Using an invalid variable name in a variable reference.
+@findex invalid-ref
+@cindex warning invalid reference
+Using an invalid variable name in a variable reference.  The default action is
+@samp{warn}.
 
 @item undefined-var
-Referencing a variable that has not been defined.
+@findex undefined-var
+@cindex warning undefined variable
+Referencing a variable that has not been defined.  The default action is
+@samp{ignore}.  Note the deprecated @code{--warn-undefined-variables} option
+sets the action for this warning to @samp{warn}.
 @end table
 
-When one of these incorrect usages is detected, GNU Make can perform one of
-these actions:
+The actions for these warnings can be changed by specifying warning control
+options.  Each warning control option consists of either a warning type, or a
+warning action, or a warning type and warning action separated by a colon
+(@code{:}).  Multiple control options are separated by either whitespace or
+commas.
 
-@table @samp
-@item ignore
-Ignore the usage.
+If the control option is just a warning type, then the action associated with
+that type is set to @code{warn}.  If the option is just an action, then that
+action is applied to all warning types (a ``global action'').
 
-@item warn
-Show a warning about the usage and continue processing the makefile.
+``Global actions'' take precedence over default actions.  Actions associated
+with a specific warning type take precedence over ``global actions'' and
+default actions.
 
-@item error
-Show an error for the usage and immediately stop processing the makefile.
-@end table
+If multiple control options provide actions for the same warning type, the
+last action specified will be used.
 
-The default action of GNU Make when no warning control options are provided
-is @samp{ignore} for @samp{undefined-var}, and @samp{warn} for
-@samp{invalid-var} and @samp{invalid-ref}.
+There are two ways to specify control options: using the @code{--warn} command
+line option, or using the @code{.WARNINGS} variable.
 
-To modify this default behavior, you can use the @code{--warn} option.  This
-option can be specified on the command line, or by adding it to the
-@code{MAKEFLAGS} variable (@pxref{Recursion, ,Recursive Use of @code{make}}).
-Settings added to @code{MAKEFLAGS} are only affect after the assignment
-statement.
+@subsubheading The @code{.WARNINGS} variable
+@findex .WARNINGS
+Warning control options provided in the @code{.WARNINGS} variable take effect
+as soon as the variable assignment is parsed and will last until this instance
+of @code{make} finishes parsing all makefiles.  These settings will not be
+passed to recursive invocations of @code{make}.
+
+Note that the value of this variable is expanded immediately, even if the
+recursive expansion assignment operator (@code{=}) is used.
+
+Each assignment of @code{.WARNINGS} completely replaces any previous settings.
+If you want to preserve the previous settings, use the @code{+=} assignment
+operator.
+
+Currently, assigning @code{.WARNINGS} as a target-specific or pattern-specific
+variable has no effect.  This may change in the future.
+
+@subsubheading The @code{--warn} option
+@cindex @code{--warn}
+The @code{--warn} option can be specified on the command line, or by adding it
+to the @code{MAKEFLAGS} variable (@pxref{Recursion, ,Recursive Use of
+@code{make}}).  Settings added to @code{MAKEFLAGS} take affect after the
+assignment is parsed.  This option is passed to sub-makes through the
+@code{MAKEFLAGS} variable.
 
 The @code{--warn} option can be provided multiple times: the effects are
 cumulative with later options overriding over earlier options.  When GNU Make
 provides warning settings to sub-makes, they are all combined into a single
-@code{--warn} option in @code{MAKEFLAGS}.
-
-If @code{--warn} is provided with no arguments then all issues are detected
-and reported at the @samp{warn} level unless otherwise specified.
-
-If one of the actions (@samp{ignore}, @samp{warn}, @samp{error}) is provided
-as an argument to @code{--warn}, then this action becomes the default for all
-warning types.  For example all warnings can be disabled by using
-@code{--warn=ignore}, or all warnings can be considered fatal errors by using
-@code{--warn=error}.
-
-Additionally, warning types can be specified.  If the warning is listed alone,
-then that warning is enabled with the @code{warn} action: e.g.,
-@code{--warn=undefined-var} will enable @samp{undefined-var} warnings with the
-@samp{warn} action.  Alternatively an action can be provided after the warning
-type, separated by a @code{:}.  So @code{--warn=undefined-var:error} enables
-@samp{undefined-var} warnings with an action of @samp{error}.
-
-More specific settings take precedence over the global setting.  For example,
-an option @code{--warn=undefined-var:error,ignore} will set the action for
-@samp{undefined-var} warnings to @samp{error}, and the action for all other
-warnings to @samp{ignore}.
+@code{--warn} option in @code{MAKEFLAGS} with a standard order.
+
+Specifying @code{--warn} with no arguments is equivalent to using
+@code{--warn=warn}, which sets the action for all warning types to
+@samp{warn}.
+
+Any action specified with an @code{--warn} option will take precedence over
+actions provided in the makefile with @code{.WARNINGS}.  This means if you use
+@code{--warn=error}, for example, all warnings will be treated as errors
+regardless of any @code{.WARNINGS} assignments.
 
 @node Temporary Files, Options Summary, Warnings, Running
 @section Temporary Files
index 39c3d80412554d574138cc9587a77d50482899e2..6f5421300ae535a1855c36b1655614ff72e00d76 100644 (file)
@@ -45,4 +45,5 @@ src/variable.h
 src/vmsfunctions.c
 src/vmsjobs.c
 src/vpath.c
+src/warning.c
 src/w32/w32os.c
index 533e7dfa719eebad9eb7ed2bd3b1d8ca84309a82..a1efa8314db440da11c9a56002fa3be239f5637c 100644 (file)
@@ -24,6 +24,7 @@ this program.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "job.h"
 #include "variable.h"
 #include "rule.h"
+#include "warning.h"
 
 /* Initially, any errors reported when expanding strings will be reported
    against the file where the error appears.  */
index b26cb2ca8d34b9f716bf2eb0049dea965cba926d..bca45529fcf82968cf950dca33d35bc566c8b24b 100644 (file)
--- a/src/job.c
+++ b/src/job.c
@@ -3635,7 +3635,7 @@ construct_command_argv (char *line, char **restp, struct file *file,
   {
     struct variable *var;
     /* Turn off undefined variables warning while we expand HOME.  */
-    enum warning_state save = warn_get (wt_undefined_var);
+    enum warning_action save = warn_get (wt_undefined_var);
     warn_set (wt_undefined_var, w_ignore);
 
     shell = allocated_expand_variable_for_file (STRING_SIZE_TUPLE ("SHELL"), file);
index 843cab094ba30a6dba17380925fea3a1919fc6b4..f4250726042a3d0702bf1b146f2ebcc970a89158 100644 (file)
@@ -273,18 +273,6 @@ static struct stringlist *eval_strings = 0;
 
 static int print_usage_flag = 0;
 
-/* The default state of warnings.  */
-
-enum warning_state default_warnings[wt_max];
-
-/* Current state of warnings.  */
-
-enum warning_state warnings[wt_max];
-
-/* Global warning settings.  */
-
-enum warning_state warn_global;
-
 /* Command line warning flags.  */
 
 static struct stringlist *warn_flags = 0;
@@ -663,15 +651,6 @@ initialize_global_hash_tables (void)
   hash_init_function_table ();
 }
 
-static void
-initialize_warnings ()
-{
-  /* All warnings must have a default.  */
-  default_warnings[wt_invalid_var] = w_warn;
-  default_warnings[wt_invalid_ref] = w_warn;
-  default_warnings[wt_undefined_var] = w_ignore;
-}
-
 /* This character map locate stop chars when parsing GNU makefiles.
    Each element is true if we should stop parsing on that character.  */
 
@@ -886,101 +865,6 @@ decode_debug_flags (void)
     debug_flag = 0;
 }
 
-static const char *w_state_map[w_error+1] = {NULL, "ignore", "warn", "error"};
-static const char *w_name_map[wt_max] = {"invalid-var",
-                                         "invalid-ref",
-                                         "undefined-var"};
-
-#define encode_warn_state(_b,_s) variable_buffer_output (_b, w_state_map[_s], strlen (w_state_map[_s]))
-#define encode_warn_name(_b,_t)  variable_buffer_output (_b, w_name_map[_t], strlen (w_name_map[_t]))
-
-static enum warning_state
-decode_warn_state (const char *state, size_t length)
-{
-  for (enum warning_state st = w_ignore; st <= w_error; ++st)
-    {
-      size_t len = strlen (w_state_map[st]);
-      if (length == len && strncasecmp (state, w_state_map[st], length) == 0)
-        return st;
-    }
-
-  return w_unset;
-}
-
-static enum warning_type
-decode_warn_name (const char *name, size_t length)
-{
-  for (enum warning_type wt = wt_invalid_var; wt < wt_max; ++wt)
-    {
-      size_t len = strlen (w_name_map[wt]);
-      if (length == len && strncasecmp (name, w_name_map[wt], length) == 0)
-        return wt;
-    }
-
-  return wt_max;
-}
-
-static void
-decode_warn_flags ()
-{
-  const char **pp;
-
-  /* */
-  if (warn_undefined_variables_flag)
-    {
-      warn_set (wt_undefined_var, w_warn);
-      warn_undefined_variables_flag = 0;
-    }
-
-  if (warn_flags)
-    for (pp=warn_flags->list; *pp; ++pp)
-      {
-        const char *p = *pp;
-
-        while (*p != '\0')
-          {
-            enum warning_state state;
-            /* See if the value is comma-separated.  */
-            const char *ep = strchr (p, ',');
-            if (!ep)
-              ep = p + strlen (p);
-
-            /* If the value is just a state set it globally.  */
-            state = decode_warn_state (p, ep - p);
-            if (state != w_unset)
-              warn_global = state;
-            else
-              {
-                enum warning_type type;
-                const char *cp = memchr (p, ':', ep - p);
-                if (!cp)
-                  cp = ep;
-                type = decode_warn_name (p, cp - p);
-                if (type == wt_max)
-                  ONS (fatal, NILF,
-                       _("unknown warning '%.*s'"), (int)(cp - p), p);
-
-                /* If there's a warning state, decode it.  */
-                if (cp == ep)
-                  state = w_warn;
-                else
-                  {
-                    ++cp;
-                    state = decode_warn_state (cp, ep - cp);
-                    if (state == w_unset)
-                      ONS (fatal, NILF,
-                           _("unknown warning state '%.*s'"), (int)(ep - cp), cp);
-                  }
-                warn_set (type, state);
-              }
-
-            p = ep;
-            while (*p == ',')
-              ++p;
-          }
-      }
-}
-
 static void
 decode_output_sync_flags (void)
 {
@@ -1323,7 +1207,7 @@ main (int argc, char **argv, char **envp)
 
   initialize_stopchar_map ();
 
-  initialize_warnings ();
+  warn_init ();
 
 #ifdef SET_STACK_SIZE
  /* Get rid of any avoidable limit on stack size.  */
@@ -1554,6 +1438,7 @@ main (int argc, char **argv, char **envp)
   define_variable_cname (".VARIABLES", "", o_default, 0)->special = 1;
   /* define_variable_cname (".TARGETS", "", o_default, 0)->special = 1; */
   define_variable_cname (".RECIPEPREFIX", "", o_default, 0)->special = 1;
+  define_variable_cname (WARNINGS_NAME, "", o_default, 0)->special = 1;
   define_variable_cname (".SHELLFLAGS", "-c", o_default, 0);
   define_variable_cname (".LOADED", "", o_default, 0);
 
@@ -3013,7 +2898,7 @@ main (int argc, char **argv, char **envp)
     /* If we detected some clock skew, generate one last warning */
     if (clock_skew_detected)
       O (error, NILF,
-         _("warning:  Clock skew detected.  Your build may be incomplete."));
+         _("warning: Clock skew detected.  Your build may be incomplete."));
 
     /* Exit.  */
     die (makefile_status);
@@ -3443,9 +3328,19 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
 
   /* If there are any options that need to be decoded do it now.  */
   decode_debug_flags ();
-  decode_warn_flags ();
   decode_output_sync_flags ();
 
+  /* Support old-style option.  */
+  if (warn_undefined_variables_flag)
+    {
+      decode_warn_actions ("undefined-var", NULL);
+      warn_undefined_variables_flag = 0;
+    }
+
+  if (warn_flags)
+    for (const char **pp = warn_flags->list; *pp; ++pp)
+      decode_warn_actions (*pp, NULL);
+
   /* Perform any special switch handling.  */
   run_silent = silent_flag;
 }
@@ -3655,38 +3550,7 @@ define_makeflags (int makefile)
         case filename:
         case strlist:
           if (cs->c == WARN_OPT)
-            {
-              enum warning_type wt;
-              char sp = '=';
-
-              /* See if any warning options are set.  */
-              for (wt = 0; wt < wt_max; ++wt)
-                if (warnings[wt] != w_unset)
-                  break;
-              if (wt == wt_max && warn_global == w_unset)
-                break;
-
-              /* Something is set so construct a --warn option.  */
-              fp = variable_buffer_output(fp, STRING_SIZE_TUPLE (" --warn"));
-              if (wt == wt_max && warn_global == w_warn)
-                break;
-
-              if (warn_global > w_unset)
-                {
-                  fp = variable_buffer_output (fp, &sp, 1);
-                  sp = ',';
-                  fp = encode_warn_state (fp, warn_global);
-                }
-              for (wt = 0; wt < wt_max; ++wt)
-                if (warnings[wt] > w_unset)
-                  {
-                    fp = variable_buffer_output (fp, &sp, 1);
-                    sp = ',';
-                    fp = encode_warn_name (fp, wt);
-                    if (warnings[wt] != w_warn)
-                      fp = encode_warn_state (variable_buffer_output(fp, ":", 1), warnings[wt]);
-                  }
-            }
+            fp = encode_warn_flag (fp);
           else
             {
               struct stringlist *sl = *(struct stringlist **) cs->value_ptr;
index e130e7612de9b220b183e18290d6d9c2b370d92c..f34ec361e4e8b0550e8184ba1ca78a82034fa402 100644 (file)
@@ -553,6 +553,8 @@ void error (const floc *flocp, size_t length, const char *fmt, ...)
             ATTRIBUTE ((__format__ (__printf__, 3, 4)));
 void fatal (const floc *flocp, size_t length, const char *fmt, ...)
             ATTRIBUTE ((noreturn, __format__ (__printf__, 3, 4)));
+char *format (const char *prefix, size_t length, const char *fmt, ...)
+              ATTRIBUTE ((__format__ (__printf__, 3, 4)));
 void out_of_memory (void) NORETURN;
 
 /* When adding macros to this list be sure to update the value of
@@ -760,6 +762,9 @@ extern int batch_mode_shell;
 #define RECIPEPREFIX_DEFAULT    '\t'
 extern char cmd_prefix;
 
+/* Setting warning actions.  */
+#define WARNINGS_NAME           ".WARNINGS"
+
 extern unsigned int no_intermediates;
 
 #if HAVE_MKFIFO
index 80fd63ace915746647161ae6c5be8600effc87be..04437c2bb0cbeef46fb6959db6c2d014e24d2e79 100644 (file)
@@ -437,6 +437,7 @@ find_next_token (const char **ptr, size_t *lengthptr)
   return (char *)p;
 }
 \f
+
 /* Write a BUFFER of size LEN to file descriptor FD.
    Retry short writes from EINTR.  Return LEN, or -1 on error.  */
 ssize_t
@@ -611,7 +612,7 @@ get_tmpdir ()
       unsigned int found = 0;
 
       for (tp = tlist; *tp; ++tp)
-        if ((tmpdir = getenv (*tp)) && *tmpdir != '\0')
+        if ((tmpdir = getenv (*tp)) != NULL && *tmpdir != '\0')
           {
             struct stat st;
             int r;
index eddabe0380f01b0707c69f81fa907842deba64cb..2cf76072af2a475461962af7848e89f616046edc 100644 (file)
@@ -475,8 +475,8 @@ error (const floc *flocp, size_t len, const char *fmt, ...)
 void
 fatal (const floc *flocp, size_t len, const char *fmt, ...)
 {
-  va_list args;
   const char *stop = _(".  Stop.\n");
+  va_list args;
   char *start;
   char *p;
 
@@ -505,6 +505,29 @@ fatal (const floc *flocp, size_t len, const char *fmt, ...)
   die (MAKE_FAILURE);
 }
 
+/* Format a message and return a pointer to an internal buffer.  */
+
+char *
+format (const char *prefix, size_t len, const char *fmt, ...)
+{
+  va_list args;
+  size_t plen = prefix ? strlen (prefix) : 0;
+  char *start;
+  char *p;
+
+  len += strlen (fmt) + plen + 1;
+  start = p = get_buffer (len);
+
+  if (plen)
+    p = mempcpy (p, prefix, plen);
+
+  va_start (args, fmt);
+  vsprintf (p, fmt, args);
+  va_end (args);
+
+  return start;
+}
+
 /* Print an error message from errno.  */
 
 void
index 1694ec7d7efb2ef2f20e94ed58df9568b1dfb576..878a562022eb164862a76b5791e2de10361e1551 100644 (file)
@@ -3068,7 +3068,7 @@ tilde_expand (const char *name)
 
       {
         /* Turn off undefined variables warning while we expand HOME.  */
-        enum warning_state save = warn_get (wt_undefined_var);
+        enum warning_action save = warn_get (wt_undefined_var);
         warn_set (wt_undefined_var, w_ignore);
 
         home_dir = allocated_expand_variable (STRING_SIZE_TUPLE ("HOME"));
index d2cfcc94f3b50c788431cb3955bc32258acf5185..bfaef6c1ae14cf810faeae231936d745a3301a73 100644 (file)
@@ -198,10 +198,8 @@ check_valid_name (const floc* flocp, const char *name, size_t length)
   if (cp == end)
     return;
 
-  if (warn_error (wt_invalid_var))
-    ONS (fatal, flocp, _("invalid variable name '%.*s'"), (int)length, name);
-
-  ONS (error, flocp, _("invalid variable name '%.*s'"), (int)length, name);
+  warning (wt_invalid_var, flocp,
+           ONS (format, 0, _("invalid variable name '%.*s'"), (int)length, name));
 }
 
 void
@@ -491,12 +489,8 @@ check_variable_reference (const char *name, size_t length)
   if (cp == end)
     return;
 
-  if (warn_error (wt_invalid_ref))
-    ONS (fatal, *expanding_var,
-         _("invalid variable reference '%.*s'"), (int)length, name);
-
-  ONS (error, *expanding_var,
-       _("invalid variable reference '%.*s'"), (int)length, name);
+  warning (wt_invalid_ref, *expanding_var,
+           ONS (format, 0, _("invalid variable reference '%.*s'"), (int)length, name));
 }
 
 /* Lookup a variable whose name is a string starting at NAME
@@ -1335,11 +1329,18 @@ set_special_var (struct variable *var, enum variable_origin origin)
     reset_makeflags (origin);
 
   else if (streq (var->name, RECIPEPREFIX_NAME))
+    /* The user is resetting the command introduction prefix.  This has to
+       happen immediately, so that subsequent rules are interpreted
+       properly.  */
+    cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0];
+
+  else if (streq (var->name, WARNINGS_NAME))
     {
-      /* The user is resetting the command introduction prefix.  This has to
-         happen immediately, so that subsequent rules are interpreted
-         properly.  */
-      cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0];
+      /* It's weird but for .WARNINGS to make sense we need to expand them
+         when they are set, even if it's a recursive variable.  */
+      char *actions = allocated_expand_variable (STRING_SIZE_TUPLE (WARNINGS_NAME));
+      decode_warn_actions (actions, &var->fileinfo);
+      free (actions);
     }
 
   return var;
@@ -1499,7 +1500,7 @@ do_variable_definition (const floc *flocp, const char *varname,
               {
                 char *s;
                 if (streq (varname, MAKEFLAGS_NAME)
-                    && (s = strstr (v->value, " -- ")))
+                    && (s = strstr (v->value, " -- ")) != NULL)
                   /* We found a separator in MAKEFLAGS.  Ignore variable
                      assignments: set_special_var() will reconstruct things.  */
                   cp = mempcpy (cp, v->value, s - v->value);
@@ -1914,6 +1915,7 @@ static const struct defined_vars defined_vars[] = {
   { STRING_SIZE_TUPLE ("-*-eval-flags-*-") },
   { STRING_SIZE_TUPLE ("VPATH") },
   { STRING_SIZE_TUPLE ("GPATH") },
+  { STRING_SIZE_TUPLE (WARNINGS_NAME) },
   { NULL, 0 }
 };
 
@@ -1927,12 +1929,9 @@ warn_undefined (const char *name, size_t len)
         if (dp->len == len && memcmp (dp->name, name, len) == 0)
           return;
 
-      if (warn_error (wt_undefined_var))
-        fatal (reading_file, len, _("reference to undefined variable '%.*s'"),
-               (int)len, name);
-      else
-        error (reading_file, len, _("reference to undefined variable '%.*s'"),
-               (int)len, name);
+      warning (wt_undefined_var, reading_file,
+               ONS (format, 0, _("reference to undefined variable '%.*s'"),
+                    (int)len, name));
     }
 }
 \f
diff --git a/src/warning.c b/src/warning.c
new file mode 100644 (file)
index 0000000..c295fdc
--- /dev/null
@@ -0,0 +1,235 @@
+/* Control warning output in GNU Make.
+Copyright (C) 2023 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
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make 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/>.  */
+
+#include "makeint.h"
+#include "warning.h"
+#include "variable.h"
+
+/* Current action for each warning.  */
+enum warning_action warnings[wt_max];
+
+/* The default behavior of warnings.  */
+static struct warning_data warn_default;
+
+/* Warning settings from the .WARNING variable.  */
+static struct warning_data warn_variable;
+
+/* Warning settings from the command line.  */
+static struct warning_data warn_flag;
+
+static const char *w_action_map[w_error+1] = {NULL, "ignore", "warn", "error"};
+static const char *w_name_map[wt_max] = {
+                                          "invalid-var",
+                                          "invalid-ref",
+                                          "undefined-var"
+                                        };
+
+#define encode_warn_action(_b,_s) \
+    variable_buffer_output (_b, w_action_map[_s], strlen (w_action_map[_s]))
+#define encode_warn_name(_b,_t)   \
+    variable_buffer_output (_b, w_name_map[_t], strlen (w_name_map[_t]))
+
+static void set_warnings ()
+{
+  /* Called whenever any warnings could change; resets the current actions.  */
+  for (enum warning_type wt = 0; wt < wt_max; ++wt)
+    warnings[wt] =
+        warn_flag.actions[wt]     != w_unset ? warn_flag.actions[wt]
+      : warn_flag.global          != w_unset ? warn_flag.global
+      : warn_variable.actions[wt] != w_unset ? warn_variable.actions[wt]
+      : warn_variable.global      != w_unset ? warn_variable.global
+      : warn_default.actions[wt];
+}
+
+void
+warn_init ()
+{
+  memset (&warn_default, '\0', sizeof (warn_default));
+  memset (&warn_variable, '\0', sizeof (warn_variable));
+  memset (&warn_flag, '\0', sizeof (warn_flag));
+
+  /* All warnings must have a default.  */
+  warn_default.global = w_warn;
+  warn_default.actions[wt_invalid_var] = w_warn;
+  warn_default.actions[wt_invalid_ref] = w_warn;
+  warn_default.actions[wt_undefined_var] = w_ignore;
+
+  set_warnings ();
+}
+
+static void
+init_data (struct warning_data *data)
+{
+  data->global = w_unset;
+  for (enum warning_type wt = wt_invalid_var; wt < wt_max; ++wt)
+    data->actions[wt] = w_unset;
+}
+
+static enum warning_action
+decode_warn_action (const char *action, size_t length)
+{
+  for (enum warning_action st = w_ignore; st <= w_error; ++st)
+    {
+      size_t len = strlen (w_action_map[st]);
+      if (length == len && strncasecmp (action, w_action_map[st], length) == 0)
+        return st;
+    }
+
+  return w_unset;
+}
+
+static enum warning_type
+decode_warn_name (const char *name, size_t length)
+{
+  for (enum warning_type wt = wt_invalid_var; wt < wt_max; ++wt)
+    {
+      size_t len = strlen (w_name_map[wt]);
+      if (length == len && strncasecmp (name, w_name_map[wt], length) == 0)
+        return wt;
+    }
+
+  return wt_max;
+}
+
+void
+decode_warn_actions (const char *value, const floc *flocp)
+{
+  struct warning_data *data = &warn_flag;
+
+  NEXT_TOKEN (value);
+
+  if (flocp)
+    {
+      data = &warn_variable;
+      /* When a variable is set to empty, reset everything.  */
+      if (*value == '\0')
+        init_data (data);
+    }
+
+  while (*value != '\0')
+    {
+      enum warning_action action;
+
+      /* Find the end of the next warning definition.  */
+      const char *ep = value;
+      while (! STOP_SET (*ep, MAP_BLANK|MAP_COMMA|MAP_NUL))
+        ++ep;
+
+      /* If the value is just an action set it globally.  */
+      action = decode_warn_action (value, ep - value);
+      if (action != w_unset)
+        data->global = action;
+      else
+        {
+          enum warning_type type;
+          const char *cp = memchr (value, ':', ep - value);
+          if (!cp)
+            cp = ep;
+          type = decode_warn_name (value, cp - value);
+          if (type == wt_max)
+            {
+              int l = (int)(cp - value);
+              if (!flocp)
+                ONS (fatal, NILF, _("unknown warning '%.*s'"), l, value);
+              ONS (error, flocp,
+                   _("unknown warning '%.*s': ignored"), l, value);
+            }
+
+          /* If there's a warning action, decode it.  */
+          if (cp == ep)
+            action = w_warn;
+          else
+            {
+              ++cp;
+              action = decode_warn_action (cp, ep - cp);
+              if (action == w_unset)
+                {
+                  int l = (int)(ep - cp);
+                  if (!flocp)
+                    ONS (fatal, NILF, _("unknown warning action '%.*s'"), l, cp);
+                  ONS (error, flocp,
+                       _("unknown warning action '%.*s': ignored"), l, cp);
+                }
+            }
+          data->actions[type] = action;
+        }
+
+      value = ep;
+      while (STOP_SET (*value, MAP_BLANK|MAP_COMMA))
+        ++value;
+    }
+
+  set_warnings ();
+}
+
+char *
+encode_warn_flag (char *fp)
+{
+  enum warning_type wt;
+  char sp = '=';
+
+  /* See if any warning options are set.  */
+  for (wt = 0; wt < wt_max; ++wt)
+    if (warn_flag.actions[wt] != w_unset)
+      break;
+  if (wt == wt_max && warn_flag.global == w_unset)
+    return fp;
+
+  /* Something is set so construct a --warn option.  */
+  fp = variable_buffer_output (fp, STRING_SIZE_TUPLE (" --warn"));
+
+  /* If only a global action set to warn, we're done.  */
+  if (wt == wt_max && warn_flag.global == w_warn)
+    return fp;
+
+  /* If a global action is set, add it.  */
+  if (warn_flag.global > w_unset)
+    {
+      fp = variable_buffer_output (fp, &sp, 1);
+      sp = ',';
+      fp = encode_warn_action (fp, warn_flag.global);
+    }
+
+  /* Add any specific actions.  */
+  if (wt != wt_max)
+    for (wt = 0; wt < wt_max; ++wt)
+      {
+        enum warning_action act = warn_flag.actions[wt];
+        if (act > w_unset)
+          {
+            fp = variable_buffer_output (fp, &sp, 1);
+            sp = ',';
+            fp = encode_warn_name (fp, wt);
+            if (act != w_warn)
+              fp = encode_warn_action (variable_buffer_output (fp, ":", 1), act);
+          }
+      }
+
+  return fp;
+}
+
+void
+warn_get_vardata (struct warning_data *data)
+{
+  memcpy (data, &warn_variable, sizeof (warn_variable));
+}
+
+void
+warn_set_vardata (const struct warning_data *data)
+{
+  memcpy (&warn_variable, data, sizeof (warn_variable));
+  set_warnings ();
+}
index 78e99893343c4ca9a8dfa5c4fc789d4998ef4574..658af93a1c28d1a8b12ebf3c785aa540015baeb3 100644 (file)
@@ -23,8 +23,8 @@ enum warning_type
     wt_max
   };
 
-/* State of a given warning.  */
-enum warning_state
+/* Action taken for a given warning.  */
+enum warning_action
   {
     w_unset = 0,
     w_ignore,
@@ -32,25 +32,24 @@ enum warning_state
     w_error
   };
 
-/* The default state of warnings.  */
-extern enum warning_state default_warnings[wt_max];
-
-/* Current state of warnings.  */
-extern enum warning_state warnings[wt_max];
+struct warning_data
+  {
+    enum warning_action global;          /* Global setting.  */
+    enum warning_action actions[wt_max]; /* Action for each warning type.  */
+  };
 
-/* Global warning settings.  */
-extern enum warning_state warn_global;
+/* Actions taken for each warning.  */
+extern enum warning_action warnings[wt_max];
 
-/* Get the current state of a given warning.  */
-#define warn_get(_w) (warnings[_w] != w_unset ? warnings[_w] \
-                      : warn_global != w_unset ? warn_global \
-                      : default_warnings[_w])
+/* Get the current action for a given warning.  */
+#define warn_get(_w)     (warnings[_w])
 
-/* Set the current state of a given warning.  Can't use w_unset here.  */
-#define warn_set(_w,_f) do{ warnings[_w] = (_f); } while (0)
+/* Set the current actin for a given warning.  Can't use w_unset here.
+   This should only be used for temporary resetting of warnings.  */
+#define warn_set(_w,_f)  do{ warnings[_w] = (_f); }while(0)
 
 /* True if we should check for the warning in the first place.  */
-#define warn_check(_w)  (warn_get (_w) > w_ignore)
+#define warn_check(_w)   (warn_get (_w) > w_ignore)
 
 /* Check if the warning is ignored.  */
 #define warn_ignored(_w) (warn_get (_w) == w_ignore)
@@ -60,3 +59,22 @@ extern enum warning_state warn_global;
 
 /* Check if the warning is in "error" mode.  */
 #define warn_error(_w)   (warn_get (_w) == w_error)
+
+void warn_init (void);
+void decode_warn_actions (const char *value, const floc *flocp);
+char *encode_warn_flag (char *fp);
+
+void warn_get_vardata (struct warning_data *data);
+void warn_set_vardata (const struct warning_data *data);
+
+#define warning(_t,_f,_m)                                   \
+    do{                                                     \
+        if (warn_check (_t))                                \
+          {                                                 \
+            char *_a = xstrdup (_m);                        \
+            if (warn_error (_t))                            \
+              fatal (_f, strlen (_a), "%s", _a);            \
+            error (_f, strlen (_a), _("warning: %s"), _a);  \
+            free (_a);                                      \
+          }                                                 \
+    }while(0)
index f41f84ff1dc339d40dfc55b5bfcdf6356b5b1811..8c66658e681a44300eb4e66a5d8700dae48adc49 100644 (file)
@@ -53,7 +53,7 @@ all:;
 X := $(averyveryveryloooooooooooooooooooooooooooongvariablename)
 !,
               '--warn=undefined-var',
-              "#MAKEFILE#:3: reference to undefined variable 'averyveryveryloooooooooooooooooooooooooooongvariablename'
+              "#MAKEFILE#:3: warning: reference to undefined variable 'averyveryveryloooooooooooooooooooooooooooongvariablename'
 #MAKE#: 'all' is up to date.\n"
 );
 
@@ -75,8 +75,8 @@ run_make_test(undef, '--warn=undefined-var:ignore', 'ref');
 
 # Check warnings
 run_make_test(undef, '--warn=undefined-var',
-              "#MAKEFILE#:7: reference to undefined variable 'UNDEFINED'
-#MAKEFILE#:9: reference to undefined variable 'UNDEFINED'
+              "#MAKEFILE#:7: warning: reference to undefined variable 'UNDEFINED'
+#MAKEFILE#:9: warning: reference to undefined variable 'UNDEFINED'
 ref");
 
 # Check and errors
@@ -96,9 +96,9 @@ define nl
 endef
 
 all: ; @echo ref $(also$(nl)bad) $(IREF) $(SIREF)',
-              '', "#MAKEFILE#:2: invalid variable reference 'bad variable'
-#MAKEFILE#:10: invalid variable reference 'also\nbad'
-#MAKEFILE#:2: invalid variable reference 'bad variable'
+              '', "#MAKEFILE#:2: warning: invalid variable reference 'bad variable'
+#MAKEFILE#:10: warning: invalid variable reference 'also\nbad'
+#MAKEFILE#:2: warning: invalid variable reference 'bad variable'
 ref");
 
 run_make_test(undef, '--warn=ignore', 'ref');
@@ -133,18 +133,26 @@ foo
 endef
 
 all: ; @echo ref',
-              '', "#MAKEFILE#:4: invalid variable name 'BAD VAR'
-#MAKEFILE#:11: invalid variable name 'NL\nVAR'
-#MAKEFILE#:13: invalid variable name 'BAD DEF'
-#MAKEFILE#:17: invalid variable name 'NL\nDEF'
+              '', "#MAKEFILE#:4: warning: invalid variable name 'BAD VAR'
+#MAKEFILE#:11: warning: invalid variable name 'NL\nVAR'
+#MAKEFILE#:13: warning: invalid variable name 'BAD DEF'
+#MAKEFILE#:17: warning: invalid variable name 'NL\nDEF'
 ref");
 
 run_make_test(undef, '--warn=ignore', 'ref');
 
 run_make_test(undef, '--warn=invalid-var:ignore', 'ref');
 
-# Check and errors
+# Check errors
 run_make_test(undef, '--warn=invalid-var:error',
               "#MAKEFILE#:4: *** invalid variable name 'BAD VAR'.  Stop.", 512);
 
+# Make sure unknown warnings and actions fail when given on the command line.
+
+run_make_test(undef, '--warn=no-such-warn',
+    "#MAKE#: *** unknown warning 'no-such-warn'.  Stop.", 512);
+
+run_make_test(undef, '--warn=invalid-var:no-such-action',
+    "#MAKE#: *** unknown warning action 'no-such-action'.  Stop.", 512);
+
 1;
diff --git a/tests/scripts/variables/WARNINGS b/tests/scripts/variables/WARNINGS
new file mode 100644 (file)
index 0000000..870a41d
--- /dev/null
@@ -0,0 +1,180 @@
+#                                                                    -*-perl-*-
+
+$description = "Test the .WARNINGS variable.";
+
+my %warn_test = (
+    'warn' => 'warn', 'error warn' => 'warn',
+    'error' => 'error',
+    'ignore error ignore invalid-var,invalid-ref,undefined-var' => 'ignore,invalid-var,invalid-ref,undefined-var',
+    'invalid-ref:ignore error invalid-var:warn,,,,,undefined-var:error,,,,,' => '=error,invalid-var,invalid-ref:ignore,undefined-var:error'
+);
+
+# Verify that values set in .WARNINGS don't get passed to sub-makes
+
+while (my ($f, $r) = each %warn_test) {
+    run_make_test(qq!
+.WARNINGS = error
+\$(info MF=\$(MAKEFLAGS))
+all:; \@#HELPER# env MAKEFLAGS
+!,
+                  '', "MF=\nMAKEFLAGS=");
+}
+
+# Verify that make's special variables don't warn even if they're not set
+run_make_test(q!
+.WARNINGS = undefined-var
+vars := $(.VARIABLES) $(MAKECMDGOALS) $(MAKE_RESTARTS) $(CURDIR)
+vars += $(GNUMAKEFLAGS) $(MAKEFLAGS) $(MFLAGS) $(MAKE_COMMAND) $(MAKE)
+vars += $(MAKEFILE_LIST) $(MAKEOVERRIDES) $(-*-command-variables-*-)
+vars += $(.RECIPEPREFIX) $(.LOADED) $(.FEATURES)
+vars += $(SHELL) $(.SHELLFLAGS) $(MAKE_TERMOUT) $(MAKE_TERMERR)
+vars += $(.DEFAULT) $(.DEFAULT_GOAL) $(-*-eval-flags-*-) $(SUFFIXES)
+vars += $(VPATH) $(GPATH)
+all:;
+!,
+              '', "#MAKE#: 'all' is up to date.");
+
+# sv 63609.
+# Test for buffer overrun in warn_undefined.
+run_make_test(q!
+.WARNINGS = undefined-var
+all:;
+X := $(averyveryveryloooooooooooooooooooooooooooongvariablename)
+!,
+              '', "#MAKEFILE#:4: warning: reference to undefined variable 'averyveryveryloooooooooooooooooooooooooooongvariablename'
+#MAKE#: 'all' is up to date.\n"
+);
+
+# Check undefined variable warnings
+
+# With no options or with ignore, nothing should happen
+run_make_test('
+.WARNINGS := $(warnval)
+EMPTY =
+EREF = $(EMPTY)
+UREF = $(UNDEFINED)
+
+SEREF := $(EREF)
+SUREF := $(UREF)
+
+all: ; @echo ref $(EREF) $(UREF)',
+              '', 'ref');
+
+run_make_test(undef, 'warnval=undefined-var:ignore', 'ref');
+
+# Check warnings
+run_make_test(undef, 'warnval=undefined-var',
+              "#MAKEFILE#:8: warning: reference to undefined variable 'UNDEFINED'
+#MAKEFILE#:10: warning: reference to undefined variable 'UNDEFINED'
+ref");
+
+# Check and errors
+run_make_test(undef, 'warnval=undefined-var:error',
+              "#MAKEFILE#:8: *** reference to undefined variable 'UNDEFINED'.  Stop.", 512);
+
+# Check invalid variable reference warnings
+
+# With no options we still check for invalid references
+run_make_test('
+.WARNINGS = $(warnval)
+IREF = $(bad variable)
+SIREF := $(IREF)
+
+define nl
+
+
+endef
+
+all: ; @echo ref $(also$(nl)bad) $(IREF) $(SIREF)',
+              '', "#MAKEFILE#:3: warning: invalid variable reference 'bad variable'
+#MAKEFILE#:11: warning: invalid variable reference 'also\nbad'
+#MAKEFILE#:3: warning: invalid variable reference 'bad variable'
+ref");
+
+run_make_test(undef, 'warnval=ignore', 'ref');
+
+run_make_test(undef, 'warnval=invalid-ref:ignore', 'ref');
+
+# Check and errors
+run_make_test(undef, 'warnval=invalid-ref:error',
+              "#MAKEFILE#:3: *** invalid variable reference 'bad variable'.  Stop.", 512);
+
+# Check invalid variable name warnings
+
+# With no options we still check for invalid references
+run_make_test('
+.WARNINGS = $(warnval)
+EMPTY =
+SPACE = $(EMPTY) $(EMPTY)
+BAD$(SPACE)VAR = foo
+
+define nl
+
+
+endef
+
+NL$(nl)VAR = bar
+
+define BAD$(SPACE)DEF :=
+foo
+endef
+
+define NL$(nl)DEF :=
+foo
+endef
+
+all: ; @echo ref',
+              '', "#MAKEFILE#:5: warning: invalid variable name 'BAD VAR'
+#MAKEFILE#:12: warning: invalid variable name 'NL\nVAR'
+#MAKEFILE#:14: warning: invalid variable name 'BAD DEF'
+#MAKEFILE#:18: warning: invalid variable name 'NL\nDEF'
+ref");
+
+run_make_test(undef, 'warnval=ignore', 'ref');
+
+run_make_test(undef, 'warnval=invalid-var:ignore', 'ref');
+
+# Check errors
+run_make_test(undef, 'warnval=invalid-var:error',
+              "#MAKEFILE#:5: *** invalid variable name 'BAD VAR'.  Stop.", 512);
+
+# Make sure unknown warnings and actions are only noted but not failed on:
+# this allows makefiles to be portable to older versions where those warnings
+# didn't exist
+
+run_make_test(q!
+.WARNINGS = no-such-warn
+all:;
+!,
+    '',"#MAKEFILE#:2: unknown warning 'no-such-warn': ignored\n#MAKE#: 'all' is up to date.");
+
+run_make_test(q!
+.WARNINGS = invalid-var:no-such-action
+all:;
+!,
+    '',"#MAKEFILE#:2: unknown warning action 'no-such-action': ignored\n#MAKE#: 'all' is up to date.");
+
+
+# Validate .WARNINGS set as target-specific variables
+# This is not supported (yet...?)
+
+# run_make_test(q!
+# ok := $(undef)
+# ref = $(undef)
+
+# all: enabled disabled enabled2 ;
+
+# .WARNINGS = undefined-var
+
+# enabled enabled2 disabled ref: ; $(info $@:$(ref))
+
+# disabled: .WARNINGS =
+# disabled: ref
+# !,
+#     '', "#MAKEFILE#:9: reference to undefined variable 'undef'\nenabled:
+# ref:
+# disabled:
+# #MAKEFILE#:9: reference to undefined variable 'undef'\nenabled2:
+# #MAKE#: 'all' is up to date.");
+
+1;