Implement the new undefine directive.
authorBoris Kolpackov <boris@kolpackov.net>
Tue, 6 Oct 2009 06:56:57 +0000 (06:56 +0000)
committerBoris Kolpackov <boris@kolpackov.net>
Tue, 6 Oct 2009 06:56:57 +0000 (06:56 +0000)
ChangeLog
NEWS
doc/make.texi
main.c
read.c
tests/ChangeLog
tests/scripts/variables/undefine [new file with mode: 0644]
variable.c
variable.h

index 83e687feaccda57a4ef9f0dc29a54ac710add0fd..f68099f22826fdaace79f34f0bea400ddba41c4b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2009-10-06  Boris Kolpackov  <boris@codesynthesis.com>
+
+       * variable.h (undefine_variable_in_set): New function declaration.
+       (undefine_variable_global): New macro.
+
+       * variable.c (undefine_variable_in_set): New function implementation.
+
+       * read.c (vmodifiers): Add undefine_v modifier.
+       (parse_var_assignment): Parse undefine.
+       (do_undefine): Handle the undefine directive.
+       (eval): Call do_undefine if undefine_v is set.
+
+       * main.c (.FEATURES): Add a keyword to indicate the new feature.
+
+       * doc/make.texi (Undefine Directive): Describe the new directive.
+
+       * NEWS: Add a note about the new directive.
+
 2009-10-05  Boris Kolpackov  <boris@codesynthesis.com>
 
        * implicit.c (pattern_search): Initialize file variables only
diff --git a/NEWS b/NEWS
index fd0a25441ed406438021297f618ed429f27ea341..c89ea462e5599d3d64e5592f185972be224be5e6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -47,6 +47,11 @@ Version 3.81.90
   patterns are preferred. To detect this feature search for 'shortest-stem'
   in the .FEATURES special variable.  
 
+* New make directive: 'undefine' allows you to undefine a variable so
+  that it appears as if it was never set. Both $(flavor) and $(origin)
+  functions will return 'undefined' for such a variable. To detect this 
+  feature search for 'undefine in the .FEATURES special variable.
+
 \f
 Version 3.81
 
index 8f86c0cc56230ec3afcc55a6969382ab0abc4e93..52a56ef50eef230e348569800204e5cb88d54e61 100644 (file)
@@ -4711,6 +4711,8 @@ they have particular specialized uses.  @xref{Automatic Variables}.
                                   the user has set it with a command argument.
 * Multi-Line::                  An alternate way to set a variable
                                   to a multi-line string.
+* Undefine Directive::          How to undefine a variable so that it appears
+                                  as if it was never set.
 * Environment::                 Variable values can come from the environment.
 * Target-specific::             Variable values can be defined on a per-target
                                   basis.
@@ -5518,7 +5520,7 @@ See the next section for information about @code{define}.
 @xref{Multi-Line, ,Defining Multi-Line Variables}.
 @end ifnottex
 
-@node Multi-Line, Environment, Override Directive, Using Variables
+@node Multi-Line, Undefine Directive, Override Directive, Using Variables
 @section Defining Multi-Line Variables
 @findex define
 @findex endef
@@ -5599,7 +5601,42 @@ endef
 @noindent
 @xref{Override Directive, ,The @code{override} Directive}.
 
-@node Environment, Target-specific, Multi-Line, Using Variables
+@node Undefine Directive, Environment, Multi-Line, Using Variables
+@section Undefining Variables
+@findex undefine
+@cindex undefining variable
+
+If you want to clear a variable, setting its value to empty is usually
+sufficient. Expanding such a variable will yield the same result (empty
+string) regardless of whether it was set or not. However, if you are
+using the @code{flavor} (@pxref{Flavor Function}) and
+@code{origin} (@pxref{Origin Function}) functions, there is a difference
+between a variable that was never set and a variable with an empty value.
+In such situations you may want to use the @code{undefine} directive to
+make a variable appear as if it was never set. For example:
+
+@example
+foo := foo
+bar = bar
+
+undefine foo
+undefine bar
+
+$(info $(origin foo))
+$(info $(flavor bar))
+@end example
+
+This example will print ``undefined'' for both variables.
+
+If you want to undefine a command-line variable definition, you can use
+the @code{override} directive together with @code{undefine}, similar to
+how this is done for variable definitions:
+
+@example
+override undefine CFLAGS
+@end example
+
+@node Environment, Target-specific, Undefine Directive, Using Variables
 @section Variables from the Environment
 
 @cindex variables, environment
@@ -10485,6 +10522,10 @@ Here is a summary of the directives GNU @code{make} recognizes:
 Define multi-line variables.@*
 @xref{Multi-Line}.
 
+@item undefine @var{variable}
+Undefining variables.@*
+@xref{Undefine Directive}.
+
 @item ifdef @var{variable}
 @itemx ifndef @var{variable}
 @itemx ifeq (@var{a},@var{b})
diff --git a/main.c b/main.c
index b79888c0e0916eb976a1190982f77bfe657efb7b..4d84b6635367d5a0ff9d39eaace400f6fba58250 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1121,7 +1121,7 @@ main (int argc, char **argv, char **envp)
   /* Set up .FEATURES */
   define_variable (".FEATURES", 9,
                    "target-specific order-only second-expansion else-if"
-                   "shortest-stem",
+                   "shortest-stem undefine",
                    o_default, 0);
 #ifndef NO_ARCHIVES
   do_variable_definition (NILF, ".FEATURES", "archives",
diff --git a/read.c b/read.c
index 7dd5090a3546f196d3eea56c06b9d7e193c9fe8a..a86f7915c3720ef5a679bd61497f7ef56c999846 100644 (file)
--- a/read.c
+++ b/read.c
@@ -62,6 +62,7 @@ struct vmodifiers
   {
     unsigned int assign_v:1;
     unsigned int define_v:1;
+    unsigned int undefine_v:1;
     unsigned int export_v:1;
     unsigned int override_v:1;
     unsigned int private_v:1;
@@ -136,6 +137,8 @@ static int eval_makefile (const char *filename, int flags);
 static int eval (struct ebuffer *buffer, int flags);
 
 static long readline (struct ebuffer *ebuf);
+static void do_undefine (char *name, enum variable_origin origin,
+                         struct ebuffer *ebuf);
 static struct variable *do_define (char *name, enum variable_origin origin,
                                    struct ebuffer *ebuf);
 static int conditional_line (char *line, int len, const struct floc *flocp);
@@ -464,12 +467,13 @@ eval_buffer (char *buffer)
   return r;
 }
 \f
-/* Check LINE to see if it's a variable assignment.
+/* Check LINE to see if it's a variable assignment or undefine.
 
    It might use one of the modifiers "export", "override", "private", or it
    might be one of the conditional tokens like "ifdef", "include", etc.
 
-   If it's not a variable assignment, VMOD.V_ASSIGN is 0.  Returns LINE.
+   If it's not a variable assignment or undefine, VMOD.V_ASSIGN is 0.
+   Returns LINE.
 
    Returns a pointer to the first non-modifier character, and sets VMOD
    based on the modifiers found if any, plus V_ASSIGN is 1.
@@ -515,6 +519,13 @@ parse_var_assignment (const char *line, struct vmodifiers *vmod)
           p = next_token (p2);
           break;
         }
+      else if (word1eq ("undefine"))
+        {
+          /* We can't have modifiers after 'undefine' */
+          vmod->undefine_v = 1;
+          p = next_token (p2);
+          break;
+        }
       else
         /* Not a variable or modifier: this is not a variable assignment.  */
         return (char *)line;
@@ -525,7 +536,7 @@ parse_var_assignment (const char *line, struct vmodifiers *vmod)
         return (char *)line;
     }
 
-  /* Found a variable assignment.  */
+  /* Found a variable assignment or undefine.  */
   vmod->assign_v = 1;
   return (char *)p;
 }
@@ -702,7 +713,14 @@ eval (struct ebuffer *ebuf, int set_default)
               continue;
             }
 
-          if (vmod.define_v)
+          if (vmod.undefine_v)
+          {
+            do_undefine (p, origin, ebuf);
+
+            /* This line has been dealt with.  */
+            goto rule_complete;
+          }
+          else if (vmod.define_v)
             v = do_define (p, origin, ebuf);
           else
             v = try_variable_definition (fstart, p, origin, 0);
@@ -1303,6 +1321,28 @@ remove_comments (char *line)
     *comment = '\0';
 }
 
+/* Execute a `undefine' directive.
+   The undefine line has already been read, and NAME is the name of
+   the variable to be undefined. */
+
+static void
+do_undefine (char *name, enum variable_origin origin, struct ebuffer *ebuf)
+{
+  char *p, *var;
+
+  /* Expand the variable name and find the beginning (NAME) and end.  */
+  var = allocated_variable_expand (name);
+  name = next_token (var);
+  if (*name == '\0')
+    fatal (&ebuf->floc, _("empty variable name"));
+  p = name + strlen (name) - 1;
+  while (p > name && isblank ((unsigned char)*p))
+    --p;
+  p[1] = '\0';
+
+  undefine_variable_global (name, p - name + 1, origin);
+}
+
 /* Execute a `define' directive.
    The first line has already been read, and NAME is the name of
    the variable to be defined.  The following lines remain to be read.  */
index b9c3d47974319647e747b6c5ff87b937616817a8..939a5f500dfb300130e203a82517094909b14d1f 100644 (file)
@@ -1,3 +1,7 @@
+2009-10-06  Boris Kolpackov  <boris@codesynthesis.com>
+
+       * scripts/variables/undefine: Tests for the new undefine feature.
+
 2009-10-03  Paul Smith  <psmith@gnu.org>
 
        * scripts/features/parallelism: Test for open Savannah bug #26846.
diff --git a/tests/scripts/variables/undefine b/tests/scripts/variables/undefine
new file mode 100644 (file)
index 0000000..38707b8
--- /dev/null
@@ -0,0 +1,73 @@
+#                                                                    -*-perl-*-
+
+$description = "Test variable undefine.";
+
+$details = "";
+
+# TEST 0: basic undefine functionality
+
+run_make_test('
+a = a
+b := b
+define c
+c
+endef
+
+$(info $(flavor a) $(flavor b) $(flavor c))
+
+n := b
+
+undefine a
+undefine $n
+undefine c
+
+$(info $(flavor a) $(flavor b) $(flavor c))
+
+
+all: ;@:
+',
+'', "recursive simple recursive\nundefined undefined undefined");
+
+
+# TEST 1: override
+
+run_make_test('
+undefine a
+override undefine b
+
+$(info $(flavor a) $(flavor b))
+
+
+all: ;@:
+',
+'a=a b=b', "recursive undefined");
+
+1;
+
+# TEST 2: undefine in eval (make sure we undefine from the global var set)
+
+run_make_test('
+define undef
+$(eval undefine $$1)
+endef
+
+a := a
+$(call undef,a)
+$(info $(flavor a))
+
+
+all: ;@:
+',
+'', "undefined");
+
+
+# TEST 3: Missing variable name
+
+run_make_test('
+a =
+undefine $a
+all: ;@echo ouch
+',
+'', "#MAKEFILE#:3: *** empty variable name.  Stop.\n", 512);
+
+1;
index 6a74dcd6c59876c15d689afaa2f65f6bd783da0f..7cee8bd1c896e0a82b2d42a1e221107092750aac 100644 (file)
@@ -276,6 +276,51 @@ define_variable_in_set (const char *name, unsigned int length,
   return v;
 }
 \f
+
+/* Undefine variable named NAME in SET. LENGTH is the length of NAME, which
+   does not need to be null-terminated. ORIGIN specifies the origin of the
+   variable (makefile, command line or environment). */
+
+static void
+free_variable_name_and_value (const void *item);
+
+void
+undefine_variable_in_set (const char *name, unsigned int length,
+                          enum variable_origin origin,
+                          struct variable_set *set)
+{
+  struct variable *v;
+  struct variable **var_slot;
+  struct variable var_key;
+
+  if (set == NULL)
+    set = &global_variable_set;
+
+  var_key.name = (char *) name;
+  var_key.length = length;
+  var_slot = (struct variable **) hash_find_slot (&set->table, &var_key);
+
+  if (env_overrides && origin == o_env)
+    origin = o_env_override;
+
+  v = *var_slot;
+  if (! HASH_VACANT (v))
+    {
+      if (env_overrides && v->origin == o_env)
+       /* V came from in the environment.  Since it was defined
+          before the switches were parsed, it wasn't affected by -e.  */
+       v->origin = o_env_override;
+
+      /* If the definition is from a stronger source than this one, don't
+         undefine it.  */
+      if ((int) origin >= (int) v->origin)
+       {
+          hash_delete_at (&set->table, var_slot);
+          free_variable_name_and_value (v);
+       }
+    }
+}
+
 /* If the variable passed in is "special", handle its special nature.
    Currently there are two such variables, both used for introspection:
    .VARIABLES expands to a list of all the variables defined in this instance
index 5275911c47e3d243f75ccbbdfaaaa03cb11acddf..84ae55e9421f249b1f14d09563ee7d150e81b389 100644 (file)
@@ -196,6 +196,15 @@ struct variable *define_variable_in_set (const char *name, unsigned int length,
 #define define_variable_for_file(n,l,v,o,r,f) \
           define_variable_in_set((n),(l),(v),(o),(r),(f)->variables->set,NILF)
 
+void undefine_variable_in_set (const char *name, unsigned int length,
+                               enum variable_origin origin,
+                               struct variable_set *set);
+
+/* Remove variable from the current variable set. */
+
+#define undefine_variable_global(n,l,o) \
+          undefine_variable_in_set((n),(l),(o),NULL)
+
 /* Warn that NAME is an undefined variable.  */
 
 #define warn_undefined(n,l) do{\