Re: PATCH: a bit of introspection in make

2023-08-09 Thread Thomas Frohwein
On Tue, Aug 08, 2023 at 01:02:53PM +0200, Marc Espie wrote:
> Actually, as far as bsd.port.mk, it doesn't need to
> move too much stuff around thanks to make's lazyness.
> 
> Note that having a list of defined MASTER_SITES variables simplifies
> the check.
> 
> I've also added a check for the right MASTER_SITES to be defined,
> since currently we do not error out until actually using it, which
> means that fiddling around with MASTER_SITES before committing may
> often go unnoticed.
> 
> (That final part is meant to go in sooner rather than later)

this simplifies things overall a little (because var.c does more of the
heavy lifting now). checked this on limited scale with
`make show=_ALL_MASTER_SITES` working as expected. The DISTFILE
assignment to non-`:[0-9]` MASTER_SITESx needs a little more work, to
fully function, but this is a step in that direction.

ok thfr@ for this.

> Index: bsd.port.mk
> ===
> RCS file: /cvs/ports/infrastructure/mk/bsd.port.mk,v
> retrieving revision 1.1592
> diff -u -p -r1.1592 bsd.port.mk
> --- bsd.port.mk   13 Jun 2023 10:28:40 -  1.1592
> +++ bsd.port.mk   8 Aug 2023 10:59:38 -
> @@ -118,9 +118,8 @@ _ALL_VARIABLES_PER_ARCH =
>  # consumers of (dump-vars) include sqlports generation and dpb
>  # dpb doesn't need everything, those are speed optimizations
>  .if ${DPB:L:Mfetch} || ${DPB:L:Mall}
> -_ALL_VARIABLES += DISTFILES PATCHFILES SUPDISTFILES DIST_SUBDIR MASTER_SITES 
> \
> - MASTER_SITES0 MASTER_SITES1 MASTER_SITES2 MASTER_SITES3 MASTER_SITES4 \
> - MASTER_SITES5 MASTER_SITES6 MASTER_SITES7 MASTER_SITES8 MASTER_SITES9 \
> +_ALL_VARIABLES += DISTFILES PATCHFILES SUPDISTFILES DIST_SUBDIR \
> + ${_ALL_MASTER_SITES} \
>   CHECKSUM_FILE FETCH_MANUALLY MISSING_FILES PERMIT_DISTFILES
>  .endif
>  .if ${DPB:L:Mtest} || ${DPB:L:Mall}
> @@ -1280,19 +1280,15 @@ MASTER_SITES ?=
>  # sites for distfiles, add them to MASTER_SITE_BACKUP
>  
>  _warn_checksum = :
> -.if !empty(MASTER_SITES:M*[^/])
> -_warn_checksum += ;echo ">>> MASTER_SITES not ending in /: 
> ${MASTER_SITES:M*[^/]}"
> -.endif
>  
> -.for _I in 0 1 2 3 4 5 6 7 8 9
> -.  if defined(MASTER_SITES${_I})
> -.if !empty(MASTER_SITES${_I}:M*[^/])
> -_warn_checksum += ;echo ">>> MASTER_SITES${_I} not ending in /: 
> ${MASTER_SITES${_I}:M*[^/]}"
> -.endif
> +_ALL_MASTER_SITES = ${.VARIABLES:MMASTER_SITES*:NMASTER_SITES_*}
> +
> +.for _S in ${_ALL_MASTER_SITES}
> +.  if !empty(${_S}:M*[^/])
> +_warn_checksum += ;echo ">>> ${_S} not ending in /: ${${_S}:M*[^/]}"
>  .  endif
>  .endfor
>  
> -
>  EXTRACT_SUFX ?= .tar.gz
>  
>  .if !empty(GH_COMMIT)
> @@ -1322,6 +1318,9 @@ _FILES=
>  .  if !empty($v)
>  .for e in ${$v}
>  .  for f m u in ${e:C/:[0-9]$//:C/^(.*)\{.*\}(.*)$/\1\2/} 
> MASTER_SITES${e:M*\:[0-9]:C/^.*:([0-9])$/\1/} 
> ${e:C/:[0-9]$//:C/^.*\{(.*)\}(.*)$/\1\2/}
> +.if !defined($m)
> +ERRORS += "Fatal: $m is not defined but referenced by $e in $v"
> +.endif
>  .if empty(_FILES:M$f)
>  _FILES += $f
>  .  if empty(DIST_SUBDIR)



Re: PATCH: a bit of introspection in make

2023-08-09 Thread Thomas Frohwein
On Tue, Aug 08, 2023 at 12:51:43PM +0200, Marc Espie wrote:
> Here's a revised diff (reordered plus actual use of the boolean)
> plus concrete example  use for bsd.port.mk (disregarding the fact
> _ALL_VARIABLES would have to move *after* all MASTER_SITES have been defined.

I tested this on a small scale and it looks okay to me. ok thfr@ for
var.c part; see other email for the bsd.port.mk part.

> Index: var.c
> ===
> RCS file: /cvs/src/usr.bin/make/var.c,v
> retrieving revision 1.104
> diff -u -p -r1.104 var.c
> --- var.c 9 Jun 2022 13:13:14 -   1.104
> +++ var.c 8 Aug 2023 10:48:05 -
> @@ -104,6 +104,8 @@ static char   varNoError[] = "";
>  bool errorIsOkay;
>  static bool  checkEnvFirst;  /* true if environment should be searched for
>* variables before the global context */
> + /* do we need to recompute varname_list */
> +static bool  varname_list_changed = true;
>  
>  void
>  Var_setCheckEnvFirst(bool yes)
> @@ -222,6 +224,7 @@ typedef struct Var_ {
>  #define VAR_FROM_ENV 8   /* Special source: environment */
>  #define VAR_SEEN_ENV 16  /* No need to go look up environment again */
>  #define VAR_IS_SHELL 32  /* Magic behavior */
> +#define VAR_IS_NAMES 1024/* Very expensive, only defined when needed */
>  /* XXX there are also some flag values which are part of the visible API
>   * and thus defined inside var.h, don't forget to look there if you want
>   * to define some new flags !
> @@ -231,6 +234,8 @@ typedef struct Var_ {
>   char name[1];   /* the variable's name */
>  }  Var;
>  
> +/* for GNU make compatibility */
> +#define VARNAME_LIST ".VARIABLES"
>  
>  static struct ohash_info var_info = {
>   offsetof(Var, name),
> @@ -245,10 +250,11 @@ static void fill_from_env(Var *);
>  static Var *create_var(const char *, const char *);
>  static void var_set_initial_value(Var *, const char *);
>  static void var_set_value(Var *, const char *);
> -#define var_get_value(v) ((v)->flags & VAR_EXEC_LATER ? \
> - var_exec_cmd(v) : \
> - Buf_Retrieve(&((v)->val)))
> -static char *var_exec_cmd(Var *);
> +static char *var_get_value(Var *);
> +static void var_exec_cmd(Var *);
> +static void varname_list_retrieve(Var *);
> +
> +
>  static void var_append_value(Var *, const char *);
>  static void poison_check(Var *);
>  static void var_set_append(const char *, const char *, const char *, int, 
> bool);
> @@ -423,6 +429,7 @@ var_set_initial_value(Var *v, const char
>   len = strlen(val);
>   Buf_Init(&(v->val), len+1);
>   Buf_AddChars(&(v->val), len, val);
> + varname_list_changed = true;
>  }
>  
>  /* Normal version of var_set_value(), to be called after variable is fully
> @@ -440,6 +447,16 @@ var_set_value(Var *v, const char *val)
>   }
>  }
>  
> +static char *
> +var_get_value(Var *v)
> +{
> + if (v->flags & VAR_IS_NAMES)
> + varname_list_retrieve(v);
> + else if (v->flags & VAR_EXEC_LATER)
> + var_exec_cmd(v);
> + return Buf_Retrieve(&(v->val));
> +}
> +
>  /* Add to a variable, insert a separating space if the variable was already
>   * defined.
>   */
> @@ -628,6 +645,7 @@ Var_Deletei(const char *name, const char
>  
>   ohash_remove(_variables, slot);
>   delete_var(v);
> + varname_list_changed = true;
>  }
>  
>  /* Set or add a global variable, either to VAR_CMD or VAR_GLOBAL.
> @@ -687,7 +705,7 @@ Var_Appendi_with_ctxt(const char *name, 
>   var_set_append(name, ename, val, ctxt, true);
>  }
>  
> -static char *
> +static void
>  var_exec_cmd(Var *v)
>  {
>   char *arg = Buf_Retrieve(&(v->val));
> @@ -699,7 +717,30 @@ var_exec_cmd(Var *v)
>   var_set_value(v, res1);
>   free(res1);
>   v->flags &= ~VAR_EXEC_LATER;
> - return Buf_Retrieve(&(v->val));
> +}
> +
> +static void
> +varname_list_retrieve(Var *v)
> +{
> + unsigned int i;
> + void *e;
> + bool first = true;
> +
> + if (!varname_list_changed)
> + return;
> + for (e = ohash_first(_variables, ); e != NULL;
> + e = ohash_next(_variables, )) {
> + Var *v2 = e;
> + if (v2->flags & VAR_DUMMY)
> + continue;
> +
> + if (first)
> + var_set_value(v, v2->name);
> + else
> + var_append_value(v, v2->name);
> + first = false;
> + }
> + varname_list_changed = false;
>  }
>  
>  /* XXX different semantics for Var_Valuei() and Var_Definedi():
> @@ -1339,6 +1380,19 @@ set_magic_shell_variable()
>   v->flags = VAR_IS_SHELL | VAR_SEEN_ENV;
>  }
>  
> +static void
> +set_magic_name_list_variable()
> +{
> + const char *name = VARNAME_LIST;
> + const char *ename = NULL;
> + uint32_t k;
> + Var *v;
> +
> + k = ohash_interval(name, );
> + v = find_global_var_without_env(name, 

Re: PATCH: a bit of introspection in make

2023-08-08 Thread Marc Espie
Actually, as far as bsd.port.mk, it doesn't need to
move too much stuff around thanks to make's lazyness.

Note that having a list of defined MASTER_SITES variables simplifies
the check.

I've also added a check for the right MASTER_SITES to be defined,
since currently we do not error out until actually using it, which
means that fiddling around with MASTER_SITES before committing may
often go unnoticed.

(That final part is meant to go in sooner rather than later)

Index: bsd.port.mk
===
RCS file: /cvs/ports/infrastructure/mk/bsd.port.mk,v
retrieving revision 1.1592
diff -u -p -r1.1592 bsd.port.mk
--- bsd.port.mk 13 Jun 2023 10:28:40 -  1.1592
+++ bsd.port.mk 8 Aug 2023 10:59:38 -
@@ -118,9 +118,8 @@ _ALL_VARIABLES_PER_ARCH =
 # consumers of (dump-vars) include sqlports generation and dpb
 # dpb doesn't need everything, those are speed optimizations
 .if ${DPB:L:Mfetch} || ${DPB:L:Mall}
-_ALL_VARIABLES += DISTFILES PATCHFILES SUPDISTFILES DIST_SUBDIR MASTER_SITES \
-   MASTER_SITES0 MASTER_SITES1 MASTER_SITES2 MASTER_SITES3 MASTER_SITES4 \
-   MASTER_SITES5 MASTER_SITES6 MASTER_SITES7 MASTER_SITES8 MASTER_SITES9 \
+_ALL_VARIABLES += DISTFILES PATCHFILES SUPDISTFILES DIST_SUBDIR \
+   ${_ALL_MASTER_SITES} \
CHECKSUM_FILE FETCH_MANUALLY MISSING_FILES PERMIT_DISTFILES
 .endif
 .if ${DPB:L:Mtest} || ${DPB:L:Mall}
@@ -1280,19 +1280,15 @@ MASTER_SITES ?=
 # sites for distfiles, add them to MASTER_SITE_BACKUP
 
 _warn_checksum = :
-.if !empty(MASTER_SITES:M*[^/])
-_warn_checksum += ;echo ">>> MASTER_SITES not ending in /: 
${MASTER_SITES:M*[^/]}"
-.endif
 
-.for _I in 0 1 2 3 4 5 6 7 8 9
-.  if defined(MASTER_SITES${_I})
-.if !empty(MASTER_SITES${_I}:M*[^/])
-_warn_checksum += ;echo ">>> MASTER_SITES${_I} not ending in /: 
${MASTER_SITES${_I}:M*[^/]}"
-.endif
+_ALL_MASTER_SITES = ${.VARIABLES:MMASTER_SITES*:NMASTER_SITES_*}
+
+.for _S in ${_ALL_MASTER_SITES}
+.  if !empty(${_S}:M*[^/])
+_warn_checksum += ;echo ">>> ${_S} not ending in /: ${${_S}:M*[^/]}"
 .  endif
 .endfor
 
-
 EXTRACT_SUFX ?= .tar.gz
 
 .if !empty(GH_COMMIT)
@@ -1322,6 +1318,9 @@ _FILES=
 .  if !empty($v)
 .for e in ${$v}
 .  for f m u in ${e:C/:[0-9]$//:C/^(.*)\{.*\}(.*)$/\1\2/} 
MASTER_SITES${e:M*\:[0-9]:C/^.*:([0-9])$/\1/} 
${e:C/:[0-9]$//:C/^.*\{(.*)\}(.*)$/\1\2/}
+.if !defined($m)
+ERRORS += "Fatal: $m is not defined but referenced by $e in $v"
+.endif
 .if empty(_FILES:M$f)
 _FILES += $f
 .  if empty(DIST_SUBDIR)



Re: PATCH: a bit of introspection in make

2023-08-08 Thread Marc Espie
Here's a revised diff (reordered plus actual use of the boolean)
plus concrete example  use for bsd.port.mk (disregarding the fact
_ALL_VARIABLES would have to move *after* all MASTER_SITES have been defined.

Index: var.c
===
RCS file: /cvs/src/usr.bin/make/var.c,v
retrieving revision 1.104
diff -u -p -r1.104 var.c
--- var.c   9 Jun 2022 13:13:14 -   1.104
+++ var.c   8 Aug 2023 10:48:05 -
@@ -104,6 +104,8 @@ static char varNoError[] = "";
 bool   errorIsOkay;
 static boolcheckEnvFirst;  /* true if environment should be searched for
 * variables before the global context */
+   /* do we need to recompute varname_list */
+static boolvarname_list_changed = true;
 
 void
 Var_setCheckEnvFirst(bool yes)
@@ -222,6 +224,7 @@ typedef struct Var_ {
 #define VAR_FROM_ENV   8   /* Special source: environment */
 #define VAR_SEEN_ENV   16  /* No need to go look up environment again */
 #define VAR_IS_SHELL   32  /* Magic behavior */
+#define VAR_IS_NAMES   1024/* Very expensive, only defined when needed */
 /* XXX there are also some flag values which are part of the visible API
  * and thus defined inside var.h, don't forget to look there if you want
  * to define some new flags !
@@ -231,6 +234,8 @@ typedef struct Var_ {
char name[1];   /* the variable's name */
 }  Var;
 
+/* for GNU make compatibility */
+#define VARNAME_LIST ".VARIABLES"
 
 static struct ohash_info var_info = {
offsetof(Var, name),
@@ -245,10 +250,11 @@ static void fill_from_env(Var *);
 static Var *create_var(const char *, const char *);
 static void var_set_initial_value(Var *, const char *);
 static void var_set_value(Var *, const char *);
-#define var_get_value(v)   ((v)->flags & VAR_EXEC_LATER ? \
-   var_exec_cmd(v) : \
-   Buf_Retrieve(&((v)->val)))
-static char *var_exec_cmd(Var *);
+static char *var_get_value(Var *);
+static void var_exec_cmd(Var *);
+static void varname_list_retrieve(Var *);
+
+
 static void var_append_value(Var *, const char *);
 static void poison_check(Var *);
 static void var_set_append(const char *, const char *, const char *, int, 
bool);
@@ -423,6 +429,7 @@ var_set_initial_value(Var *v, const char
len = strlen(val);
Buf_Init(&(v->val), len+1);
Buf_AddChars(&(v->val), len, val);
+   varname_list_changed = true;
 }
 
 /* Normal version of var_set_value(), to be called after variable is fully
@@ -440,6 +447,16 @@ var_set_value(Var *v, const char *val)
}
 }
 
+static char *
+var_get_value(Var *v)
+{
+   if (v->flags & VAR_IS_NAMES)
+   varname_list_retrieve(v);
+   else if (v->flags & VAR_EXEC_LATER)
+   var_exec_cmd(v);
+   return Buf_Retrieve(&(v->val));
+}
+
 /* Add to a variable, insert a separating space if the variable was already
  * defined.
  */
@@ -628,6 +645,7 @@ Var_Deletei(const char *name, const char
 
ohash_remove(_variables, slot);
delete_var(v);
+   varname_list_changed = true;
 }
 
 /* Set or add a global variable, either to VAR_CMD or VAR_GLOBAL.
@@ -687,7 +705,7 @@ Var_Appendi_with_ctxt(const char *name, 
var_set_append(name, ename, val, ctxt, true);
 }
 
-static char *
+static void
 var_exec_cmd(Var *v)
 {
char *arg = Buf_Retrieve(&(v->val));
@@ -699,7 +717,30 @@ var_exec_cmd(Var *v)
var_set_value(v, res1);
free(res1);
v->flags &= ~VAR_EXEC_LATER;
-   return Buf_Retrieve(&(v->val));
+}
+
+static void
+varname_list_retrieve(Var *v)
+{
+   unsigned int i;
+   void *e;
+   bool first = true;
+
+   if (!varname_list_changed)
+   return;
+   for (e = ohash_first(_variables, ); e != NULL;
+   e = ohash_next(_variables, )) {
+   Var *v2 = e;
+   if (v2->flags & VAR_DUMMY)
+   continue;
+
+   if (first)
+   var_set_value(v, v2->name);
+   else
+   var_append_value(v, v2->name);
+   first = false;
+   }
+   varname_list_changed = false;
 }
 
 /* XXX different semantics for Var_Valuei() and Var_Definedi():
@@ -1339,6 +1380,19 @@ set_magic_shell_variable()
v->flags = VAR_IS_SHELL | VAR_SEEN_ENV;
 }
 
+static void
+set_magic_name_list_variable()
+{
+   const char *name = VARNAME_LIST;
+   const char *ename = NULL;
+   uint32_t k;
+   Var *v;
+
+   k = ohash_interval(name, );
+   v = find_global_var_without_env(name, ename, k);
+   var_set_initial_value(v, "");
+   v->flags = VAR_IS_NAMES;
+}
 /*
  * Var_Init
  * Initialize the module
@@ -1348,11 +1402,10 @@ Var_Init(void)
 {
ohash_init(_variables, 10, _info);
set_magic_shell_variable();
-
+   set_magic_name_list_variable();
 
errorIsOkay = true;

Re: PATCH: a bit of introspection in make

2023-08-07 Thread Marc Espie
On Mon, Aug 07, 2023 at 10:05:37PM -0400, Thomas Frohwein wrote:
> I looked through the file and it seems that varname_list_changed is
> never set to anything but true. Is there a bit missing, like down lower
> in varname_list_retrieve()?

Yep, I removed it at some point because it looked like some extra
unneeded work, and then I did change my mind because actual patterns
require it several times. So, yeah, it should be set to false there.

As for patterns, well, it will allow to write loops like

.for v in ${.VARIABLES:MMASTER_SITES*}

instead of having to look at MASTER_SITES0...9 manually, just as a for
instance.



Re: PATCH: a bit of introspection in make

2023-08-07 Thread Thomas Frohwein
On Mon, Aug 07, 2023 at 04:42:54PM +0200, Marc Espie wrote:
> I think it could be occasionally useful to know which variables have
> been defined in make.
> 
> Incidentally, this DOES exist in GNU make, so I've reused the same name
> for basically the same functionality.
> 
> I haven't checked whether NetBSD/FreeBSD introduced something similar.
> 
> This is a fairly straightforward patch, introduces .VARIABLES corresponding
> to the full list of global variables (from the command line and the Makefile)
> that have been defined.
> 
> (^ says the guy who had to remember a few details from his own(!) var.c 
> implementation from a few years ago)
> 
> I just took var_get_value offline from the old macro, for readability,
> even though it's likely the compiler may still decide to inline it.
> 
> For efficiency, that list is only computed as needed, since it is
> somewhat long.
> 
> For debugging purposes, this can come in fairly handy, and I see at 
> least another application in ports.
> 
> Comments and nits welcome.
> 
> Note that the list is completely unsorted. This could be sorted through
> since we already have the code for dumping purposes, but it would be even
> more expensive (the order will be "random", as per the hash)

I can't judge all the implications of this, but can speak to that I
compiled it and that I get a very long list of variables with:

$ make show=.VARIABLES

> Index: var.c
> ===
> RCS file: /cvs/src/usr.bin/make/var.c,v
> retrieving revision 1.104
> diff -u -p -r1.104 var.c
> --- var.c 9 Jun 2022 13:13:14 -   1.104
> +++ var.c 7 Aug 2023 14:33:42 -
> @@ -104,6 +104,8 @@ static char   varNoError[] = "";
>  bool errorIsOkay;
>  static bool  checkEnvFirst;  /* true if environment should be searched for
>* variables before the global context */
> + /* do we need to recompute varname_list */
> +static bool  varname_list_changed = true;

I assume the comment /* do we need to recompute varname_list */ is not
meant to be contiguous with the 2 comment lines above (/* true if...
* variables before...). I suggest using whitespace (newline?) to make
that clearer.

I looked through the file and it seems that varname_list_changed is
never set to anything but true. Is there a bit missing, like down lower
in varname_list_retrieve()?

>  
>  void
>  Var_setCheckEnvFirst(bool yes)
> @@ -228,9 +230,12 @@ typedef struct Var_ {
>   */
>  #define POISONS (POISON_NORMAL | POISON_EMPTY | POISON_NOT_DEFINED)
>   /* Defined in var.h */
> +#define VAR_IS_NAMES 1024/* Very expensive, only defined when needed */
>   char name[1];   /* the variable's name */
>  }  Var;
>  
> +/* for GNU make compatibility */
> +#define VARNAME_LIST ".VARIABLES"
>  
>  static struct ohash_info var_info = {
>   offsetof(Var, name),
> @@ -245,10 +250,11 @@ static void fill_from_env(Var *);
>  static Var *create_var(const char *, const char *);
>  static void var_set_initial_value(Var *, const char *);
>  static void var_set_value(Var *, const char *);
> -#define var_get_value(v) ((v)->flags & VAR_EXEC_LATER ? \
> - var_exec_cmd(v) : \
> - Buf_Retrieve(&((v)->val)))
> -static char *var_exec_cmd(Var *);
> +static char *var_get_value(Var *);
> +static void var_exec_cmd(Var *);
> +static void varname_list_retrieve(Var *);
> +
> +
>  static void var_append_value(Var *, const char *);
>  static void poison_check(Var *);
>  static void var_set_append(const char *, const char *, const char *, int, 
> bool);
> @@ -423,6 +429,7 @@ var_set_initial_value(Var *v, const char
>   len = strlen(val);
>   Buf_Init(&(v->val), len+1);
>   Buf_AddChars(&(v->val), len, val);
> + varname_list_changed = true;
>  }
>  
>  /* Normal version of var_set_value(), to be called after variable is fully
> @@ -440,6 +447,16 @@ var_set_value(Var *v, const char *val)
>   }
>  }
>  
> +static char *
> +var_get_value(Var *v)
> +{
> + if (v->flags & VAR_IS_NAMES)
> + varname_list_retrieve(v);
> + else if (v->flags & VAR_EXEC_LATER)
> + var_exec_cmd(v);
> + return Buf_Retrieve(&(v->val));
> +}
> +
>  /* Add to a variable, insert a separating space if the variable was already
>   * defined.
>   */
> @@ -628,6 +645,7 @@ Var_Deletei(const char *name, const char
>  
>   ohash_remove(_variables, slot);
>   delete_var(v);
> + varname_list_changed = true;
>  }
>  
>  /* Set or add a global variable, either to VAR_CMD or VAR_GLOBAL.
> @@ -687,7 +705,7 @@ Var_Appendi_with_ctxt(const char *name, 
>   var_set_append(name, ename, val, ctxt, true);
>  }
>  
> -static char *
> +static void
>  var_exec_cmd(Var *v)
>  {
>   char *arg = Buf_Retrieve(&(v->val));
> @@ -699,7 +717,27 @@ var_exec_cmd(Var *v)
>   var_set_value(v, res1);
>   free(res1);
>   v->flags &= ~VAR_EXEC_LATER;
> - 

PATCH: a bit of introspection in make

2023-08-07 Thread Marc Espie
I think it could be occasionally useful to know which variables have
been defined in make.

Incidentally, this DOES exist in GNU make, so I've reused the same name
for basically the same functionality.

I haven't checked whether NetBSD/FreeBSD introduced something similar.

This is a fairly straightforward patch, introduces .VARIABLES corresponding
to the full list of global variables (from the command line and the Makefile)
that have been defined.

(^ says the guy who had to remember a few details from his own(!) var.c 
implementation from a few years ago)

I just took var_get_value offline from the old macro, for readability,
even though it's likely the compiler may still decide to inline it.

For efficiency, that list is only computed as needed, since it is
somewhat long.

For debugging purposes, this can come in fairly handy, and I see at 
least another application in ports.

Comments and nits welcome.

Note that the list is completely unsorted. This could be sorted through
since we already have the code for dumping purposes, but it would be even
more expensive (the order will be "random", as per the hash)

Index: var.c
===
RCS file: /cvs/src/usr.bin/make/var.c,v
retrieving revision 1.104
diff -u -p -r1.104 var.c
--- var.c   9 Jun 2022 13:13:14 -   1.104
+++ var.c   7 Aug 2023 14:33:42 -
@@ -104,6 +104,8 @@ static char varNoError[] = "";
 bool   errorIsOkay;
 static boolcheckEnvFirst;  /* true if environment should be searched for
 * variables before the global context */
+   /* do we need to recompute varname_list */
+static boolvarname_list_changed = true;
 
 void
 Var_setCheckEnvFirst(bool yes)
@@ -228,9 +230,12 @@ typedef struct Var_ {
  */
 #define POISONS (POISON_NORMAL | POISON_EMPTY | POISON_NOT_DEFINED)
/* Defined in var.h */
+#define VAR_IS_NAMES   1024/* Very expensive, only defined when needed */
char name[1];   /* the variable's name */
 }  Var;
 
+/* for GNU make compatibility */
+#define VARNAME_LIST ".VARIABLES"
 
 static struct ohash_info var_info = {
offsetof(Var, name),
@@ -245,10 +250,11 @@ static void fill_from_env(Var *);
 static Var *create_var(const char *, const char *);
 static void var_set_initial_value(Var *, const char *);
 static void var_set_value(Var *, const char *);
-#define var_get_value(v)   ((v)->flags & VAR_EXEC_LATER ? \
-   var_exec_cmd(v) : \
-   Buf_Retrieve(&((v)->val)))
-static char *var_exec_cmd(Var *);
+static char *var_get_value(Var *);
+static void var_exec_cmd(Var *);
+static void varname_list_retrieve(Var *);
+
+
 static void var_append_value(Var *, const char *);
 static void poison_check(Var *);
 static void var_set_append(const char *, const char *, const char *, int, 
bool);
@@ -423,6 +429,7 @@ var_set_initial_value(Var *v, const char
len = strlen(val);
Buf_Init(&(v->val), len+1);
Buf_AddChars(&(v->val), len, val);
+   varname_list_changed = true;
 }
 
 /* Normal version of var_set_value(), to be called after variable is fully
@@ -440,6 +447,16 @@ var_set_value(Var *v, const char *val)
}
 }
 
+static char *
+var_get_value(Var *v)
+{
+   if (v->flags & VAR_IS_NAMES)
+   varname_list_retrieve(v);
+   else if (v->flags & VAR_EXEC_LATER)
+   var_exec_cmd(v);
+   return Buf_Retrieve(&(v->val));
+}
+
 /* Add to a variable, insert a separating space if the variable was already
  * defined.
  */
@@ -628,6 +645,7 @@ Var_Deletei(const char *name, const char
 
ohash_remove(_variables, slot);
delete_var(v);
+   varname_list_changed = true;
 }
 
 /* Set or add a global variable, either to VAR_CMD or VAR_GLOBAL.
@@ -687,7 +705,7 @@ Var_Appendi_with_ctxt(const char *name, 
var_set_append(name, ename, val, ctxt, true);
 }
 
-static char *
+static void
 var_exec_cmd(Var *v)
 {
char *arg = Buf_Retrieve(&(v->val));
@@ -699,7 +717,27 @@ var_exec_cmd(Var *v)
var_set_value(v, res1);
free(res1);
v->flags &= ~VAR_EXEC_LATER;
-   return Buf_Retrieve(&(v->val));
+}
+
+static void
+varname_list_retrieve(Var *v)
+{
+   unsigned int i;
+   void *e;
+   bool first = true;
+
+   for (e = ohash_first(_variables, ); e != NULL;
+   e = ohash_next(_variables, )) {
+   Var *v2 = e;
+   if (v2->flags & VAR_DUMMY)
+   continue;
+
+   if (first)
+   var_set_value(v, v2->name);
+   else
+   var_append_value(v, v2->name);
+   first = false;
+   }
 }
 
 /* XXX different semantics for Var_Valuei() and Var_Definedi():
@@ -1339,6 +1377,19 @@ set_magic_shell_variable()
v->flags = VAR_IS_SHELL | VAR_SEEN_ENV;
 }
 
+static void
+set_magic_name_list_variable()
+{
+   const