]> git.ipfire.org Git - thirdparty/make.git/commitdiff
[SV 63347] Always add command line variable assignments to MAKEFLAGS
authorDmitry Goncharov <dgoncharov@users.sf.net>
Sun, 27 Nov 2022 19:09:17 +0000 (14:09 -0500)
committerPaul Smith <psmith@gnu.org>
Mon, 28 Nov 2022 15:50:55 +0000 (10:50 -0500)
This commit introduces two visible changes:
1. Keep command line variable assignments in MAKEFLAGS at all times,
   even while parsing makefiles.
2. Define makeflags immediately when a makefile modifies MAKEFLAGS.

The new MAKEFLAGS and MAKEOVERRIDES initialization procedure:
1. decode_switches (argc, argv, o_command) is called to parse command
   line variable assignments.
2. Command line variable assignments go through quote_for_env.
   Initialize -*-command-variables-*- to the quoted values.
3. MAKEOVERRIDES is initialized to refer to -*-command-variables-*-
   with origin o_env to keep the definitions in the database intact.
4. define_makeflags() is called which adds MAKEOVERRIDES to MAKEFLAGS.
5. Makefiles are parsed.  If a makefile modifies MAKEFLAGS, the new
   value of MAKEFLAGS is defined right away.
6. Env switches are decoded again as o_env.  The definitions set by
   decode_switches at step 1 stay intact, as o_command beats o_env.

We must preserve the original intact definitions in order to detect
failure cases; for example:
  $ cat makefile
  all:; $(hello)
  $ make hello='$(world'
  makefile:1: *** unterminated variable reference.  Stop.

* src/makeint.h: Declare enum variable_origin, struct variable and
define_makeflags().  Add parameter origin to decode_env_switches().
* src/main.c (define_makeflags): Remove "all". If a variable is
assigned on the command line then append MAKEOVERRIDES to MAKEFLAGS.
(decode_env_switches): Replace parameter env with origin.
(decode_switches): Replace parameter env with origin.
Treat origin == o_command as env == 0.
(handle_non_switch_argument): Replace parameter env with origin.
Treat origin == o_command as env == 0.
(main): Call decode_switches() with origin==o_command before parsing
makefiles.  Call decode_switches() with origin==o_env after parsing
makefiles.
* src/variable.c (set_special_var): Define makeflags at parse time,
each time a makefile modifies MAKEFLAGS.
(do_variable_definition): Strip command line variable assignments from
MAKEFLAGS before appending extra flags.  set_special_var() adds them
back.
* tests/scripts/variables/MAKEFLAGS: Add tests.

src/main.c
src/makeint.h
src/variable.c
tests/scripts/variables/MAKEFLAGS

index b31ddd4de8eec612337fdab7080ef9ae42aa7a1d..d8a73725474f3bb53b13e3be9d7e8ceafddc7e64 100644 (file)
@@ -105,8 +105,8 @@ double atof ();
 static void clean_jobserver (int status);
 static void print_data_base (void);
 static void print_version (void);
-static void decode_switches (int argc, const char **argv, int env);
-static struct variable *define_makeflags (int all, int makefile);
+static void decode_switches (int argc, const char **argv,
+                             enum variable_origin origin);
 static char *quote_for_env (char *out, const char *in);
 static void initialize_global_hash_tables (void);
 
@@ -1572,13 +1572,13 @@ main (int argc, char **argv, char **envp)
   /* Decode the switches.  */
   if (lookup_variable (STRING_SIZE_TUPLE (GNUMAKEFLAGS_NAME)))
     {
-      decode_env_switches (STRING_SIZE_TUPLE (GNUMAKEFLAGS_NAME));
+      decode_env_switches (STRING_SIZE_TUPLE (GNUMAKEFLAGS_NAME), o_command);
 
       /* Clear GNUMAKEFLAGS to avoid duplication.  */
       define_variable_cname (GNUMAKEFLAGS_NAME, "", o_env, 0);
     }
 
-  decode_env_switches (STRING_SIZE_TUPLE (MAKEFLAGS_NAME));
+  decode_env_switches (STRING_SIZE_TUPLE (MAKEFLAGS_NAME), o_command);
 
 #if 0
   /* People write things like:
@@ -1599,7 +1599,7 @@ main (int argc, char **argv, char **envp)
     int env_slots = arg_job_slots;
     arg_job_slots = INVALID_JOB_SLOTS;
 
-    decode_switches (argc, (const char **)argv, 0);
+    decode_switches (argc, (const char **)argv, o_command);
     argv_slots = arg_job_slots;
 
     if (arg_job_slots == INVALID_JOB_SLOTS)
@@ -2022,7 +2022,7 @@ main (int argc, char **argv, char **envp)
 
   /* Set up the MAKEFLAGS and MFLAGS variables for makefiles to see.
      Initialize it to be exported but allow the makefile to reset it.  */
-  define_makeflags (0, 0)->export = v_export;
+  define_makeflags (0)->export = v_export;
 
   /* Define the default variables.  */
   define_default_variables ();
@@ -2072,12 +2072,12 @@ main (int argc, char **argv, char **envp)
     arg_job_slots = INVALID_JOB_SLOTS;
 
     /* Decode switches again, for variables set by the makefile.  */
-    decode_env_switches (STRING_SIZE_TUPLE (GNUMAKEFLAGS_NAME));
+    decode_env_switches (STRING_SIZE_TUPLE (GNUMAKEFLAGS_NAME), o_env);
 
     /* Clear GNUMAKEFLAGS to avoid duplication.  */
     define_variable_cname (GNUMAKEFLAGS_NAME, "", o_override, 0);
 
-    decode_env_switches (STRING_SIZE_TUPLE (MAKEFLAGS_NAME));
+    decode_env_switches (STRING_SIZE_TUPLE (MAKEFLAGS_NAME), o_env);
 #if 0
     decode_env_switches (STRING_SIZE_TUPLE ("MFLAGS"));
 #endif
@@ -2260,7 +2260,7 @@ main (int argc, char **argv, char **envp)
 
   /* Set up MAKEFLAGS and MFLAGS again, so they will be right.  */
 
-  define_makeflags (1, 0);
+  define_makeflags (0);
 
   /* Make each 'struct goaldep' point at the 'struct file' for the file
      depended on.  Also do magic for special targets.  */
@@ -2420,7 +2420,7 @@ main (int argc, char **argv, char **envp)
       }
 
       /* Set up 'MAKEFLAGS' specially while remaking makefiles.  */
-      define_makeflags (1, 1);
+      define_makeflags (1);
 
       {
         int orig_db_level = db_level;
@@ -2812,7 +2812,7 @@ main (int argc, char **argv, char **envp)
     }
 
   /* Set up 'MAKEFLAGS' again for the normal targets.  */
-  define_makeflags (1, 0);
+  define_makeflags (0);
 
   /* Set always_make_flag if -B was given.  */
   always_make_flag = always_make_set;
@@ -3000,7 +3000,7 @@ init_switches (void)
 
 /* Non-option argument.  It might be a variable definition.  */
 static void
-handle_non_switch_argument (const char *arg, int env)
+handle_non_switch_argument (const char *arg, enum variable_origin origin)
 {
   struct variable *v;
 
@@ -3033,7 +3033,7 @@ handle_non_switch_argument (const char *arg, int env)
       }
   }
 #endif
-  v = try_variable_definition (0, arg, o_command, 0);
+  v = try_variable_definition (0, arg, origin, 0);
   if (v != 0)
     {
       /* It is indeed a variable definition.  If we don't already have this
@@ -3053,11 +3053,12 @@ handle_non_switch_argument (const char *arg, int env)
           command_variables = cv;
         }
     }
-  else if (! env)
+  else if (arg[0] != '\0' && origin == o_command)
     {
-      /* Not an option or variable definition; it must be a goal
-         target!  Enter it as a file and add it to the dep chain of
-         goals.  */
+      /* Not an option or variable definition; it must be a goal target.
+         Enter it as a file and add it to the dep chain of goals.
+         Check ARG[0] because if the top makefile resets MAKEOVERRIDES
+         then ARG points to an empty string in the submake.  */
       struct file *f = enter_file (strcache_add (expand_command_line_file (arg)));
       f->cmd_target = 1;
 
@@ -3105,7 +3106,7 @@ handle_non_switch_argument (const char *arg, int env)
    They came from the environment if ENV is nonzero.  */
 
 static void
-decode_switches (int argc, const char **argv, int env)
+decode_switches (int argc, const char **argv, enum variable_origin origin)
 {
   int bad = 0;
   const struct command_switch *cs;
@@ -3119,7 +3120,7 @@ decode_switches (int argc, const char **argv, int env)
 
   /* Let getopt produce error messages for the command line,
      but not for options from the environment.  */
-  opterr = !env;
+  opterr = origin == o_command;
   /* Reset getopt's state.  */
   optind = 0;
 
@@ -3135,7 +3136,7 @@ decode_switches (int argc, const char **argv, int env)
         break;
       else if (c == 1)
         /* An argument not starting with a dash.  */
-        handle_non_switch_argument (coptarg, env);
+        handle_non_switch_argument (coptarg, origin);
       else if (c == '?')
         /* Bad option.  We will print a usage message and die later.
            But continue to parse the other options so the user can
@@ -3149,7 +3150,7 @@ decode_switches (int argc, const char **argv, int env)
                  this switch.  We test this individually inside the
                  switch below rather than just once outside it, so that
                  options which are to be ignored still consume args.  */
-              int doit = !env || cs->env;
+              int doit = origin == o_command || cs->env;
 
               switch (cs->type)
                 {
@@ -3299,9 +3300,9 @@ decode_switches (int argc, const char **argv, int env)
      to be returned in order, this only happens when there is a "--"
      argument to prevent later arguments from being options.  */
   while (optind < argc)
-    handle_non_switch_argument (argv[optind++], env);
+    handle_non_switch_argument (argv[optind++], origin);
 
-  if (bad && !env)
+  if (bad && origin == o_command)
     print_usage (bad);
 
   /* If there are any options that need to be decoded do it now.  */
@@ -3321,7 +3322,7 @@ decode_switches (int argc, const char **argv, int env)
    decode_switches.  */
 
 void
-decode_env_switches (const char *envar, size_t len)
+decode_env_switches (const char *envar, size_t len, enum variable_origin origin)
 {
   char *varref = alloca (2 + len + 2);
   char *value, *p, *buf;
@@ -3383,7 +3384,7 @@ decode_env_switches (const char *envar, size_t len)
     argv[1] = buf;
 
   /* Parse those words.  */
-  decode_switches (argc, argv, 1);
+  decode_switches (argc, argv, origin);
 }
 \f
 /* Quote the string IN so that it will be interpreted as a single word with
@@ -3408,11 +3409,11 @@ quote_for_env (char *out, const char *in)
 }
 
 /* Define the MAKEFLAGS and MFLAGS variables to reflect the settings of the
-   command switches.  Include options with args if ALL is nonzero.
+   command switches. Always include options with args.
    Don't include options with the 'no_makefile' flag set if MAKEFILE.  */
 
-static struct variable *
-define_makeflags (int all, int makefile)
+struct variable *
+define_makeflags (int makefile)
 {
   const char ref[] = "MAKEOVERRIDES";
   const char posixref[] = "-*-command-variables-*-";
@@ -3597,25 +3598,24 @@ define_makeflags (int all, int makefile)
       p = mempcpy (p, evalref, CSTRLEN (evalref));
     }
 
-  if (all)
-    {
-      /* If there are any overrides to add, write a reference to
-         $(MAKEOVERRIDES), which contains command-line variable definitions.
-         Separate the variables from the switches with a "--" arg.  */
+  {
+    /* If there are any overrides to add, write a reference to
+       $(MAKEOVERRIDES), which contains command-line variable definitions.
+       Separate the variables from the switches with a "--" arg.  */
 
-      const char *r = posix_pedantic ? posixref : ref;
-      size_t l = strlen (r);
-      v = lookup_variable (r, l);
+    const char *r = posix_pedantic ? posixref : ref;
+    size_t l = strlen (r);
+    v = lookup_variable (r, l);
 
-      if (v && v->value && v->value[0] != '\0')
-        {
-          p = stpcpy (p, " -- ");
-          *(p++) = '$';
-          *(p++) = '(';
-          p = mempcpy (p, r, l);
-          *(p++) = ')';
-        }
-    }
+    if (v && v->value && v->value[0] != '\0')
+      {
+        p = stpcpy (p, " -- ");
+        *(p++) = '$';
+        *(p++) = '(';
+        p = mempcpy (p, r, l);
+        *(p++) = ')';
+      }
+  }
 
   /* If there is a leading dash, omit it.  */
   if (flagstring[0] == '-')
index c726abe08e4e9c1c6878bf1a77accaff2108991b..d6ac21b8ff5b82cca72ae684f7045fb73df7530c 100644 (file)
@@ -558,7 +558,11 @@ void out_of_memory (void) NORETURN;
 #define ONS(_t,_a,_f,_n,_s)   _t((_a), INTSTR_LENGTH + strlen (_s), \
                                  (_f), (_n), (_s))
 
-void decode_env_switches (const char*, size_t line);
+enum variable_origin;
+void decode_env_switches (const char*, size_t line,
+                          enum variable_origin origin);
+struct variable;
+struct variable *define_makeflags (int makefile);
 void temp_stdin_unlink (void);
 void die (int) NORETURN;
 void pfatal_with_name (const char *) NORETURN;
index 0bd9963b38e4bf1fdbe485a850d25937efb8a9df..d8bf42886cfcdb216c639ff7efe2a769e3d43954 100644 (file)
@@ -1215,7 +1215,7 @@ target_environment (struct file *file, int recursive)
 }
 \f
 static struct variable *
-set_special_var (struct variable *var)
+set_special_var (struct variable *var, enum variable_origin origin)
 {
   if (streq (var->name, RECIPEPREFIX_NAME))
     {
@@ -1225,7 +1225,10 @@ set_special_var (struct variable *var)
       cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0];
     }
   else if (streq (var->name, MAKEFLAGS_NAME))
-    decode_env_switches (STRING_SIZE_TUPLE(MAKEFLAGS_NAME));
+    {
+      decode_env_switches (STRING_SIZE_TUPLE(MAKEFLAGS_NAME), origin);
+      define_makeflags (rebuilding_makefiles);
+    }
 
   return var;
 }
@@ -1261,7 +1264,7 @@ do_variable_definition (const floc *flocp, const char *varname,
                         const char *value, enum variable_origin origin,
                         enum variable_flavor flavor, int target_var)
 {
-  const char *p;
+  const char *newval;
   char *alloc_value = NULL;
   struct variable *v;
   int append = 0;
@@ -1276,7 +1279,7 @@ do_variable_definition (const floc *flocp, const char *varname,
          We have to allocate memory since otherwise it'll clobber the
          variable buffer, and we may still need that if we're looking at a
          target-specific variable.  */
-      p = alloc_value = allocated_variable_expand (value);
+      newval = alloc_value = allocated_variable_expand (value);
       break;
     case f_expand:
       {
@@ -1285,16 +1288,16 @@ do_variable_definition (const floc *flocp, const char *varname,
            tokens to '$$' to resolve to '$' when recursively expanded.  */
         char *t = allocated_variable_expand (value);
         char *np = alloc_value = xmalloc (strlen (t) * 2 + 1);
-        p = t;
-        while (p[0] != '\0')
+        char *op = t;
+        while (op[0] != '\0')
           {
-            if (p[0] == '$')
+            if (op[0] == '$')
               *(np++) = '$';
-            *(np++) = *(p++);
+            *(np++) = *(op++);
           }
         *np = '\0';
-        p = alloc_value;
         free (t);
+        newval = alloc_value;
         break;
       }
     case f_shell:
@@ -1302,9 +1305,10 @@ do_variable_definition (const floc *flocp, const char *varname,
         /* A shell definition "var != value".  Expand value, pass it to
            the shell, and store the result in recursively-expanded var. */
         char *q = allocated_variable_expand (value);
-        p = alloc_value = shell_result (q);
+        alloc_value = shell_result (q);
         free (q);
         flavor = f_recursive;
+        newval = alloc_value;
         break;
       }
     case f_conditional:
@@ -1320,7 +1324,7 @@ do_variable_definition (const floc *flocp, const char *varname,
     case f_recursive:
       /* A recursive variable definition "var = value".
          The value is used verbatim.  */
-      p = value;
+      newval = value;
       break;
     case f_append:
     case f_append_value:
@@ -1345,15 +1349,16 @@ do_variable_definition (const floc *flocp, const char *varname,
           {
             /* There was no old value.
                This becomes a normal recursive definition.  */
-            p = value;
+            newval = value;
             flavor = f_recursive;
           }
         else
           {
             /* Paste the old and new values together in VALUE.  */
 
-            size_t oldlen, vallen;
+            size_t oldlen, vallen, alloclen;
             const char *val;
+            char *cp;
             char *tp = NULL;
 
             val = value;
@@ -1378,18 +1383,25 @@ do_variable_definition (const floc *flocp, const char *varname,
               }
 
             oldlen = strlen (v->value);
-            p = alloc_value = xmalloc (oldlen + 1 + vallen + 1);
+            alloclen = oldlen + 1 + vallen + 1;
+            cp = alloc_value = xmalloc (alloclen);
 
             if (oldlen)
               {
-                memcpy (alloc_value, v->value, oldlen);
-                alloc_value[oldlen] = ' ';
-                ++oldlen;
+                char *s;
+                if (streq (varname, MAKEFLAGS_NAME)
+                    && (s = strstr (v->value, " -- ")))
+                  /* We found a separator in MAKEFLAGS.  Ignore variable
+                     assignments: set_special_var() will reconstruct things.  */
+                  cp = mempcpy (cp, v->value, s - v->value);
+                else
+                  cp = mempcpy (cp, v->value, oldlen);
+                *(cp++) = ' ';
               }
 
-            memcpy (&alloc_value[oldlen], val, vallen + 1);
-
+            memcpy (cp, val, vallen + 1);
             free (tp);
+            newval = alloc_value;
           }
       }
       break;
@@ -1399,6 +1411,8 @@ do_variable_definition (const floc *flocp, const char *varname,
       abort ();
     }
 
+  assert (newval);
+
 #ifdef __MSDOS__
   /* Many Unix Makefiles include a line saying "SHELL=/bin/sh", but
      non-Unix systems don't conform to this default configuration (in
@@ -1440,16 +1454,16 @@ do_variable_definition (const floc *flocp, const char *varname,
           char *fake_env[2];
           size_t pathlen = 0;
 
-          shellbase = strrchr (p, '/');
-          bslash = strrchr (p, '\\');
+          shellbase = strrchr (newval, '/');
+          bslash = strrchr (newval, '\\');
           if (!shellbase || bslash > shellbase)
             shellbase = bslash;
-          if (!shellbase && p[1] == ':')
-            shellbase = p + 1;
+          if (!shellbase && newval[1] == ':')
+            shellbase = newval + 1;
           if (shellbase)
             shellbase++;
           else
-            shellbase = p;
+            shellbase = newval;
 
           /* Search for the basename of the shell (with standard
              executable extensions) along the $PATH.  */
@@ -1490,7 +1504,7 @@ do_variable_definition (const floc *flocp, const char *varname,
          set no_default_sh_exe to indicate sh was found and
          set new value for SHELL variable.  */
 
-      if (find_and_set_default_shell (p))
+      if (find_and_set_default_shell (newval))
         {
           v = define_variable_in_set (varname, strlen (varname), default_shell,
                                       origin, flavor == f_recursive,
@@ -1504,11 +1518,11 @@ do_variable_definition (const floc *flocp, const char *varname,
         {
           char *tp = alloc_value;
 
-          alloc_value = allocated_variable_expand (p);
+          alloc_value = allocated_variable_expand (newval);
 
           if (find_and_set_default_shell (alloc_value))
             {
-              v = define_variable_in_set (varname, strlen (varname), p,
+              v = define_variable_in_set (varname, strlen (varname), newval,
                                           origin, flavor == f_recursive,
                                           (target_var
                                            ? current_variable_set_list->set
@@ -1536,7 +1550,7 @@ do_variable_definition (const floc *flocp, const char *varname,
      invoked in places where we want to define globally visible variables,
      make sure we define this variable in the global set.  */
 
-  v = define_variable_in_set (varname, strlen (varname), p, origin,
+  v = define_variable_in_set (varname, strlen (varname), newval, origin,
                               flavor == f_recursive || flavor == f_expand,
                               (target_var
                                ? current_variable_set_list->set : NULL),
@@ -1546,7 +1560,7 @@ do_variable_definition (const floc *flocp, const char *varname,
 
  done:
   free (alloc_value);
-  return v->special ? set_special_var (v) : v;
+  return v->special ? set_special_var (v, origin) : v;
 }
 \f
 /* Parse P (a null-terminated string) as a variable definition.
index a41f1cf0db2784a3975cb74976544e034c653167..e81c3277520fd01cd8b66b53174b56e552519351 100644 (file)
@@ -65,79 +65,113 @@ rmdir('bar');
 # Test that command line switches are all present in MAKEFLAGS.
 # sv 62514.
 my @opts;
+my @flavors;
 
 # Simple flags.
 @opts = ('i', 'k', 'n', 'q', 'r', 's', 'w', 'd');
 exists $FEATURES{'check-symlink'} and push @opts, 'L';
+@flavors = ('=', ':=', ':::=', '+=-');
 
+for my $fl (@flavors) {
 for my $opt (@opts) {
-  run_make_test(q!
-MAKEFLAGS:=B
-all:; $(info makeflags='$(MAKEFLAGS)')
-!, "-$opt", "/makeflags='B$opt'/");
+  run_make_test("
+MAKEFLAGS${fl}B
+all:; \$(info makeflags='\$(MAKEFLAGS)')
+", "-$opt", "/makeflags='B$opt'/");
+}
 }
 
 # Switches which carry arguments.
 @opts = (' -I/tmp', ' -Onone', ' --debug=b', ' -l2.5');
+for my $fl (@flavors) {
 for my $opt (@opts) {
-  run_make_test(q!
-MAKEFLAGS:=B
-all:; $(info makeflags='$(MAKEFLAGS)')
-!, "$opt", "/makeflags='B$opt'/");
+  run_make_test("
+MAKEFLAGS${fl}B
+all:; \$(info makeflags='\$(MAKEFLAGS)')
+", "$opt", "/makeflags='B$opt'/");
+}
 }
 
 # Long options which take no arguments.
 # sv 62514.
 @opts = (' --no-print-directory', ' --warn-undefined-variables', ' --trace');
+for my $fl (@flavors) {
 for my $opt (@opts) {
-run_make_test(q!
-MAKEFLAGS:=B
-all:; $(info makeflags='$(MAKEFLAGS)')
-!, "$opt", "/makeflags='B$opt'/");
+run_make_test("
+MAKEFLAGS${fl}B
+all:; \$(info makeflags='\$(MAKEFLAGS)')
+", "$opt", "/makeflags='B$opt'/");
+}
 }
 
 # Test that make filters out duplicates.
 # Each option is specified in the makefile, env and on the command line.
 @opts = (' -I/tmp', ' -Onone', ' --debug=b', ' -l2.5');
-$ENV{'MAKEFLAGS'} = $opt;
+for my $fl (@flavors) {
 for my $opt (@opts) {
+$ENV{'MAKEFLAGS'} = $opt;
   run_make_test("
-MAKEFLAGS:=B $opt
+MAKEFLAGS${fl}B $opt
 all:; \$(info makeflags='\$(MAKEFLAGS)')
 ", "$opt", "/makeflags='B$opt'/");
 }
+}
 
 # Test that make filters out duplicates.
 # Each option is specified in the makefile, env and on the command line.
 # decode_switches reallocates when the number of parameters in sl->list exceeds 5.
 # This test exercises the realloc branch.
+for my $fl (@flavors) {
 $ENV{'MAKEFLAGS'} = '-I1 -Onone --debug=b -l2.5 -I2 -I3 -I4 -I5 -I6 -I2 -I2';
-run_make_test(q!
-MAKEFLAGS:=B -I1 -Onone --debug=b -l2.5 -I2 -I3 -I4 -I5 -I6 -I2 -I2
-all:; $(info makeflags='$(MAKEFLAGS)')
-!,
+run_make_test("
+MAKEFLAGS${fl}B -I1 -Onone --debug=b -l2.5 -I2 -I3 -I4 -I5 -I6 -I2 -I2
+all:; \$(info makeflags='\$(MAKEFLAGS)')
+",
 '-I1 -Onone --debug=b -l2.5 -I2 -I3 -I4 -I5 -I6',
 "/makeflags='B -I1 -I2 -I3 -I4 -I5 -I6 -l2.5 -Onone --debug=b'/");
+}
 
 # A mix of multiple flags from env, the makefile and command line.
 # Skip -L since it's not available everywhere
+for my $fl (@flavors) {
 $ENV{'MAKEFLAGS'} = 'ikB --no-print-directory --warn-undefined-variables --trace';
-run_make_test(q!
-MAKEFLAGS:=iknqrswd -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5
-all:; $(info makeflags='$(MAKEFLAGS)')
-!,
+run_make_test("
+MAKEFLAGS${fl}iknqrswd -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5
+all:; \$(info makeflags='\$(MAKEFLAGS)')
+",
 '-Onone -l2.5 -l2.5 -Onone -I/tmp -iknqrswd -i -n -s -k -I/tmp',
 "/makeflags='Bdiknqrsw -I/tmp -l2.5 -Onone --trace --warn-undefined-variables'/");
+}
 
-# Verify MAKEFLAGS are all available to shell functions
+# Verify MAKEFLAGS are all available to shell function at parse time.
+for my $fl (@flavors) {
+my $answer = 'Biknqrs -I/tmp -l2.5 -Onone --no-print-directory --warn-undefined-variables';
 $ENV{'MAKEFLAGS'} = 'ikB --no-print-directory --warn-undefined-variables';
-run_make_test(q!
-MAKEFLAGS := iknqrsw -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5 --no-print-directory
-XX := $(shell echo "$$MAKEFLAGS")
-all:; $(info makeflags='$(XX)')
-!,
-    '-Onone -l2.5 -l2.5 -Onone -I/tmp -iknqrs -i -n -s -k -I/tmp',
-    "makeflags='iknqrsw -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5 --no-print-directory'");
+run_make_test("
+MAKEFLAGS${fl}iknqrsw -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5 --no-print-directory
+\$(info at parse time '\$(MAKEFLAGS)')
+XX := \$(shell echo \"\$\$MAKEFLAGS\")
+all:; \$(info at build time makeflags='\$(XX)')
+",
+'-Onone -l2.5 -l2.5 -Onone -I/tmp -iknqrs -i -n -s -k -I/tmp',
+"at parse time '$answer'
+at build time makeflags='$answer'");
+}
+
+# Verify MAKEFLAGS and command line definitions are all available to shell function at parse time.
+for my $fl (@flavors) {
+$ENV{'MAKEFLAGS'} = 'ikB --no-print-directory --warn-undefined-variables';
+my $answer = 'Biknqrs -I/tmp -l2.5 -Onone --no-print-directory --warn-undefined-variables -- hello=world';
+run_make_test("
+MAKEFLAGS${fl}iknqrsw -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5 --no-print-directory
+\$(info at parse time '\$(MAKEFLAGS)')
+XX := \$(shell echo \"\$\$MAKEFLAGS\")
+all:; \$(info at build time makeflags='\$(XX)')
+",
+'-Onone -l2.5 -l2.5 -Onone -I/tmp -iknqrs -i -n -s -k -I/tmp hello=world',
+"at parse time '$answer'
+at build time makeflags='$answer'");
+}
 
 # Verify that command line arguments are included in MAKEFLAGS
 run_make_test(q!
@@ -155,4 +189,183 @@ echo /erR --trace --no-print-directory -- FOO=bar/
 /erR --trace --no-print-directory -- FOO=bar/");
 
 
+# sv 63347.
+# Verify that command line arguments are included in MAKEFLAGS
+# when makefiles are parsed.
+my $answer = 'erR -- hello:=world FOO=bar';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, '-e FOO=bar -r -R hello:=world',
+"$answer
+$answer
+#MAKE#: 'all' is up to date.\n");
+
+# sv 63347.
+# Same as above, with makefile setting the value of the same variables as
+# defined on the cli.
+my $answer = 'erR -- hello:=world FOO=bar';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+FOO=moon
+hello:=moon
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, '-e FOO=bar -r -R hello:=world',
+"$answer
+$answer
+$answer
+#MAKE#: 'all' is up to date.\n");
+
+# sv 63347.
+# Same as above, with makefile overriding the value of cli definition.
+my $answer = 'erR -- hello:=world FOO=bar';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+override FOO=moon
+override hello:=moon
+export hello
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, '-e FOO=bar -r -R hello:=world',
+"$answer
+$answer
+$answer
+#MAKE#: 'all' is up to date.\n");
+
+# Same as above, and makefile overrides the value of cli definition.
+# resets MAKEOVERRIDES.
+my $answer = 'rR -- hello:=world FOO=bar';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+override FOO=moon
+override hello:=moon
+export hello
+$(info $(MAKEFLAGS))
+MAKEOVERRIDES=
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, 'FOO=bar -r -R hello:=world',
+"$answer
+$answer
+rR -- \nrR
+#MAKE#: 'all' is up to date.\n");
+
+# sv 63347.
+# MAKEFLAGS set is env and makefile sets MAKEFLAGS and there is a command
+# line definition.
+my $answer = ' -- bye=moon hello=world';
+$ENV{'MAKEFLAGS'} = 'hello=world';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, 'bye=moon',
+" -- bye=moon hello=world
+ -- bye=moon hello=world
+#MAKE#: 'all' is up to date.\n");
+
+# sv 63347.
+# Conditional assignment and MAKEFLAGS.
+my $answer = 'B -- bye=moon hello=world';
+$ENV{'MAKEFLAGS'} = 'hello=world';
+run_make_test(q!
+$(info $(MAKEFLAGS))
+MAKEFLAGS?=-k
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, '-B bye=moon',
+"$answer
+$answer
+$answer
+#MAKE#: 'all' is up to date.\n");
+
+# sv 63347.
+# MAKEFLAGS set is env and makefile sets MAKEFLAGS and there is a command
+# line definition.
+for my $fl (@flavors) {
+my $answer = ' -- bye=moon hello=world';
+$ENV{'MAKEFLAGS'} = 'hello=world';
+run_make_test("
+\$(info \$(MAKEFLAGS))
+MAKEFLAGS${fl}R
+\$(info \$(MAKEFLAGS))
+all:; \$(info \$(MAKEFLAGS))
+", 'bye=moon',
+"$answer
+R$answer
+rR$answer
+#MAKE#: 'all' is up to date.\n");
+}
+
+# sv 63347.
+# Test changes introduced by makefiles to MAKEFLAGS.
+for my $fl (@flavors) {
+my $answer = 'rR --no-print-directory -- hello:=world FOO=bar';
+run_make_test(q!
+MAKEFLAGS+=--no-print-directory
+$(info $(MAKEFLAGS))
+MAKEFLAGS+=-k
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, 'FOO=bar -r -R hello:=world',
+"$answer
+k$answer
+k$answer
+#MAKE#: 'all' is up to date.\n");
+}
+
+# sv 63347.
+# Test changes introduced by makefiles to MAKEFLAGS.
+# Same as above, but with -e.
+for my $fl (@flavors) {
+my $answer = 'erR -- hello:=world FOO=bar';
+run_make_test(q!
+MAKEFLAGS+=--no-print-directory
+$(info $(MAKEFLAGS))
+MAKEFLAGS+=-k
+$(info $(MAKEFLAGS))
+all:; $(info $(MAKEFLAGS))
+!, '-e FOO=bar -r -R hello:=world',
+"$answer
+$answer
+$answer
+#MAKE#: 'all' is up to date.\n");
+}
+
+mkdir('bye', 0777);
+
+create_file('bye/makefile',
+'hello=moon
+all:; $(info $(hello))');
+
+# sv 63347.
+# Test that a cli definition takes precendence over a definition set in
+# submake.
+run_make_test(q!
+v:=$(shell $(MAKE) -C bye --no-print-directory)
+all: ; $(info $(v))
+!, 'hello=world', "world #MAKE#[1]: 'all' is up to date.\n#MAKE#: 'all' is up to date.");
+
+# Same as above with the shell assignment operator.
+run_make_test(q!
+v \!= $(MAKE) -C bye --no-print-directory
+all: ; $(info $(v))
+!, 'hello=world', "world #MAKE#[1]: 'all' is up to date.\n#MAKE#: 'all' is up to date.");
+
+unlink('bye/makefile');
+rmdir('bye');
+
+# sv 63347
+# Invalid command line variable definition.
+run_make_test(q!
+all:; $(info $(hello))
+!, 'hello=\'$(world\'', "#MAKEFILE#:2: *** unterminated variable reference.  Stop.\n", 512);
+
+# sv 63347
+# An unused invalid command line variable definition is ignored.
+run_make_test(q!
+all:; $(info good)
+!, 'hello=\'$(world\'', "good\n#MAKE#: 'all' is up to date.\n");
+
+
 1;