Subject: new feature for src/usr.bin/make: read-only variables
To: None <tech-userlevel@NetBSD.org, tech-pkg@NetBSD.org>
From: Roland Illig <rillig@NetBSD.org>
List: tech-userlevel
Date: 09/02/2005 11:37:25
This is a multi-part message in MIME format.
--------------080503080400050703050705
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

This patch adds a new feature to make(1). Variables can be marked
read-only, so there is no chance of overwriting them accidentally. The
intended usage is:

.BEGIN_READONLY:
CFLAGS.NetBSD=   -Wall
CFLAGS.SunOS=    -O
CFLAGS.IRIX=     -v
.END_READONLY:

PROG= foobar
.READONLY: PROG

This patch is mostly useful for pkgsrc, as pkgsrc contains really much 
code in Makefiles with more than 1000 global variables.

Roland

--------------080503080400050703050705
Content-Type: text/plain;
 name="make-readonly-variables.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="make-readonly-variables.patch"

Index: nonints.h
===================================================================
RCS file: /cvsroot/src/usr.bin/make/nonints.h,v
retrieving revision 1.34
diff -u -p -r1.34 nonints.h
--- nonints.h	8 May 2005 00:38:47 -0000	1.34
+++ nonints.h	2 Sep 2005 09:35:36 -0000
@@ -152,6 +152,8 @@ void Suff_AddSuffix(char *, GNode **);
 Lst Suff_GetPath(char *);
 void Suff_DoPaths(void);
 void Suff_AddInclude(char *);
+void Var_Make_Readonly __P((char *, GNode *));
+void Var_Set_Readonly_Mode __P((int));
 void Suff_AddLib(char *);
 void Suff_FindDeps(GNode *);
 Lst Suff_FindPath(GNode *);
Index: parse.c
===================================================================
RCS file: /cvsroot/src/usr.bin/make/parse.c,v
retrieving revision 1.106
diff -u -p -r1.106 parse.c
--- parse.c	9 Aug 2005 21:36:42 -0000	1.106
+++ parse.c	2 Sep 2005 09:35:36 -0000
@@ -186,8 +186,10 @@ Lst         	defIncPath;	/* default dire
  */
 typedef enum {
     Begin,  	    /* .BEGIN */
+    Begin_Readonly, /* .BEGIN_READONLY */
     Default,	    /* .DEFAULT */
     End,    	    /* .END */
+    End_Readonly,   /* .END_READONLY */
     Ignore,	    /* .IGNORE */
     Includes,	    /* .INCLUDES */
     Interrupt,	    /* .INTERRUPT */
@@ -209,6 +211,7 @@ typedef enum {
     Posix,	    /* .POSIX */
 #endif
     Precious,	    /* .PRECIOUS */
+    Readonly,       /* .READONLY */
     ExShell,	    /* .SHELL */
     Silent,	    /* .SILENT */
     SingleShell,    /* .SINGLESHELL */
@@ -241,8 +244,10 @@ static struct {
     int	    	  op;	    	/* Operator when used as a source */
 } parseKeywords[] = {
 { ".BEGIN", 	  Begin,    	0 },
+{ ".BEGIN_READONLY", Begin_Readonly, 0 },
 { ".DEFAULT",	  Default,  	0 },
 { ".END",   	  End,	    	0 },
+{ ".END_READONLY", End_Readonly, 0 },
 { ".EXEC",	  Attribute,   	OP_EXEC },
 { ".IGNORE",	  Ignore,   	OP_IGNORE },
 { ".INCLUDES",	  Includes, 	0 },
@@ -270,6 +275,7 @@ static struct {
 { ".POSIX",	  Posix,	0 },
 #endif
 { ".PRECIOUS",	  Precious, 	OP_PRECIOUS },
+{ ".READONLY",    Readonly,     0 },
 { ".RECURSIVE",	  Attribute,	OP_MAKE },
 { ".SHELL", 	  ExShell,    	0 },
 { ".SILENT",	  Silent,   	OP_SILENT },
@@ -1271,6 +1277,12 @@ ParseDoDependency(char *line)
      */
     if (!*line) {
 	switch (specType) {
+	    case Begin_Readonly:
+		Var_Set_Readonly_Mode(1);
+		break;
+	    case End_Readonly:
+		Var_Set_Readonly_Mode(0);
+		break;
 	    case Suffixes:
 		Suff_ClearSuffixes();
 		break;
@@ -1318,7 +1330,8 @@ ParseDoDependency(char *line)
      */
     if ((specType == Suffixes) || (specType == ExPath) ||
 	(specType == Includes) || (specType == Libs) ||
-	(specType == Null) || (specType == ExObjdir))
+	(specType == Null) || (specType == ExObjdir) ||
+	(specType == Readonly))
     {
 	while (*line) {
 	    /*
@@ -1372,6 +1385,9 @@ ParseDoDependency(char *line)
 		case ExObjdir:
 		    Main_SetObjdir(line);
 		    break;
+		case Readonly:
+		    Var_Make_Readonly (line, VAR_GLOBAL);
+		    break;
 		default:
 		    break;
 	    }
Index: var.c
===================================================================
RCS file: /cvsroot/src/usr.bin/make/var.c,v
retrieving revision 1.100
diff -u -p -r1.100 var.c
--- var.c	27 Aug 2005 08:04:26 -0000	1.100
+++ var.c	2 Sep 2005 09:35:38 -0000
@@ -167,6 +167,11 @@ static char	varNoError[] = "";
 GNode          *VAR_GLOBAL;   /* variables from the makefile */
 GNode          *VAR_CMD;      /* variables defined on the command-line */
 
+/* When non-zero, all assignments mark the destination variable read-only,
+ * so it cannot be modified again.
+ */
+static int var_readonly_mode = 0;
+
 #define FIND_CMD	0x1   /* look in VAR_CMD when searching */
 #define FIND_GLOBAL	0x2   /* look in VAR_GLOBAL as well */
 #define FIND_ENV  	0x4   /* look in the environment also */
@@ -185,6 +190,7 @@ typedef struct Var {
 #define VAR_KEEP	8	    /* Variable is VAR_JUNK, but we found
 				     * a use for it in some modifier and
 				     * the value is therefore valid */
+#define VAR_READONLY	16
 }  Var;
 
 
@@ -448,8 +454,13 @@ VarAdd(const char *name, const char *val
     h = Hash_CreateEntry(&ctxt->context, name, NULL);
     Hash_SetValue(h, v);
     v->name = h->name;
+
+    if (var_readonly_mode)
+	v->flags |= VAR_READONLY;
+
     if (DEBUG(VAR)) {
-	printf("%s:%s = %s\n", ctxt->name, name, val);
+	printf("%s:%s = %s%s\n", ctxt->name, name, val,
+	    (v->flags & VAR_READONLY) ? " (read-only)" : "");
     }
 }
 
@@ -479,6 +490,9 @@ Var_Delete(const char *name, GNode *ctxt
 	Var 	  *v;
 
 	v = (Var *)Hash_GetValue(ln);
+	if (v->flags & VAR_READONLY) {
+	    Parse_Error(PARSE_FATAL, "%s: Readonly variables cannot be deleted.", v->name);
+	}
 	if (v->name != ln->name)
 		free(v->name);
 	Hash_DeleteEntry(&ctxt->context, ln);
@@ -487,6 +501,25 @@ Var_Delete(const char *name, GNode *ctxt
     }
 }
 
+void
+Var_Make_Readonly(char *name, GNode *ctxt)
+{
+	Hash_Entry *ln;
+	Var *v;
+
+	if (DEBUG(VAR)) {
+		printf("%s:make_readonly %s\n", ctxt->name, name);
+	}
+
+	ln = Hash_FindEntry(&ctxt->context, name);
+	if (ln == NULL) {
+		Parse_Error(PARSE_WARNING, ".readonly on undefined variables has no effect.");
+	} else {
+		v = (Var *)Hash_GetValue(ln);
+		v->flags |= VAR_READONLY;
+	}
+}
+
 /*-
  *-----------------------------------------------------------------------
  * Var_Set --
@@ -531,12 +564,18 @@ Var_Set(const char *name, const char *va
     v = VarFind(name, ctxt, 0);
     if (v == (Var *)NIL) {
 	VarAdd(name, val, ctxt);
+    } else if (v->flags & VAR_READONLY) {
+ 	Parse_Error(PARSE_FATAL, "%s: Readonly variables cannot be modified.", v->name);
     } else {
 	Buf_Discard(v->val, Buf_Size(v->val));
 	Buf_AddBytes(v->val, strlen(val), (const Byte *)val);
 
+	if (var_readonly_mode)
+	    v->flags |= VAR_READONLY;
+ 
 	if (DEBUG(VAR)) {
-	    printf("%s:%s = %s\n", ctxt->name, name, val);
+	    printf("%s:%s = %s%s\n", ctxt->name, name, val,
+		(v->flags & VAR_READONLY) ? " (read-only)" : "");
 	}
     }
     /*
@@ -603,13 +642,19 @@ Var_Append(const char *name, const char 
 
     if (v == (Var *)NIL) {
 	VarAdd(name, val, ctxt);
+    } else if (v->flags & VAR_READONLY) {
+	Parse_Error(PARSE_FATAL, "%s: Readonly variables cannot be modified.", v->name);
     } else {
 	Buf_AddByte(v->val, (Byte)' ');
 	Buf_AddBytes(v->val, strlen(val), (const Byte *)val);
 
+	if (var_readonly_mode)
+	    v->flags |= VAR_READONLY;
+
 	if (DEBUG(VAR)) {
-	    printf("%s:%s = %s\n", ctxt->name, name,
-		   (char *)Buf_GetAll(v->val, NULL));
+	    printf("%s:%s = %s%s\n", ctxt->name, name,
+		   (char *) Buf_GetAll(v->val, (int *)NULL),
+		   (v->flags & VAR_READONLY) ? " (read-only)" : "");
 	}
 
 	if (v->flags & VAR_FROM_ENV) {
@@ -3522,3 +3567,9 @@ Var_Dump(GNode *ctxt)
 	    VarPrintVar(Hash_GetValue(h));
     }
 }
+
+void
+Var_Set_Readonly_Mode(int onoff)
+{
+	var_readonly_mode = onoff;
+}

--------------080503080400050703050705--