From 2611e1991fabe2a3ae929c6ebd4afbd4f550f306 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Sun, 26 Feb 2023 18:04:14 -0500 Subject: Introduce a --warn command line option Replace the singleton --warn-undefined-variables with infrastructure to manage multiple warnings: the --warn option can take an action "ignore", "warn", or "error" (which will apply to all warnings), or a specific warning type and an action for that type. Multiple options can be provided and are consolidated. * NEWS: Announce the new option. * doc/make.1: Document in the man page. * doc/make.texi (Warnings): Document in the user's manual. * Makefile.am: Add new header warning.h. * src/warning.h: Define enum for actions and warning types, and macros to test whether they are set. Keep the default settings separate so that we can correctly reconstruct MAKEFLAGS. * src/makeint.h: Remove deprecated warn_undefined_variables_flag. * src/main.c: Create global variables to hold warning settings. (switches): Add a new switch for --warn. (initialize_warnings): Set the default warning actions. (main): Call initialize_warnings(). (encode_warning_state, decode_warning_state): Convert warning states between strings and enums. (encode_warning_name, decode_warning_name): Convert warning names between strings and enums. (decode_warn_flags): Convert a --warn option into enum values. If deprecated warn_undefined_variables_flag is set convert it to --warn. (decode_switches): Don't remove duplicates of --warn since order matters. Call decode_warn_flags() to handle --warn. (define_makeflags): Special-case handling of --warn options written to MAKEFLAGS: write out the current settings. * src/read.c (tilde_expand): Use new warning control macros. * src/variable.c (warn_undefined): Ditto. * src/job.c (construct_command_argv): Ditto. * tests/scripts/options/warn: Rename from warn-undefined-variables and add tests for --warn. * tests/scripts/variables/MAKEFLAGS: Expect the new behavior. --- src/job.c | 9 +-- src/main.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++------ src/makeint.h | 4 +- src/read.c | 10 +-- src/variable.c | 11 +++- src/warning.h | 60 ++++++++++++++++++ 6 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 src/warning.h (limited to 'src') diff --git a/src/job.c b/src/job.c index d7b584f5..b26cb2ca 100644 --- a/src/job.c +++ b/src/job.c @@ -27,6 +27,7 @@ this program. If not, see . */ #include "os.h" #include "dep.h" #include "shuffle.h" +#include "warning.h" /* Default shell to use. */ #if MK_OS_W32 @@ -3633,9 +3634,9 @@ construct_command_argv (char *line, char **restp, struct file *file, { struct variable *var; - /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */ - int save = warn_undefined_variables_flag; - warn_undefined_variables_flag = 0; + /* Turn off undefined variables warning while we expand HOME. */ + enum warning_state save = warn_get (wt_undefined_var); + warn_set (wt_undefined_var, w_ignore); shell = allocated_expand_variable_for_file (STRING_SIZE_TUPLE ("SHELL"), file); #if MK_OS_W32 @@ -3704,7 +3705,7 @@ construct_command_argv (char *line, char **restp, struct file *file, ifs = allocated_expand_variable_for_file (STRING_SIZE_TUPLE ("IFS"), file); - warn_undefined_variables_flag = save; + warn_set (wt_undefined_var, save); } argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs, diff --git a/src/main.c b/src/main.c index bf74ab94..221870ab 100644 --- a/src/main.c +++ b/src/main.c @@ -25,6 +25,7 @@ this program. If not, see . */ #include "debug.h" #include "getopt.h" #include "shuffle.h" +#include "warning.h" #include #if MK_OS_W32 @@ -272,10 +273,26 @@ 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; + /* If nonzero, we should print a warning message for each reference to an undefined variable. */ -int warn_undefined_variables_flag; +static int warn_undefined_variables_flag; /* If nonzero, always build all targets, regardless of whether they appear out of date or not. */ @@ -393,7 +410,7 @@ static const char *const usage[] = -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE\n\ Consider FILE to be infinitely new.\n"), N_("\ - --warn-undefined-variables Warn when an undefined variable is referenced.\n"), + --warn[=CONTROL] Control warnings for makefile issues.\n"), NULL }; @@ -439,7 +456,8 @@ struct command_switch Order matters here: this is the order MAKEFLAGS will be constructed. So be sure all simple flags (single char, no argument) come first. */ -#define TEMP_STDIN_OPT (CHAR_MAX+10) +#define TEMP_STDIN_OPT (CHAR_MAX+10) +#define WARN_OPT (CHAR_MAX+13) static struct command_switch switches[] = { @@ -498,7 +516,8 @@ static struct command_switch switches[] = { TEMP_STDIN_OPT, filename, &makefiles, 0, 0, 0, 0, 0, 0, "temp-stdin", 0 }, { CHAR_MAX+11, string, &shuffle_mode, 1, 1, 0, 0, "random", 0, "shuffle", 0 }, { CHAR_MAX+12, string, &jobserver_style, 1, 0, 0, 0, 0, 0, "jobserver-style", 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + { WARN_OPT, strlist, &warn_flags, 1, 1, 0, 0, "warn", NULL, "warn", NULL }, + { 0, 0, NULL, 0, 0, 0, 0, NULL, NULL, NULL, NULL } }; /* Secondary long names for options. */ @@ -644,6 +663,13 @@ initialize_global_hash_tables (void) hash_init_function_table (); } +static void +initialize_warnings () +{ + /* All warnings must have a default. */ + 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. */ @@ -858,6 +884,99 @@ 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] = {"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_undefined_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) { @@ -1198,7 +1317,9 @@ main (int argc, char **argv, char **envp) output_init (&make_sync); - initialize_stopchar_map(); + initialize_stopchar_map (); + + initialize_warnings (); #ifdef SET_STACK_SIZE /* Get rid of any avoidable limit on stack size. */ @@ -3205,8 +3326,9 @@ decode_switches (int argc, const char **argv, enum variable_origin origin) } /* Filter out duplicate options. - * Allow duplicate makefiles for backward compatibility. */ - if (cs->c != 'f') + Order matters for warnings. + Allow duplicate makefiles for backward compatibility. */ + if (cs->c != 'f' && cs->c != WARN_OPT) { unsigned int k; for (k = 0; k < sl->idx; ++k) @@ -3317,6 +3439,7 @@ 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 (); /* Perform any special switch handling. */ @@ -3527,20 +3650,54 @@ define_makeflags (int makefile) case filename: case strlist: - { - struct stringlist *sl = *(struct stringlist **) cs->value_ptr; - if (sl != 0) - { - unsigned int i; - for (i = 0; i < sl->idx; ++i) + 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) { - ADD_OPT (cs); - if (!short_option (cs->c)) - fp = variable_buffer_output (fp, "=", 1); - fp = variable_buffer_output (fp, sl->list[i], strlen (sl->list[i])); + 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 + { + struct stringlist *sl = *(struct stringlist **) cs->value_ptr; + if (sl != 0) + { + unsigned int i; + for (i = 0; i < sl->idx; ++i) + { + ADD_OPT (cs); + if (!short_option (cs->c)) + fp = variable_buffer_output (fp, "=", 1); + fp = variable_buffer_output (fp, sl->list[i], strlen (sl->list[i])); + } + } + } break; default: diff --git a/src/makeint.h b/src/makeint.h index 00f72190..e130e761 100644 --- a/src/makeint.h +++ b/src/makeint.h @@ -738,10 +738,10 @@ extern unsigned short stopchar_map[]; extern int just_print_flag, run_silent, ignore_errors_flag, keep_going_flag; extern int print_data_base_flag, question_flag, touch_flag, always_make_flag; extern int env_overrides, no_builtin_rules_flag, no_builtin_variables_flag; -extern int print_version_flag, check_symlink_flag, export_all_variables; -extern int warn_undefined_variables_flag, posix_pedantic; +extern int print_version_flag, check_symlink_flag, posix_pedantic; extern int not_parallel, second_expansion, clock_skew_detected; extern int rebuilding_makefiles, one_shell, output_sync, verify_flag; +extern int export_all_variables; extern unsigned long command_count; extern const char *default_shell; diff --git a/src/read.c b/src/read.c index 2d25be6a..6913928f 100644 --- a/src/read.c +++ b/src/read.c @@ -27,7 +27,7 @@ this program. If not, see . */ #include "rule.h" #include "debug.h" #include "hash.h" - +#include "warning.h" #if MK_OS_W32 # include @@ -3067,13 +3067,13 @@ tilde_expand (const char *name) int is_variable; { - /* Turn off --warn-undefined-variables while we expand HOME. */ - int save = warn_undefined_variables_flag; - warn_undefined_variables_flag = 0; + /* Turn off undefined variables warning while we expand HOME. */ + enum warning_state save = warn_get (wt_undefined_var); + warn_set (wt_undefined_var, w_ignore); home_dir = allocated_expand_variable (STRING_SIZE_TUPLE ("HOME")); - warn_undefined_variables_flag = save; + warn_set (wt_undefined_var, save); } is_variable = home_dir[0] != '\0'; diff --git a/src/variable.c b/src/variable.c index e2a529de..f1911759 100644 --- a/src/variable.c +++ b/src/variable.c @@ -30,6 +30,7 @@ this program. If not, see . */ #include "pathstuff.h" #endif #include "hash.h" +#include "warning.h" /* Incremented every time we enter target_environment(). */ unsigned long long env_recursion = 0; @@ -1868,15 +1869,19 @@ static const struct defined_vars defined_vars[] = { void warn_undefined (const char *name, size_t len) { - if (warn_undefined_variables_flag) + if (warn_check (wt_undefined_var)) { const struct defined_vars *dp; for (dp = defined_vars; dp->name != NULL; ++dp) if (dp->len == len && memcmp (dp->name, name, len) == 0) return; - error (reading_file, len, _("warning: undefined variable '%.*s'"), - (int)len, name); + 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); } } diff --git a/src/warning.h b/src/warning.h new file mode 100644 index 00000000..778ee407 --- /dev/null +++ b/src/warning.h @@ -0,0 +1,60 @@ +/* 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 . */ + +/* Types of warnings we can show. */ +enum warning_type + { + wt_undefined_var = 0, /* Reference an undefined variable name. */ + wt_max + }; + +/* State of a given warning. */ +enum warning_state + { + w_unset = 0, + w_ignore, + w_warn, + 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]; + +/* Global warning settings. */ +extern enum warning_state warn_global; + +/* 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]) + +/* 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. */ +#define warn_check(_w) (warn_get (_w) > w_ignore) + +/* Check if the warning is ignored. */ +#define warn_ignored(_w) (warn_get (_w) == w_ignore) + +/* Check if the warning is in "warn" mode. */ +#define warn_warned(_w) (warn_get (_w) == w_warn) + +/* Check if the warning is in "error" mode. */ +#define warn_error(_w) (warn_get (_w) == w_error) -- cgit v1.2.1