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/12/2000 19:02:34
Sorry for the re-post, there was a bug in the original patch :-)
I've also included a test makefile - so that people who worry that
this change will break things such as configure scripts
generating makefile in ${.OBJDIR} still work.

> 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, ${.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.
> 
> Does anyone see anything dreadfully wrong with this?
> My make build is now progressing happily btw.

Here's the amended patch:

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/12 07:40:26
@@ -649,6 +649,8 @@ main(argc, argv)
 	Var_Set(MAKEFLAGS, "", VAR_GLOBAL);
 	Var_Set("MFLAGS", "", VAR_GLOBAL);
 
+    Check_Cwd(NULL);		/* initialize it */
+	
 	/*
 	 * First snag any flags out of the MAKE environment variable.
 	 * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
@@ -931,6 +933,97 @@ 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', ${.MAKE:T} or ${.MAKE} is in a
+ * command without a preceding 'cd', and if so does a
+ * chdir(${.CURDIR}) so that the assumptions made by the Makefile hold
+ * true.
+ *
+ * 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.
+ */
+
+void
+Check_Cwd(argv)
+     char **argv;
+{
+    static char *make[4];
+    static char *curdir = NULL;
+    char *cp, *cp2, **mp;
+    int i;
+    int n;
+    
+    if (curdir == NULL) {
+        make[0] = "make";
+        make[2] = Var_Value(".MAKE", VAR_GLOBAL, &cp);
+        if ((make[1] = strrchr(make[2], '/')) == NULL) {
+            make[1] = make[2];
+            make[2] = NULL;
+        } else
+            ++make[1];
+        if (strcmp(make[1], "make") == 0) {
+            make[1] = NULL;
+            if (cp)
+                free(cp);
+        }
+        make[3] = NULL;
+        curdir = Var_Value(".CURDIR", VAR_GLOBAL, &cp);
+    }
+    if (argv == NULL)
+        return;         /* initialization only */
+
+    if (getenv("MAKEOBJDIR") == NULL &&
+        getenv("MAKEOBJDIRPREFIX") == NULL)
+        return;
+    
+    for (i = 0; argv[i] != 0; ++i) {
+        if (strcmp(argv[i], "cd") == 0) {
+            break;
+        }
+        for (mp = make; *mp != NULL; ++mp) {
+            if (strcmp(argv[i], *mp) == 0 ||
+                (cp = strstr(argv[i], *mp)) != NULL) {
+                if (cp != NULL) {
+                    /*
+                     * check for the word 'make'
+                     * buried in the middle of something.
+                     */
+                    n = strlen(*mp);
+                    if (cp[n] != '\0' && cp[n] != ' ' && cp[n] != '\t')
+                        continue;
+                    if (cp[0] != '/' && cp > argv[i] && cp[-1] != '/' &&
+                        cp[-1] != ' ' && cp[-1] != '\t')
+                        continue;
+                    /*
+                     * If the word 'cd' comes first.
+                     * no need to chdir()
+                     */
+                    cp2 = strstr(argv[i], "cd");
+                    if (cp2 != NULL && cp2 < cp &&
+                        (cp2[2] == ' ' || cp2[2] == '\t') &&
+                        (cp2 == argv[i] ||
+                         (cp2[-1] == ' ' || cp2[-1] == '\t' ||
+                          cp2[-1] == ';'))) {
+                        return;
+                    }
+                }
+                chdir(curdir);
+                return;
+            }
+        }
+    }
+}
+
 
 /*-
  * Cmd_Exec --
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/12 07:40:28
@@ -395,5 +395,6 @@ int Make_HandleUse __P((GNode *, GNode *
 void Make_Update __P((GNode *));
 void Make_DoAllVar __P((GNode *));
 Boolean Make_Run __P((Lst));
+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/12 07:40:08
@@ -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/12 07:40:22
@@ -1279,6 +1279,7 @@ JobExec(job, argv)
 	(void) setpgid(0, getpid());
 # endif
 #endif /* USE_PGRP */
+	Check_Cwd(argv);
 
 #ifdef REMOTE
 	if (job->flags & JOB_REMOTE) {

and the test setup

do 
mkdir -p /tmp/`pwd`
and run 
MAKEOBJDIRPREFIX=/tmp make -f mw {clean,old,new,sane}

old 	tests how these things normally work
new 	simulates what the above patch causes to happen
sane 	allows you to check after patching that chdir does not happen
	when it should not.

sane	is the only one that will behave differently with and without the
	patch.

#!/bin/sh
# This is a shell archive.
# remove everything above the "#!/bin/sh" line
# and feed to /bin/sh
# Use -c option to overwrite existing files
#
# Contents: 
#	mw
#	makefile
#	configure
#
# packed by: <sjg@zen.quick.com.au> on Sun Mar 12 18:56:07 EST 2000
#
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f mw -a x$1 != x-c ; then
  echo shar: Will not over-write existing file \"mw\"
else
  echo shar: Extracting \"mw\" \(     508  characters\)
  sed 's/^X//' > mw << '!EOF'
X# original behaviour
Xold:	config.status
X	make
X
X# simulate the changed behaviour
Xnew:	config.status
X	cd ${.CURDIR} && make
X
X# test make(1) after patching
Xsane:	config.status
X	@echo make here\; `pwd`, test `pwd` = ${.CURDIR}; test `pwd` = ${.CURDIR} 
X	
X	@echo cd somewhere\; make here\; test `pwd` = ${.OBJDIR}; test `pwd` = ${.OBJDIR}
X	@echo nomakehere\; test `pwd` = ${.OBJDIR}; test `pwd` = ${.OBJDIR}
X	
Xconfig.status:
X	@echo objdir=`pwd`
X	/bin/sh ${.CURDIR}/configure
X
Xclean:
X	rm -f config.status makefile
!EOF
  if test      508  -ne `wc -c < mw`; then
    echo shar: \"mw\" unpacked with wrong size!
  fi
  
fi
if test -f makefile -a x$1 != x-c ; then
  echo shar: Will not over-write existing file \"makefile\"
else
  echo shar: Extracting \"makefile\" \(      40  characters\)
  sed 's/^X//' > makefile << '!EOF'
Xall:
X	@echo oops wrong makefile!; false
!EOF
  if test       40  -ne `wc -c < makefile`; then
    echo shar: \"makefile\" unpacked with wrong size!
  fi
  
fi
if test -f configure -a x$1 != x-c ; then
  echo shar: Will not over-write existing file \"configure\"
else
  echo shar: Extracting \"configure\" \(      94  characters\)
  sed 's/^X//' > configure << '!EOF'
X:
Xcat <<'!EOF' > makefile
Xtry:
X	@echo objdir=`pwd`
X	@echo it works!
X
X!EOF
Xtouch config.status
!EOF
  if test       94  -ne `wc -c < configure`; then
    echo shar: \"configure\" unpacked with wrong size!
  fi
  
fi
exit 0