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.
This commit is contained in:
Paul Smith 2023-03-18 17:24:45 -04:00
parent 03ecd94488
commit a0d1e76d60
17 changed files with 629 additions and 249 deletions

View 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/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/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/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 \ 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 \ src/w32/compat/posixfcn.c src/w32/include/dirent.h \

View File

@ -271,6 +271,7 @@ call :Compile src/strcache
call :Compile src/variable call :Compile src/variable
call :Compile src/version call :Compile src/version
call :Compile src/vpath call :Compile src/vpath
call :Compile src/warning
call :Compile src/w32/pathstuff call :Compile src/w32/pathstuff
call :Compile src/w32/w32os call :Compile src/w32/w32os
call :Compile src/w32/compat/posixfcn call :Compile src/w32/compat/posixfcn

View 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 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/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/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/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/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 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 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 off
echo commands.o > respf.$$$ 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 (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.$$$ 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 gcc -c -I./src -I%XSRC%/src -I./lib -I%XSRC%/lib -DHAVE_CONFIG_H -O2 -g %XSRC%/src/guile.c -o guile.o

View 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 a prerequisite listed in @code{.EXTRA_PREREQS} as a prerequisite to
itself. itself.
@item .WARNINGS
Changes the actions taken when @code{make} detects warning conditions in the
makefile. @xref{Warnings, ,Makefile Warnings}.
@end table @end table
@node Conditionals, Functions, Using Variables, Top @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 @section Makefile Warnings
@cindex warnings @cindex warnings
GNU Make can detect some types of incorrect usage in makefiles and show GNU Make can detect some types of incorrect usage in makefiles. When one of
warnings about them. Currently these issues can be detected: these incorrect usages is detected, GNU Make can perform one of these actions:
@table @samp
@item invalid-var
Assigning to an invalid variable name (e.g., a name containing whitespace).
@item invalid-ref
Using an invalid variable name in a variable reference.
@item undefined-var
Referencing a variable that has not been defined.
@end table
When one of these incorrect usages is detected, GNU Make can perform one of
these actions:
@table @samp @table @samp
@item ignore @item ignore
@cindex warning action ignore
@cindex ignore, warning action
Ignore the usage. Ignore the usage.
@item warn @item warn
@cindex warning action warn
@cindex warn, warning action
Show a warning about the usage and continue processing the makefile. Show a warning about the usage and continue processing the makefile.
@item error @item error
@cindex warning action error
@cindex error, warning action
Show an error for the usage and immediately stop processing the makefile. Show an error for the usage and immediately stop processing the makefile.
@end table @end table
The default action of GNU Make when no warning control options are provided @noindent
is @samp{ignore} for @samp{undefined-var}, and @samp{warn} for The types of warnings GNU Make can detect are:
@samp{invalid-var} and @samp{invalid-ref}.
To modify this default behavior, you can use the @code{--warn} option. This @table @samp
option can be specified on the command line, or by adding it to the @item invalid-var
@code{MAKEFLAGS} variable (@pxref{Recursion, ,Recursive Use of @code{make}}). @findex invalid-var
Settings added to @code{MAKEFLAGS} are only affect after the assignment @cindex warning invalid variable
statement. Assigning to an invalid variable name (e.g., a name containing whitespace).
The default action is @samp{warn}.
@item invalid-ref
@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
@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
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.
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'').
``Global actions'' take precedence over default actions. Actions associated
with a specific warning type take precedence over ``global actions'' and
default actions.
If multiple control options provide actions for the same warning type, the
last action specified will be used.
There are two ways to specify control options: using the @code{--warn} command
line option, or using the @code{.WARNINGS} variable.
@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 The @code{--warn} option can be provided multiple times: the effects are
cumulative with later options overriding over earlier options. When GNU Make cumulative with later options overriding over earlier options. When GNU Make
provides warning settings to sub-makes, they are all combined into a single provides warning settings to sub-makes, they are all combined into a single
@code{--warn} option in @code{MAKEFLAGS}. @code{--warn} option in @code{MAKEFLAGS} with a standard order.
If @code{--warn} is provided with no arguments then all issues are detected Specifying @code{--warn} with no arguments is equivalent to using
and reported at the @samp{warn} level unless otherwise specified. @code{--warn=warn}, which sets the action for all warning types to
@samp{warn}.
If one of the actions (@samp{ignore}, @samp{warn}, @samp{error}) is provided Any action specified with an @code{--warn} option will take precedence over
as an argument to @code{--warn}, then this action becomes the default for all actions provided in the makefile with @code{.WARNINGS}. This means if you use
warning types. For example all warnings can be disabled by using @code{--warn=error}, for example, all warnings will be treated as errors
@code{--warn=ignore}, or all warnings can be considered fatal errors by using regardless of any @code{.WARNINGS} assignments.
@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}.
@node Temporary Files, Options Summary, Warnings, Running @node Temporary Files, Options Summary, Warnings, Running
@section Temporary Files @section Temporary Files

View File

@ -45,4 +45,5 @@ src/variable.h
src/vmsfunctions.c src/vmsfunctions.c
src/vmsjobs.c src/vmsjobs.c
src/vpath.c src/vpath.c
src/warning.c
src/w32/w32os.c src/w32/w32os.c

View File

@ -24,6 +24,7 @@ this program. If not, see <https://www.gnu.org/licenses/>. */
#include "job.h" #include "job.h"
#include "variable.h" #include "variable.h"
#include "rule.h" #include "rule.h"
#include "warning.h"
/* Initially, any errors reported when expanding strings will be reported /* Initially, any errors reported when expanding strings will be reported
against the file where the error appears. */ against the file where the error appears. */

View File

@ -3635,7 +3635,7 @@ construct_command_argv (char *line, char **restp, struct file *file,
{ {
struct variable *var; struct variable *var;
/* Turn off undefined variables warning while we expand HOME. */ /* 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); warn_set (wt_undefined_var, w_ignore);
shell = allocated_expand_variable_for_file (STRING_SIZE_TUPLE ("SHELL"), file); shell = allocated_expand_variable_for_file (STRING_SIZE_TUPLE ("SHELL"), file);

View File

@ -273,18 +273,6 @@ static struct stringlist *eval_strings = 0;
static int print_usage_flag = 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. */ /* Command line warning flags. */
static struct stringlist *warn_flags = 0; static struct stringlist *warn_flags = 0;
@ -663,15 +651,6 @@ initialize_global_hash_tables (void)
hash_init_function_table (); 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. /* This character map locate stop chars when parsing GNU makefiles.
Each element is true if we should stop parsing on that character. */ Each element is true if we should stop parsing on that character. */
@ -886,101 +865,6 @@ decode_debug_flags (void)
debug_flag = 0; 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 static void
decode_output_sync_flags (void) decode_output_sync_flags (void)
{ {
@ -1323,7 +1207,7 @@ main (int argc, char **argv, char **envp)
initialize_stopchar_map (); initialize_stopchar_map ();
initialize_warnings (); warn_init ();
#ifdef SET_STACK_SIZE #ifdef SET_STACK_SIZE
/* Get rid of any avoidable limit on 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 (".VARIABLES", "", o_default, 0)->special = 1;
/* define_variable_cname (".TARGETS", "", 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 (".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 (".SHELLFLAGS", "-c", o_default, 0);
define_variable_cname (".LOADED", "", 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 we detected some clock skew, generate one last warning */
if (clock_skew_detected) if (clock_skew_detected)
O (error, NILF, O (error, NILF,
_("warning: Clock skew detected. Your build may be incomplete.")); _("warning: Clock skew detected. Your build may be incomplete."));
/* Exit. */ /* Exit. */
die (makefile_status); 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. */ /* If there are any options that need to be decoded do it now. */
decode_debug_flags (); decode_debug_flags ();
decode_warn_flags ();
decode_output_sync_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. */ /* Perform any special switch handling. */
run_silent = silent_flag; run_silent = silent_flag;
} }
@ -3655,38 +3550,7 @@ define_makeflags (int makefile)
case filename: case filename:
case strlist: case strlist:
if (cs->c == WARN_OPT) if (cs->c == WARN_OPT)
{ fp = encode_warn_flag (fp);
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]);
}
}
else else
{ {
struct stringlist *sl = *(struct stringlist **) cs->value_ptr; struct stringlist *sl = *(struct stringlist **) cs->value_ptr;

View File

@ -553,6 +553,8 @@ void error (const floc *flocp, size_t length, const char *fmt, ...)
ATTRIBUTE ((__format__ (__printf__, 3, 4))); ATTRIBUTE ((__format__ (__printf__, 3, 4)));
void fatal (const floc *flocp, size_t length, const char *fmt, ...) void fatal (const floc *flocp, size_t length, const char *fmt, ...)
ATTRIBUTE ((noreturn, __format__ (__printf__, 3, 4))); 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; void out_of_memory (void) NORETURN;
/* When adding macros to this list be sure to update the value of /* 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' #define RECIPEPREFIX_DEFAULT '\t'
extern char cmd_prefix; extern char cmd_prefix;
/* Setting warning actions. */
#define WARNINGS_NAME ".WARNINGS"
extern unsigned int no_intermediates; extern unsigned int no_intermediates;
#if HAVE_MKFIFO #if HAVE_MKFIFO

View File

@ -437,6 +437,7 @@ find_next_token (const char **ptr, size_t *lengthptr)
return (char *)p; return (char *)p;
} }
/* Write a BUFFER of size LEN to file descriptor FD. /* Write a BUFFER of size LEN to file descriptor FD.
Retry short writes from EINTR. Return LEN, or -1 on error. */ Retry short writes from EINTR. Return LEN, or -1 on error. */
ssize_t ssize_t
@ -611,7 +612,7 @@ get_tmpdir ()
unsigned int found = 0; unsigned int found = 0;
for (tp = tlist; *tp; ++tp) for (tp = tlist; *tp; ++tp)
if ((tmpdir = getenv (*tp)) && *tmpdir != '\0') if ((tmpdir = getenv (*tp)) != NULL && *tmpdir != '\0')
{ {
struct stat st; struct stat st;
int r; int r;

View File

@ -475,8 +475,8 @@ error (const floc *flocp, size_t len, const char *fmt, ...)
void void
fatal (const floc *flocp, size_t len, const char *fmt, ...) fatal (const floc *flocp, size_t len, const char *fmt, ...)
{ {
va_list args;
const char *stop = _(". Stop.\n"); const char *stop = _(". Stop.\n");
va_list args;
char *start; char *start;
char *p; char *p;
@ -505,6 +505,29 @@ fatal (const floc *flocp, size_t len, const char *fmt, ...)
die (MAKE_FAILURE); 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. */ /* Print an error message from errno. */
void void

View File

@ -3068,7 +3068,7 @@ tilde_expand (const char *name)
{ {
/* Turn off undefined variables warning while we expand HOME. */ /* 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); warn_set (wt_undefined_var, w_ignore);
home_dir = allocated_expand_variable (STRING_SIZE_TUPLE ("HOME")); home_dir = allocated_expand_variable (STRING_SIZE_TUPLE ("HOME"));

View File

@ -198,10 +198,8 @@ check_valid_name (const floc* flocp, const char *name, size_t length)
if (cp == end) if (cp == end)
return; return;
if (warn_error (wt_invalid_var)) warning (wt_invalid_var, flocp,
ONS (fatal, flocp, _("invalid variable name '%.*s'"), (int)length, name); ONS (format, 0, _("invalid variable name '%.*s'"), (int)length, name));
ONS (error, flocp, _("invalid variable name '%.*s'"), (int)length, name);
} }
void void
@ -491,12 +489,8 @@ check_variable_reference (const char *name, size_t length)
if (cp == end) if (cp == end)
return; return;
if (warn_error (wt_invalid_ref)) warning (wt_invalid_ref, *expanding_var,
ONS (fatal, *expanding_var, ONS (format, 0, _("invalid variable reference '%.*s'"), (int)length, name));
_("invalid variable reference '%.*s'"), (int)length, name);
ONS (error, *expanding_var,
_("invalid variable reference '%.*s'"), (int)length, name);
} }
/* Lookup a variable whose name is a string starting at 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); reset_makeflags (origin);
else if (streq (var->name, RECIPEPREFIX_NAME)) 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 /* It's weird but for .WARNINGS to make sense we need to expand them
happen immediately, so that subsequent rules are interpreted when they are set, even if it's a recursive variable. */
properly. */ char *actions = allocated_expand_variable (STRING_SIZE_TUPLE (WARNINGS_NAME));
cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0]; decode_warn_actions (actions, &var->fileinfo);
free (actions);
} }
return var; return var;
@ -1499,7 +1500,7 @@ do_variable_definition (const floc *flocp, const char *varname,
{ {
char *s; char *s;
if (streq (varname, MAKEFLAGS_NAME) if (streq (varname, MAKEFLAGS_NAME)
&& (s = strstr (v->value, " -- "))) && (s = strstr (v->value, " -- ")) != NULL)
/* We found a separator in MAKEFLAGS. Ignore variable /* We found a separator in MAKEFLAGS. Ignore variable
assignments: set_special_var() will reconstruct things. */ assignments: set_special_var() will reconstruct things. */
cp = mempcpy (cp, v->value, s - v->value); 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 ("-*-eval-flags-*-") },
{ STRING_SIZE_TUPLE ("VPATH") }, { STRING_SIZE_TUPLE ("VPATH") },
{ STRING_SIZE_TUPLE ("GPATH") }, { STRING_SIZE_TUPLE ("GPATH") },
{ STRING_SIZE_TUPLE (WARNINGS_NAME) },
{ NULL, 0 } { NULL, 0 }
}; };
@ -1927,12 +1929,9 @@ warn_undefined (const char *name, size_t len)
if (dp->len == len && memcmp (dp->name, name, len) == 0) if (dp->len == len && memcmp (dp->name, name, len) == 0)
return; return;
if (warn_error (wt_undefined_var)) warning (wt_undefined_var, reading_file,
fatal (reading_file, len, _("reference to undefined variable '%.*s'"), ONS (format, 0, _("reference to undefined variable '%.*s'"),
(int)len, name); (int)len, name));
else
error (reading_file, len, _("reference to undefined variable '%.*s'"),
(int)len, name);
} }
} }

235
src/warning.c Normal file
View File

@ -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 ();
}

View File

@ -23,8 +23,8 @@ enum warning_type
wt_max wt_max
}; };
/* State of a given warning. */ /* Action taken for a given warning. */
enum warning_state enum warning_action
{ {
w_unset = 0, w_unset = 0,
w_ignore, w_ignore,
@ -32,25 +32,24 @@ enum warning_state
w_error w_error
}; };
/* The default state of warnings. */ struct warning_data
extern enum warning_state default_warnings[wt_max]; {
enum warning_action global; /* Global setting. */
enum warning_action actions[wt_max]; /* Action for each warning type. */
};
/* Current state of warnings. */ /* Actions taken for each warning. */
extern enum warning_state warnings[wt_max]; extern enum warning_action warnings[wt_max];
/* Global warning settings. */ /* Get the current action for a given warning. */
extern enum warning_state warn_global; #define warn_get(_w) (warnings[_w])
/* Get the current state of a given warning. */ /* Set the current actin for a given warning. Can't use w_unset here.
#define warn_get(_w) (warnings[_w] != w_unset ? warnings[_w] \ This should only be used for temporary resetting of warnings. */
: warn_global != w_unset ? warn_global \ #define warn_set(_w,_f) do{ warnings[_w] = (_f); }while(0)
: default_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)
/* True if we should check for the warning in the first place. */ /* 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. */ /* Check if the warning is ignored. */
#define warn_ignored(_w) (warn_get (_w) == w_ignore) #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. */ /* Check if the warning is in "error" mode. */
#define warn_error(_w) (warn_get (_w) == w_error) #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)

View File

@ -53,7 +53,7 @@ all:;
X := $(averyveryveryloooooooooooooooooooooooooooongvariablename) X := $(averyveryveryloooooooooooooooooooooooooooongvariablename)
!, !,
'--warn=undefined-var', '--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" #MAKE#: 'all' is up to date.\n"
); );
@ -75,8 +75,8 @@ run_make_test(undef, '--warn=undefined-var:ignore', 'ref');
# Check warnings # Check warnings
run_make_test(undef, '--warn=undefined-var', run_make_test(undef, '--warn=undefined-var',
"#MAKEFILE#:7: reference to undefined variable 'UNDEFINED' "#MAKEFILE#:7: warning: reference to undefined variable 'UNDEFINED'
#MAKEFILE#:9: reference to undefined variable 'UNDEFINED' #MAKEFILE#:9: warning: reference to undefined variable 'UNDEFINED'
ref"); ref");
# Check and errors # Check and errors
@ -96,9 +96,9 @@ define nl
endef endef
all: ; @echo ref $(also$(nl)bad) $(IREF) $(SIREF)', all: ; @echo ref $(also$(nl)bad) $(IREF) $(SIREF)',
'', "#MAKEFILE#:2: invalid variable reference 'bad variable' '', "#MAKEFILE#:2: warning: invalid variable reference 'bad variable'
#MAKEFILE#:10: invalid variable reference 'also\nbad' #MAKEFILE#:10: warning: invalid variable reference 'also\nbad'
#MAKEFILE#:2: invalid variable reference 'bad variable' #MAKEFILE#:2: warning: invalid variable reference 'bad variable'
ref"); ref");
run_make_test(undef, '--warn=ignore', 'ref'); run_make_test(undef, '--warn=ignore', 'ref');
@ -133,18 +133,26 @@ foo
endef endef
all: ; @echo ref', all: ; @echo ref',
'', "#MAKEFILE#:4: invalid variable name 'BAD VAR' '', "#MAKEFILE#:4: warning: invalid variable name 'BAD VAR'
#MAKEFILE#:11: invalid variable name 'NL\nVAR' #MAKEFILE#:11: warning: invalid variable name 'NL\nVAR'
#MAKEFILE#:13: invalid variable name 'BAD DEF' #MAKEFILE#:13: warning: invalid variable name 'BAD DEF'
#MAKEFILE#:17: invalid variable name 'NL\nDEF' #MAKEFILE#:17: warning: invalid variable name 'NL\nDEF'
ref"); ref");
run_make_test(undef, '--warn=ignore', 'ref'); run_make_test(undef, '--warn=ignore', 'ref');
run_make_test(undef, '--warn=invalid-var:ignore', 'ref'); run_make_test(undef, '--warn=invalid-var:ignore', 'ref');
# Check and errors # Check errors
run_make_test(undef, '--warn=invalid-var:error', run_make_test(undef, '--warn=invalid-var:error',
"#MAKEFILE#:4: *** invalid variable name 'BAD VAR'. Stop.", 512); "#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; 1;

View File

@ -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;