Subject: Re: fix for MAKEOBJDIRPREFIX=/obj make build
To: None <tech-toolchain@netbsd.org>
From: Simon J. Gerraty <sjg@quick.com.au>
List: tech-toolchain
Date: 03/30/2000 17:19:17
Sorry for letting this slide, below are the diffs for the version I
settled on.  It works with and without -jN.  I've happily done build
world using MAKEOBJDIRPREFIX and a make with this patch.

Please note that this change does _nothing_ if MAKEOBJDIRPREFIX and
MAKEOBJDIR are not set and defining NOCHECKMAKECHDIR turns it off
regardless. 

Also note that this feature does NOT break situations where a
configure script builds a makefile in the objdir.

Original description:
> Attempting to do a make build with MAKEOBJDIRPREFIX set (so that the
> src tree can remain read-only) fails utterly when it encounters: 
> 
> 	${MAKE} cleandir
> 	${MAKE} includes
> 
> etc.  This is due to the fact that unlike ./obj* (links or not), even
> if a directory says NOOBJ, if any of its children don't then
> MAKEOBJDIRPREFX`pwd` will exist and make(1) will have cd'd to it.
> This means that the ${MAKE} includes is run in a directory with no
> Makefile. 
> 
> Changing the above to:
> 
> 	cd ${.CURDIR} && ${MAKE} cleandir
> 
> works fine, but its bogus to expect everyone to fix their makefiles.
> 
> The fix below, only applies if MAKEOBJDIRPREFIX or MAKEOBJDIR is set.
> What it does is spot if ${.MAKE} or ${.MAKE:T} are invoked
> without a preceeding 'cd' and if so does a chdir(${.CURDIR}).  This
> only happens in the child just before exec(2), so does not affect the
> parent.

Anyway here's the patch.  I've been running this on 1.3.2, 1.4.x and
-current for some weeks now without any problems.  And done several
make builds of -current with MAKEOBJDIRPREFIX set.

--sjg


Index: main.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/main.c,v
retrieving revision 1.51
diff -u -p -r1.51 main.c
--- main.c	2000/02/08 12:43:25	1.51
+++ main.c	2000/03/30 07:02:39
@@ -140,6 +140,7 @@ Boolean			checkEnvFirst;	/* -e flag */
 Boolean			mkIncPath;	/* -m flag */
 static Boolean		jobsRunning;	/* TRUE if the jobs might be running */
 
+static char *		Check_Cwd_av __P((int, char **, int));
 static void		MainParseArgs __P((int, char **));
 char *			chdir_verify_path __P((char *, char *));
 static int		ReadMakefile __P((ClientData, ClientData));
@@ -751,6 +752,9 @@ main(argc, argv)
 	if (p1)
 	    free(p1);
 
+	Check_Cwd_av(0, NULL, 0);	/* initialize it */
+	
+
 	/*
 	 * For compatibility, look at the directories in the VPATH variable
 	 * and add them to the search path, if the variable is defined. The
@@ -930,6 +934,181 @@ found:		Var_Set("MAKEFILE", fname, VAR_G
 	}
 	free(path);
 	return(TRUE);
+}
+
+
+/*
+ * If MAKEOBJDIRPREFIX is in use, make ends up not in .CURDIR
+ * in situations that would not arrise with ./obj (links or not).
+ * This tends to break things like:
+ *
+ * build:
+ * 	${MAKE} includes
+ *
+ * This function spots when ${.MAKE:T} or ${.MAKE} is a command (as
+ * opposed to an argument) in a command line and if so returns
+ * ${.CURDIR} so caller can chdir() so that the assumptions made by
+ * the Makefile hold true.
+ *
+ * If ${.MAKE} does not contain any '/', then ${.MAKE:T} is skipped.
+ *
+ * The chdir() only happens in the child process, and does nothing if
+ * MAKEOBJDIRPREFIX and MAKEOBJDIR are not in the environment so it
+ * should not break anything.  Also if NOCHECKMAKECHDIR is set we
+ * do nothing - to ensure historic semantics can be retained.
+ */
+static int  Check_Cwd_Off = 0;
+
+static char *
+Check_Cwd_av(ac, av, copy)
+     int ac;
+     char **av;
+     int copy;
+{
+    static char *make[4];
+    static char *curdir = NULL;
+    char *cp, **mp;
+    int is_cmd, next_cmd;
+    int i;
+    int n;
+
+    if (Check_Cwd_Off)
+	return NULL;
+    
+    if (make[0] == NULL) {
+	if (Var_Exists("NOCHECKMAKECHDIR", VAR_GLOBAL)) {
+	    Check_Cwd_Off = 1;
+	    return NULL;
+	}
+	    
+        make[1] = Var_Value(".MAKE", VAR_GLOBAL, &cp);
+        if ((make[0] = strrchr(make[1], '/')) == NULL) {
+            make[0] = make[1];
+            make[1] = NULL;
+        } else
+            ++make[0];
+        make[2] = NULL;
+        curdir = Var_Value(".CURDIR", VAR_GLOBAL, &cp);
+    }
+    if (ac == 0 || av == NULL)
+        return NULL;			/* initialization only */
+
+    if (getenv("MAKEOBJDIR") == NULL &&
+        getenv("MAKEOBJDIRPREFIX") == NULL)
+        return NULL;
+
+    
+    next_cmd = 1;
+    for (i = 0; i < ac; ++i) {
+	is_cmd = next_cmd;
+
+	n = strlen(av[i]);
+	cp = &(av[i])[n - 1];
+	if (strspn(av[i], "|&;") == n) {
+	    next_cmd = 1;
+	    continue;
+	} else if (*cp == ';' || *cp == '&' || *cp == '|' || *cp == ')') {
+	    next_cmd = 1;
+	    if (copy) {
+		do {
+		    *cp-- = '\0';
+		} while (*cp == ';' || *cp == '&' || *cp == '|' ||
+			 *cp == ')' || *cp == '}') ;
+	    } else {
+		/*
+		 * XXX this should not happen.
+		 */
+		fprintf(stderr, "WARNING: raw arg ends in shell meta '%s'\n",
+			av[i]);
+	    }
+	} else
+	    next_cmd = 0;
+
+	cp = av[i];
+	if (*cp == ';' || *cp == '&' || *cp == '|')
+	    is_cmd = 1;
+	
+#ifdef check_cwd_debug
+	fprintf(stderr, "av[%d] == %s '%s'",
+		i, (is_cmd) ? "cmd" : "arg", av[i]);
+#endif
+	if (is_cmd != 0) {
+	    while (*cp == '(' || *cp == '{' ||
+		   *cp == ';' || *cp == '&' || *cp == '|')
+		++cp;
+	    if (strcmp(cp, "cd") == 0 || strcmp(cp, "chdir") == 0) {
+#ifdef check_cwd_debug
+		fprintf(stderr, " == cd, done.\n");
+#endif
+		return NULL;
+	    }
+	    for (mp = make; *mp != NULL; ++mp) {
+		n = strlen(*mp);
+		if (strcmp(cp, *mp) == 0) {
+#ifdef check_cwd_debug
+		    fprintf(stderr, " %s == '%s', chdir(%s)\n",
+			    cp, *mp, curdir);
+#endif
+		    return curdir;
+		}
+	    }
+	}
+#ifdef check_cwd_debug
+	fprintf(stderr, "\n");
+#endif
+    }
+    return NULL;
+}
+
+char *
+Check_Cwd_Cmd(cmd)
+     char *cmd;
+{
+    char *cp, *bp, **av;
+    int ac;
+
+    if (Check_Cwd_Off)
+	return NULL;
+    
+    if (cmd) {
+	av = brk_string(cmd, &ac, TRUE, &bp);
+#ifdef check_cwd_debug
+	fprintf(stderr, "splitting: '%s' -> %d words\n",
+		cmd, ac);
+#endif
+    } else {
+	ac = 0;
+	av = NULL;
+	bp = NULL;
+    }
+    cp = Check_Cwd_av(ac, av, 1);
+    if (bp) {
+	free(av);
+	free(bp);
+    }
+    return cp;
+}
+
+void
+Check_Cwd(argv)
+    char **argv;
+{
+    char *cp;
+    int ac;
+    
+    if (Check_Cwd_Off)
+	return;
+    
+    for (ac = 0; argv[ac] != NULL; ++ac)
+	/* NOTHING */;
+    if (ac == 3 && *argv[1] == '-') {
+	cp =  Check_Cwd_Cmd(argv[2]);
+    } else {
+	cp = Check_Cwd_av(ac, argv, 0);
+    }
+    if (cp) {
+	chdir(cp);
+    }
 }
 
 /*-
Index: make.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/make.h,v
retrieving revision 1.23
diff -u -p -r1.23 make.h
--- make.h	1999/09/16 19:57:54	1.23
+++ make.h	2000/03/30 07:02:43
@@ -395,5 +395,7 @@ int Make_HandleUse __P((GNode *, GNode *
 void Make_Update __P((GNode *));
 void Make_DoAllVar __P((GNode *));
 Boolean Make_Run __P((Lst));
+char * Check_Cwd_Cmd __P((char *));
+void Check_Cwd __P((char **));
 
 #endif /* _MAKE_H_ */
Index: compat.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/compat.c,v
retrieving revision 1.29
diff -u -p -r1.29 compat.c
--- compat.c	2000/01/21 17:08:35	1.29
+++ compat.c	2000/03/30 07:02:25
@@ -281,6 +281,7 @@ CompatRunCommand (cmdp, gnp)
 	Fatal("Could not fork");
     }
     if (cpid == 0) {
+	Check_Cwd(av);
 	if (local) {
 	    execvp(av[0], av);
 	    (void) write (2, av[0], strlen (av[0]));
Index: job.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/make/job.c,v
retrieving revision 1.32
diff -u -p -r1.32 job.c
--- job.c	2000/01/21 17:08:35	1.32
+++ job.c	2000/03/30 07:02:34
@@ -532,7 +532,8 @@ JobPrintCommand(cmdp, jobp)
     LstNode 	  cmdNode;  	    /* Node for replacing the command */
     char     	  *cmd = (char *) cmdp;
     Job           *job = (Job *) jobp;
-
+    char	*cp;
+    
     noSpecials = noExecute && !(job->node->type & OP_MAKE);
 
     if (strcmp(cmd, "...") == 0) {
@@ -640,6 +641,9 @@ JobPrintCommand(cmdp, jobp)
 	}
     }
 
+    if ((cp = Check_Cwd_Cmd(cmd)) != NULL) {
+	    DBPRINTF("cd %s; ", cp);
+    }		    
     DBPRINTF(cmdTemplate, cmd);
     free(cmdStart);