From a0d1e76d604df5bd006fd5ed6a69963d3c6b4d7a Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Sat, 18 Mar 2023 17:24:45 -0400 Subject: Add support for .WARNINGS special variable 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. --- Makefile.am | 2 +- build_w32.bat | 1 + builddos.bat | 3 +- doc/make.texi | 131 ++++++++++++++-------- po/POTFILES.in | 1 + src/expand.c | 1 + src/job.c | 2 +- src/main.c | 166 +++------------------------ src/makeint.h | 5 + src/misc.c | 3 +- src/output.c | 25 ++++- src/read.c | 2 +- src/variable.c | 41 ++++--- src/warning.c | 235 +++++++++++++++++++++++++++++++++++++++ src/warning.h | 50 ++++++--- tests/scripts/options/warn | 30 +++-- tests/scripts/variables/WARNINGS | 180 ++++++++++++++++++++++++++++++ 17 files changed, 629 insertions(+), 249 deletions(-) create mode 100644 src/warning.c create mode 100644 tests/scripts/variables/WARNINGS diff --git a/Makefile.am b/Makefile.am index 8741b562..05be3453 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/build_w32.bat b/build_w32.bat index 3e023002..3abc43ed 100644 --- a/build_w32.bat +++ b/build_w32.bat @@ -271,6 +271,7 @@ call :Compile src/strcache call :Compile src/variable call :Compile src/version call :Compile src/vpath +call :Compile src/warning call :Compile src/w32/pathstuff call :Compile src/w32/w32os call :Compile src/w32/compat/posixfcn diff --git a/builddos.bat b/builddos.bat index 3e540a98..c933c1f0 100644 --- a/builddos.bat +++ b/builddos.bat @@ -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 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/default.c -o default.o gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/variable.c -o variable.o +gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/warning.c -o warning.o gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/expand.c -o expand.o gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/function.c -o function.o gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/vpath.c -o vpath.o @@ -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 @echo off echo commands.o > respf.$$$ -for %%f in (job output dir file misc main read remake rule implicit default variable load) do echo %%f.o >> respf.$$$ +for %%f in (job output dir file misc main read remake rule implicit default variable warning load) do echo %%f.o >> respf.$$$ for %%f in (expand function vpath hash strcache version ar arscan signame remote-stub getopt getopt1 shuffle) do echo %%f.o >> respf.$$$ for %%f in (lib\glob lib\fnmatch) do echo %%f.o >> respf.$$$ gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/guile.c -o guile.o diff --git a/doc/make.texi b/doc/make.texi index b3cc57a0..2dd6f1c9 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -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 diff --git a/po/POTFILES.in b/po/POTFILES.in index 39c3d804..6f542130 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -45,4 +45,5 @@ src/variable.h src/vmsfunctions.c src/vmsjobs.c src/vpath.c +src/warning.c src/w32/w32os.c diff --git a/src/expand.c b/src/expand.c index 533e7dfa..a1efa831 100644 --- a/src/expand.c +++ b/src/expand.c @@ -24,6 +24,7 @@ this program. If not, see . */ #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. */ diff --git a/src/job.c b/src/job.c index b26cb2ca..bca45529 100644 --- 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); diff --git a/src/main.c b/src/main.c index 843cab09..f4250726 100644 --- a/src/main.c +++ b/src/main.c @@ -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; diff --git a/src/makeint.h b/src/makeint.h index e130e761..f34ec361 100644 --- a/src/makeint.h +++ b/src/makeint.h @@ -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 diff --git a/src/misc.c b/src/misc.c index 80fd63ac..04437c2b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -437,6 +437,7 @@ find_next_token (const char **ptr, size_t *lengthptr) return (char *)p; } + /* 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; diff --git a/src/output.c b/src/output.c index eddabe03..2cf76072 100644 --- a/src/output.c +++ b/src/output.c @@ -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 diff --git a/src/read.c b/src/read.c index 1694ec7d..878a5620 100644 --- a/src/read.c +++ b/src/read.c @@ -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")); diff --git a/src/variable.c b/src/variable.c index d2cfcc94..bfaef6c1 100644 --- a/src/variable.c +++ b/src/variable.c @@ -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)); } } diff --git a/src/warning.c b/src/warning.c new file mode 100644 index 00000000..c295fdc7 --- /dev/null +++ b/src/warning.c @@ -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 . */ + +#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 (); +} diff --git a/src/warning.h b/src/warning.h index 78e99893..658af93a 100644 --- a/src/warning.h +++ b/src/warning.h @@ -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) diff --git a/tests/scripts/options/warn b/tests/scripts/options/warn index f41f84ff..8c66658e 100644 --- a/tests/scripts/options/warn +++ b/tests/scripts/options/warn @@ -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 index 00000000..870a41d0 --- /dev/null +++ b/tests/scripts/variables/WARNINGS @@ -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; -- cgit v1.2.1