]> xenbits.xensource.com Git - people/liuw/freebsd.git/commitdiff
Merge bmake 20151020
authorsjg <sjg@FreeBSD.org>
Fri, 23 Oct 2015 17:38:01 +0000 (17:38 +0000)
committersjg <sjg@FreeBSD.org>
Fri, 23 Oct 2015 17:38:01 +0000 (17:38 +0000)
36 files changed:
1  2 
contrib/bmake/ChangeLog
contrib/bmake/FILES
contrib/bmake/Makefile
contrib/bmake/arch.c
contrib/bmake/compat.c
contrib/bmake/cond.c
contrib/bmake/configure.in
contrib/bmake/for.c
contrib/bmake/job.c
contrib/bmake/main.c
contrib/bmake/make-bootstrap.sh.in
contrib/bmake/make.c
contrib/bmake/make.h
contrib/bmake/meta.c
contrib/bmake/metachar.c
contrib/bmake/metachar.h
contrib/bmake/mk/ChangeLog
contrib/bmake/mk/auto.obj.mk
contrib/bmake/mk/dirdeps.mk
contrib/bmake/mk/doc.mk
contrib/bmake/mk/gendirdeps.mk
contrib/bmake/mk/host-target.mk
contrib/bmake/mk/install-mk
contrib/bmake/mk/meta.autodep.mk
contrib/bmake/mk/meta.stage.mk
contrib/bmake/mk/meta.sys.mk
contrib/bmake/mk/own.mk
contrib/bmake/mk/rst2htm.mk
contrib/bmake/nonints.h
contrib/bmake/os.sh
contrib/bmake/parse.c
contrib/bmake/suff.c
contrib/bmake/unit-tests/varmisc.exp
contrib/bmake/unit-tests/varmisc.mk
contrib/bmake/var.c
usr.bin/bmake/Makefile

index e1d6f10c3414c3e9aede090520bb9e5519475932,0000000000000000000000000000000000000000..ec3255e6ffd5aa03c0c1042b9dfc33547c54bc74
mode 100644,000000..100644
--- /dev/null
@@@ -1,1754 -1,0 +1,1791 @@@
++2015-10-20  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * Makefile (MAKE_VERSION): 20151020
++        Merge with NetBSD make, pick up
++        o var.c: fix uninitialized var 
++
++2015-10-12  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * var.c: the conditional expressions used with ':?' can be
++      expensive, if already discarding do not evaluate or expand
++      anything. 
++
++2015-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * Makefile (MAKE_VERSION): 20151010
++        Merge with NetBSD make, pick up
++        o Add Boolean wantit flag to Var_Subst and Var_Parse
++          when FALSE we know we are discarding the result and can
++          skip operations like Cmd_Exec.
++
++2015-10-09  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * Makefile (MAKE_VERSION): 20151009
++        Merge with NetBSD make, pick up
++        o var.c: don't check for NULL before free()
++        o meta.c: meta_oodate, do not hard code ignore of makeDependfile
++
++2015-09-10  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * Makefile (MAKE_VERSION): 20150910
++        Merge with NetBSD make, pick up
++        o main.c: with -w print Enter/Leaving messages for objdir too
++          if necessary.
++        o centralize shell metachar handling
++      
++      * FILES: add metachar.[ch]
++
 +2015-06-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150606
 +        Merge with NetBSD make, pick up
 +        o make.1: document .OBJDIR target
 +
 +2015-05-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150505
 +        Merge with NetBSD make, pick up
 +        o cond.c: be strict about lhs of comparison when evaluating .if
 +          but less so when called from variable expansion.
 +        o unit-tests/cond2.mk: test various error conditions
 +
 +2015-05-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * machine.sh (MACHINE): Add Bitrig 
 +        patch from joerg@netbsd.org
 +
 +2015-04-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150418
 +        Merge with NetBSD make, pick up
 +        o job.c: use memmove() rather than memcpy()
 +
 +      * unit-tests/varshell.mk: SunOS cannot handle the TERMINATED_BY_SIGNAL
 +        case, so skip it.
 +
 +2015-04-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150411
 +        bump version - only mk/ changes.
 +      
 +2015-04-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150410
 +        Merge with NetBSD make, pick up
 +        o document different handling of '-' in jobs mode vs compat
 +        o fix jobs mode so that '-' only applies to whole job
 +          when shell lacks hasErrCtl
 +        o meta.c: use separate vars to track lcwd and latestdir (read)
 +          per process
 +      
 +2015-04-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20150401
 +        Merge with NetBSD make, pick up
 +        o meta.c: close meta file in child
 +      
 +      * Makefile: use BINDIR.bmake if set.
 +        Same for MANDIR and SHAREDIR
 +        Handy for testing release candidates
 +        in various environments.
 +      
 +2015-03-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * move initialization of savederr to block where it is used
 +        to avoid spurious warning from gcc5
 +
 +2014-11-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20141111
 +        just a cooler number
 +
 +2014-11-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20141105
 +        Merge with NetBSD make, pick up
 +        o revert major overhaul of suffix handling
 +          and POSIX compliance - too much breakage
 +          and impossible to make backwards compatible.
 +        o we still have the new unit test structure which is ok.
 +        o meta.c ensure "-- filemon" is at start of line.
 +
 +2014-09-17  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * configure.in: test that result of getconf PATH_MAX is numeric
 +        and discard if not.  Apparently needed for Hurd.
 +
 +2014-08-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20140830
 +        Merge with NetBSD make, pick up
 +        o major overhaul of suffix handling
 +        o improved POSIX compliance
 +        o overhauled unit-tests
 +
 +2014-06-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20140620
 +        Merge with NetBSD make, pick up
 +        o var.c return varNoError rather than var_Error for ::= modifiers.
 +
 +2014-05-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20140522
 +        Merge with NetBSD make, pick up
 +        o var.c detect some parse errors.
 +
 +2014-04-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Fix spelling errors - patch from Pedro Giffuni
 +
 +2014-02-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20140214
 +        Merge with NetBSD make, pick up
 +        o .INCLUDEFROM*
 +        o use Var_Value to get MAKEOBJDIR[PREFIX]
 +        o reduced realloc'ign in brk_string.
 +      * configure.in: add a check for compiler supporting __func__
 +
 +2014-01-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: ignore mksrc=none
 +
 +2014-01-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (DEFAULT_SYS_PATH?): use just ${prefix}/share/mk
 +
 +2014-01-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +      
 +      * Makefile (MAKE_VERSION): 20140101
 +      * configure.in: set bmake_path_max to min(_SC_PATH_MAX,1024)
 +      * Makefile.config: defined BMAKE_PATH_MAX to bmake_path_max
 +      * make.h: use BMAKE_PATH_MAX if MAXPATHLEN not defined (needed for
 +        Hurd) 
 +      * configure.in: Add AC_PREREQ and check for
 +        sysctl; patch from Andrew Shadura andrewsh at debian.org
 +
 +2013-10-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20131010
 +      * lose the const from arg to systcl to avoid problems on older BSDs.
 +
 +2013-10-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20131001
 +        Merge with NetBSD make, pick up
 +        o main.c: for NATIVE build sysctl to get MACHINE_ARCH from
 +          hw.machine_arch if necessary.
 +        o meta.c: meta_oodate - need to look at src of Link and target
 +          of Move as well.
 +      * main.c: check that CTL_HW and HW_MACHINE_ARCH exist.
 +        provide __arraycount() if needed.
 +
 +2013-09-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130904
 +        Merge with NetBSD make, pick up
 +        o Add VAR_INTERNAL context, so that internal setting of
 +          MAKEFILE does not override value set by makefiles.
 +
 +2013-09-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130902
 +        Merge with NetBSD make, pick up
 +        o CompatRunCommand: only apply shellErrFlag when errCheck is true
 +
 +2013-08-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130828
 +        Merge with NetBSD make, pick up
 +        o Fix VAR :sh = syntax from Will Andrews at freebsd.org
 +        o Call Job_SetPrefix() from Job_Init() so makefiles have
 +          opportunity to set .MAKE.JOB.PREFIX
 +
 +2013-07-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130730
 +        Merge with NetBSD make, pick up
 +        o Allow suppression of --- job -- tokens by setting
 +          .MAKE.JOB.PREFIX empty.
 +
 +2013-07-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130716
 +        Merge with NetBSD make, pick up
 +        o number of gmake compatibility tweaks
 +          -w for gmake style entering/leaving messages
 +          if .MAKE.LEVEL > 0 indicate it in progname "make[1]" etc.
 +          handle MAKEFLAGS containing only letters.
 +        o when overriding a GLOBAL variable on the command line,
 +          delete it from GLOBAL context so -V doesn't show the wrong
 +          value.
 +      
 +2013-07-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * configure.in: We don't need MAKE_LEVEL_SAFE anymore.
 +
 +      * Makefile (MAKE_VERSION): 20130706
 +        Merge with NetBSD make, pick up
 +        o Shell_Init(): export shellErrFlag if commandShell hasErrCtl is
 +          true so that CompatRunCommand() can use it, to ensure
 +          consistent behavior with jobs mode.
 +        o use MAKE_LEVEL_ENV to define the variable to propagate
 +          .MAKE.LEVEL - currently set to MAKELEVEL (same as gmake).
 +        o meta.c: use .MAKE.META.IGNORE_PATHS to allow customization of
 +          paths to ignore.
 +
 +2013-06-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130604
 +        Merge with NetBSD make, pick up
 +        o job.c: JobCreatePipe: do fcntl() after any tweaking of fd's
 +          to avoid leaking descriptors.
 +
 +2013-05-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130528
 +        Merge with NetBSD make, pick up
 +        o var.c: cleanup some left-overs in VarHash()
 +
 +2013-05-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130520
 +        generate manifest from component FILES rather than have to
 +        update FILES when mk/FILES changes.
 +
 +2013-05-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130518
 +        Merge with NetBSD make, pick up
 +        o suff.c: don't skip all processsing for .PHONY targets
 +          else wildcard srcs do not get expanded.
 +        o var.c: expand name of variable to delete if necessary.
 +
 +2013-03-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130330
 +        Merge with NetBSD make, pick up
 +        o meta.c: refine the handling of .OODATE in commands.
 +          Rather than suppress command comparison for the entire script
 +          as though .NOMETA_CMP had been used, only suppress it for the
 +          one command line.
 +          This allows something like ${.OODATE:M.NOMETA_CMP} to be used to 
 +          suppress comparison of a command without otherwise affecting it.
 +        o make.1: document that
 +
 +2013-03-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130321
 +        yes, not quite right but its a cooler number.
 +        Merge with NetBSD make, pick up
 +        o parse.c: fix ParseGmakeExport to be portable 
 +          and add a unit-test.
 +      * meta.c: call meta_init() before makefiles are read and if built
 +        with filemon support set .MAKE.PATH_FILEMON to _PATH_FILEMON
 +        this let's makefiles test for support.
 +        Call meta_mode_init() to process .MAKE.MODE.
 +
 +2013-03-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130305
 +        Merge with NetBSD make, pick up
 +        o run .STALE: target when a dependency from .depend is missing.
 +        o job.c: add Job_RunTarget() for the above and .BEGIN
 +
 +2013-03-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130303
 +        Merge with NetBSD make, pick up
 +        o main.c: set .MAKE.OS to utsname.sysname
 +        o job.c: more checks for read and poll errors
 +        o var.c: lose VarChangeCase() saves 4% time
 +
 +2013-03-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: remove MAKEOBJDIRPREFIX from environment since we
 +        want to use MAKEOBJDIR
 +
 +2013-01-27  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Merge with NetBSD make, pick up
 +        o make.1: more info on how shell commands are handled.
 +        o job.c,main.c: detect write errors to job pipes.
 +
 +2013-01-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile (MAKE_VERSION): 20130123
 +        Merge with NetBSD make, pick up
 +        o meta.c: if script uses .OODATE and meta_oodate() decides
 +          rebuild is needed, .OODATE will be empty - set it to .ALLSRC.
 +        o var.c: in debug output indicate which variabale modifiers
 +          apply to.
 +        o remove Check_Cwd logic the makefiles have been fixed.
 +      
 +2012-12-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * makefile.in: add a simple makefile for folk who insist on
 +        ./configure; make; make install
 +        it just runs boot-strap
 +      * include mk/* to accommodate the above
 +      * boot-strap:  re-work to accommodate the above
 +        mksrc defaults to $Mydir/mk
 +        allow op={configure,build,install,clean,all}
 +        add options to facilitate install
 +      * Makefile.config.in: just the bits set by configure
 +      * Makefile: bump version to 20121212
 +        abandon Makefile.in (NetBSD Makefile)
 +        leverage mk/* instead
 +      * configure.in: ensure srcdir is absolute
 +
 +2012-11-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20121111
 +        fix generation of bmake.cat1
 +
 +2012-11-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20121109
 +        Merge with NetBSD make, pick up
 +        o make.c: MakeBuildChild: return 0 so search continues if a
 +          .ORDER dependency is detected.
 +        o unit-tests/order: test the above
 +      
 +2012-11-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20121102
 +        Merge with NetBSD make, pick up
 +        o cond.c: allow cond_state[] to grow.
 +          In meta mode with a very large tree, we can hit the limit
 +          while processing dirdeps.
 +      
 +2012-10-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in: we need to use ${srcdir} not ${.CURDIR}
 +
 +2012-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20121010
 +        o protect syntax that only bmake parses correctly.
 +        o remove auto setting of FORCE_MACHINE, use configure's
 +          --with-force-machine=whatever if that is desired.
 +      
 +2012-10-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in: do not lose history from make.1 when generating bmake.1
 +
 +2012-10-07  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20121007
 +        Merge with NetBSD make, pick up
 +        o compat.c: ignore empty commands - same as jobs mode.
 +        o make.1: document meta chars that cause use of shell
 +
 +2012-09-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120911
 +      * bsd.after-import.mk: include Makefile.inc early and allow it to
 +        override PROG
 +
 +2012-08-31  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120831
 +        Merge with NetBSD make, pick up
 +        o cast sizeof() to int for comparison
 +        o minor make.1 tweak
 +
 +2012-08-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120830
 +        Merge with NetBSD make, pick up
 +        o .MAKE.EXPAND_VARIABLES knob can control default behavior of -V
 +        o debug flag -dV causes -V to show raw value regardless.
 +      
 +2012-07-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * bsd.after-import.mk (after-import): ensure unit-tests/Makefile
 +        gets SRCTOP set.
 +
 +2012-07-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120704
 +        Merge with NetBSD make, pick up
 +        o Job_ParseShell should call Shell_Init if it has been
 +          previously called.
 +      * Makefile.in: set USE_META based on configure result.
 +        also .PARSEDIR is safer indicator of bmake.
 +
 +2012-06-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in: bump version to 20120626
 +        ensure CPPFLAGS is in CFLAGS
 +      * meta.c: avoid nested externs
 +      * bsd.after-import.mk: avoid ${.CURDIR}/Makefile as target
 +      
 +2012-06-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120620
 +        Merge with NetBSD make, pick up
 +        o make_malloc.c: avoid including make_malloc.h again
 +
 +      * Makefile.in: avoid bmake only syntax or protect with
 +        .if defined(.MAKE.LEVEL)
 +      * bsd.after-import.mk: replace .-include with .sinclude
 +        ensure? SRCTOP gets a value
 +      * configure.in: look for filemon.h in /usr/include/dev/filemon first.
 +
 +2012-06-19  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120612
 +        Merge with NetBSD make, pick up
 +        o use MAKE_ATTR_* rather than those defined by cdefs.h or compiler
 +          for greater portability.
 +        o unit-tests/forloop: check that .for works as expected wrt
 +          number of times and with "quoted strings".
 +      
 +2012-06-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120606
 +        Merge with NetBSD make, pick up
 +        o compat.c: use kill(2) rather than raise(3).
 +      * configure.in: look for sys/dev/filemon
 +      * bsd.after-import.mk: add a .-include "Makefile.inc" to Makefile
 +        and pass BOOTSTRAP_XTRAS to boot-strap.
 +
 +2012-06-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120604
 +        Merge with NetBSD make, pick up
 +        o util.c and var.c share same var for tracking if environ
 +          has been reallocated.
 +        o util.c provide getenv with setenv.
 +      * Add MAKE_LEVEL_SAFE as an alternate means of passing MAKE_LEVEL
 +        when the shell actively strips .MAKE.* from the environment.
 +        We still refer to the variable always as .MAKE.LEVEL
 +      * util.c fix bug in findenv() was finding prefix of name.
 +      * compat.c: re-raising SIGINT etc after running .INTERRUPT
 +        results in more reliable termination of all activity on many
 +        platforms.
 +
 +2012-06-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120602
 +        Merge with NetBSD make, pick up
 +        o for.c: handle quoted items in .for list
 +
 +2012-05-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120530
 +        Merge with NetBSD make, pick up
 +        o compat.c: ignore empty command.
 +
 +2012-05-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120524
 +      * FILES: add bsd.after-import.mk:
 +        A simple means of integrating bmake into a BSD build system.
 +
 +2012-05-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120520
 +        Merge with NetBSD make, pick up
 +        o increased limit for nested conditionals.
 +      
 +2012-05-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120518
 +        Merge with NetBSD make, pick up
 +        o use _exit(2) in signal hanlder
 +        o Don't use the [dir] cache when building nodes that might have
 +          changed since the last exec.
 +        o Avoid nested extern declaration warnings.
 +
 +2012-04-27  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.c (fgetLine): avoid %z - not portable.
 +      * parse.c: Since we moved include of sys/mman.h
 +        and def's of MAP_COPY etc. we got dups from a merge.
 +
 +2012-04-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120420
 +        Merge with NetBSD make, pick up
 +        o restore duplicate supression in .MAKE.MAKEFILES
 +          runtime saving can be significant.
 +        o Var_Subst() uses Buf_DestroyCompact() to reduce memory
 +          consumption up to 20%. 
 +
 +2012-04-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120420
 +          Merge with NetBSD make, pick up
 +        o remove duplicate supression in .MAKE.MAKEFILES
 +        o improved dir cache behavior
 +        o gmake'ish export command
 +      
 +2012-03-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20120325
 +        Merge with NetBSD make, pick up
 +        o fix parsing of :[#] in conditionals.
 +
 +2012-02-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in: replace use of .Nx in bmake.1 with NetBSD
 +        since some systems cannot cope with .Nx <version>
 +
 +2011-11-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20111111
 +        Merge with NetBSD make, pick up
 +        o debug output for .PARSEDIR and .PARSEFILE
 +
 +2011-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION):  bump version to 20111010
 +
 +2011-10-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: check for an expected file in the dirs we look for.
 +      * make-bootstrap.sh: pass on LDSTATIC
 +
 +2011-10-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20111001
 +        Merge with NetBSD make, pick up
 +        o ensure .PREFIX is set for .PHONY
 +          and .TARGET set for .PHONY run via .END
 +        o __dead used consistently
 +      
 +2011-09-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): 20110909 is a better number ;-)
 +
 +2011-09-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110905
 +        Merge with NetBSD make, pick up
 +        o meta_oodate: ignore makeDependfile
 +      
 +2011-08-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110828
 +        Merge with NetBSD make, pick up
 +        o silent=yes in .MAKE.MODE causes meta mode to mark targets 
 +          as SILENT if a .meta file is created
 +
 +2011-08-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110818
 +        Merge with NetBSD make, pick up
 +        o in meta mode, if target flagged .META a missing .meta file
 +          means target is out-of-date
 +        o fixes for gcc 4.5 warnings
 +        o simplify job printing code
 +      
 +2011-08-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110808
 +        Merge with NetBSD make, pick up
 +        o do not touch OP_SPECIAL targets when doing make -t
 +      
 +2011-06-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110622
 +        Merge with NetBSD make, pick up
 +        o meta_oodate detect corrupted .meta file and declare oodate.
 +      * configure.in: add check for setsid
 +      
 +2011-06-07  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Merge with NetBSD make, pick up
 +        o unit-tests/modts now works on MirBSD
 +
 +2011-06-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110606
 +        Merge with NetBSD make, pick up
 +        o ApplyModifiers: when we parse a variable which is not
 +          the entire modifier string, or not followed by ':', do not
 +          consider it as containing modifiers.
 +        o loadfile: ensure newline at end of mapped file.
 +
 +2011-05-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110505
 +        Merge with NetBSD make, pick up
 +        o .MAKE.META.BAILIWICK - list of prefixes which define the scope
 +          of make's control.  In meta mode, any generated file within
 +          said bailiwick, which  is found to be missing, causes current
 +          target to be out-of-date. 
 +      
 +2011-04-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110411
 +        Merge with NetBSD make, pick up
 +        o when long modifiers fail to match, check sysV style.
 +          - add a test case
 +      
 +2011-04-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110410
 +        Merge with NetBSD make, pick up
 +        o :hash - cheap 32bit hash of value
 +        o :localtime, :gmtime - use value as format string for strftime.
 +      
 +2011-03-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110330
 +        mostly because its a cooler version.
 +        Merge with NetBSD make, pick up
 +        o NetBSD tags for meta.[ch]
 +        o job.c call meta_job_finish() after meta_job_error().
 +        o meta_job_error() should call meta_job_finish() to ensure
 +          .meta file is closed, and safe to copy - if .ERROR target wants.
 +         meta_job_finish() is safe to call repeatedly.
 +      
 +2011-03-29  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * unit-tests/modts: use printf if it is a builtin, 
 +        to save us from MirBSD
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110329
 +        Merge with NetBSD make, pick up
 +        o fix for use after free() in CondDoExists().
 +        o meta_oodate() report extra commands and return earlier.
 +      
 +2011-03-27  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110327
 +        Merge with NetBSD make, pick up
 +        o meta.c, if .MAKE.MODE contains curdirOk=yes
 +          allow creating .meta files in .CURDIR
 +      * boot-strap (TOOL_DIFF): aparently at least on linux distro
 +        formats the output of 'type' differently - so eat any "()"
 +
 +2011-03-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110306
 +        Merge with NetBSD make, pick up
 +        o meta.c, only do getcwd() once
 +      
 +2011-03-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110305
 +        Merge with NetBSD make, pick up
 +        o correct sysV substitution handling of empty lhs and variable
 +        o correct exists() check for dir with trailing /
 +        o correct handling of modifiers for non-existant variables
 +          during evaluation of conditionals.
 +        o ensure MAP_FILE is defined.
 +        o meta.c use curdir[] now exported by main.c
 +      
 +2011-02-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110225
 +        Merge with NetBSD make, pick up
 +        o fix for incorrect .PARSEDIR when .OBJDIR is re-computed after
 +          makefiles have been read.
 +        o fix example of :? modifier in man page.
 +      
 +2011-02-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110214
 +        Merge with NetBSD make, pick up
 +        o meta.c handle realpath() failing when generating meta file
 +          name.
 +
 +      * sigcompat.c: convert to ansi so we can use higher warning levels.
 +
 +
 +2011-02-07  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110207
 +        Merge with NetBSD make, pick up
 +        o fix for bug in meta mode.
 +      
 +2011-01-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * parse.c: SunOS 5.8 at least does not have MAP_FILE
 +
 +2011-01-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20110101
 +        Merge with NetBSD make, pick up
 +        o use mmap(2) if available, for reading makefiles
 +
 +2010-12-15  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20101215
 +        Merge with NetBSD make, pick up
 +        o ensure meta_job_error() does not report a previous .meta file
 +          as being culprit.
 +
 +2010-12-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20101210
 +        Merge with NetBSD make, pick up
 +        o meta_oodate: track cwd per process, and only consider target
 +          out-of-date if missing file is outside make's CWD.
 +          Ignore files in /tmp/ etc.
 +        o to ensure unit-tests results match, need to control LC_ALL
 +          as well as LANG.
 +        o fix for parsing bug in var.c
 +
 +2010-11-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20101126
 +        Merge with NetBSD make, pick up
 +        o if stale dependency is an IMPSRC, search via .PATH
 +        o meta_oodate: if a referenced file is missing, target is
 +          out-of-date.
 +        o meta_oodate: if a target uses .OODATE in its commands,
 +          it (.OODATE) needs to be recomputed.
 +        o keep a pointer to youngest child node, rather than just its
 +          mtime.
 +      
 +2010-11-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20101101
 +
 +2010-10-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * machine.sh: like os.sh, 
 +      allow for uname -p producing useless drivel
 +
 +2010-09-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: document configure knobs for meta and filemon.
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100911
 +        Merge with NetBSD make, pick up
 +        o meta.c - meta mode
 +
 +      * make-bootstrap.sh.in: handle meta.c
 +      * configure.in: add knobs for use_meta and filemon_h
 +        also, look for dirname, str[e]sep and strlcpy
 +      * util.c: add simple err[x] and warn[x]
 +
 +2010-08-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap (TOOL_DIFF): set this to ensure tests use
 +        the same version of diff that configure tested
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100808
 +        Merge with NetBSD make, pick up
 +        o in jobs mode, when we discover we cannot make something,
 +          call PrintOnError before exit.
 +      
 +2010-08-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100806
 +        Merge with NetBSD make, pick up
 +        o formatting fixes for ignored errors
 +        o ensure jobs are cleaned up regardless of where wait() was called.
 +
 +2010-06-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100618
 +      * os.sh (MACHINE_ARCH): watch out for drivel from uname -p
 +
 +2010-06-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100616
 +        Merge with NetBSD make, pick up
 +        o man page update
 +        o call PrintOnError from JobFinish when we detect an error we
 +          are not ignoring. 
 +      
 +2010-06-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100606
 +        Merge with NetBSD make, pick up
 +        o man page update
 +
 +2010-06-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100605
 +        Merge with NetBSD make, pick up
 +        o use bmake_signal() which is a wrapper around sigaction() 
 +          in place of signal()
 +        o add .export-env to allow exporting variables to environment
 +          without tracking (so no re-export when the internal value is
 +          changed).
 +      
 +2010-05-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100524
 +        Merge with NetBSD make, pick up
 +        o fix for .info et al being greedy.
 +
 +2010-05-23  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100520
 +        Merge with NetBSD make, pick up
 +        o back to using realpath on argv[0] 
 +          but only if contains '/' and does not start with '/'.
 +
 +2010-05-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: use absolute path for bmake when running tests.
 +
 +      * Makefile.in (MAKE_VERSION):  bump version to 20100510
 +        Merge with NetBSD make, pick up
 +        o revert use of realpath on argv[0]
 +          too many corner cases.
 +        o print MAKE_PRINT_VAR_ON_ERROR before running .ERROR target.
 +
 +2010-05-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100505
 +        Merge with NetBSD make, pick up
 +        o fix for missed SIGCHLD when compiled with SunPRO
 +          actually for bmake, defining FORCE_POSIX_SIGNALS would have
 +          done the job.
 +
 +2010-04-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100430
 +        Merge with NetBSD make, pick up
 +        o fflush stdout before writing to stdout
 +      
 +2010-04-23  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100423
 +        Merge with NetBSD make, pick up
 +        o updated unit tests for Haiku (this time for sure).
 +      * boot-strap: based on patch from joerg 
 +        honor --with-default-sys-path better.
 +      * boot-strap: remove mention of --with-prefix-sys-path
 +      
 +2010-04-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100422
 +      * Merge with NetBSD make, pick up
 +        o fix for vfork() on Darwin.
 +        o fix for bogus $TMPDIR.
 +        o set .MAKE.MODE=compat for -B
 +        o set .MAKE.JOBS=max_jobs for -j max_jobs
 +        o allow unit-tests to run without any *.mk
 +        o unit-tests/modmisc be more conservative in dirs presumed to exist.
 +      * boot-strap: ignore /usr/share/mk except on NetBSD.
 +      * unit-tests/Makefile.in: set LANG=C when running unit-tests to
 +        ensure sort(1) behaves as expected. 
 +      
 +2010-04-21  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * boot-strap: add FindHereOrAbove so we can use -m .../mk
 +
 +2010-04-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100420
 +      * Merge with NetBSD make, pick up
 +        o fix for variable realpath() behavior.
 +          we have to stat(2) the result to be sure.
 +        o fix for .export (all) when nested vars use :sh
 +      
 +2010-04-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100414
 +      * Merge with NetBSD make, pick up
 +        o use realpath to resolve argv[0] (for .MAKE) if needed.
 +        o add realpath from libc.
 +        o add :tA to resolve variable via realpath(3) if possible.
 +
 +2010-04-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100408
 +      * Merge with NetBSD make, pick up
 +        o unit tests for .ERROR, .error
 +        o fix for .ERROR to ensure it cannot be default target.
 +
 +2010-04-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100406
 +      * Merge with NetBSD make, pick up
 +        o fix for compat mode "Error code" going to debug_file.
 +        o fix for .ALLSRC being populated twice.
 +        o support for .info, .warning and .error directives
 +        o .MAKE.MODE to control make's operational mode
 +        o .MAKE.MAKEFILE_PREFERENCE to control the preferred makefile
 +          name(s).
 +        o .MAKE.DEPENDFILE to control the name of the depend file
 +        o .ERROR target - run on failure.
 +      
 +2010-03-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * make-bootstrap.sh.in: extract MAKE_VERSION from Makefile
 +
 +      * os.sh,arch.c: patch for Haiku from joerg at netbsd
 +
 +2010-03-17  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100222
 +      * Merge with NetBSD make, pick up
 +        o better error msg for .for with mutiple inter vars
 +      
 +      * boot-strap: 
 +        o use make-bootstrap.sh from joerg at netbsd
 +          to avoid the need for a native make when bootstrapping.
 +        o add "" everywhere ;-)
 +        o if /usr/share/tmac/andoc.tmac exists install nroff bmake.1
 +          otherwise the pre-formated version.
 +
 +2010-01-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20100102
 +      * Merge with NetBSD make, pick up:
 +        o fix for -m .../
 +
 +2009-11-18  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20091118
 +      * Merge with NetBSD make, pick up:
 +        o .unexport
 +        o report lines that start with '.' and should have ':'
 +          (catch typo's of .el*if).
 +      
 +2009-10-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * configure.in: Ensure that srcdir and mksrc are absolute paths.
 +
 +2009-10-09  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): fix version to 20091007
 +
 +2009-10-07  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 200910007
 +      * Merge with NetBSD make, pick up:
 +        o fix for parsing of :S;...;...; applied to .for loop iterator
 +          appearing in a dependency line. 
 +      
 +2009-09-09  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20090909
 +      * Merge with NetBSD make, pick up:
 +        o fix for -C, .CURDIR and .OBJDIR
 +      * boot-strap: 
 +        o allow share_dir to be set independent of prefix.
 +        o select default share_dir better when prefix ends in $HOST_TARGET
 +        o if FORCE_BSD_MK etc were set, include them in the suggested
 +          install-mk command.
 +
 +2009-09-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20090908
 +      * Merge with NetBSD make, pick up:
 +        o .MAKE.LEVEL for recursion tracking
 +        o fix for :M scanning \:
 +
 +2009-09-03  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * configure.in: Don't -D__EXTENSIONS__ if
 +      AC_USE_SYSTEM_EXTENSIONS says "no".
 +
 +2009-08-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (MAKE_VERSION): bump version to 20090826
 +      Simplify MAKE_VERSION to just the bare date.
 +      * Merge with NetBSD make, pick up:
 +        o -C directory support.
 +        o support for SIGINFO
 +        o use $TMPDIR for temp files.
 +        o child of vfork should be careful about modifying parent's state.
 +      
 +
 +2009-03-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Appy some patches for MiNT from David Brownlee
 +
 +2009-02-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20090222
 +      * Merge with NetBSD make, pick up:
 +        o Possible null pointer de-ref in Var_Set.
 +
 +2009-02-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20090204
 +      * Merge with NetBSD make, pick up:
 +        o bmake_malloc et al moved to their own .c
 +        o Count both () and {} when looking for the end of a :M pattern
 +        o Change 'Buffer' so that it is the actual struct, not a pointer to it.
 +        o strlist.c - functions for processing extendable arrays of pointers to strings.
 +        o ClientData replaced with void *, so const void * can be used.
 +        o New debug flag C for DEBUG_CWD
 +
 +2008-11-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20081111
 +        Apply patch from Joerg Sonnenberge to
 +        configure.in:
 +        o remove some redundant checks
 +        o check for emlloc etc only in libutil and require the whole family.
 +        util.c:
 +        o remove [v]asprintf which is no longer used.
 +      
 +2008-11-04  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20081101
 +      * Merge with NetBSD make, pick up:
 +        o util.c: avoid use of putenv() - christos
 +
 +2008-10-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20081030
 +        pick up man page tweaks.
 +
 +2008-10-29  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in: move processing of LIBOBJS to after is definition!
 +        thus we'll have getenv.c in SRCS only if needed.
 +
 +      * make.1: add examples of how to use :?
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20081029
 +      * Merge with NetBSD make, pick up:
 +        o fix for .END processing with -j
 +        o segfault from Parse_Error when no makefile is open
 +        o handle numeric expressions in any variable expansion
 +        o debug output now defaults to stderr, -dF to change it - apb
 +        o make now uses bmake_malloc etc so that it can build natively 
 +          on A/UX - wasn't an issue for bmake, but we want to keep in sync.
 +
 +2008-09-27  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20080808
 +      * Merge with NetBSD make, pick up:
 +        o fix for PR/38840: Pierre Pronchery: make crashes while parsing
 +          long lines in Makefiles 
 +        o optimizations for VarQuote by joerg
 +        o fix for PR/38756: dominik: make dumps core on invalid makefile
 +      
 +2008-05-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20080515
 +      * Merge with NetBSD make, pick up:
 +        o fix skip setting vars in VAR_GLOBAL context, to handle
 +          cases where VAR_CMD is used for other than command line vars.
 +
 +2008-05-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * boot-strap (make_version): we may need to look in
 +      $prefix/share/mk for sys.mk 
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20080514
 +      * Merge with NetBSD make, pick up:
 +        o skip setting vars in VAR_GLOBAL context, when already set in
 +        VAR_CMD which takes precedence.
 +
 +2008-03-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION):  bump version to 20080330
 +      * Merge with NetBSD make, pick up:
 +        o fix for ?= when LHS contains variable reference.
 +
 +2008-02-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * merge some patches from NetBSD pkgsrc.
 +      
 +      * makefile.boot.in (BOOTSTRAP_SYS_PATH): Allow better control of
 +      the MAKSYSPATH used during bootstrap. 
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20080215
 +      * Merge with NetBSD make, pick up:
 +        o warn if non-space chars follow 'empty' in a conditional.
 +
 +2008-01-18  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20080118
 +      * Merge with NetBSD make, pick up:
 +        o consider dependencies read from .depend as optional - dsl
 +        o remember when buffer for reading makefile grows - dsl
 +        o add -dl (aka LOUD) - David O'Brien
 +
 +2007-10-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20071022
 +      * Merge with NetBSD make, pick up:
 +        o Allow .PATH<suffix> to be used for .include ""
 +
 +      * boot-strap: source default settings from .bmake-boot-strap.rc
 +
 +2007-10-16  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in: fix maninstall on various systems 
 +        provided that our man.mk is used.
 +        For non-BSD systems we install the preformatted page
 +        into $MANDIR/cat1
 +
 +2007-10-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * boot-strap: make bmake.1 too, so maninstall works.
 +
 +2007-10-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20071014
 +      * Merge with NetBSD make, pick up:
 +        o revamped handling of defshell - configure no longer needs to
 +          know the content of the shells array - apb
 +        o stop Var_Subst modifying its input - apb
 +        o avoid calling ParseTrackInput too often - dsl
 +
 +2007-10-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20071011
 +      * Merge with NetBSD make, pick up:
 +        o fix Shell_Init for case that _BASENAME_DEFSHELL is absolute path.
 +
 +      * sigcompat.c: some tweaks for HP-UX 11.x based on 
 +        patch from Tobias Nygren
 +
 +      * configure.in: update handling of --with-defshell to match
 +        new make behavior.  --with-defshell=/usr/xpg4/bin/sh
 +        will now do what one might hope - provided the chosen shell
 +        behaves enough like sh.
 +
 +2007-10-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20071008
 +      * Merge with NetBSD make, pick up:
 +        o .MAKE.JOB.PREFIX - control the token output before jobs - sjg
 +        o .export/.MAKE.EXPORTED - export of variables - sjg
 +        o .MAKE.MAKEFILES - track all makefiles read - sjg
 +        o performance improvements - dsl
 +        o revamp parallel job scheduling - dsl
 +      
 +2006-07-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20060728
 +      * Merge with NetBSD make, pick up:
 +        o extra debug info during variable and cond processing - sjg
 +        o shell definition now covers newline - rillig
 +        o minor mem leak in PrintOnError - sjg
 +
 +2006-05-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION):  bump to 20060511
 +      * Merge with NetBSD make, pick up:
 +        o more memory leaks - coverity
 +        o possible overflow in ArchFindMember - coverity
 +        o extract variable modifier code out of Var_Parse()
 +          so it can be called recursively - sjg
 +        o unit-tests/moderrs - sjg
 +
 +2006-04-12  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20060412
 +      * Merge with NetBSD make, pick up:
 +        o fixes for some memory leaks - coverity
 +        o only read first sys.mk etc when searching sysIncPath - sjg
 +
 +      * main.c (ReadMakefile): remove hack for __INTERIX that prevented
 +      setting ${MAKEFILE} - OBATA Akio
 +
 +2006-03-18  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20060318
 +      * Merge with NetBSD make, pick up:
 +        o cleanup of job.c to remove remote handling, distcc is more
 +          useful and this code was likely bit-rotting - dsl
 +        o fix for :P modifier - sjg
 +      * boot-strap: set default prefix to something reasonable 
 +        (for me anyway). 
 +
 +2006-03-01  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20060301
 +      * Merge with NetBSD make, pick up:
 +        o make .WAIT apply recursively, document and test case - apb
 +        o allow variable modifiers in a variable appear anywhere in
 +          modifier list, document and test case - sjg
 +
 +2006-02-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20060222
 +      * Merge with NetBSD make, pick up:
 +        o improved job token handling - dsl
 +        o SIG_DFL the correct signal before exec - dsl
 +        o more debug info during parsing - dsl
 +        o allow variable modifiers to be specified via variable - sjg
 +      * boot-strap: explain why we died if no mksrc
 +
 +2005-11-05  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20051105
 +      * configure.in: always set default_sys_path 
 +        default is ${prefix}/share/mk
 +        - remove prefix_sys_path, anyone wanting more than above
 +          needs to set it manually.
 +
 +2005-11-04  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * boot-strap: make this a bit easier for pkgsrc folk.
 +        bootstrap still fails on IRIX64 since MACHINE_ARCH gets set to
 +        'mips' while pkgsrc wants 'mipseb' or 'mipsel'
 +
 +2005-11-02  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20051102
 +      * job.c (JobFinish): fix likely ancient merge lossage
 +      fix from Todd Vierling.
 +      * boot-strap (srcdir): allow setting mksrc=none
 +
 +2005-10-31  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20051031
 +      * ranlib.h: skip on OSF too.
 +        (NetBSD PR 31864)
 +
 +2005-10-10  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20051002
 +        fix a silly typo
 +
 +2005-10-09  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20051001
 +        support for UnixWare and some other systems,
 +        based on patches from pkgsrc/bootstrap
 +
 +2005-09-03  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20050901
 +      * Merge with NetBSD make, pick up:
 +        o possible parse error causing us to wander off.
 +
 +2005-06-06  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20050606
 +      * Merge with NetBSD make, pick up:
 +        o :0x modifier for randomizing a list
 +        o fixes for a number of -Wuninitialized issues.
 +
 +2005-05-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20050530
 +      * Merge with NetBSD make, pick up:
 +        o Handle dependencies for .BEGIN, .END and .INTERRUPT
 +
 +      * README: was seriously out of date.
 +      
 +2005-03-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Important to use .MAKE rather than MAKE.
 +
 +2005-03-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20050315
 +      * Merge with NetBSD make, pick up:
 +        o don't mistake .elsefoo for .else
 +        o use suffix-specific search path correctly
 +        o bunch of style nits
 +
 +2004-05-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * boot-strap: 
 +      o ensure that args to --src and --with-mksrc
 +        are resolved before giving them to configure.
 +      o add -o "objdir" so that builder can control it,
 +        default is $OS as determined by os.sh
 +      o add -q to suppress all the install instructions.
 +
 +2004-05-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Remove __IDSTRING()
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20040508
 +      * Merge with NetBSD make, pick up:
 +        o posix fixes
 +          - remove '-e' from compat mode
 +          - add support for '+' command-line prefix.
 +        o fix for handling '--' on command-line.
 +        o fix include in lst.lib/lstInt.h to simplify '-I's
 +        o we also picked up replacement of MAKE_BOOTSTRAP 
 +          with !MAKE_NATIVE which is a noop, but possibly confusing.
 +
 +2004-04-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20040414
 +      * Merge with NetBSD make, pick up:
 +        o allow quoted strings on lhs of conditionals
 +        o issue warning when extra .else is seen
 +        o print line numer when errors encountered during parsing from
 +        string.
 +
 +2004-02-20  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION):  bump to 20040220
 +      * Merge with NetBSD make, pick up:
 +        o fix for old :M parsing bug.
 +        o re-jigged unit-tests
 +
 +2004-02-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (accept test): use ${.MAKE:S,^./,${.CURDIR}/,}
 +      so that './bmake -f Makefile test' works.
 +      
 +2004-02-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in: (BMAKE_VERSION): bump to 20040214
 +      * Merge with NetBSD make, pick up:
 +        o search upwards for *.mk
 +        o fix for double free of var substitution buffers
 +        o use of getopt replaced with custom code, since the usage
 +        (re-scanning) isn't posix compatible.
 +
 +2004-02-12  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * arch.c: don't include ranlib.h on ELF systems
 +      (thanks to Chuck Cranor <chuck@ece.cmu.edu>).
 +
 +2004-01-18  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump to 20040118
 +
 +      * boot-strap (while): export vars we assign to on cmdline
 +      * unit-test/Makefile.in: ternary is .PHONY
 +
 +2004-01-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20040108
 +      * Merge with NetBSD make, pick up:
 +        o fix for ternary modifier
 +
 +2004-01-06  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20040105
 +      * Merge with NetBSD make, pick up:
 +        o fix for cond.c to handle compound expressions better
 +        o variable expansion within sysV style replacements
 +        
 +2003-12-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Make portable snprintf safer - output to /dev/null first to
 +      check space needed.
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20031222
 +      * Merge with NetBSD make, pick up:
 +        o -dg3 to show input graph when things go wrong.
 +        o explicitly look for makefiles in objdir if not found in curdir so
 +          that errors in .depend etc will be reported accurarely. 
 +        o avoid use of -e in shell scripts in jobs mode, use '|| exit $?'
 +          instead as it more accurately reflects the expected behavior and
 +          is more consistently implemented.
 +        o avoid use of asprintf.
 +
 +2003-09-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * util.c: Add asprintf and vasprintf.
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20030928
 +      * Merge with NetBSD make, pick up:
 +      :[] modifier - allows picking words from a variable.
 +      :tW modifier - allows treating value as one big word.
 +      W flag for :C and :S - allows treating value as one big word.
 +      
 +2003-09-12  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make
 +      pick up -de flag to enable printing failed command.
 +      don't skip 1st two dir entries (normally . and ..) since
 +      coda does not have them.
 +
 +2003-09-09  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20030909
 +      * Merge with NetBSD make, pick up:
 +      - changes for -V '${VAR}' to print fully expanded value
 +        cf. -V VAR
 +      - CompatRunCommand now prints the command that failed.
 +      - several files got updated 3 clause Berkeley license.
 +      
 +2003-08-02  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * boot-strap: Allow setting configure args on command line.
 +
 +2003-07-31  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * configure.in: add --with-defshell to allow sh or ksh
 +      to be selected as default shell.
 +
 +      * Makefile.in: bump version to 20030731
 +
 +      * Merge with NetBSD make 
 +      Pick up .SHELL spec for ksh and associate man page changes.
 +      Also compat mode now uses the same shell specs.
 +
 +2003-07-29  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * var.c (Var_Parse): ensure delim is initialized.
 +
 +      * unit-tests/Makefile.in: use single quotes to avoid problems from
 +      some shells.
 +
 +      * makefile.boot.in:
 +      Run the unit-tests as part of the bootstrap procedure.
 +
 +2003-07-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * unit-tests/Makefile.in: always force complaints from
 +      ${TEST_MAKE} to be from 'make'.
 +
 +      * configure.in: add check for 'diff -u'
 +      also fix some old autoconf'isms
 +      
 +      * Makefile.in (BMAKE_VERSION): bump version to 20030728.
 +      if using GCC add -Wno-cast-qual to CFLAGS for var.o
 +
 +      * Merge with NetBSD make
 +      Pick up fix for :ts parsing error in some cases.
 +      Pick unit-tests.
 +
 +2003-07-23  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in (BMAKE_VERSION): bump version to 20030723.
 +
 +      * var.c (Var_Parse): fix bug in :ts modifier, after const
 +      correctness fixes, must pass nstr to VarModify.
 +
 +2003-07-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Makefile.in: BMAKE_VERSION switch to a date based version.
 +      We'll generally use the date of last import from NetBSD.
 +
 +      * Merge with NetBSD make
 +      Pick up fixes for const-correctness, now passes WARNS=3 on
 +      NetBSD.
 +      Pick up :ts modifier, allows controlling the separator used
 +      between words in variable expansion.
 +
 +2003-07-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * FILES: include boot-strap and os.sh
 +
 +      * Makefile.in: only set WARNS if we are NetBSD, the effect on
 +      FreeBSD is known to be bad.
 +
 +      * makefile.boot.in (bootstrap): make this the default target.
 +
 +      * Makefile.in: bump version to 3.1.19
 +
 +      * machine.sh: avoid A-Z with tr as it is bound to lose.
 +
 +2003-07-10  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make
 +      Pick up fix for PR/19781 - unhelpful error msg on unclosed ${var:foo
 +      Plus some doc fixes.
 +      
 +2003-04-27  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make
 +      Pick up fix for PR/1523 - don't count a library as built, if there
 +      is no way to build it 
 +
 +      * Bump version to 3.1.18
 +
 +2003-03-23  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make
 +      Pick up fix for ParseDoSpecialSrc - we only use it if .WAIT
 +      appears in src list.
 +
 +2003-03-21  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make (mmm 10th anniversary!)
 +      pick up fix for .WAIT in srcs that refer to $@ or $* (PR#20828)
 +      pick up -X which tells us to not export VAR=val via setenv if
 +      we are already doing so via MAKEFLAGS.  This saves valuable env
 +      space on systems like Darwin.
 +      set MAKE_VERSION to 3.1.17
 +
 +      * parse.c: pix up fix for suffix rules
 +
 +2003-03-06  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make.
 +      pick up fix for propagating -B via MAKEFLAGS.
 +      set MAKE_VERSION to 3.1.16
 +
 +      * Apply some patches from pkgsrc-bootstrap/bmake
 +      Originally by Grant Beattie <grant@netbsd.org>
 +      I may have missed some - since they are based on bmake-3.1.12
 +      
 +2002-12-03  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * makefile.boot.in (bmake): update install targets for those that
 +      use them, also clear MAKEFLAGS when invoking bmake.boot to avoid
 +      havoc from gmake -w.  Thanks to Harlan Stenn <hstenn@cisco.com>.
 +
 +      * bmake.cat1: update the pre-formatted man page!
 +
 +2002-11-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make.
 +      pick up fix for premature free of pointer used in call
 +      to Dir_InitCur().
 +      set MAKE_VERSION to 3.1.15
 +
 +2002-11-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * configure.in: determine suitable value for MKSRC.
 +      override using --with-mksrc=PATH.
 +
 +      * machine.sh: use `uname -p` for MACHINE_ARCH on modern SunOS systems.
 +      configs(8) will use 'sun4' as an alias for 'sparc'.
 +
 +2002-11-25  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Merge with NetBSD make.
 +      pick up ${.PATH}
 +      pick up fix for finding ../cat.c via .PATH when .CURDIR=..
 +      set MAKE_VERSION to 3.1.14
 +      add configure checks for killpg and sys/socket.h
 +
 +2002-09-16  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * tag bmake-3-1-13
 +      
 +      * makefile.boot.in (bmake): use install-mk
 +      Also setup ./mk before trying to invoke bmake.boot incase we
 +      needed install-mk to create a sys.mk for us. 
 +
 +      * configure.in: If we need to add -I${srcdir}/missing, make it an
 +      absolute path so that it works for lst.lib too.
 +
 +      * make.h: always include sys/cdefs.h since we provide one if the
 +      host does not.
 +      
 +      * Makefile.in (install-mk): 
 +      use MKSRC/install-mk which will do the right thing.
 +      use uname -p for ARCH if possible.
 +      since install-mk will setup links bsd.prog.mk -> prog.mk if
 +      needed, just .include bsd.prog.mk
 +
 +      * Merge with NetBSD make (NetBSD-1.6)
 +      Code is ansi-C only now.
 +      Bug in handling of dotLast is fixed.
 +      Can now assign .OBJDIR and make will reset its notions of life.
 +      New modifiers :tu :tl for toUpper and toLower.
 +
 +Tue Oct 16 12:18:42 2001  Simon J. Gerraty  <sjg@zen.crufty.net>
 +
 +      * Merge with NetBSD make
 +      pick up fix for .END failure in compat mode.
 +      pick up fix for extra va_end() in ParseVErrorInternal.
 +
 +Thu Oct 11 13:20:06 2001  Simon J. Gerraty  <sjg@zen.crufty.net>
 +
 +      * configure.in: for systems that have sys/cdefs.h check if it is
 +      compatible.  If not, include the one under missing, but tell it to
 +      include the native one too - necessary on Linux.
 +
 +      * missing/sys/cdefs.h: if NEED_HOST_CDEFS_H is defined, use
 +      include_next (for gcc) to get the native sys/cdefs.h
 +
 +Tue Aug 21 02:29:34 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * job.c (JobFinish): Fix an earlier merge bug that resulted in
 +      leaking descriptors when using -jN.
 +      
 +      * job.c (JobPrintCommand): See if "curdir" exists before
 +      attempting to chdir().  Doing the chdir directly in make (when in
 +      compat mode) fails silently, so let the -jN version do the same.
 +      This can happen when building kernels in an object tree and
 +      playing clever games to reset .CURDIR.
 +
 +      * Merged with NetBSD make
 +      pick up .USEBEFORE
 +
 +Tue Jun 26 23:45:11 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * makefile.boot.in: Give bmake.boot a MAKESYSPATH that might work.
 +
 +Tue Jun 12 16:48:57 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * var.c (Var_Set): Add 4th (flags) arg so VarLoopExpand can tell
 +      us not to export the iterator variable when using VAR_CMD context.
 +
 +Sun Jun 10 21:55:21 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * job.c (Job_CatchChildren): don't call Job_CatchOutput() here,
 +      its the wrong "fix".
 +
 +Sat Jun  9 00:11:24 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Redesigned export of VAR_CMD's via MAKEFLAGS.
 +      We now simply append the variable names to .MAKEOVERRIDES, and
 +      handle duplicate suppression and quoting in ExportMAKEFLAGS using:
 +      ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}
 +      Apart from fixing quoting bugs in previous version, this allows us
 +      to export vars to the environment by simply doing:
 +      .MAKEOVERRIDES+= PATH 
 +      Merged again with NetBSD make, but the above is the only change.
 +
 +      * configure.in: added
 +      --disable-pwd-override          disable $PWD overriding getcwd()
 +      --disable-check-make-chdir      disable make trying to guess 
 +              when it should automatically cd ${.CURDIR}
 +
 +      * Merge with NetBSD make, changes include:
 +      parse.c (ParseDoDependency): Spot that the syntax error is
 +      caused by an unresolved cvs/rcs conflict and say so.
 +      var.c: most of Var* functions now take a ctxt as 1st arg.
 +      now does variable substituion on rhs of sysv style modifiers.
 +      
 +      * var.c (Var_Set): exporting of command line variables (VAR_CMD)
 +      is now done here.  We append the name='value' to .MAKEOVERRIDES
 +      rather than directly into MAKEFLAGS as this allows a Makefile to
 +      use .MAKEOVERRIDES= to disable this behaviour.  GNU make uses a
 +      very similar mechanism.  Note that in adding name='value' to
 +      .MAKEOVERRIDES we do the moral equivalent of:
 +      .MAKEOVERRIDES:= ${.MAKEOVERRIDES:Nname=*} name='val'
 +
 +Fri Jun  1 14:08:02 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * make-conf.h (USE_IOVEC): make it conditional on HAVE_SYS_UIO_H
 +
 +      * Merged with NetBSD make
 +      make -dx can now be used to run commands via sh -x
 +      better error messages on exec failures.
 +
 +Thu May 31 01:44:54 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Makefile.in (main.o): depends on ${SRCS} ${MAKEFILE} so that
 +      MAKE_VERSION gets updated.  Also don't use ?= for MAKE_VERSION,
 +      MACHINE etc otherwise they propagate from the previous bmake.
 +
 +      * configure.in (machine): allow --with-machine=generic to make
 +      configure use machine.sh to set MACHINE. 
 +
 +      * job.c (JobInterrupt): convert to using WAIT_T and friends.
 +
 +      * Makefile.in: mention in bmake.1 that we use autoconf.
 +
 +      * make.1: mention MAKE_PRINT_VAR_ON_ERROR.
 +
 +Wed May 30 23:17:18 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * main.c (ReadMakefile): don't set MAKEFILE if reading ".depend"
 +      as that rather defeats the usefulness of ${MAKEFILE}.
 +
 +      * main.c (MainParseArgs): append command line variable assignments
 +      to MAKEFLAGS so that they get propagated to child make's.
 +      Apparently this is required POSIX behaviour?  Its useful anyway.
 +
 +Tue May 29 02:20:07 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * compat.c (CompatRunCommand): don't use perror() since stdio may
 +      cause problems in child of vfork().
 +
 +      * compat.c, main.c: Call PrintOnError() when we are going to bail.
 +      This routine prints out the .curdir where we stopped and will also
 +      display any vars listed in ${MAKE_PRINT_VAR_ON_ERROR}.
 +
 +      * main.c: add ${.newline} to hold a "\n" - sometimes handy in
 +      :@ expansion.
 +
 +      * var.c: VarLoopExpand: ignore addSpace if a \n is present.
 +
 +      * Added RCSid's for the files we've touched.
 +      
 +Thu May 24 15:41:37 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * configure.in: Thanks to some clues from mdb@juniper.net,
 +      added autoconf magic to control setting of MACHINE, MACHINE_ARCH
 +      as well as what ends up in _PATH_DEFSYSPATH.  We now have:
 +
 +  --with-machine=MACHINE  explicitly set MACHINE
 +  --with-force-machine=MACHINE  set FORCE_MACHINE
 +  --with-machine_arch=MACHINE_ARCH  explicitly set MACHINE_ARCH
 +  --with-default-sys-path=PATH:DIR:LIST  use an explicit _PATH_DEFSYSPATH
 +  --with-prefix-sys-path=PATH:DIR:LIST  prefix _PATH_PREFIX_SYSPATH
 +  --with-path-objdirprefix=PATH  override _PATH_OBJDIRPREFIX
 +      
 +      If _PATH_OBJDIRPREFIX is set to "no" we won't define it.
 +
 +      * makefile: added a pathetically simple makefile to drive
 +      bootstrapping.  Running configure by hand is more useful.
 +
 +      * Makefile.in: added MAKE_VERSION, and reworked things to be less
 +      dependent on NetBSD bsd.*.mk
 +      
 +      * pathnames.h: allow NO_PATH_OBJDIRPREFIX to stop us defining
 +      _PATH_OBJDIRPREFIX for those that don't want a default.
 +      construct _PATH_DEFSYSPATH from the info we get from configure.
 +
 +      * main.c: allow for no _PATH_OBJDIRPREFIX, set ${MAKE_VERSION}
 +      if MAKE_VERSION is defined.
 +      
 +      * compat.c: when we bail, print out the .CURDIR we were in.
 +      
 +Sat May 12 00:34:12 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +
 +      * var.c: fixed a bug in the handling of the modifier :P
 +      if the node as found but the path was null, we segfault trying to
 +      duplicate it.
 +
 +Mon Mar  5 16:20:33 2001  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +      
 +      * make.c: Make_OODate's test for a library out of date was using
 +      cmtime where it should have used mtime (my bug).
 +
 +      * compat.c: Use perror() to tell us what really went wrong when we
 +      cannot exec a command.
 +      
 +Fri Dec 15 10:11:08  2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +      
 +Sat Jun 10 10:11:08  2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +      
 +Thu Jun  1 10:11:08  2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +      
 +Tue May 30 10:11:08  2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Merged with NetBSD make
 +      
 +Thu Apr 27 00:07:47 2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * util.c: don't provide signal() since we use sigcompat.c
 +
 +      * Makefile.in: added a build target.
 +
 +      * var.c (Var_Parse): added ODE modifiers :U, :D, :L, :P, :@ and :!
 +      These allow some quite clever magic.
 +
 +      * main.c (main): added support for getenv(MAKESYSPATH).
 +
 +Mon Apr  2 16:25:13 2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Disable $PWD overriding getcwd() if MAKEOBJDIRPREFIX is set.
 +      This avoids objdir having a different value depending on how a
 +      directory was reached (via command line, or subdir.mk).
 +
 +      * If FORCE_MACHINE is defined, ignore getenv("MACHINE").
 +      
 +Mon Apr  2 23:15:31 2000  Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Do a chdir(${.CURDIR}) before invoking ${.MAKE} or ${.MAKE:T} if
 +      MAKEOBJDIRPREFIX is set and NOCHECKMAKECHDIR is not.
 +      I've been testing this in NetBSD's make for some weeks.
 +
 +      * Turn Makefile into Makefile.in and make it useful.
 +      
 +Tue Feb 29 22:08:00 2000 Simon J. Gerraty  <sjg@zen.quick.com.au>
 +
 +      * Imported NetBSD's -current make(1) and resolve conflicts.
 +      
 +      * Applied autoconf patches from bmake v2
 +
 +      * Imported clean code base from NetBSD-1.0
index c46e74bd7a46cb8c86ffc57bd5cfc02ee5900407,0000000000000000000000000000000000000000..720eb1b9bfb737cbdd535c43fc7df9a63bcbe353
mode 100644,000000..100644
--- /dev/null
@@@ -1,167 -1,0 +1,169 @@@
 +ChangeLog
 +FILES
 +Makefile
 +Makefile.config.in
 +PSD.doc/Makefile
 +PSD.doc/tutorial.ms
 +README
 +aclocal.m4
 +arch.c
 +bmake.1
 +bmake.cat1
 +boot-strap
 +bsd.after-import.mk
 +buf.c
 +buf.h
 +compat.c
 +cond.c
 +config.h.in
 +configure
 +configure.in
 +dir.c
 +dir.h
 +dirname.c
 +find_lib.sh
 +for.c
 +getopt.c
 +hash.c
 +hash.h
 +install-sh
 +job.c
 +job.h
 +lst.h
 +lst.lib/Makefile
 +lst.lib/lstAppend.c
 +lst.lib/lstAtEnd.c
 +lst.lib/lstAtFront.c
 +lst.lib/lstClose.c
 +lst.lib/lstConcat.c
 +lst.lib/lstDatum.c
 +lst.lib/lstDeQueue.c
 +lst.lib/lstDestroy.c
 +lst.lib/lstDupl.c
 +lst.lib/lstEnQueue.c
 +lst.lib/lstFind.c
 +lst.lib/lstFindFrom.c
 +lst.lib/lstFirst.c
 +lst.lib/lstForEach.c
 +lst.lib/lstForEachFrom.c
 +lst.lib/lstInit.c
 +lst.lib/lstInsert.c
 +lst.lib/lstInt.h
 +lst.lib/lstIsAtEnd.c
 +lst.lib/lstIsEmpty.c
 +lst.lib/lstLast.c
 +lst.lib/lstMember.c
 +lst.lib/lstNext.c
 +lst.lib/lstOpen.c
 +lst.lib/lstPrev.c
 +lst.lib/lstRemove.c
 +lst.lib/lstReplace.c
 +lst.lib/lstSucc.c
 +machine.sh
 +main.c
 +make-bootstrap.sh.in
 +make-conf.h
 +make.1
 +make.c
 +make.h
 +make_malloc.c
 +make_malloc.h
 +makefile.in
 +meta.c
 +meta.h
++metachar.c
++metachar.h
 +missing/sys/cdefs.h
 +mkdeps.sh
 +nonints.h
 +os.sh
 +parse.c
 +pathnames.h
 +ranlib.h
 +realpath.c
 +setenv.c
 +sigcompat.c
 +sprite.h
 +str.c
 +stresep.c
 +strlcpy.c
 +strlist.c
 +strlist.h
 +suff.c
 +targ.c
 +trace.c
 +trace.h
 +unit-tests/Makefile.in
 +unit-tests/comment.exp
 +unit-tests/comment.mk
 +unit-tests/cond1.exp
 +unit-tests/cond1.mk
 +unit-tests/cond2.exp
 +unit-tests/cond2.mk
 +unit-tests/doterror.exp
 +unit-tests/doterror.mk
 +unit-tests/dotwait.exp
 +unit-tests/dotwait.mk
 +unit-tests/error.exp
 +unit-tests/error.mk
 +unit-tests/escape.exp
 +unit-tests/escape.mk
 +unit-tests/export-all.exp
 +unit-tests/export-all.mk
 +unit-tests/export-env.exp
 +unit-tests/export-env.mk
 +unit-tests/export.exp
 +unit-tests/export.mk
 +unit-tests/forloop.exp
 +unit-tests/forloop.mk
 +unit-tests/forsubst.exp
 +unit-tests/forsubst.mk
 +unit-tests/hash.exp
 +unit-tests/hash.mk
 +unit-tests/impsrc.exp
 +unit-tests/impsrc.mk
 +unit-tests/misc.exp
 +unit-tests/misc.mk
 +unit-tests/moderrs.exp
 +unit-tests/moderrs.mk
 +unit-tests/modmatch.exp
 +unit-tests/modmatch.mk
 +unit-tests/modmisc.exp
 +unit-tests/modmisc.mk
 +unit-tests/modorder.exp
 +unit-tests/modorder.mk
 +unit-tests/modts.exp
 +unit-tests/modts.mk
 +unit-tests/modword.exp
 +unit-tests/modword.mk
 +unit-tests/order.exp
 +unit-tests/order.mk
 +unit-tests/phony-end.exp
 +unit-tests/phony-end.mk
 +unit-tests/posix.exp
 +unit-tests/posix.mk
 +unit-tests/posix1.exp
 +unit-tests/posix1.mk
 +unit-tests/qequals.exp
 +unit-tests/qequals.mk
 +unit-tests/suffixes.exp
 +unit-tests/suffixes.mk
 +unit-tests/sunshcmd.exp
 +unit-tests/sunshcmd.mk
 +unit-tests/sysv.exp
 +unit-tests/sysv.mk
 +unit-tests/ternary.exp
 +unit-tests/ternary.mk
 +unit-tests/unexport-env.exp
 +unit-tests/unexport-env.mk
 +unit-tests/unexport.exp
 +unit-tests/unexport.mk
 +unit-tests/varcmd.exp
 +unit-tests/varcmd.mk
 +unit-tests/varmisc.exp
 +unit-tests/varmisc.mk
 +unit-tests/varshell.exp
 +unit-tests/varshell.mk
 +util.c
 +var.c
 +wait.h
index e5427a877f202f0a7977387c3351cb2e8f9cd676,0000000000000000000000000000000000000000..a059ec4df01a341daf40c58175d06c628fab0879
mode 100644,000000..100644
--- /dev/null
@@@ -1,221 -1,0 +1,222 @@@
- #     $Id: Makefile,v 1.39 2015/06/07 15:54:37 sjg Exp $
++#     $Id: Makefile,v 1.44 2015/10/20 21:41:40 sjg Exp $
 +
 +# Base version on src date
- MAKE_VERSION= 20150606
++MAKE_VERSION= 20151020
 +
 +PROG= bmake
 +
 +SRCS= \
 +      arch.c \
 +      buf.c \
 +      compat.c \
 +      cond.c \
 +      dir.c \
 +      for.c \
 +      hash.c \
 +      job.c \
 +      main.c \
 +      make.c \
 +      make_malloc.c \
 +      meta.c \
++      metachar.c \
 +      parse.c \
 +      str.c \
 +      strlist.c \
 +      suff.c \
 +      targ.c \
 +      trace.c \
 +      util.c \
 +      var.c
 +
 +# from lst.lib/
 +SRCS+= \
 +      lstAppend.c \
 +      lstAtEnd.c \
 +      lstAtFront.c \
 +      lstClose.c \
 +      lstConcat.c \
 +      lstDatum.c \
 +      lstDeQueue.c \
 +      lstDestroy.c \
 +      lstDupl.c \
 +      lstEnQueue.c \
 +      lstFind.c \
 +      lstFindFrom.c \
 +      lstFirst.c \
 +      lstForEach.c \
 +      lstForEachFrom.c \
 +      lstInit.c \
 +      lstInsert.c \
 +      lstIsAtEnd.c \
 +      lstIsEmpty.c \
 +      lstLast.c \
 +      lstMember.c \
 +      lstNext.c \
 +      lstOpen.c \
 +      lstPrev.c \
 +      lstRemove.c \
 +      lstReplace.c \
 +      lstSucc.c
 +
 +# this file gets generated by configure
 +.-include "Makefile.config"
 +
 +.if !empty(LIBOBJS)
 +SRCS+= ${LIBOBJS:T:.o=.c}
 +.endif
 +
 +# just in case
 +prefix?= /usr
 +srcdir?= ${.CURDIR}
 +
 +DEFAULT_SYS_PATH?= ${prefix}/share/mk
 +
 +CPPFLAGS+= -DUSE_META
 +CFLAGS+= ${CPPFLAGS}
 +CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\"
 +CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE
 +CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}}
 +COPTS.main.c+= "-DMAKE_VERSION=\"${MAKE_VERSION}\""
 +
 +# meta mode can be useful even without filemon 
 +FILEMON_H ?= /usr/include/dev/filemon/filemon.h
 +.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h"
 +COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H}
 +.endif
 +
 +.PATH:        ${srcdir}
 +.PATH:        ${srcdir}/lst.lib
 +
 +.if make(obj) || make(clean)
 +SUBDIR+= unit-tests
 +.endif
 +
 +# start-delete1 for bsd.after-import.mk
 +# we skip a lot of this when building as part of FreeBSD etc.
 +
 +# list of OS's which are derrived from BSD4.4
 +BSD44_LIST= NetBSD FreeBSD OpenBSD DragonFly MirBSD Bitrig
 +# we are...
 +OS!= uname -s
 +# are we 4.4BSD ?
 +isBSD44:=${BSD44_LIST:M${OS}}
 +
 +.if ${isBSD44} == ""
 +MANTARGET= cat
 +INSTALL?=${srcdir}/install-sh
 +.if (${MACHINE} == "sun386")
 +# even I don't have one of these anymore :-)
 +CFLAGS+= -DPORTAR
 +.elif (${MACHINE} != "sunos")
 +SRCS+= sigcompat.c
 +CFLAGS+= -DSIGNAL_FLAGS=SA_RESTART
 +.endif
 +.else
 +MANTARGET?= man
 +.endif
 +
 +# turn this on by default - ignored if we are root
 +WITH_INSTALL_AS_USER=
 +
 +# suppress with -DWITHOUT_*
 +OPTIONS_DEFAULT_YES+= \
 +      AUTOCONF_MK \
 +      INSTALL_MK \
 +      PROG_LINK
 +
 +OPTIONS_DEFAULT_NO+= \
 +      PROG_VERSION
 +
 +# process options now
 +.include <own.mk>
 +
 +.if ${MK_PROG_VERSION} == "yes"
 +PROG_NAME= ${PROG}-${MAKE_VERSION}
 +.if ${MK_PROG_LINK} == "yes"
 +SYMLINKS+= ${PROG}-${MAKE_VERSION} ${BINDIR}/${PROG}
 +.endif
 +.endif
 +
 +EXTRACT_MAN=no
 +# end-delete1
 +
 +MAN= ${PROG}.1
 +MAN1= ${MAN}
 +
 +.if (${PROG} != "make")
 +CLEANFILES+= my.history
 +.if make(${MAN}) || !exists(${srcdir}/${MAN})
 +my.history: ${MAKEFILE}
 +      @(echo ".Nm"; \
 +      echo "is derived from NetBSD"; \
 +      echo ".Xr make 1 ."; \
 +      echo "It uses autoconf to facilitate portability to other platforms."; \
 +      echo ".Pp") > $@
 +
 +.NOPATH: ${MAN}
 +${MAN}:       make.1 my.history
 +      @echo making $@
 +      @sed -e 's/^.Nx/NetBSD/' -e '/^.Nm/s/make/${PROG}/' \
 +      -e '/^.Sh HISTORY/rmy.history' \
 +      -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@
 +
 +all beforeinstall: ${MAN}
 +_mfromdir=.
 +.endif
 +.endif
 +
 +MANTARGET?= cat
 +MANDEST?= ${MANDIR}/${MANTARGET}1
 +
 +.if ${MANTARGET} == "cat"
 +_mfromdir=${srcdir}
 +.endif
 +
 +.include <prog.mk>
 +
 +CPPFLAGS+= -DMAKE_NATIVE -DHAVE_CONFIG_H
 +COPTS.var.c += -Wno-cast-qual
 +COPTS.job.c += -Wno-format-nonliteral
 +COPTS.parse.c += -Wno-format-nonliteral
 +COPTS.var.c += -Wno-format-nonliteral
 +
 +# Force these
 +SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share}
 +BINDIR= ${BINDIR.bmake:U${prefix}/bin}
 +MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man}
 +
 +.if !exists(.depend)
 +${OBJS}: config.h
 +.endif
 +
 +# make sure that MAKE_VERSION gets updated.
 +main.o: ${SRCS} ${MAKEFILE}
 +
 +# start-delete2 for bsd.after-import.mk
 +.if ${MK_AUTOCONF_MK} == "yes"
 +.include <autoconf.mk>
 +.endif
 +SHARE_MK?=${SHAREDIR}/mk
 +MKSRC=${srcdir}/mk
 +INSTALL?=${srcdir}/install-sh
 +
 +.if ${MK_INSTALL_MK} == "yes"
 +install: install-mk
 +.endif
 +
 +beforeinstall:
 +      test -d ${DESTDIR}${BINDIR} || ${INSTALL} -m 775 -d ${DESTDIR}${BINDIR}
 +      test -d ${DESTDIR}${MANDEST} || ${INSTALL} -m 775 -d ${DESTDIR}${MANDEST}
 +
 +install-mk:
 +.if exists(${MKSRC}/install-mk)
 +      test -d ${DESTDIR}${SHARE_MK} || ${INSTALL} -m 775 -d ${DESTDIR}${SHARE_MK}
 +      sh ${MKSRC}/install-mk -v -m 644 ${DESTDIR}${SHARE_MK}
 +.else
 +      @echo need to unpack mk.tar.gz under ${srcdir} or set MKSRC; false
 +.endif
 +# end-delete2
 +
 +# A simple unit-test driver to help catch regressions
 +accept test:
 +      cd ${.CURDIR}/unit-tests && MAKEFLAGS= ${.MAKE} -r -m / TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET}
index 943f41ee8392351b38a6a8edec14c98354f9a561,0000000000000000000000000000000000000000..9ceee10cddb73e0db0328532fc40b5270c311f32
mode 100644,000000..100644
--- /dev/null
@@@ -1,1403 -1,0 +1,1403 @@@
- /*    $NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $  */
++/*    $NetBSD: arch.c,v 1.64 2015/10/11 04:51:24 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $";
++static char rcsid[] = "$NetBSD: arch.c,v 1.64 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)arch.c    8.2 (Berkeley) 1/2/94";
 +#else
- __RCSID("$NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $");
++__RCSID("$NetBSD: arch.c,v 1.64 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * arch.c --
 + *    Functions to manipulate libraries, archives and their members.
 + *
 + *    Once again, cacheing/hashing comes into play in the manipulation
 + * of archives. The first time an archive is referenced, all of its members'
 + * headers are read and hashed and the archive closed again. All hashed
 + * archives are kept on a list which is searched each time an archive member
 + * is referenced.
 + *
 + * The interface to this module is:
 + *    Arch_ParseArchive       Given an archive specification, return a list
 + *                            of GNode's, one for each member in the spec.
 + *                            FAILURE is returned if the specification is
 + *                            invalid for some reason.
 + *
 + *    Arch_Touch              Alter the modification time of the archive
 + *                            member described by the given node to be
 + *                            the current time.
 + *
 + *    Arch_TouchLib           Update the modification time of the library
 + *                            described by the given node. This is special
 + *                            because it also updates the modification time
 + *                            of the library's table of contents.
 + *
 + *    Arch_MTime              Find the modification time of a member of
 + *                            an archive *in the archive*. The time is also
 + *                            placed in the member's GNode. Returns the
 + *                            modification time.
 + *
 + *    Arch_MemTime            Find the modification time of a member of
 + *                            an archive. Called when the member doesn't
 + *                            already exist. Looks in the archive for the
 + *                            modification time. Returns the modification
 + *                            time.
 + *
 + *    Arch_FindLib            Search for a library along a path. The
 + *                            library name in the GNode should be in
 + *                            -l<name> format.
 + *
 + *    Arch_LibOODate          Special function to decide if a library node
 + *                            is out-of-date.
 + *
 + *    Arch_Init               Initialize this module.
 + *
 + *    Arch_End                Cleanup this module.
 + */
 +
 +#ifdef HAVE_CONFIG_H
 +# include "config.h"
 +#endif
 +#include    <sys/types.h>
 +#include    <sys/stat.h>
 +#include    <sys/time.h>
 +#include    <sys/param.h>
 +#include    <ctype.h>
 +#ifdef HAVE_AR_H
 +#include    <ar.h>
 +#else
 +struct ar_hdr {
 +        char ar_name[16];               /* name */
 +        char ar_date[12];               /* modification time */
 +        char ar_uid[6];                 /* user id */
 +        char ar_gid[6];                 /* group id */
 +        char ar_mode[8];                /* octal file permissions */
 +        char ar_size[10];               /* size in bytes */
 +#ifndef ARFMAG
 +#define ARFMAG  "`\n"
 +#endif
 +        char ar_fmag[2];                /* consistency check */
 +};
 +#endif
 +#if defined(HAVE_RANLIB_H) && !(defined(__ELF__) || defined(NO_RANLIB))
 +#include    <ranlib.h>
 +#endif
 +#include    <fcntl.h>
 +#include    <stdio.h>
 +#include    <stdlib.h>
 +#ifdef HAVE_UTIME_H
 +#include    <utime.h>
 +#endif
 +
 +#include    "make.h"
 +#include    "hash.h"
 +#include    "dir.h"
 +
 +#ifdef TARGET_MACHINE
 +#undef MAKE_MACHINE
 +#define MAKE_MACHINE TARGET_MACHINE
 +#endif
 +#ifdef TARGET_MACHINE_ARCH
 +#undef MAKE_MACHINE_ARCH
 +#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
 +#endif
 +
 +static Lst      archives;   /* Lst of archives we've already examined */
 +
 +typedef struct Arch {
 +    char        *name;      /* Name of archive */
 +    Hash_Table          members;    /* All the members of the archive described
 +                             * by <name, struct ar_hdr *> key/value pairs */
 +    char        *fnametab;  /* Extended name table strings */
 +    size_t      fnamesize;  /* Size of the string table */
 +} Arch;
 +
 +static int ArchFindArchive(const void *, const void *);
 +#ifdef CLEANUP
 +static void ArchFree(void *);
 +#endif
 +static struct ar_hdr *ArchStatMember(char *, char *, Boolean);
 +static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *);
 +#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__)
 +#define SVR4ARCHIVES
 +static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
 +#endif
 +
 +
 +#if defined(_AIX)
 +# define AR_NAME _ar_name.ar_name
 +# define AR_FMAG _ar_name.ar_fmag
 +# define SARMAG  SAIAMAG
 +# define ARMAG   AIAMAG
 +# define ARFMAG  AIAFMAG
 +#endif
 +#ifndef  AR_NAME
 +# define AR_NAME ar_name
 +#endif
 +#ifndef  AR_DATE
 +# define AR_DATE ar_date
 +#endif
 +#ifndef  AR_SIZE
 +# define AR_SIZE ar_size
 +#endif
 +#ifndef  AR_FMAG
 +# define AR_FMAG ar_fmag
 +#endif
 +#ifndef ARMAG
 +# define ARMAG        "!<arch>\n"
 +#endif
 +#ifndef SARMAG
 +# define SARMAG       8
 +#endif
 +
 +#define AR_MAX_NAME_LEN           (sizeof(arh.AR_NAME)-1)
 +
 +#ifdef CLEANUP
 +/*-
 + *-----------------------------------------------------------------------
 + * ArchFree --
 + *    Free memory used by an archive
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +ArchFree(void *ap)
 +{
 +    Arch *a = (Arch *)ap;
 +    Hash_Search         search;
 +    Hash_Entry          *entry;
 +
 +    /* Free memory from hash entries */
 +    for (entry = Hash_EnumFirst(&a->members, &search);
 +       entry != NULL;
 +       entry = Hash_EnumNext(&search))
 +      free(Hash_GetValue(entry));
 +
 +    free(a->name);
 +    if (a->fnametab)
 +      free(a->fnametab);
 +    Hash_DeleteTable(&a->members);
 +    free(a);
 +}
 +#endif
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_ParseArchive --
 + *    Parse the archive specification in the given line and find/create
 + *    the nodes for the specified archive members, placing their nodes
 + *    on the given list.
 + *
 + * Input:
 + *    linePtr         Pointer to start of specification
 + *    nodeLst         Lst on which to place the nodes
 + *    ctxt            Context in which to expand variables
 + *
 + * Results:
 + *    SUCCESS if it was a valid specification. The linePtr is updated
 + *    to point to the first non-space after the archive spec. The
 + *    nodes for the members are placed on the given list.
 + *
 + * Side Effects:
 + *    Some nodes may be created. The given list is extended.
 + *
 + *-----------------------------------------------------------------------
 + */
 +ReturnStatus
 +Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt)
 +{
 +    char          *cp;            /* Pointer into line */
 +    GNode         *gn;            /* New node */
 +    char          *libName;       /* Library-part of specification */
 +    char          *memName;       /* Member-part of specification */
 +    char          *nameBuf;       /* temporary place for node name */
 +    char          saveChar;       /* Ending delimiter of member-name */
 +    Boolean       subLibName;     /* TRUE if libName should have/had
 +                                   * variable substitution performed on it */
 +
 +    libName = *linePtr;
 +
 +    subLibName = FALSE;
 +
 +    for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
 +      if (*cp == '$') {
 +          /*
 +           * Variable spec, so call the Var module to parse the puppy
 +           * so we can safely advance beyond it...
 +           */
 +          int         length;
 +          void        *freeIt;
 +          char        *result;
 +
-           result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
++          result = Var_Parse(cp, ctxt, TRUE, TRUE, &length, &freeIt);
 +          if (freeIt)
 +              free(freeIt);
 +          if (result == var_Error) {
 +              return(FAILURE);
 +          } else {
 +              subLibName = TRUE;
 +          }
 +
 +          cp += length-1;
 +      }
 +    }
 +
 +    *cp++ = '\0';
 +    if (subLibName) {
-       libName = Var_Subst(NULL, libName, ctxt, TRUE);
++      libName = Var_Subst(NULL, libName, ctxt, TRUE, TRUE);
 +    }
 +
 +
 +    for (;;) {
 +      /*
 +       * First skip to the start of the member's name, mark that
 +       * place and skip to the end of it (either white-space or
 +       * a close paren).
 +       */
 +      Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
 +
 +      while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) {
 +          cp++;
 +      }
 +      memName = cp;
 +      while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) {
 +          if (*cp == '$') {
 +              /*
 +               * Variable spec, so call the Var module to parse the puppy
 +               * so we can safely advance beyond it...
 +               */
 +              int     length;
 +              void    *freeIt;
 +              char    *result;
 +
-               result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
++              result = Var_Parse(cp, ctxt, TRUE, TRUE, &length, &freeIt);
 +              if (freeIt)
 +                  free(freeIt);
 +              if (result == var_Error) {
 +                  return(FAILURE);
 +              } else {
 +                  doSubst = TRUE;
 +              }
 +
 +              cp += length;
 +          } else {
 +              cp++;
 +          }
 +      }
 +
 +      /*
 +       * If the specification ends without a closing parenthesis,
 +       * chances are there's something wrong (like a missing backslash),
 +       * so it's better to return failure than allow such things to happen
 +       */
 +      if (*cp == '\0') {
 +          printf("No closing parenthesis in archive specification\n");
 +          return (FAILURE);
 +      }
 +
 +      /*
 +       * If we didn't move anywhere, we must be done
 +       */
 +      if (cp == memName) {
 +          break;
 +      }
 +
 +      saveChar = *cp;
 +      *cp = '\0';
 +
 +      /*
 +       * XXX: This should be taken care of intelligently by
 +       * SuffExpandChildren, both for the archive and the member portions.
 +       */
 +      /*
 +       * If member contains variables, try and substitute for them.
 +       * This will slow down archive specs with dynamic sources, of course,
 +       * since we'll be (non-)substituting them three times, but them's
 +       * the breaks -- we need to do this since SuffExpandChildren calls
 +       * us, otherwise we could assume the thing would be taken care of
 +       * later.
 +       */
 +      if (doSubst) {
 +          char    *buf;
 +          char    *sacrifice;
 +          char    *oldMemName = memName;
 +          size_t   sz;
 +
-           memName = Var_Subst(NULL, memName, ctxt, TRUE);
++          memName = Var_Subst(NULL, memName, ctxt, TRUE, TRUE);
 +
 +          /*
 +           * Now form an archive spec and recurse to deal with nested
 +           * variables and multi-word variable values.... The results
 +           * are just placed at the end of the nodeLst we're returning.
 +           */
 +          sz = strlen(memName)+strlen(libName)+3;
 +          buf = sacrifice = bmake_malloc(sz);
 +
 +          snprintf(buf, sz, "%s(%s)", libName, memName);
 +
 +          if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
 +              /*
 +               * Must contain dynamic sources, so we can't deal with it now.
 +               * Just create an ARCHV node for the thing and let
 +               * SuffExpandChildren handle it...
 +               */
 +              gn = Targ_FindNode(buf, TARG_CREATE);
 +
 +              if (gn == NULL) {
 +                  free(buf);
 +                  return(FAILURE);
 +              } else {
 +                  gn->type |= OP_ARCHV;
 +                  (void)Lst_AtEnd(nodeLst, gn);
 +              }
 +          } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
 +              /*
 +               * Error in nested call -- free buffer and return FAILURE
 +               * ourselves.
 +               */
 +              free(buf);
 +              return(FAILURE);
 +          }
 +          /*
 +           * Free buffer and continue with our work.
 +           */
 +          free(buf);
 +      } else if (Dir_HasWildcards(memName)) {
 +          Lst   members = Lst_Init(FALSE);
 +          char  *member;
 +          size_t sz = MAXPATHLEN, nsz;
 +          nameBuf = bmake_malloc(sz);
 +
 +          Dir_Expand(memName, dirSearchPath, members);
 +          while (!Lst_IsEmpty(members)) {
 +              member = (char *)Lst_DeQueue(members);
 +              nsz = strlen(libName) + strlen(member) + 3;
 +              if (sz > nsz)
 +                  nameBuf = bmake_realloc(nameBuf, sz = nsz * 2);
 +
 +              snprintf(nameBuf, sz, "%s(%s)", libName, member);
 +              free(member);
 +              gn = Targ_FindNode(nameBuf, TARG_CREATE);
 +              if (gn == NULL) {
 +                  free(nameBuf);
 +                  return (FAILURE);
 +              } else {
 +                  /*
 +                   * We've found the node, but have to make sure the rest of
 +                   * the world knows it's an archive member, without having
 +                   * to constantly check for parentheses, so we type the
 +                   * thing with the OP_ARCHV bit before we place it on the
 +                   * end of the provided list.
 +                   */
 +                  gn->type |= OP_ARCHV;
 +                  (void)Lst_AtEnd(nodeLst, gn);
 +              }
 +          }
 +          Lst_Destroy(members, NULL);
 +          free(nameBuf);
 +      } else {
 +          size_t      sz = strlen(libName) + strlen(memName) + 3;
 +          nameBuf = bmake_malloc(sz);
 +          snprintf(nameBuf, sz, "%s(%s)", libName, memName);
 +          gn = Targ_FindNode(nameBuf, TARG_CREATE);
 +          free(nameBuf);
 +          if (gn == NULL) {
 +              return (FAILURE);
 +          } else {
 +              /*
 +               * We've found the node, but have to make sure the rest of the
 +               * world knows it's an archive member, without having to
 +               * constantly check for parentheses, so we type the thing with
 +               * the OP_ARCHV bit before we place it on the end of the
 +               * provided list.
 +               */
 +              gn->type |= OP_ARCHV;
 +              (void)Lst_AtEnd(nodeLst, gn);
 +          }
 +      }
 +      if (doSubst) {
 +          free(memName);
 +      }
 +
 +      *cp = saveChar;
 +    }
 +
 +    /*
 +     * If substituted libName, free it now, since we need it no longer.
 +     */
 +    if (subLibName) {
 +      free(libName);
 +    }
 +
 +    /*
 +     * We promised the pointer would be set up at the next non-space, so
 +     * we must advance cp there before setting *linePtr... (note that on
 +     * entrance to the loop, cp is guaranteed to point at a ')')
 +     */
 +    do {
 +      cp++;
 +    } while (*cp != '\0' && isspace ((unsigned char)*cp));
 +
 +    *linePtr = cp;
 +    return (SUCCESS);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ArchFindArchive --
 + *    See if the given archive is the one we are looking for. Called
 + *    From ArchStatMember and ArchFindMember via Lst_Find.
 + *
 + * Input:
 + *    ar              Current list element
 + *    archName        Name we want
 + *
 + * Results:
 + *    0 if it is, non-zero if it isn't.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +ArchFindArchive(const void *ar, const void *archName)
 +{
 +    return (strcmp(archName, ((const Arch *)ar)->name));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ArchStatMember --
 + *    Locate a member of an archive, given the path of the archive and
 + *    the path of the desired member.
 + *
 + * Input:
 + *    archive         Path to the archive
 + *    member          Name of member. If it is a path, only the last
 + *                    component is used.
 + *    hash            TRUE if archive should be hashed if not already so.
 + *
 + * Results:
 + *    A pointer to the current struct ar_hdr structure for the member. Note
 + *    That no position is returned, so this is not useful for touching
 + *    archive members. This is mostly because we have no assurances that
 + *    The archive will remain constant after we read all the headers, so
 + *    there's not much point in remembering the position...
 + *
 + * Side Effects:
 + *
 + *-----------------------------------------------------------------------
 + */
 +static struct ar_hdr *
 +ArchStatMember(char *archive, char *member, Boolean hash)
 +{
 +    FILE *      arch;       /* Stream to archive */
 +    int                 size;       /* Size of archive member */
 +    char        *cp;        /* Useful character pointer */
 +    char        magic[SARMAG];
 +    LstNode     ln;         /* Lst member containing archive descriptor */
 +    Arch        *ar;        /* Archive descriptor */
 +    Hash_Entry          *he;        /* Entry containing member's description */
 +    struct ar_hdr arh;        /* archive-member header for reading archive */
 +    char        memName[MAXPATHLEN+1];
 +                          /* Current member name while hashing. */
 +
 +    /*
 +     * Because of space constraints and similar things, files are archived
 +     * using their final path components, not the entire thing, so we need
 +     * to point 'member' to the final component, if there is one, to make
 +     * the comparisons easier...
 +     */
 +    cp = strrchr(member, '/');
 +    if (cp != NULL) {
 +      member = cp + 1;
 +    }
 +
 +    ln = Lst_Find(archives, archive, ArchFindArchive);
 +    if (ln != NULL) {
 +      ar = (Arch *)Lst_Datum(ln);
 +
 +      he = Hash_FindEntry(&ar->members, member);
 +
 +      if (he != NULL) {
 +          return ((struct ar_hdr *)Hash_GetValue(he));
 +      } else {
 +          /* Try truncated name */
 +          char copy[AR_MAX_NAME_LEN+1];
 +          size_t len = strlen(member);
 +
 +          if (len > AR_MAX_NAME_LEN) {
 +              len = AR_MAX_NAME_LEN;
 +              strncpy(copy, member, AR_MAX_NAME_LEN);
 +              copy[AR_MAX_NAME_LEN] = '\0';
 +          }
 +          if ((he = Hash_FindEntry(&ar->members, copy)) != NULL)
 +              return ((struct ar_hdr *)Hash_GetValue(he));
 +          return NULL;
 +      }
 +    }
 +
 +    if (!hash) {
 +      /*
 +       * Caller doesn't want the thing hashed, just use ArchFindMember
 +       * to read the header for the member out and close down the stream
 +       * again. Since the archive is not to be hashed, we assume there's
 +       * no need to allocate extra room for the header we're returning,
 +       * so just declare it static.
 +       */
 +       static struct ar_hdr   sarh;
 +
 +       arch = ArchFindMember(archive, member, &sarh, "r");
 +
 +       if (arch == NULL) {
 +          return NULL;
 +      } else {
 +          fclose(arch);
 +          return (&sarh);
 +      }
 +    }
 +
 +    /*
 +     * We don't have this archive on the list yet, so we want to find out
 +     * everything that's in it and cache it so we can get at it quickly.
 +     */
 +    arch = fopen(archive, "r");
 +    if (arch == NULL) {
 +      return NULL;
 +    }
 +
 +    /*
 +     * We use the ARMAG string to make sure this is an archive we
 +     * can handle...
 +     */
 +    if ((fread(magic, SARMAG, 1, arch) != 1) ||
 +      (strncmp(magic, ARMAG, SARMAG) != 0)) {
 +          fclose(arch);
 +          return NULL;
 +    }
 +
 +    ar = bmake_malloc(sizeof(Arch));
 +    ar->name = bmake_strdup(archive);
 +    ar->fnametab = NULL;
 +    ar->fnamesize = 0;
 +    Hash_InitTable(&ar->members, -1);
 +    memName[AR_MAX_NAME_LEN] = '\0';
 +
 +    while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) {
 +      if (strncmp( arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) {
 +          /*
 +           * The header is bogus, so the archive is bad
 +           * and there's no way we can recover...
 +           */
 +          goto badarch;
 +      } else {
 +          /*
 +           * We need to advance the stream's pointer to the start of the
 +           * next header. Files are padded with newlines to an even-byte
 +           * boundary, so we need to extract the size of the file from the
 +           * 'size' field of the header and round it up during the seek.
 +           */
 +          arh.AR_SIZE[sizeof(arh.AR_SIZE)-1] = '\0';
 +          size = (int)strtol(arh.AR_SIZE, NULL, 10);
 +
 +          (void)strncpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME));
 +          for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) {
 +              continue;
 +          }
 +          cp[1] = '\0';
 +
 +#ifdef SVR4ARCHIVES
 +          /*
 +           * svr4 names are slash terminated. Also svr4 extended AR format.
 +           */
 +          if (memName[0] == '/') {
 +              /*
 +               * svr4 magic mode; handle it
 +               */
 +              switch (ArchSVR4Entry(ar, memName, size, arch)) {
 +              case -1:  /* Invalid data */
 +                  goto badarch;
 +              case 0:   /* List of files entry */
 +                  continue;
 +              default:  /* Got the entry */
 +                  break;
 +              }
 +          }
 +          else {
 +              if (cp[0] == '/')
 +                  cp[0] = '\0';
 +          }
 +#endif
 +
 +#ifdef AR_EFMT1
 +          /*
 +           * BSD 4.4 extended AR format: #1/<namelen>, with name as the
 +           * first <namelen> bytes of the file
 +           */
 +          if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
 +              isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) {
 +
 +              unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
 +
 +              if (elen > MAXPATHLEN)
 +                      goto badarch;
 +              if (fread(memName, elen, 1, arch) != 1)
 +                      goto badarch;
 +              memName[elen] = '\0';
 +              fseek(arch, -elen, SEEK_CUR);
 +              if (DEBUG(ARCH) || DEBUG(MAKE)) {
 +                  fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName);
 +              }
 +          }
 +#endif
 +
 +          he = Hash_CreateEntry(&ar->members, memName, NULL);
 +          Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr)));
 +          memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr));
 +      }
 +      fseek(arch, (size + 1) & ~1, SEEK_CUR);
 +    }
 +
 +    fclose(arch);
 +
 +    (void)Lst_AtEnd(archives, ar);
 +
 +    /*
 +     * Now that the archive has been read and cached, we can look into
 +     * the hash table to find the desired member's header.
 +     */
 +    he = Hash_FindEntry(&ar->members, member);
 +
 +    if (he != NULL) {
 +      return ((struct ar_hdr *)Hash_GetValue(he));
 +    } else {
 +      return NULL;
 +    }
 +
 +badarch:
 +    fclose(arch);
 +    Hash_DeleteTable(&ar->members);
 +    if (ar->fnametab)
 +      free(ar->fnametab);
 +    free(ar);
 +    return NULL;
 +}
 +
 +#ifdef SVR4ARCHIVES
 +/*-
 + *-----------------------------------------------------------------------
 + * ArchSVR4Entry --
 + *    Parse an SVR4 style entry that begins with a slash.
 + *    If it is "//", then load the table of filenames
 + *    If it is "/<offset>", then try to substitute the long file name
 + *    from offset of a table previously read.
 + *
 + * Results:
 + *    -1: Bad data in archive
 + *     0: A table was loaded from the file
 + *     1: Name was successfully substituted from table
 + *     2: Name was not successfully substituted from table
 + *
 + * Side Effects:
 + *    If a table is read, the file pointer is moved to the next archive
 + *    member
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
 +{
 +#define ARLONGNAMES1 "//"
 +#define ARLONGNAMES2 "/ARFILENAMES"
 +    size_t entry;
 +    char *ptr, *eptr;
 +
 +    if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
 +      strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
 +
 +      if (ar->fnametab != NULL) {
 +          if (DEBUG(ARCH)) {
 +              fprintf(debug_file, "Attempted to redefine an SVR4 name table\n");
 +          }
 +          return -1;
 +      }
 +
 +      /*
 +       * This is a table of archive names, so we build one for
 +       * ourselves
 +       */
 +      ar->fnametab = bmake_malloc(size);
 +      ar->fnamesize = size;
 +
 +      if (fread(ar->fnametab, size, 1, arch) != 1) {
 +          if (DEBUG(ARCH)) {
 +              fprintf(debug_file, "Reading an SVR4 name table failed\n");
 +          }
 +          return -1;
 +      }
 +      eptr = ar->fnametab + size;
 +      for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
 +          switch (*ptr) {
 +          case '/':
 +              entry++;
 +              *ptr = '\0';
 +              break;
 +
 +          case '\n':
 +              break;
 +
 +          default:
 +              break;
 +          }
 +      if (DEBUG(ARCH)) {
 +          fprintf(debug_file, "Found svr4 archive name table with %lu entries\n",
 +                  (u_long)entry);
 +      }
 +      return 0;
 +    }
 +
 +    if (name[1] == ' ' || name[1] == '\0')
 +      return 2;
 +
 +    entry = (size_t)strtol(&name[1], &eptr, 0);
 +    if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
 +      if (DEBUG(ARCH)) {
 +          fprintf(debug_file, "Could not parse SVR4 name %s\n", name);
 +      }
 +      return 2;
 +    }
 +    if (entry >= ar->fnamesize) {
 +      if (DEBUG(ARCH)) {
 +          fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n",
 +                 name, (u_long)ar->fnamesize);
 +      }
 +      return 2;
 +    }
 +
 +    if (DEBUG(ARCH)) {
 +      fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]);
 +    }
 +
 +    (void)strncpy(name, &ar->fnametab[entry], MAXPATHLEN);
 +    name[MAXPATHLEN] = '\0';
 +    return 1;
 +}
 +#endif
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ArchFindMember --
 + *    Locate a member of an archive, given the path of the archive and
 + *    the path of the desired member. If the archive is to be modified,
 + *    the mode should be "r+", if not, it should be "r".
 + *
 + * Input:
 + *    archive         Path to the archive
 + *    member          Name of member. If it is a path, only the last
 + *                    component is used.
 + *    arhPtr          Pointer to header structure to be filled in
 + *    mode            The mode for opening the stream
 + *
 + * Results:
 + *    An FILE *, opened for reading and writing, positioned at the
 + *    start of the member's struct ar_hdr, or NULL if the member was
 + *    nonexistent. The current struct ar_hdr for member.
 + *
 + * Side Effects:
 + *    The passed struct ar_hdr structure is filled in.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static FILE *
 +ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr,
 +    const char *mode)
 +{
 +    FILE *      arch;       /* Stream to archive */
 +    int                 size;       /* Size of archive member */
 +    char        *cp;        /* Useful character pointer */
 +    char        magic[SARMAG];
 +    size_t      len, tlen;
 +
 +    arch = fopen(archive, mode);
 +    if (arch == NULL) {
 +      return NULL;
 +    }
 +
 +    /*
 +     * We use the ARMAG string to make sure this is an archive we
 +     * can handle...
 +     */
 +    if ((fread(magic, SARMAG, 1, arch) != 1) ||
 +      (strncmp(magic, ARMAG, SARMAG) != 0)) {
 +          fclose(arch);
 +          return NULL;
 +    }
 +
 +    /*
 +     * Because of space constraints and similar things, files are archived
 +     * using their final path components, not the entire thing, so we need
 +     * to point 'member' to the final component, if there is one, to make
 +     * the comparisons easier...
 +     */
 +    cp = strrchr(member, '/');
 +    if (cp != NULL) {
 +      member = cp + 1;
 +    }
 +    len = tlen = strlen(member);
 +    if (len > sizeof(arhPtr->AR_NAME)) {
 +      tlen = sizeof(arhPtr->AR_NAME);
 +    }
 +
 +    while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
 +      if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG) ) != 0) {
 +           /*
 +            * The header is bogus, so the archive is bad
 +            * and there's no way we can recover...
 +            */
 +           fclose(arch);
 +           return NULL;
 +      } else if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) {
 +          /*
 +           * If the member's name doesn't take up the entire 'name' field,
 +           * we have to be careful of matching prefixes. Names are space-
 +           * padded to the right, so if the character in 'name' at the end
 +           * of the matched string is anything but a space, this isn't the
 +           * member we sought.
 +           */
 +          if (tlen != sizeof(arhPtr->AR_NAME) && arhPtr->AR_NAME[tlen] != ' '){
 +              goto skip;
 +          } else {
 +              /*
 +               * To make life easier, we reposition the file at the start
 +               * of the header we just read before we return the stream.
 +               * In a more general situation, it might be better to leave
 +               * the file at the actual member, rather than its header, but
 +               * not here...
 +               */
 +              fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR);
 +              return (arch);
 +          }
 +      } else
 +#ifdef AR_EFMT1
 +              /*
 +               * BSD 4.4 extended AR format: #1/<namelen>, with name as the
 +               * first <namelen> bytes of the file
 +               */
 +          if (strncmp(arhPtr->AR_NAME, AR_EFMT1,
 +                                      sizeof(AR_EFMT1) - 1) == 0 &&
 +              isdigit((unsigned char)arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) {
 +
 +              unsigned int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1)-1]);
 +              char ename[MAXPATHLEN + 1];
 +
 +              if (elen > MAXPATHLEN) {
 +                      fclose(arch);
 +                      return NULL;
 +              }
 +              if (fread(ename, elen, 1, arch) != 1) {
 +                      fclose(arch);
 +                      return NULL;
 +              }
 +              ename[elen] = '\0';
 +              if (DEBUG(ARCH) || DEBUG(MAKE)) {
 +                  fprintf(debug_file, "ArchFind: Extended format entry for %s\n", ename);
 +              }
 +              if (strncmp(ename, member, len) == 0) {
 +                      /* Found as extended name */
 +                      fseek(arch, -sizeof(struct ar_hdr) - elen, SEEK_CUR);
 +                      return (arch);
 +              }
 +              fseek(arch, -elen, SEEK_CUR);
 +              goto skip;
 +      } else
 +#endif
 +      {
 +skip:
 +          /*
 +           * This isn't the member we're after, so we need to advance the
 +           * stream's pointer to the start of the next header. Files are
 +           * padded with newlines to an even-byte boundary, so we need to
 +           * extract the size of the file from the 'size' field of the
 +           * header and round it up during the seek.
 +           */
 +          arhPtr->ar_size[sizeof(arhPtr->AR_SIZE)-1] = '\0';
 +          size = (int)strtol(arhPtr->AR_SIZE, NULL, 10);
 +          fseek(arch, (size + 1) & ~1, SEEK_CUR);
 +      }
 +    }
 +
 +    /*
 +     * We've looked everywhere, but the member is not to be found. Close the
 +     * archive and return NULL -- an error.
 +     */
 +    fclose(arch);
 +    return NULL;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_Touch --
 + *    Touch a member of an archive.
 + *
 + * Input:
 + *    gn              Node of member to touch
 + *
 + * Results:
 + *    The 'time' field of the member's header is updated.
 + *
 + * Side Effects:
 + *    The modification time of the entire archive is also changed.
 + *    For a library, this could necessitate the re-ranlib'ing of the
 + *    whole thing.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Arch_Touch(GNode *gn)
 +{
 +    FILE *      arch;   /* Stream open to archive, positioned properly */
 +    struct ar_hdr arh;          /* Current header describing member */
 +    char *p1, *p2;
 +
 +    arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1),
 +                        Var_Value(MEMBER, gn, &p2),
 +                        &arh, "r+");
 +    if (p1)
 +      free(p1);
 +    if (p2)
 +      free(p2);
 +    snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now);
 +
 +    if (arch != NULL) {
 +      (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
 +      fclose(arch);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_TouchLib --
 + *    Given a node which represents a library, touch the thing, making
 + *    sure that the table of contents also is touched.
 + *
 + * Input:
 + *    gn              The node of the library to touch
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Both the modification time of the library and of the RANLIBMAG
 + *    member are set to 'now'.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +#if !defined(RANLIBMAG)
 +Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
 +#else
 +Arch_TouchLib(GNode *gn)
 +#endif
 +{
 +#ifdef RANLIBMAG
 +    FILE *        arch;       /* Stream open to archive */
 +    struct ar_hdr   arh;              /* Header describing table of contents */
 +    struct utimbuf  times;    /* Times for utime() call */
 +
 +    arch = ArchFindMember(gn->path, UNCONST(RANLIBMAG), &arh, "r+");
 +    snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now);
 +
 +    if (arch != NULL) {
 +      (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
 +      fclose(arch);
 +
 +      times.actime = times.modtime = now;
 +      utime(gn->path, &times);
 +    }
 +#endif
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_MTime --
 + *    Return the modification time of a member of an archive.
 + *
 + * Input:
 + *    gn              Node describing archive member
 + *
 + * Results:
 + *    The modification time(seconds).
 + *
 + * Side Effects:
 + *    The mtime field of the given node is filled in with the value
 + *    returned by the function.
 + *
 + *-----------------------------------------------------------------------
 + */
 +time_t
 +Arch_MTime(GNode *gn)
 +{
 +    struct ar_hdr *arhPtr;    /* Header of desired member */
 +    time_t      modTime;    /* Modification time as an integer */
 +    char *p1, *p2;
 +
 +    arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1),
 +                           Var_Value(MEMBER, gn, &p2),
 +                           TRUE);
 +    if (p1)
 +      free(p1);
 +    if (p2)
 +      free(p2);
 +
 +    if (arhPtr != NULL) {
 +      modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10);
 +    } else {
 +      modTime = 0;
 +    }
 +
 +    gn->mtime = modTime;
 +    return (modTime);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_MemMTime --
 + *    Given a non-existent archive member's node, get its modification
 + *    time from its archived form, if it exists.
 + *
 + * Results:
 + *    The modification time.
 + *
 + * Side Effects:
 + *    The mtime field is filled in.
 + *
 + *-----------------------------------------------------------------------
 + */
 +time_t
 +Arch_MemMTime(GNode *gn)
 +{
 +    LstNode     ln;
 +    GNode       *pgn;
 +    char        *nameStart,
 +                *nameEnd;
 +
 +    if (Lst_Open(gn->parents) != SUCCESS) {
 +      gn->mtime = 0;
 +      return (0);
 +    }
 +    while ((ln = Lst_Next(gn->parents)) != NULL) {
 +      pgn = (GNode *)Lst_Datum(ln);
 +
 +      if (pgn->type & OP_ARCHV) {
 +          /*
 +           * If the parent is an archive specification and is being made
 +           * and its member's name matches the name of the node we were
 +           * given, record the modification time of the parent in the
 +           * child. We keep searching its parents in case some other
 +           * parent requires this child to exist...
 +           */
 +          nameStart = strchr(pgn->name, '(') + 1;
 +          nameEnd = strchr(nameStart, ')');
 +
 +          if ((pgn->flags & REMAKE) &&
 +              strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
 +                                   gn->mtime = Arch_MTime(pgn);
 +          }
 +      } else if (pgn->flags & REMAKE) {
 +          /*
 +           * Something which isn't a library depends on the existence of
 +           * this target, so it needs to exist.
 +           */
 +          gn->mtime = 0;
 +          break;
 +      }
 +    }
 +
 +    Lst_Close(gn->parents);
 +
 +    return (gn->mtime);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_FindLib --
 + *    Search for a library along the given search path.
 + *
 + * Input:
 + *    gn              Node of library to find
 + *    path            Search path
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The node's 'path' field is set to the found path (including the
 + *    actual file name, not -l...). If the system can handle the -L
 + *    flag when linking (or we cannot find the library), we assume that
 + *    the user has placed the .LIBRARIES variable in the final linking
 + *    command (or the linker will know where to find it) and set the
 + *    TARGET variable for this node to be the node's name. Otherwise,
 + *    we set the TARGET variable to be the full path of the library,
 + *    as returned by Dir_FindFile.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Arch_FindLib(GNode *gn, Lst path)
 +{
 +    char          *libName;   /* file name for archive */
 +    size_t         sz = strlen(gn->name) + 6 - 2;
 +
 +    libName = bmake_malloc(sz);
 +    snprintf(libName, sz, "lib%s.a", &gn->name[2]);
 +
 +    gn->path = Dir_FindFile(libName, path);
 +
 +    free(libName);
 +
 +#ifdef LIBRARIES
 +    Var_Set(TARGET, gn->name, gn, 0);
 +#else
 +    Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0);
 +#endif /* LIBRARIES */
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_LibOODate --
 + *    Decide if a node with the OP_LIB attribute is out-of-date. Called
 + *    from Make_OODate to make its life easier.
 + *
 + *    There are several ways for a library to be out-of-date that are
 + *    not available to ordinary files. In addition, there are ways
 + *    that are open to regular files that are not available to
 + *    libraries. A library that is only used as a source is never
 + *    considered out-of-date by itself. This does not preclude the
 + *    library's modification time from making its parent be out-of-date.
 + *    A library will be considered out-of-date for any of these reasons,
 + *    given that it is a target on a dependency line somewhere:
 + *        Its modification time is less than that of one of its
 + *              sources (gn->mtime < gn->cmgn->mtime).
 + *        Its modification time is greater than the time at which the
 + *              make began (i.e. it's been modified in the course
 + *              of the make, probably by archiving).
 + *        The modification time of one of its sources is greater than
 + *              the one of its RANLIBMAG member (i.e. its table of contents
 + *              is out-of-date). We don't compare of the archive time
 + *              vs. TOC time because they can be too close. In my
 + *              opinion we should not bother with the TOC at all since
 + *              this is used by 'ar' rules that affect the data contents
 + *              of the archive, not by ranlib rules, which affect the
 + *              TOC.
 + *
 + * Input:
 + *    gn              The library's graph node
 + *
 + * Results:
 + *    TRUE if the library is out-of-date. FALSE otherwise.
 + *
 + * Side Effects:
 + *    The library will be hashed if it hasn't been already.
 + *
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Arch_LibOODate(GNode *gn)
 +{
 +    Boolean     oodate;
 +
 +    if (gn->type & OP_PHONY) {
 +      oodate = TRUE;
 +    } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
 +      oodate = FALSE;
 +    } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) ||
 +             (gn->mtime > now) ||
 +             (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) {
 +      oodate = TRUE;
 +    } else {
 +#ifdef RANLIBMAG
 +      struct ar_hdr   *arhPtr;    /* Header for __.SYMDEF */
 +      int             modTimeTOC; /* The table-of-contents's mod time */
 +
 +      arhPtr = ArchStatMember(gn->path, UNCONST(RANLIBMAG), FALSE);
 +
 +      if (arhPtr != NULL) {
 +          modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10);
 +
 +          if (DEBUG(ARCH) || DEBUG(MAKE)) {
 +              fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
 +          }
 +          oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC);
 +      } else {
 +          /*
 +           * A library w/o a table of contents is out-of-date
 +           */
 +          if (DEBUG(ARCH) || DEBUG(MAKE)) {
 +              fprintf(debug_file, "No t.o.c....");
 +          }
 +          oodate = TRUE;
 +      }
 +#else
 +      oodate = FALSE;
 +#endif
 +    }
 +    return (oodate);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_Init --
 + *    Initialize things for this module.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The 'archives' list is initialized.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Arch_Init(void)
 +{
 +    archives = Lst_Init(FALSE);
 +}
 +
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_End --
 + *    Cleanup things for this module.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The 'archives' list is freed
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Arch_End(void)
 +{
 +#ifdef CLEANUP
 +    Lst_Destroy(archives, ArchFree);
 +#endif
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Arch_IsLib --
 + *    Check if the node is a library
 + *
 + * Results:
 + *    True or False.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +Arch_IsLib(GNode *gn)
 +{
 +    static const char armag[] = "!<arch>\n";
 +    char buf[sizeof(armag)-1];
 +    int fd;
 +
 +    if ((fd = open(gn->path, O_RDONLY)) == -1)
 +      return FALSE;
 +
 +    if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
 +      (void)close(fd);
 +      return FALSE;
 +    }
 +
 +    (void)close(fd);
 +
 +    return memcmp(buf, armag, sizeof(buf)) == 0;
 +}
index fd00eb7e03c3aa8ad63e01e48c1cf256cf10bef2,0000000000000000000000000000000000000000..6cf963ffb2da73bdd17874c0682090daba00b74a
mode 100644,000000..100644
--- /dev/null
@@@ -1,767 -1,0 +1,747 @@@
- /*    $NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $        */
++/*    $NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg Exp $ */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1988, 1989 by Adam de Boor
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $";
++static char rcsid[] = "$NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)compat.c  8.2 (Berkeley) 3/19/94";
 +#else
- __RCSID("$NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $");
++__RCSID("$NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * compat.c --
 + *    The routines in this file implement the full-compatibility
 + *    mode of PMake. Most of the special functionality of PMake
 + *    is available in this mode. Things not supported:
 + *        - different shells.
 + *        - friendly variable substitution.
 + *
 + * Interface:
 + *    Compat_Run          Initialize things for this module and recreate
 + *                        thems as need creatin'
 + */
 +
 +#ifdef HAVE_CONFIG_H
 +# include   "config.h"
 +#endif
 +#include    <sys/types.h>
 +#include    <sys/stat.h>
 +#include    "wait.h"
 +
 +#include    <ctype.h>
 +#include    <errno.h>
 +#include    <signal.h>
 +#include    <stdio.h>
 +
 +#include    "make.h"
 +#include    "hash.h"
 +#include    "dir.h"
 +#include    "job.h"
++#include    "metachar.h"
 +#include    "pathnames.h"
 +
- /*
-  * The following array is used to make a fast determination of which
-  * characters are interpreted specially by the shell.  If a command
-  * contains any of these characters, it is executed by the shell, not
-  * directly by us.
-  */
- static char       meta[256];
 +
 +static GNode      *curTarg = NULL;
 +static GNode      *ENDNode;
 +static void CompatInterrupt(int);
 +
- static void
- Compat_Init(void)
- {
-     const char *cp;
-     Shell_Init();             /* setup default shell */
-     
-     for (cp = "~#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
-       meta[(unsigned char) *cp] = 1;
-     }
-     /*
-      * The null character serves as a sentinel in the string.
-      */
-     meta[0] = 1;
- }
 +/*-
 + *-----------------------------------------------------------------------
 + * CompatInterrupt --
 + *    Interrupt the creation of the current target and remove it if
 + *    it ain't precious.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The target is removed and the process exits. If .INTERRUPT exists,
 + *    its commands are run first WITH INTERRUPTS IGNORED..
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +CompatInterrupt(int signo)
 +{
 +    GNode   *gn;
 +
 +    if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
 +      char      *p1;
 +      char      *file = Var_Value(TARGET, curTarg, &p1);
 +
 +      if (!noExecute && eunlink(file) != -1) {
 +          Error("*** %s removed", file);
 +      }
 +      if (p1)
 +          free(p1);
 +
 +      /*
 +       * Run .INTERRUPT only if hit with interrupt signal
 +       */
 +      if (signo == SIGINT) {
 +          gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
 +          if (gn != NULL) {
 +              Compat_Make(gn, gn);
 +          }
 +      }
 +
 +    }
 +    if (signo == SIGQUIT)
 +      _exit(signo);
 +    bmake_signal(signo, SIG_DFL);
 +    kill(myPid, signo);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CompatRunCommand --
 + *    Execute the next command for a target. If the command returns an
 + *    error, the node's made field is set to ERROR and creation stops.
 + *
 + * Input:
 + *    cmdp            Command to execute
 + *    gnp             Node from which the command came
 + *
 + * Results:
 + *    0 if the command succeeded, 1 if an error occurred.
 + *
 + * Side Effects:
 + *    The node's 'made' field may be set to ERROR.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +CompatRunCommand(void *cmdp, void *gnp)
 +{
 +    char        *cmdStart;    /* Start of expanded command */
 +    char        *cp, *bp;
 +    Boolean     silent,       /* Don't print command */
 +                doIt;         /* Execute even if -n */
 +    volatile Boolean errCheck;        /* Check errors */
 +    WAIT_T      reason;       /* Reason for child's death */
 +    int                 status;       /* Description of child's death */
 +    pid_t       cpid;         /* Child actually found */
 +    pid_t       retstat;      /* Result of wait */
 +    LstNode     cmdNode;      /* Node where current command is located */
 +    const char  ** volatile av;       /* Argument vector for thing to exec */
 +    char      ** volatile mav;/* Copy of the argument vector for freeing */
 +    int                 argc;         /* Number of arguments in av or 0 if not
 +                               * dynamically allocated */
 +    Boolean     local;        /* TRUE if command should be executed
 +                               * locally */
 +    Boolean     useShell;     /* TRUE if command should be executed
 +                               * using a shell */
 +    char        * volatile cmd = (char *)cmdp;
 +    GNode       *gn = (GNode *)gnp;
 +
 +    silent = gn->type & OP_SILENT;
 +    errCheck = !(gn->type & OP_IGNORE);
 +    doIt = FALSE;
 +    
 +    cmdNode = Lst_Member(gn->commands, cmd);
-     cmdStart = Var_Subst(NULL, cmd, gn, FALSE);
++    cmdStart = Var_Subst(NULL, cmd, gn, FALSE, TRUE);
 +
 +    /*
 +     * brk_string will return an argv with a NULL in av[0], thus causing
 +     * execvp to choke and die horribly. Besides, how can we execute a null
 +     * command? In any case, we warn the user that the command expanded to
 +     * nothing (is this the right thing to do?).
 +     */
 +
 +    if (*cmdStart == '\0') {
 +      free(cmdStart);
 +      return(0);
 +    }
 +    cmd = cmdStart;
 +    Lst_Replace(cmdNode, cmdStart);
 +
 +    if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
 +      (void)Lst_AtEnd(ENDNode->commands, cmdStart);
 +      return(0);
 +    }
 +    if (strcmp(cmdStart, "...") == 0) {
 +      gn->type |= OP_SAVE_CMDS;
 +      return(0);
 +    }
 +
 +    while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
 +      switch (*cmd) {
 +      case '@':
 +          silent = DEBUG(LOUD) ? FALSE : TRUE;
 +          break;
 +      case '-':
 +          errCheck = FALSE;
 +          break;
 +      case '+':
 +          doIt = TRUE;
-           if (!meta[0])               /* we came here from jobs */
-               Compat_Init();
++          if (!shellName)             /* we came here from jobs */
++              Shell_Init();
 +          break;
 +      }
 +      cmd++;
 +    }
 +
 +    while (isspace((unsigned char)*cmd))
 +      cmd++;
 +
 +    /*
 +     * If we did not end up with a command, just skip it.
 +     */
 +    if (!*cmd)
 +      return (0);
 +
 +#if !defined(MAKE_NATIVE)
 +    /*
 +     * In a non-native build, the host environment might be weird enough
 +     * that it's necessary to go through a shell to get the correct
 +     * behaviour.  Or perhaps the shell has been replaced with something
 +     * that does extra logging, and that should not be bypassed.
 +     */
 +    useShell = TRUE;
 +#else
 +    /*
 +     * Search for meta characters in the command. If there are no meta
 +     * characters, there's no need to execute a shell to execute the
 +     * command.
++     *
++     * Additionally variable assignments and empty commands
++     * go to the shell. Therefore treat '=' and ':' like shell
++     * meta characters as documented in make(1).
 +     */
-     for (cp = cmd; !meta[(unsigned char)*cp]; cp++) {
-       continue;
-     }
-     useShell = (*cp != '\0');
++    
++    useShell = needshell(cmd, FALSE);
 +#endif
 +
 +    /*
 +     * Print the command before echoing if we're not supposed to be quiet for
 +     * this one. We also print the command if -n given.
 +     */
 +    if (!silent || NoExecute(gn)) {
 +      printf("%s\n", cmd);
 +      fflush(stdout);
 +    }
 +
 +    /*
 +     * If we're not supposed to execute any commands, this is as far as
 +     * we go...
 +     */
 +    if (!doIt && NoExecute(gn)) {
 +      return (0);
 +    }
 +    if (DEBUG(JOB))
 +      fprintf(debug_file, "Execute: '%s'\n", cmd);
 +
 +again:
 +    if (useShell) {
 +      /*
 +       * We need to pass the command off to the shell, typically
 +       * because the command contains a "meta" character.
 +       */
 +      static const char *shargv[5];
 +      int shargc;
 +
 +      shargc = 0;
 +      shargv[shargc++] = shellPath;
 +      /*
 +       * The following work for any of the builtin shell specs.
 +       */
 +      if (errCheck && shellErrFlag) {
 +          shargv[shargc++] = shellErrFlag;
 +      }
 +      if (DEBUG(SHELL))
 +              shargv[shargc++] = "-xc";
 +      else
 +              shargv[shargc++] = "-c";
 +      shargv[shargc++] = cmd;
 +      shargv[shargc++] = NULL;
 +      av = shargv;
 +      argc = 0;
 +      bp = NULL;
 +      mav = NULL;
 +    } else {
 +      /*
 +       * No meta-characters, so no need to exec a shell. Break the command
 +       * into words to form an argument vector we can execute.
 +       */
 +      mav = brk_string(cmd, &argc, TRUE, &bp);
 +      if (mav == NULL) {
 +              useShell = 1;
 +              goto again;
 +      }
 +      av = (void *)mav;
 +    }
 +
 +    local = TRUE;
 +
 +#ifdef USE_META
 +    if (useMeta) {
 +      meta_compat_start();
 +    }
 +#endif
 +    
 +    /*
 +     * Fork and execute the single command. If the fork fails, we abort.
 +     */
 +    cpid = vFork();
 +    if (cpid < 0) {
 +      Fatal("Could not fork");
 +    }
 +    if (cpid == 0) {
 +      Var_ExportVars();
 +#ifdef USE_META
 +      if (useMeta) {
 +          meta_compat_child();
 +      }
 +#endif
 +      if (local)
 +          (void)execvp(av[0], (char *const *)UNCONST(av));
 +      else
 +          (void)execv(av[0], (char *const *)UNCONST(av));
 +      execError("exec", av[0]);
 +      _exit(1);
 +    }
 +    if (mav)
 +      free(mav);
 +    if (bp)
 +      free(bp);
 +    Lst_Replace(cmdNode, NULL);
 +
 +#ifdef USE_META
 +    if (useMeta) {
 +      meta_compat_parent();
 +    }
 +#endif
 +
 +    /*
 +     * The child is off and running. Now all we can do is wait...
 +     */
 +    while (1) {
 +
 +      while ((retstat = wait(&reason)) != cpid) {
 +          if (retstat > 0)
 +              JobReapChild(retstat, reason, FALSE); /* not ours? */
 +          if (retstat == -1 && errno != EINTR) {
 +              break;
 +          }
 +      }
 +
 +      if (retstat > -1) {
 +          if (WIFSTOPPED(reason)) {
 +              status = WSTOPSIG(reason);              /* stopped */
 +          } else if (WIFEXITED(reason)) {
 +              status = WEXITSTATUS(reason);           /* exited */
 +#if defined(USE_META) && defined(USE_FILEMON_ONCE)
 +              if (useMeta) {
 +                  meta_cmd_finish(NULL);
 +              }
 +#endif
 +              if (status != 0) {
 +                  if (DEBUG(ERROR)) {
 +                      fprintf(debug_file, "\n*** Failed target:  %s\n*** Failed command: ",
 +                          gn->name);
 +                      for (cp = cmd; *cp; ) {
 +                          if (isspace((unsigned char)*cp)) {
 +                              fprintf(debug_file, " ");
 +                              while (isspace((unsigned char)*cp))
 +                                  cp++;
 +                          } else {
 +                              fprintf(debug_file, "%c", *cp);
 +                              cp++;
 +                          }
 +                      }
 +                      fprintf(debug_file, "\n");
 +                  }
 +                  printf("*** Error code %d", status);
 +              }
 +          } else {
 +              status = WTERMSIG(reason);              /* signaled */
 +              printf("*** Signal %d", status);
 +          }
 +
 +
 +          if (!WIFEXITED(reason) || (status != 0)) {
 +              if (errCheck) {
 +#ifdef USE_META
 +                  if (useMeta) {
 +                      meta_job_error(NULL, gn, 0, status);
 +                  }
 +#endif
 +                  gn->made = ERROR;
 +                  if (keepgoing) {
 +                      /*
 +                       * Abort the current target, but let others
 +                       * continue.
 +                       */
 +                      printf(" (continuing)\n");
 +                  }
 +              } else {
 +                  /*
 +                   * Continue executing commands for this target.
 +                   * If we return 0, this will happen...
 +                   */
 +                  printf(" (ignored)\n");
 +                  status = 0;
 +              }
 +          }
 +          break;
 +      } else {
 +          Fatal("error in wait: %d: %s", retstat, strerror(errno));
 +          /*NOTREACHED*/
 +      }
 +    }
 +    free(cmdStart);
 +
 +    return (status);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Compat_Make --
 + *    Make a target.
 + *
 + * Input:
 + *    gnp             The node to make
 + *    pgnp            Parent to abort if necessary
 + *
 + * Results:
 + *    0
 + *
 + * Side Effects:
 + *    If an error is detected and not being ignored, the process exits.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +Compat_Make(void *gnp, void *pgnp)
 +{
 +    GNode *gn = (GNode *)gnp;
 +    GNode *pgn = (GNode *)pgnp;
 +
-     if (!meta[0])             /* we came here from jobs */
-       Compat_Init();
++    if (!shellName)           /* we came here from jobs */
++      Shell_Init();
 +    if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
 +      /*
 +       * First mark ourselves to be made, then apply whatever transformations
 +       * the suffix module thinks are necessary. Once that's done, we can
 +       * descend and make all our children. If any of them has an error
 +       * but the -k flag was given, our 'make' field will be set FALSE again.
 +       * This is our signal to not attempt to do anything but abort our
 +       * parent as well.
 +       */
 +      gn->flags |= REMAKE;
 +      gn->made = BEINGMADE;
 +      if ((gn->type & OP_MADE) == 0)
 +          Suff_FindDeps(gn);
 +      Lst_ForEach(gn->children, Compat_Make, gn);
 +      if ((gn->flags & REMAKE) == 0) {
 +          gn->made = ABORTED;
 +          pgn->flags &= ~REMAKE;
 +          goto cohorts;
 +      }
 +
 +      if (Lst_Member(gn->iParents, pgn) != NULL) {
 +          char *p1;
 +          Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
 +          if (p1)
 +              free(p1);
 +      }
 +
 +      /*
 +       * All the children were made ok. Now cmgn->mtime contains the
 +       * modification time of the newest child, we need to find out if we
 +       * exist and when we were modified last. The criteria for datedness
 +       * are defined by the Make_OODate function.
 +       */
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, "Examining %s...", gn->name);
 +      }
 +      if (! Make_OODate(gn)) {
 +          gn->made = UPTODATE;
 +          if (DEBUG(MAKE)) {
 +              fprintf(debug_file, "up-to-date.\n");
 +          }
 +          goto cohorts;
 +      } else if (DEBUG(MAKE)) {
 +          fprintf(debug_file, "out-of-date.\n");
 +      }
 +
 +      /*
 +       * If the user is just seeing if something is out-of-date, exit now
 +       * to tell him/her "yes".
 +       */
 +      if (queryFlag) {
 +          exit(1);
 +      }
 +
 +      /*
 +       * We need to be re-made. We also have to make sure we've got a $?
 +       * variable. To be nice, we also define the $> variable using
 +       * Make_DoAllVar().
 +       */
 +      Make_DoAllVar(gn);
 +
 +      /*
 +       * Alter our type to tell if errors should be ignored or things
 +       * should not be printed so CompatRunCommand knows what to do.
 +       */
 +      if (Targ_Ignore(gn)) {
 +          gn->type |= OP_IGNORE;
 +      }
 +      if (Targ_Silent(gn)) {
 +          gn->type |= OP_SILENT;
 +      }
 +
 +      if (Job_CheckCommands(gn, Fatal)) {
 +          /*
 +           * Our commands are ok, but we still have to worry about the -t
 +           * flag...
 +           */
 +          if (!touchFlag || (gn->type & OP_MAKE)) {
 +              curTarg = gn;
 +#ifdef USE_META
 +              if (useMeta && !NoExecute(gn)) {
 +                  meta_job_start(NULL, gn);
 +              }
 +#endif
 +              Lst_ForEach(gn->commands, CompatRunCommand, gn);
 +              curTarg = NULL;
 +          } else {
 +              Job_Touch(gn, gn->type & OP_SILENT);
 +          }
 +      } else {
 +          gn->made = ERROR;
 +      }
 +#ifdef USE_META
 +      if (useMeta && !NoExecute(gn)) {
 +          meta_job_finish(NULL);
 +      }
 +#endif
 +
 +      if (gn->made != ERROR) {
 +          /*
 +           * If the node was made successfully, mark it so, update
 +           * its modification time and timestamp all its parents. Note
 +           * that for .ZEROTIME targets, the timestamping isn't done.
 +           * This is to keep its state from affecting that of its parent.
 +           */
 +          gn->made = MADE;
 +          pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
 +          if (!(gn->type & OP_EXEC)) {
 +              pgn->flags |= CHILDMADE;
 +              Make_TimeStamp(pgn, gn);
 +          }
 +      } else if (keepgoing) {
 +          pgn->flags &= ~REMAKE;
 +      } else {
 +          PrintOnError(gn, "\n\nStop.");
 +          exit(1);
 +      }
 +    } else if (gn->made == ERROR) {
 +      /*
 +       * Already had an error when making this beastie. Tell the parent
 +       * to abort.
 +       */
 +      pgn->flags &= ~REMAKE;
 +    } else {
 +      if (Lst_Member(gn->iParents, pgn) != NULL) {
 +          char *p1;
 +          Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
 +          if (p1)
 +              free(p1);
 +      }
 +      switch(gn->made) {
 +          case BEINGMADE:
 +              Error("Graph cycles through %s", gn->name);
 +              gn->made = ERROR;
 +              pgn->flags &= ~REMAKE;
 +              break;
 +          case MADE:
 +              if ((gn->type & OP_EXEC) == 0) {
 +                  pgn->flags |= CHILDMADE;
 +                  Make_TimeStamp(pgn, gn);
 +              }
 +              break;
 +          case UPTODATE:
 +              if ((gn->type & OP_EXEC) == 0) {
 +                  Make_TimeStamp(pgn, gn);
 +              }
 +              break;
 +          default:
 +              break;
 +      }
 +    }
 +
 +cohorts:
 +    Lst_ForEach(gn->cohorts, Compat_Make, pgnp);
 +    return (0);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Compat_Run --
 + *    Initialize this mode and start making.
 + *
 + * Input:
 + *    targs           List of target nodes to re-create
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Guess what?
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Compat_Run(Lst targs)
 +{
 +    GNode       *gn = NULL;/* Current root target */
 +    int                 errors;   /* Number of targets not remade due to errors */
 +
-     Compat_Init();
++    if (!shellName)
++      Shell_Init();
 +
 +    if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) {
 +      bmake_signal(SIGINT, CompatInterrupt);
 +    }
 +    if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) {
 +      bmake_signal(SIGTERM, CompatInterrupt);
 +    }
 +    if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) {
 +      bmake_signal(SIGHUP, CompatInterrupt);
 +    }
 +    if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
 +      bmake_signal(SIGQUIT, CompatInterrupt);
 +    }
 +
 +    ENDNode = Targ_FindNode(".END", TARG_CREATE);
 +    ENDNode->type = OP_SPECIAL;
 +    /*
 +     * If the user has defined a .BEGIN target, execute the commands attached
 +     * to it.
 +     */
 +    if (!queryFlag) {
 +      gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
 +      if (gn != NULL) {
 +          Compat_Make(gn, gn);
 +            if (gn->made == ERROR) {
 +                PrintOnError(gn, "\n\nStop.");
 +                exit(1);
 +            }
 +      }
 +    }
 +
 +    /*
 +     * Expand .USE nodes right now, because they can modify the structure
 +     * of the tree.
 +     */
 +    Make_ExpandUse(targs);
 +
 +    /*
 +     * For each entry in the list of targets to create, call Compat_Make on
 +     * it to create the thing. Compat_Make will leave the 'made' field of gn
 +     * in one of several states:
 +     *            UPTODATE        gn was already up-to-date
 +     *            MADE            gn was recreated successfully
 +     *            ERROR           An error occurred while gn was being created
 +     *            ABORTED         gn was not remade because one of its inferiors
 +     *                            could not be made due to errors.
 +     */
 +    errors = 0;
 +    while (!Lst_IsEmpty (targs)) {
 +      gn = (GNode *)Lst_DeQueue(targs);
 +      Compat_Make(gn, gn);
 +
 +      if (gn->made == UPTODATE) {
 +          printf("`%s' is up to date.\n", gn->name);
 +      } else if (gn->made == ABORTED) {
 +          printf("`%s' not remade because of errors.\n", gn->name);
 +          errors += 1;
 +      }
 +    }
 +
 +    /*
 +     * If the user has defined a .END target, run its commands.
 +     */
 +    if (errors == 0) {
 +      Compat_Make(ENDNode, ENDNode);
 +      if (gn->made == ERROR) {
 +          PrintOnError(gn, "\n\nStop.");
 +          exit(1);
 +      }
 +    }
 +}
index b05a56cbcc86e80469ec2514034788a7d4213de6,0000000000000000000000000000000000000000..2d2f08760c7a56e608cdb845cfb8e08f17209013
mode 100644,000000..100644
--- /dev/null
@@@ -1,1434 -1,0 +1,1434 @@@
- /*    $NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $    */
++/*    $NetBSD: cond.c,v 1.69 2015/10/11 04:51:24 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1988, 1989 by Adam de Boor
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $";
++static char rcsid[] = "$NetBSD: cond.c,v 1.69 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)cond.c    8.2 (Berkeley) 1/2/94";
 +#else
- __RCSID("$NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $");
++__RCSID("$NetBSD: cond.c,v 1.69 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * cond.c --
 + *    Functions to handle conditionals in a makefile.
 + *
 + * Interface:
 + *    Cond_Eval       Evaluate the conditional in the passed line.
 + *
 + */
 +
 +#include    <ctype.h>
 +#include    <errno.h>    /* For strtoul() error checking */
 +
 +#include    "make.h"
 +#include    "hash.h"
 +#include    "dir.h"
 +#include    "buf.h"
 +
 +/*
 + * The parsing of conditional expressions is based on this grammar:
 + *    E -> F || E
 + *    E -> F
 + *    F -> T && F
 + *    F -> T
 + *    T -> defined(variable)
 + *    T -> make(target)
 + *    T -> exists(file)
 + *    T -> empty(varspec)
 + *    T -> target(name)
 + *    T -> commands(name)
 + *    T -> symbol
 + *    T -> $(varspec) op value
 + *    T -> $(varspec) == "string"
 + *    T -> $(varspec) != "string"
 + *    T -> "string"
 + *    T -> ( E )
 + *    T -> ! T
 + *    op -> == | != | > | < | >= | <=
 + *
 + * 'symbol' is some other symbol to which the default function (condDefProc)
 + * is applied.
 + *
 + * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
 + * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||',
 + * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate
 + * the other terminal symbols, using either the default function or the
 + * function given in the terminal, and return the result as either TOK_TRUE
 + * or TOK_FALSE.
 + *
 + * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons.
 + *
 + * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on
 + * error.
 + */
 +typedef enum {
 +    TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
 +    TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
 +} Token;
 +
 +/*-
 + * Structures to handle elegantly the different forms of #if's. The
 + * last two fields are stored in condInvert and condDefProc, respectively.
 + */
 +static void CondPushBack(Token);
 +static int CondGetArg(char **, char **, const char *);
 +static Boolean CondDoDefined(int, const char *);
 +static int CondStrMatch(const void *, const void *);
 +static Boolean CondDoMake(int, const char *);
 +static Boolean CondDoExists(int, const char *);
 +static Boolean CondDoTarget(int, const char *);
 +static Boolean CondDoCommands(int, const char *);
 +static Boolean CondCvtArg(char *, double *);
 +static Token CondToken(Boolean);
 +static Token CondT(Boolean);
 +static Token CondF(Boolean);
 +static Token CondE(Boolean);
 +static int do_Cond_EvalExpression(Boolean *);
 +
 +static const struct If {
 +    const char        *form;        /* Form of if */
 +    int               formlen;      /* Length of form */
 +    Boolean   doNot;        /* TRUE if default function should be negated */
 +    Boolean   (*defProc)(int, const char *); /* Default function to apply */
 +} ifs[] = {
 +    { "def",    3,      FALSE,  CondDoDefined },
 +    { "ndef",   4,      TRUE,   CondDoDefined },
 +    { "make",   4,      FALSE,  CondDoMake },
 +    { "nmake",          5,      TRUE,   CondDoMake },
 +    { "",       0,      FALSE,  CondDoDefined },
 +    { NULL,     0,      FALSE,  NULL }
 +};
 +
 +static const struct If *if_info;        /* Info for current statement */
 +static char     *condExpr;            /* The expression to parse */
 +static Token    condPushBack=TOK_NONE;        /* Single push-back token used in
 +                                       * parsing */
 +
 +static unsigned int   cond_depth = 0;         /* current .if nesting level */
 +static unsigned int   cond_min_depth = 0;     /* depth at makefile open */
 +
 +/*
 + * Indicate when we should be strict about lhs of comparisons.
 + * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc)
 + * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers
 + * since lhs is already expanded and we cannot tell if 
 + * it was a variable reference or not.
 + */
 +static Boolean lhsStrict;
 +
 +static int
 +istoken(const char *str, const char *tok, size_t len)
 +{
 +      return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * CondPushBack --
 + *    Push back the most recent token read. We only need one level of
 + *    this, so the thing is just stored in 'condPushback'.
 + *
 + * Input:
 + *    t               Token to push back into the "stream"
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    condPushback is overwritten.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +CondPushBack(Token t)
 +{
 +    condPushBack = t;
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondGetArg --
 + *    Find the argument of a built-in function.
 + *
 + * Input:
 + *    parens          TRUE if arg should be bounded by parens
 + *
 + * Results:
 + *    The length of the argument and the address of the argument.
 + *
 + * Side Effects:
 + *    The pointer is set to point to the closing parenthesis of the
 + *    function call.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +CondGetArg(char **linePtr, char **argPtr, const char *func)
 +{
 +    char        *cp;
 +    int                 argLen;
 +    Buffer      buf;
 +    int           paren_depth;
 +    char          ch;
 +
 +    cp = *linePtr;
 +    if (func != NULL)
 +      /* Skip opening '(' - verfied by caller */
 +      cp++;
 +
 +    if (*cp == '\0') {
 +      /*
 +       * No arguments whatsoever. Because 'make' and 'defined' aren't really
 +       * "reserved words", we don't print a message. I think this is better
 +       * than hitting the user with a warning message every time s/he uses
 +       * the word 'make' or 'defined' at the beginning of a symbol...
 +       */
 +      *argPtr = NULL;
 +      return (0);
 +    }
 +
 +    while (*cp == ' ' || *cp == '\t') {
 +      cp++;
 +    }
 +
 +    /*
 +     * Create a buffer for the argument and start it out at 16 characters
 +     * long. Why 16? Why not?
 +     */
 +    Buf_Init(&buf, 16);
 +
 +    paren_depth = 0;
 +    for (;;) {
 +      ch = *cp;
 +      if (ch == 0 || ch == ' ' || ch == '\t')
 +          break;
 +      if ((ch == '&' || ch == '|') && paren_depth == 0)
 +          break;
 +      if (*cp == '$') {
 +          /*
 +           * Parse the variable spec and install it as part of the argument
 +           * if it's valid. We tell Var_Parse to complain on an undefined
 +           * variable, so we don't do it too. Nor do we return an error,
 +           * though perhaps we should...
 +           */
 +          char        *cp2;
 +          int         len;
 +          void        *freeIt;
 +
-           cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &freeIt);
++          cp2 = Var_Parse(cp, VAR_CMD, TRUE, TRUE, &len, &freeIt);
 +          Buf_AddBytes(&buf, strlen(cp2), cp2);
 +          if (freeIt)
 +              free(freeIt);
 +          cp += len;
 +          continue;
 +      }
 +      if (ch == '(')
 +          paren_depth++;
 +      else
 +          if (ch == ')' && --paren_depth < 0)
 +              break;
 +      Buf_AddByte(&buf, *cp);
 +      cp++;
 +    }
 +
 +    *argPtr = Buf_GetAll(&buf, &argLen);
 +    Buf_Destroy(&buf, FALSE);
 +
 +    while (*cp == ' ' || *cp == '\t') {
 +      cp++;
 +    }
 +
 +    if (func != NULL && *cp++ != ')') {
 +      Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
 +                   func);
 +      return (0);
 +    }
 +
 +    *linePtr = cp;
 +    return (argLen);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondDoDefined --
 + *    Handle the 'defined' function for conditionals.
 + *
 + * Results:
 + *    TRUE if the given variable is defined.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg)
 +{
 +    char    *p1;
 +    Boolean result;
 +
 +    if (Var_Value(arg, VAR_CMD, &p1) != NULL) {
 +      result = TRUE;
 +    } else {
 +      result = FALSE;
 +    }
 +    if (p1)
 +      free(p1);
 +    return (result);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondStrMatch --
 + *    Front-end for Str_Match so it returns 0 on match and non-zero
 + *    on mismatch. Callback function for CondDoMake via Lst_Find
 + *
 + * Results:
 + *    0 if string matches pattern
 + *
 + * Side Effects:
 + *    None
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +CondStrMatch(const void *string, const void *pattern)
 +{
 +    return(!Str_Match(string, pattern));
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondDoMake --
 + *    Handle the 'make' function for conditionals.
 + *
 + * Results:
 + *    TRUE if the given target is being made.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg)
 +{
 +    return Lst_Find(create, arg, CondStrMatch) != NULL;
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondDoExists --
 + *    See if the given file exists.
 + *
 + * Results:
 + *    TRUE if the file exists and FALSE if it does not.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg)
 +{
 +    Boolean result;
 +    char    *path;
 +
 +    path = Dir_FindFile(arg, dirSearchPath);
 +    if (DEBUG(COND)) {
 +      fprintf(debug_file, "exists(%s) result is \"%s\"\n",
 +             arg, path ? path : "");
 +    }    
 +    if (path != NULL) {
 +      result = TRUE;
 +      free(path);
 +    } else {
 +      result = FALSE;
 +    }
 +    return (result);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondDoTarget --
 + *    See if the given node exists and is an actual target.
 + *
 + * Results:
 + *    TRUE if the node exists as a target and FALSE if it does not.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg)
 +{
 +    GNode   *gn;
 +
 +    gn = Targ_FindNode(arg, TARG_NOCREATE);
 +    return (gn != NULL) && !OP_NOP(gn->type);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * CondDoCommands --
 + *    See if the given node exists and is an actual target with commands
 + *    associated with it.
 + *
 + * Results:
 + *    TRUE if the node exists as a target and has commands associated with
 + *    it and FALSE if it does not.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg)
 +{
 +    GNode   *gn;
 +
 +    gn = Targ_FindNode(arg, TARG_NOCREATE);
 +    return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondCvtArg --
 + *    Convert the given number into a double.
 + *    We try a base 10 or 16 integer conversion first, if that fails
 + *    then we try a floating point conversion instead.
 + *
 + * Results:
 + *    Sets 'value' to double value of string.
 + *    Returns 'true' if the convertion suceeded
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +CondCvtArg(char *str, double *value)
 +{
 +    char *eptr, ech;
 +    unsigned long l_val;
 +    double d_val;
 +
 +    errno = 0;
 +    l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
 +    ech = *eptr;
 +    if (ech == 0 && errno != ERANGE) {
 +      d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
 +    } else {
 +      if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E')
 +          return FALSE;
 +      d_val = strtod(str, &eptr);
 +      if (*eptr)
 +          return FALSE;
 +    }
 +
 +    *value = d_val;
 +    return TRUE;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * CondGetString --
 + *    Get a string from a variable reference or an optionally quoted
 + *    string.  This is called for the lhs and rhs of string compares.
 + *
 + * Results:
 + *    Sets freeIt if needed,
 + *    Sets quoted if string was quoted,
 + *    Returns NULL on error,
 + *    else returns string - absent any quotes.
 + *
 + * Side Effects:
 + *    Moves condExpr to end of this token.
 + *
 + *
 + *-----------------------------------------------------------------------
 + */
 +/* coverity:[+alloc : arg-*2] */
 +static char *
 +CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS)
 +{
 +    Buffer buf;
 +    char *cp;
 +    char *str;
 +    int       len;
 +    int qt;
 +    char *start;
 +
 +    Buf_Init(&buf, 0);
 +    str = NULL;
 +    *freeIt = NULL;
 +    *quoted = qt = *condExpr == '"' ? 1 : 0;
 +    if (qt)
 +      condExpr++;
 +    for (start = condExpr; *condExpr && str == NULL; condExpr++) {
 +      switch (*condExpr) {
 +      case '\\':
 +          if (condExpr[1] != '\0') {
 +              condExpr++;
 +              Buf_AddByte(&buf, *condExpr);
 +          }
 +          break;
 +      case '"':
 +          if (qt) {
 +              condExpr++;             /* we don't want the quotes */
 +              goto got_str;
 +          } else
 +              Buf_AddByte(&buf, *condExpr); /* likely? */
 +          break;
 +      case ')':
 +      case '!':
 +      case '=':
 +      case '>':
 +      case '<':
 +      case ' ':
 +      case '\t':
 +          if (!qt)
 +              goto got_str;
 +          else
 +              Buf_AddByte(&buf, *condExpr);
 +          break;
 +      case '$':
 +          /* if we are in quotes, then an undefined variable is ok */
 +          str = Var_Parse(condExpr, VAR_CMD, (qt ? 0 : doEval),
-                           &len, freeIt);
++                          TRUE, &len, freeIt);
 +          if (str == var_Error) {
 +              if (*freeIt) {
 +                  free(*freeIt);
 +                  *freeIt = NULL;
 +              }
 +              /*
 +               * Even if !doEval, we still report syntax errors, which
 +               * is what getting var_Error back with !doEval means.
 +               */
 +              str = NULL;
 +              goto cleanup;
 +          }
 +          condExpr += len;
 +          /*
 +           * If the '$' was first char (no quotes), and we are
 +           * followed by space, the operator or end of expression,
 +           * we are done.
 +           */
 +          if ((condExpr == start + len) &&
 +              (*condExpr == '\0' ||
 +               isspace((unsigned char) *condExpr) ||
 +               strchr("!=><)", *condExpr))) {
 +              goto cleanup;
 +          }
 +          /*
 +           * Nope, we better copy str to buf
 +           */
 +          for (cp = str; *cp; cp++) {
 +              Buf_AddByte(&buf, *cp);
 +          }
 +          if (*freeIt) {
 +              free(*freeIt);
 +              *freeIt = NULL;
 +          }
 +          str = NULL;                 /* not finished yet */
 +          condExpr--;                 /* don't skip over next char */
 +          break;
 +      default:
 +          if (strictLHS && !qt && *start != '$' &&
 +              !isdigit((unsigned char) *start)) {
 +              /* lhs must be quoted, a variable reference or number */
 +              if (*freeIt) {
 +                  free(*freeIt);
 +                  *freeIt = NULL;
 +              }
 +              str = NULL;
 +              goto cleanup;
 +          }
 +          Buf_AddByte(&buf, *condExpr);
 +          break;
 +      }
 +    }
 + got_str:
 +    str = Buf_GetAll(&buf, NULL);
 +    *freeIt = str;
 + cleanup:
 +    Buf_Destroy(&buf, FALSE);
 +    return str;
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondToken --
 + *    Return the next token from the input.
 + *
 + * Results:
 + *    A Token for the next lexical token in the stream.
 + *
 + * Side Effects:
 + *    condPushback will be set back to TOK_NONE if it is used.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Token
 +compare_expression(Boolean doEval)
 +{
 +    Token     t;
 +    char      *lhs;
 +    char      *rhs;
 +    char      *op;
 +    void      *lhsFree;
 +    void      *rhsFree;
 +    Boolean lhsQuoted;
 +    Boolean rhsQuoted;
 +    double    left, right;
 +
 +    t = TOK_ERROR;
 +    rhs = NULL;
 +    lhsFree = rhsFree = FALSE;
 +    lhsQuoted = rhsQuoted = FALSE;
 +    
 +    /*
 +     * Parse the variable spec and skip over it, saving its
 +     * value in lhs.
 +     */
 +    lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict);
 +    if (!lhs)
 +      goto done;
 +
 +    /*
 +     * Skip whitespace to get to the operator
 +     */
 +    while (isspace((unsigned char) *condExpr))
 +      condExpr++;
 +
 +    /*
 +     * Make sure the operator is a valid one. If it isn't a
 +     * known relational operator, pretend we got a
 +     * != 0 comparison.
 +     */
 +    op = condExpr;
 +    switch (*condExpr) {
 +      case '!':
 +      case '=':
 +      case '<':
 +      case '>':
 +          if (condExpr[1] == '=') {
 +              condExpr += 2;
 +          } else {
 +              condExpr += 1;
 +          }
 +          break;
 +      default:
 +          if (!doEval) {
 +              t = TOK_FALSE;
 +              goto done;
 +          }
 +          /* For .ifxxx "..." check for non-empty string. */
 +          if (lhsQuoted) {
 +              t = lhs[0] != 0;
 +              goto done;
 +          }
 +          /* For .ifxxx <number> compare against zero */
 +          if (CondCvtArg(lhs, &left)) { 
 +              t = left != 0.0;
 +              goto done;
 +          }
 +          /* For .if ${...} check for non-empty string (defProc is ifdef). */
 +          if (if_info->form[0] == 0) {
 +              t = lhs[0] != 0;
 +              goto done;
 +          }
 +          /* Otherwise action default test ... */
 +          t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot;
 +          goto done;
 +    }
 +
 +    while (isspace((unsigned char)*condExpr))
 +      condExpr++;
 +
 +    if (*condExpr == '\0') {
 +      Parse_Error(PARSE_WARNING,
 +                  "Missing right-hand-side of operator");
 +      goto done;
 +    }
 +
 +    rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE);
 +    if (!rhs)
 +      goto done;
 +
 +    if (rhsQuoted || lhsQuoted) {
 +do_string_compare:
 +      if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
 +          Parse_Error(PARSE_WARNING,
 +    "String comparison operator should be either == or !=");
 +          goto done;
 +      }
 +
 +      if (DEBUG(COND)) {
 +          fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
 +                 lhs, rhs, op);
 +      }
 +      /*
 +       * Null-terminate rhs and perform the comparison.
 +       * t is set to the result.
 +       */
 +      if (*op == '=') {
 +          t = strcmp(lhs, rhs) == 0;
 +      } else {
 +          t = strcmp(lhs, rhs) != 0;
 +      }
 +    } else {
 +      /*
 +       * rhs is either a float or an integer. Convert both the
 +       * lhs and the rhs to a double and compare the two.
 +       */
 +    
 +      if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right))
 +          goto do_string_compare;
 +
 +      if (DEBUG(COND)) {
 +          fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left,
 +                 right, op);
 +      }
 +      switch(op[0]) {
 +      case '!':
 +          if (op[1] != '=') {
 +              Parse_Error(PARSE_WARNING,
 +                          "Unknown operator");
 +              goto done;
 +          }
 +          t = (left != right);
 +          break;
 +      case '=':
 +          if (op[1] != '=') {
 +              Parse_Error(PARSE_WARNING,
 +                          "Unknown operator");
 +              goto done;
 +          }
 +          t = (left == right);
 +          break;
 +      case '<':
 +          if (op[1] == '=') {
 +              t = (left <= right);
 +          } else {
 +              t = (left < right);
 +          }
 +          break;
 +      case '>':
 +          if (op[1] == '=') {
 +              t = (left >= right);
 +          } else {
 +              t = (left > right);
 +          }
 +          break;
 +      }
 +    }
 +
 +done:
 +    if (lhsFree)
 +      free(lhsFree);
 +    if (rhsFree)
 +      free(rhsFree);
 +    return t;
 +}
 +
 +static int
 +get_mpt_arg(char **linePtr, char **argPtr, const char *func MAKE_ATTR_UNUSED)
 +{
 +    /*
 +     * Use Var_Parse to parse the spec in parens and return
 +     * TOK_TRUE if the resulting string is empty.
 +     */
 +    int           length;
 +    void    *freeIt;
 +    char    *val;
 +    char    *cp = *linePtr;
 +
 +    /* We do all the work here and return the result as the length */
 +    *argPtr = NULL;
 +
-     val = Var_Parse(cp - 1, VAR_CMD, FALSE, &length, &freeIt);
++    val = Var_Parse(cp - 1, VAR_CMD, FALSE, TRUE, &length, &freeIt);
 +    /*
 +     * Advance *linePtr to beyond the closing ). Note that
 +     * we subtract one because 'length' is calculated from 'cp - 1'.
 +     */
 +    *linePtr = cp - 1 + length;
 +
 +    if (val == var_Error) {
 +      free(freeIt);
 +      return -1;
 +    }
 +
 +    /* A variable is empty when it just contains spaces... 4/15/92, christos */
 +    while (isspace(*(unsigned char *)val))
 +      val++;
 +
 +    /*
 +     * For consistency with the other functions we can't generate the
 +     * true/false here.
 +     */
 +    length = *val ? 2 : 1;
 +    if (freeIt)
 +      free(freeIt);
 +    return length;
 +}
 +
 +static Boolean
 +CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED)
 +{
 +    return arglen == 1;
 +}
 +
 +static Token
 +compare_function(Boolean doEval)
 +{
 +    static const struct fn_def {
 +      const char  *fn_name;
 +      int         fn_name_len;
 +        int         (*fn_getarg)(char **, char **, const char *);
 +      Boolean     (*fn_proc)(int, const char *);
 +    } fn_defs[] = {
 +      { "defined",   7, CondGetArg, CondDoDefined },
 +      { "make",      4, CondGetArg, CondDoMake },
 +      { "exists",    6, CondGetArg, CondDoExists },
 +      { "empty",     5, get_mpt_arg, CondDoEmpty },
 +      { "target",    6, CondGetArg, CondDoTarget },
 +      { "commands",  8, CondGetArg, CondDoCommands },
 +      { NULL,        0, NULL, NULL },
 +    };
 +    const struct fn_def *fn_def;
 +    Token     t;
 +    char      *arg = NULL;
 +    int       arglen;
 +    char *cp = condExpr;
 +    char *cp1;
 +
 +    for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
 +      if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len))
 +          continue;
 +      cp += fn_def->fn_name_len;
 +      /* There can only be whitespace before the '(' */
 +      while (isspace(*(unsigned char *)cp))
 +          cp++;
 +      if (*cp != '(')
 +          break;
 +
 +      arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name);
 +      if (arglen <= 0) {
 +          condExpr = cp;
 +          return arglen < 0 ? TOK_ERROR : TOK_FALSE;
 +      }
 +      /* Evaluate the argument using the required function. */
 +      t = !doEval || fn_def->fn_proc(arglen, arg);
 +      if (arg)
 +          free(arg);
 +      condExpr = cp;
 +      return t;
 +    }
 +
 +    /* Push anything numeric through the compare expression */
 +    cp = condExpr;
 +    if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0]))
 +      return compare_expression(doEval);
 +
 +    /*
 +     * Most likely we have a naked token to apply the default function to.
 +     * However ".if a == b" gets here when the "a" is unquoted and doesn't
 +     * start with a '$'. This surprises people.
 +     * If what follows the function argument is a '=' or '!' then the syntax
 +     * would be invalid if we did "defined(a)" - so instead treat as an
 +     * expression.
 +     */
 +    arglen = CondGetArg(&cp, &arg, NULL);
 +    for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++)
 +      continue;
 +    if (*cp1 == '=' || *cp1 == '!')
 +      return compare_expression(doEval);
 +    condExpr = cp;
 +
 +    /*
 +     * Evaluate the argument using the default function.
 +     * This path always treats .if as .ifdef. To get here the character
 +     * after .if must have been taken literally, so the argument cannot
 +     * be empty - even if it contained a variable expansion.
 +     */
 +    t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot;
 +    if (arg)
 +      free(arg);
 +    return t;
 +}
 +
 +static Token
 +CondToken(Boolean doEval)
 +{
 +    Token t;
 +
 +    t = condPushBack;
 +    if (t != TOK_NONE) {
 +      condPushBack = TOK_NONE;
 +      return t;
 +    }
 +
 +    while (*condExpr == ' ' || *condExpr == '\t') {
 +      condExpr++;
 +    }
 +
 +    switch (*condExpr) {
 +
 +    case '(':
 +      condExpr++;
 +      return TOK_LPAREN;
 +
 +    case ')':
 +      condExpr++;
 +      return TOK_RPAREN;
 +
 +    case '|':
 +      if (condExpr[1] == '|') {
 +          condExpr++;
 +      }
 +      condExpr++;
 +      return TOK_OR;
 +
 +    case '&':
 +      if (condExpr[1] == '&') {
 +          condExpr++;
 +      }
 +      condExpr++;
 +      return TOK_AND;
 +
 +    case '!':
 +      condExpr++;
 +      return TOK_NOT;
 +
 +    case '#':
 +    case '\n':
 +    case '\0':
 +      return TOK_EOF;
 +
 +    case '"':
 +    case '$':
 +      return compare_expression(doEval);
 +
 +    default:
 +      return compare_function(doEval);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * CondT --
 + *    Parse a single term in the expression. This consists of a terminal
 + *    symbol or TOK_NOT and a terminal symbol (not including the binary
 + *    operators):
 + *        T -> defined(variable) | make(target) | exists(file) | symbol
 + *        T -> ! T | ( E )
 + *
 + * Results:
 + *    TOK_TRUE, TOK_FALSE or TOK_ERROR.
 + *
 + * Side Effects:
 + *    Tokens are consumed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Token
 +CondT(Boolean doEval)
 +{
 +    Token   t;
 +
 +    t = CondToken(doEval);
 +
 +    if (t == TOK_EOF) {
 +      /*
 +       * If we reached the end of the expression, the expression
 +       * is malformed...
 +       */
 +      t = TOK_ERROR;
 +    } else if (t == TOK_LPAREN) {
 +      /*
 +       * T -> ( E )
 +       */
 +      t = CondE(doEval);
 +      if (t != TOK_ERROR) {
 +          if (CondToken(doEval) != TOK_RPAREN) {
 +              t = TOK_ERROR;
 +          }
 +      }
 +    } else if (t == TOK_NOT) {
 +      t = CondT(doEval);
 +      if (t == TOK_TRUE) {
 +          t = TOK_FALSE;
 +      } else if (t == TOK_FALSE) {
 +          t = TOK_TRUE;
 +      }
 +    }
 +    return (t);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondF --
 + *    Parse a conjunctive factor (nice name, wot?)
 + *        F -> T && F | T
 + *
 + * Results:
 + *    TOK_TRUE, TOK_FALSE or TOK_ERROR
 + *
 + * Side Effects:
 + *    Tokens are consumed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Token
 +CondF(Boolean doEval)
 +{
 +    Token   l, o;
 +
 +    l = CondT(doEval);
 +    if (l != TOK_ERROR) {
 +      o = CondToken(doEval);
 +
 +      if (o == TOK_AND) {
 +          /*
 +           * F -> T && F
 +           *
 +           * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to
 +           * parse the r.h.s. anyway (to throw it away).
 +           * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no.
 +           */
 +          if (l == TOK_TRUE) {
 +              l = CondF(doEval);
 +          } else {
 +              (void)CondF(FALSE);
 +          }
 +      } else {
 +          /*
 +           * F -> T
 +           */
 +          CondPushBack(o);
 +      }
 +    }
 +    return (l);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * CondE --
 + *    Main expression production.
 + *        E -> F || E | F
 + *
 + * Results:
 + *    TOK_TRUE, TOK_FALSE or TOK_ERROR.
 + *
 + * Side Effects:
 + *    Tokens are, of course, consumed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Token
 +CondE(Boolean doEval)
 +{
 +    Token   l, o;
 +
 +    l = CondF(doEval);
 +    if (l != TOK_ERROR) {
 +      o = CondToken(doEval);
 +
 +      if (o == TOK_OR) {
 +          /*
 +           * E -> F || E
 +           *
 +           * A similar thing occurs for ||, except that here we make sure
 +           * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
 +           * Once again, if l is TOK_FALSE, the result is the r.h.s. and once
 +           * again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
 +           */
 +          if (l == TOK_FALSE) {
 +              l = CondE(doEval);
 +          } else {
 +              (void)CondE(FALSE);
 +          }
 +      } else {
 +          /*
 +           * E -> F
 +           */
 +          CondPushBack(o);
 +      }
 +    }
 +    return (l);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Cond_EvalExpression --
 + *    Evaluate an expression in the passed line. The expression
 + *    consists of &&, ||, !, make(target), defined(variable)
 + *    and parenthetical groupings thereof.
 + *
 + * Results:
 + *    COND_PARSE      if the condition was valid grammatically
 + *    COND_INVALID    if not a valid conditional.
 + *
 + *    (*value) is set to the boolean value of the condition
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint, Boolean strictLHS)
 +{
 +    static const struct If *dflt_info;
 +    const struct If *sv_if_info = if_info;
 +    char *sv_condExpr = condExpr;
 +    Token sv_condPushBack = condPushBack;
 +    int rval;
 +
 +    lhsStrict = strictLHS;
 +
 +    while (*line == ' ' || *line == '\t')
 +      line++;
 +
 +    if (info == NULL && (info = dflt_info) == NULL) {
 +      /* Scan for the entry for .if - it can't be first */
 +      for (info = ifs; ; info++)
 +          if (info->form[0] == 0)
 +              break;
 +      dflt_info = info;
 +    }
 +
 +    if_info = info != NULL ? info : ifs + 4;
 +    condExpr = line;
 +    condPushBack = TOK_NONE;
 +
 +    rval = do_Cond_EvalExpression(value);
 +
 +    if (rval == COND_INVALID && eprint)
 +      Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line);
 +
 +    if_info = sv_if_info;
 +    condExpr = sv_condExpr;
 +    condPushBack = sv_condPushBack;
 +
 +    return rval;
 +}
 +
 +static int
 +do_Cond_EvalExpression(Boolean *value)
 +{
 +
 +    switch (CondE(TRUE)) {
 +    case TOK_TRUE:
 +      if (CondToken(TRUE) == TOK_EOF) {
 +          *value = TRUE;
 +          return COND_PARSE;
 +      }
 +      break;
 +    case TOK_FALSE:
 +      if (CondToken(TRUE) == TOK_EOF) {
 +          *value = FALSE;
 +          return COND_PARSE;
 +      }
 +      break;
 +    default:
 +    case TOK_ERROR:
 +      break;
 +    }
 +
 +    return COND_INVALID;
 +}
 +
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Cond_Eval --
 + *    Evaluate the conditional in the passed line. The line
 + *    looks like this:
 + *        .<cond-type> <expr>
 + *    where <cond-type> is any of if, ifmake, ifnmake, ifdef,
 + *    ifndef, elif, elifmake, elifnmake, elifdef, elifndef
 + *    and <expr> consists of &&, ||, !, make(target), defined(variable)
 + *    and parenthetical groupings thereof.
 + *
 + * Input:
 + *    line            Line to parse
 + *
 + * Results:
 + *    COND_PARSE      if should parse lines after the conditional
 + *    COND_SKIP       if should skip lines after the conditional
 + *    COND_INVALID    if not a valid conditional.
 + *
 + * Side Effects:
 + *    None.
 + *
 + * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
 + * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF)
 + * otherwise .else could be treated as '.elif 1'.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +Cond_Eval(char *line)
 +{
 +#define           MAXIF      128      /* maximum depth of .if'ing */
 +#define           MAXIF_BUMP  32      /* how much to grow by */
 +    enum if_states {
 +      IF_ACTIVE,              /* .if or .elif part active */
 +      ELSE_ACTIVE,            /* .else part active */
 +      SEARCH_FOR_ELIF,        /* searching for .elif/else to execute */
 +      SKIP_TO_ELSE,           /* has been true, but not seen '.else' */
 +      SKIP_TO_ENDIF           /* nothing else to execute */
 +    };
 +    static enum if_states *cond_state = NULL;
 +    static unsigned int max_if_depth = MAXIF;
 +
 +    const struct If *ifp;
 +    Boolean       isElif;
 +    Boolean       value;
 +    int                   level;      /* Level at which to report errors. */
 +    enum if_states  state;
 +
 +    level = PARSE_FATAL;
 +    if (!cond_state) {
 +      cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state));
 +      cond_state[0] = IF_ACTIVE;
 +    }
 +    /* skip leading character (the '.') and any whitespace */
 +    for (line++; *line == ' ' || *line == '\t'; line++)
 +      continue;
 +
 +    /* Find what type of if we're dealing with.  */
 +    if (line[0] == 'e') {
 +      if (line[1] != 'l') {
 +          if (!istoken(line + 1, "ndif", 4))
 +              return COND_INVALID;
 +          /* End of conditional section */
 +          if (cond_depth == cond_min_depth) {
 +              Parse_Error(level, "if-less endif");
 +              return COND_PARSE;
 +          }
 +          /* Return state for previous conditional */
 +          cond_depth--;
 +          return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
 +      }
 +
 +      /* Quite likely this is 'else' or 'elif' */
 +      line += 2;
 +      if (istoken(line, "se", 2)) {
 +          /* It is else... */
 +          if (cond_depth == cond_min_depth) {
 +              Parse_Error(level, "if-less else");
 +              return COND_PARSE;
 +          }
 +
 +          state = cond_state[cond_depth];
 +          switch (state) {
 +          case SEARCH_FOR_ELIF:
 +              state = ELSE_ACTIVE;
 +              break;
 +          case ELSE_ACTIVE:
 +          case SKIP_TO_ENDIF:
 +              Parse_Error(PARSE_WARNING, "extra else");
 +              /* FALLTHROUGH */
 +          default:
 +          case IF_ACTIVE:
 +          case SKIP_TO_ELSE:
 +              state = SKIP_TO_ENDIF;
 +              break;
 +          }
 +          cond_state[cond_depth] = state;
 +          return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
 +      }
 +      /* Assume for now it is an elif */
 +      isElif = TRUE;
 +    } else
 +      isElif = FALSE;
 +
 +    if (line[0] != 'i' || line[1] != 'f')
 +      /* Not an ifxxx or elifxxx line */
 +      return COND_INVALID;
 +
 +    /*
 +     * Figure out what sort of conditional it is -- what its default
 +     * function is, etc. -- by looking in the table of valid "ifs"
 +     */
 +    line += 2;
 +    for (ifp = ifs; ; ifp++) {
 +      if (ifp->form == NULL)
 +          return COND_INVALID;
 +      if (istoken(ifp->form, line, ifp->formlen)) {
 +          line += ifp->formlen;
 +          break;
 +      }
 +    }
 +
 +    /* Now we know what sort of 'if' it is... */
 +
 +    if (isElif) {
 +      if (cond_depth == cond_min_depth) {
 +          Parse_Error(level, "if-less elif");
 +          return COND_PARSE;
 +      }
 +      state = cond_state[cond_depth];
 +      if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
 +          Parse_Error(PARSE_WARNING, "extra elif");
 +          cond_state[cond_depth] = SKIP_TO_ENDIF;
 +          return COND_SKIP;
 +      }
 +      if (state != SEARCH_FOR_ELIF) {
 +          /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
 +          cond_state[cond_depth] = SKIP_TO_ELSE;
 +          return COND_SKIP;
 +      }
 +    } else {
 +      /* Normal .if */
 +      if (cond_depth + 1 >= max_if_depth) {
 +          /*
 +           * This is rare, but not impossible.
 +           * In meta mode, dirdeps.mk (only runs at level 0)
 +           * can need more than the default.
 +           */
 +          max_if_depth += MAXIF_BUMP;
 +          cond_state = bmake_realloc(cond_state, max_if_depth *
 +              sizeof(*cond_state));
 +      }
 +      state = cond_state[cond_depth];
 +      cond_depth++;
 +      if (state > ELSE_ACTIVE) {
 +          /* If we aren't parsing the data, treat as always false */
 +          cond_state[cond_depth] = SKIP_TO_ELSE;
 +          return COND_SKIP;
 +      }
 +    }
 +
 +    /* And evaluate the conditional expresssion */
 +    if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) {
 +      /* Syntax error in conditional, error message already output. */
 +      /* Skip everything to matching .endif */
 +      cond_state[cond_depth] = SKIP_TO_ELSE;
 +      return COND_SKIP;
 +    }
 +
 +    if (!value) {
 +      cond_state[cond_depth] = SEARCH_FOR_ELIF;
 +      return COND_SKIP;
 +    }
 +    cond_state[cond_depth] = IF_ACTIVE;
 +    return COND_PARSE;
 +}
 +
 +
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Cond_End --
 + *    Make sure everything's clean at the end of a makefile.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Parse_Error will be called if open conditionals are around.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Cond_restore_depth(unsigned int saved_depth)
 +{
 +    int open_conds = cond_depth - cond_min_depth;
 +
 +    if (open_conds != 0 || saved_depth > cond_depth) {
 +      Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds,
 +                  open_conds == 1 ? "" : "s");
 +      cond_depth = cond_min_depth;
 +    }
 +
 +    cond_min_depth = saved_depth;
 +}
 +
 +unsigned int
 +Cond_save_depth(void)
 +{
 +    int depth = cond_min_depth;
 +
 +    cond_min_depth = cond_depth;
 +    return depth;
 +}
index e2249b0db261efd467895199b1e3bba5d0e52901,0000000000000000000000000000000000000000..ae3e425f16aff349765cb27de2f60eba7719aaf4
mode 100644,000000..100644
--- /dev/null
@@@ -1,388 -1,0 +1,389 @@@
- dnl   $Id: configure.in,v 1.53 2014/11/06 01:49:40 sjg Exp $
 +dnl
 +dnl RCSid:
- AC_INIT([bmake], [20140214], [sjg@NetBSD.org])
++dnl   $Id: configure.in,v 1.54 2015/10/10 04:17:10 sjg Exp $
 +dnl
 +dnl Process this file with autoconf to produce a configure script
 +dnl
 +AC_PREREQ(2.50)
++AC_INIT([bmake], [20151009], [sjg@NetBSD.org])
 +AC_CONFIG_HEADERS(config.h)
 +
 +dnl make srcdir absolute
 +case "$srcdir" in
 +/*) ;;
 +*) srcdir=`cd $srcdir && pwd`;;
 +esac
 +
 +dnl
 +AC_ARG_WITH(defshell,
 +[  --with-defshell=SHELL  use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions],
 +[case "${withval}" in
 +yes)   AC_MSG_ERROR(bad value ${withval} given for bmake DEFSHELL) ;;
 +no)    ;;
 +*)     case "$with_defshell" in
 +       sh)  DEFSHELL_INDEX=DEFSHELL_INDEX_SH;;        # it's the default anyway
 +       ksh) DEFSHELL_INDEX=DEFSHELL_INDEX_KSH;;
 +       csh) DEFSHELL_INDEX=DEFSHELL_INDEX_CSH;;       # kidding right?
 +       *)   defshell_path=$with_defshell;;    # better be sh compatible!
 +       esac
 +       ;;
 +       esac])
 +dnl
 +use_meta=yes
 +AC_ARG_WITH(meta,
 +[ --without-meta dissable use of meta-mode],
 +[case "${withval}" in
 +yes|no) use_meta=${withval};;
 +*) AC_MSG_ERROR(bad value ${withval} given for meta) ;;
 +esac])
 +dnl
 +AC_ARG_WITH(filemon,
 +[ --with-filemon=path/filemon.h indicate path to filemon.h for meta-mode],
 +[ case "/${withval}" in
 +/no|*/filemon.h) filemon_h="${withval}";;
 +*/filemon*) filemon_h="${withval}/filemon.h";;
 +*) AC_MSG_ERROR(bad value ${withval} given for filemon) ;;
 +esac],
 +[
 +OS=`uname -s`
 +for d in "/usr/include/dev/filemon" "$prefix/include/dev/filemon" "$srcdir/filemon" "$srcdir/../filemon" "$srcdir/../../sys/dev/filemon"
 +do
 +      for x in "/$OS" ""
 +      do
 +              filemon_h="$d$x/filemon.h"
 +              test -s "$filemon_h" && break
 +      done
 +      test -s "$filemon_h" && break
 +done
 +test -s "${filemon_h:-/dev/null}" || filemon_h=no
 +])
 +dnl echo "Note: use_meta=$use_meta filemon_h=$filemon_h" >&6
 +case "$use_meta" in
 +yes)
 +      case "$filemon_h" in
 +      *.h) echo "Using: filemon=$filemon_h" >&6;;
 +      esac
 +      ;;
 +esac
 +dnl
 +dnl Check for OS problems
 +dnl Solaris's signal.h only privides sigset_t etc if one of
 +dnl _EXTENSIONS_ _POSIX_C_SOURCE or _XOPEN_SOURCE are defined.
 +dnl The later two seem to cause more problems than they solve so if we
 +dnl see _EXTENSIONS_ we use it.
 +AC_USE_SYSTEM_EXTENSIONS
 +dnl Checks for programs.
 +AC_PROG_CC
 +AC_PROG_GCC_TRADITIONAL
 +AC_PROG_INSTALL
 +dnl Executable suffix - normally empty; .exe on os2.
 +AC_SUBST(ac_exe_suffix)dnl
 +dnl
 +dnl Hurd refuses to define PATH_MAX or MAXPATHLEN
 +if test -x /usr/bin/getconf; then
 +   bmake_path_max=`getconf PATH_MAX / 2> /dev/null`
 +   # only a numeric response is useful
 +   test ${bmake_path_max:-0} -gt 0 2> /dev/null || bmake_path_max=
 +fi
 +bmake_path_max=${bmake_path_max:-1024}
 +if test $bmake_path_max -gt 1024; then
 +   # this is all we expect
 +   bmake_path_max=1024
 +fi
 +echo "Using: BMAKE_PATH_MAX=$bmake_path_max" >&6
 +AC_SUBST(bmake_path_max)dnl
 +dnl
 +dnl AC_C_CROSS
 +dnl
 +
 +dnl Checks for header files.
 +AC_HEADER_STDC
 +AC_HEADER_SYS_WAIT
 +AC_HEADER_DIRENT
 +dnl Keep this list sorted
 +AC_CHECK_HEADERS( \
 +      ar.h \
 +      err.h \
 +      fcntl.h \
++      limits.h \
 +      paths.h \
 +      poll.h \
 +      ranlib.h \
 +      string.h \
 +      sys/mman.h \
 +      sys/select.h \
 +      sys/socket.h \
 +      sys/sysctl.h \
 +      sys/time.h \
 +      sys/uio.h \
 +      unistd.h \
 +      utime.h \
 +      )
 +
 +dnl Both *BSD and Linux have sys/cdefs.h, most do not.
 +dnl If it is missing, we add -I${srcdir}/missing to CFLAGS
 +dnl also if sys/cdefs.h does not have __RCSID we need to use ours
 +dnl but we need to include the host's one too *sigh*
 +AC_CHECK_HEADER(sys/cdefs.h,
 +echo $ECHO_N "checking whether sys/cdefs.h is compatible... $ECHO_C" >&6
 +AC_EGREP_CPP(yes,
 +[#include <sys/cdefs.h>
 +#ifdef __RCSID
 +yes
 +#endif
 +],
 +echo yes  >&6,
 +echo no  >&6; CPPFLAGS="${CPPFLAGS} -I`cd ${srcdir}/missing && pwd` -DNEED_HOST_CDEFS_H"),
 +CPPFLAGS="${CPPFLAGS} -I`cd ${srcdir}/missing && pwd`")
 +
 +dnl Checks for typedefs, structures, and compiler characteristics.
 +AC_C___ATTRIBUTE__
 +AC_C_BIGENDIAN
 +AC_C_CONST
 +AC_TYPE_OFF_T
 +AC_TYPE_PID_T
 +AC_TYPE_SIZE_T
 +AC_DECL_SYS_SIGLIST
 +AC_HEADER_TIME
 +AC_STRUCT_TM
 +
 +dnl Checks for library functions.
 +AC_TYPE_SIGNAL
 +AC_FUNC_VFORK
 +AC_FUNC_VPRINTF
 +AC_FUNC_WAIT3
 +dnl Keep this list sorted
 +AC_CHECK_FUNCS( \
 +      err \
 +      errx \
 +      getcwd \
 +      getenv \
 +      getopt \
 +      getwd \
 +      killpg \
 +      mmap \
 +      putenv \
 +      select \
 +      setenv \
 +      setpgid \
 +      setsid \
 +      sigaction \
 +      sigvec \
 +      snprintf \
 +      strerror \
 +      strftime \
 +      strsep \
 +      strtod \
 +      strtol \
 +      sysctl \
 +      unsetenv \
 +      vsnprintf \
 +      wait3 \
 +      wait4 \
 +      waitpid \
 +      warn \
 +      warnx \
 +      )
 +
 +dnl functions which we may need to provide
 +AC_REPLACE_FUNCS( \
 +      realpath \
 +      dirname \
 +      stresep \
 +      strlcpy \
 +      )
 +
 +AC_CHECK_LIB([util], [emalloc],
 +    [ AC_CHECK_LIB([util], [erealloc],
 +      [ AC_CHECK_LIB([util], [estrdup],
 +        [ AC_CHECK_LIB([util], [estrndup],
 +        [ LIBS="$LIBS -lutil"
 +          CPPFLAGS="$CPPFLAGS -DUSE_EMALLOC" ])])])])
 +
 +dnl
 +dnl Structures
 +dnl
 +AC_HEADER_STAT
 +AC_STRUCT_ST_RDEV
 +dnl
 +echo "checking if compiler supports __func__" >&6
 +AC_LANG(C)
 +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[[const char *func = __func__;]])],,
 +      AC_DEFINE(__func__, __FUNCTION__, C99 function name))
 +dnl
 +dnl we want this for unit-tests/Makefile
 +echo $ECHO_N "checking if diff -u works... $ECHO_C" >&6
 +if diff -u /dev/null /dev/null > /dev/null 2>&1; then
 +   diff_u=-u
 +   echo yes >&6
 +else
 +   diff_u=
 +   echo no >&6
 +fi
 +dnl
 +dnl AC_* don't quite cut it.
 +dnl 
 +echo "checking for MACHINE & MACHINE_ARCH..." >&6
 +cat > conftest.$ac_ext <<EOF
 +#include "confdefs.h"
 +#include <sys/param.h>
 +#ifdef MACHINE
 +machine=MACHINE
 +#endif
 +#ifdef MACHINE_ARCH
 +machine_arch=MACHINE_ARCH
 +#endif
 +EOF
 +
 +default_machine=`(eval "$ac_cpp conftest.$ac_ext") 2>&5 | 
 +      egrep machine= | tr -d ' "'`
 +rm -rf conftest*
 +if test "$default_machine"; then
 +      eval "$default_machine"
 +fi
 +machine=${machine:-`$srcdir/machine.sh`}
 +machine_arch=${machine_arch:-`$srcdir/machine.sh arch`}
 +echo "defaults: MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6
 +dnl
 +dnl now allow overrides
 +dnl
 +AC_ARG_WITH(machine,
 +[  --with-machine=MACHINE  explicitly set MACHINE],
 +[case "${withval}" in
 +yes)   AC_MSG_ERROR(bad value ${withval} given for bmake MACHINE) ;;
 +no)    ;;
 +generic) machine=`$srcdir/machine.sh`;;
 +*)     machine=$with_machine;;
 +esac])
 +force_machine=
 +AC_ARG_WITH(force_machine,
 +[  --with-force-machine=MACHINE  set FORCE_MACHINE],
 +[case "${withval}" in
 +yes)   force_machine=FORCE_;;
 +no)    ;;
 +*)     force_machine=FORCE_; machine=$with_force_machine;;
 +esac])
 +dnl
 +force_machine_arch=
 +AC_ARG_WITH(force_machine_arch,
 +[  --with-force-machine-arch=MACHINE  set FORCE_MACHINE_ARCH],
 +[case "${withval}" in
 +yes)   force_machine_arch=FORCE_;;
 +no)    ;;
 +*)     force_machine_arch=FORCE_; machine_arch=$with_force_machine;;
 +esac])
 +dnl
 +AC_ARG_WITH(machine_arch,
 +[  --with-machine_arch=MACHINE_ARCH  explicitly set MACHINE_ARCH],
 +[case "${withval}" in
 +yes)   AC_MSG_ERROR(bad value ${withval} given for bmake MACHINE_ARCH) ;;
 +no)    ;;
 +*)     machine_arch=$with_machine_arch;;
 +esac])
 +dnl
 +dnl Tell them what we ended up with
 +dnl
 +echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6
 +dnl
 +dnl Allow folk to control _PATH_DEFSYSPATH
 +dnl
 +default_sys_path=\${prefix}/share/mk
 +AC_ARG_WITH(default-sys-path,
 +[  --with-default-sys-path=PATH:DIR:LIST  use an explicit _PATH_DEFSYSPATH
 +      MAKESYSPATH is a ':' separated list of directories 
 +      that bmake will search for system .mk files. 
 +      _PATH_DEFSYSPATH is its default value.],
 +[case "${withval}" in
 +yes)  AC_MSG_ERROR(bad value ${withval} given for bmake _PATH_DEFSYSPATH) ;;
 +no)   ;;
 +*)    default_sys_path="$with_default_sys_path"
 +      ;;
 +esac])
 +dnl
 +dnl Some folk don't like this one
 +dnl
 +AC_ARG_WITH(path-objdirprefix,
 +[  --with-path-objdirprefix=PATH  override _PATH_OBJDIRPREFIX],
 +[case "${withval}" in
 +yes)   AC_MSG_ERROR(bad value ${withval} given for bmake _PATH_OBJDIRPREFIX) ;;
 +no)    CPPFLAGS="$CPPFLAGS -DNO_PATH_OBJDIRPREFIX" ;;
 +*)     CPPFLAGS="$CPPFLAGS \"-D_PATH_OBJDIRPREFIX=\\\"$with_path-objdir\\\"\"" ;;
 +esac])
 +dnl
 +dnl And this can be handy to do with out.
 +dnl
 +AC_ARG_ENABLE(pwd-override,
 +[  --disable-pwd-override  disable \$PWD overriding getcwd()],
 +[case "${enableval}" in
 +yes)   ;;
 +no)    CPPFLAGS="$CPPFLAGS -DNO_PWD_OVERRIDE" ;;
 +*)     AC_MSG_ERROR(bad value ${enableval} given for pwd-override option) ;;
 +esac])
 +dnl
 +dnl Just for grins
 +dnl
 +AC_ARG_ENABLE(check-make-chdir,
 +[  --disable-check-make-chdir disable make trying to guess 
 +      when it should automatically cd \${.CURDIR}],
 +[case "${enableval}" in
 +yes)   ;;
 +no)    CPPFLAGS="$CPPFLAGS -DNO_CHECK_MAKE_CHDIR" ;;
 +*)     AC_MSG_ERROR(bad value ${enableval} given for check-make-chdir option) ;;
 +esac])
 +dnl
 +dnl On non-BSD systems, bootstrap won't work without mk
 +dnl
 +AC_ARG_WITH(mksrc,
 +[  --with-mksrc=PATH tell makefile.boot where to find mk src],
 +[case "${withval}" in
 +""|yes|no) ;;
 +*) test -s $withval/install-mk && mksrc=$withval ||
 +AC_MSG_ERROR(bad value ${withval} given for mksrc cannot find install-mk)
 +;;
 +esac
 +])
 +dnl
 +dnl Now make sure we have a value
 +dnl
 +srcdir=`cd $srcdir && pwd`
 +for mksrc in $mksrc $srcdir/mk $srcdir/../mk mk
 +do
 +      test -s $mksrc/install-mk || continue
 +      mksrc=`cd $mksrc && pwd`
 +      break
 +done
 +mksrc=`echo $mksrc | sed "s,$srcdir,\\\${srcdir},"`
 +echo "Using: MKSRC=$mksrc" 1>&6
 +dnl On some systems we want a different default shell by default
 +if test -x /usr/xpg4/bin/sh; then
 +        defshell_path=${defshell_path:-/usr/xpg4/bin/sh}
 +fi
 +if test -n "$defshell_path"; then
 +      echo "Using: SHELL=$defshell_path"  >&6
 +      AC_DEFINE_UNQUOTED(DEFSHELL_CUSTOM, "$defshell_path", Path of default shell)
 +fi
 +if test -n "$DEFSHELL_INDEX"; then
 +       AC_DEFINE_UNQUOTED(DEFSHELL_INDEX, $DEFSHELL_INDEX, Shell spec to use by default)
 +fi        
 +dnl
 +AC_SUBST(machine)
 +AC_SUBST(force_machine)
 +AC_SUBST(machine_arch)
 +AC_SUBST(mksrc)
 +AC_SUBST(default_sys_path)
 +AC_SUBST(INSTALL)
 +AC_SUBST(GCC)
 +AC_SUBST(diff_u)
 +AC_SUBST(use_meta)
 +AC_SUBST(filemon_h)
 +AC_OUTPUT(makefile Makefile.config make-bootstrap.sh unit-tests/Makefile)
 +
 +cat <<EOF
 +
 +You can now run
 +
 +      sh ./make-bootstrap.sh
 +
 +to produce a fully functional bmake.
 +
 +EOF
index 33bcf1392b8ec2808ab90f31646ec48553a77832,0000000000000000000000000000000000000000..7f10e5626f9a3bc67ae9a7c42c71cff3d600e66a
mode 100644,000000..100644
--- /dev/null
@@@ -1,496 -1,0 +1,496 @@@
- /*    $NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $     */
++/*    $NetBSD: for.c,v 1.50 2015/10/11 04:51:24 sjg Exp $     */
 +
 +/*
 + * Copyright (c) 1992, The Regents of the University of California.
 + * All rights reserved.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $";
++static char rcsid[] = "$NetBSD: for.c,v 1.50 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)for.c     8.1 (Berkeley) 6/6/93";
 +#else
- __RCSID("$NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $");
++__RCSID("$NetBSD: for.c,v 1.50 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * for.c --
 + *    Functions to handle loops in a makefile.
 + *
 + * Interface:
 + *    For_Eval        Evaluate the loop in the passed line.
 + *    For_Run         Run accumulated loop
 + *
 + */
 +
 +#include    <assert.h>
 +#include    <ctype.h>
 +
 +#include    "make.h"
 +#include    "hash.h"
 +#include    "dir.h"
 +#include    "buf.h"
 +#include    "strlist.h"
 +
 +#define FOR_SUB_ESCAPE_CHAR  1
 +#define FOR_SUB_ESCAPE_BRACE 2
 +#define FOR_SUB_ESCAPE_PAREN 4
 +
 +/*
 + * For statements are of the form:
 + *
 + * .for <variable> in <varlist>
 + * ...
 + * .endfor
 + *
 + * The trick is to look for the matching end inside for for loop
 + * To do that, we count the current nesting level of the for loops.
 + * and the .endfor statements, accumulating all the statements between
 + * the initial .for loop and the matching .endfor;
 + * then we evaluate the for loop for each variable in the varlist.
 + *
 + * Note that any nested fors are just passed through; they get handled
 + * recursively in For_Eval when we're expanding the enclosing for in
 + * For_Run.
 + */
 +
 +static int      forLevel = 0;         /* Nesting level        */
 +
 +/*
 + * State of a for loop.
 + */
 +typedef struct _For {
 +    Buffer      buf;                  /* Body of loop         */
 +    strlist_t     vars;                       /* Iteration variables  */
 +    strlist_t     items;              /* Substitution items */
 +    char          *parse_buf;
 +    int           short_var;
 +    int           sub_next;
 +} For;
 +
 +static For        *accumFor;            /* Loop being accumulated */
 +
 +\f
 +
 +static char *
 +make_str(const char *ptr, int len)
 +{
 +      char *new_ptr;
 +
 +      new_ptr = bmake_malloc(len + 1);
 +      memcpy(new_ptr, ptr, len);
 +      new_ptr[len] = 0;
 +      return new_ptr;
 +}
 +
 +static void
 +For_Free(For *arg)
 +{
 +    Buf_Destroy(&arg->buf, TRUE);
 +    strlist_clean(&arg->vars);
 +    strlist_clean(&arg->items);
 +    free(arg->parse_buf);
 +
 +    free(arg);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * For_Eval --
 + *    Evaluate the for loop in the passed line. The line
 + *    looks like this:
 + *        .for <variable> in <varlist>
 + *
 + * Input:
 + *    line            Line to parse
 + *
 + * Results:
 + *      0: Not a .for statement, parse the line
 + *    1: We found a for loop
 + *     -1: A .for statement with a bad syntax error, discard.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +For_Eval(char *line)
 +{
 +    For *new_for;
 +    char *ptr = line, *sub;
 +    int len;
 +    int escapes;
 +    unsigned char ch;
 +    char **words, *word_buf;
 +    int n, nwords;
 +
 +    /* Skip the '.' and any following whitespace */
 +    for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
 +      continue;
 +
 +    /*
 +     * If we are not in a for loop quickly determine if the statement is
 +     * a for.
 +     */
 +    if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
 +          !isspace((unsigned char) ptr[3])) {
 +      if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
 +          Parse_Error(PARSE_FATAL, "for-less endfor");
 +          return -1;
 +      }
 +      return 0;
 +    }
 +    ptr += 3;
 +
 +    /*
 +     * we found a for loop, and now we are going to parse it.
 +     */
 +
 +    new_for = bmake_malloc(sizeof *new_for);
 +    memset(new_for, 0, sizeof *new_for);
 +
 +    /* Grab the variables. Terminate on "in". */
 +    for (;; ptr += len) {
 +      while (*ptr && isspace((unsigned char) *ptr))
 +          ptr++;
 +      if (*ptr == '\0') {
 +          Parse_Error(PARSE_FATAL, "missing `in' in for");
 +          For_Free(new_for);
 +          return -1;
 +      }
 +      for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
 +          continue;
 +      if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
 +          ptr += 2;
 +          break;
 +      }
 +      if (len == 1)
 +          new_for->short_var = 1;
 +      strlist_add_str(&new_for->vars, make_str(ptr, len), len);
 +    }
 +
 +    if (strlist_num(&new_for->vars) == 0) {
 +      Parse_Error(PARSE_FATAL, "no iteration variables in for");
 +      For_Free(new_for);
 +      return -1;
 +    }
 +
 +    while (*ptr && isspace((unsigned char) *ptr))
 +      ptr++;
 +
 +    /*
 +     * Make a list with the remaining words
 +     * The values are substituted as ${:U<value>...} so we must \ escape
 +     * characters that break that syntax.
 +     * Variables are fully expanded - so it is safe for escape $.
 +     * We can't do the escapes here - because we don't know whether
 +     * we are substuting into ${...} or $(...).
 +     */
-     sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
++    sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE, TRUE);
 +
 +    /*
 +     * Split into words allowing for quoted strings.
 +     */
 +    words = brk_string(sub, &nwords, FALSE, &word_buf);
 +
 +    free(sub);
 +    
 +    if (words != NULL) {
 +      for (n = 0; n < nwords; n++) {
 +          ptr = words[n];
 +          if (!*ptr)
 +              continue;
 +          escapes = 0;
 +          while ((ch = *ptr++)) {
 +              switch(ch) {
 +              case ':':
 +              case '$':
 +              case '\\':
 +                  escapes |= FOR_SUB_ESCAPE_CHAR;
 +                  break;
 +              case ')':
 +                  escapes |= FOR_SUB_ESCAPE_PAREN;
 +                  break;
 +              case /*{*/ '}':
 +                  escapes |= FOR_SUB_ESCAPE_BRACE;
 +                  break;
 +              }
 +          }
 +          /*
 +           * We have to dup words[n] to maintain the semantics of
 +           * strlist.
 +           */
 +          strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes);
 +      }
 +
 +      free(words);
 +      free(word_buf);
 +
 +      if ((len = strlist_num(&new_for->items)) > 0 &&
 +          len % (n = strlist_num(&new_for->vars))) {
 +          Parse_Error(PARSE_FATAL,
 +                      "Wrong number of words (%d) in .for substitution list"
 +                      " with %d vars", len, n);
 +          /*
 +           * Return 'success' so that the body of the .for loop is
 +           * accumulated.
 +           * Remove all items so that the loop doesn't iterate.
 +           */
 +          strlist_clean(&new_for->items);
 +      }
 +    }
 +
 +    Buf_Init(&new_for->buf, 0);
 +    accumFor = new_for;
 +    forLevel = 1;
 +    return 1;
 +}
 +
 +/*
 + * Add another line to a .for loop.
 + * Returns 0 when the matching .endfor is reached.
 + */
 +
 +int
 +For_Accum(char *line)
 +{
 +    char *ptr = line;
 +
 +    if (*ptr == '.') {
 +
 +      for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
 +          continue;
 +
 +      if (strncmp(ptr, "endfor", 6) == 0 &&
 +              (isspace((unsigned char) ptr[6]) || !ptr[6])) {
 +          if (DEBUG(FOR))
 +              (void)fprintf(debug_file, "For: end for %d\n", forLevel);
 +          if (--forLevel <= 0)
 +              return 0;
 +      } else if (strncmp(ptr, "for", 3) == 0 &&
 +               isspace((unsigned char) ptr[3])) {
 +          forLevel++;
 +          if (DEBUG(FOR))
 +              (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
 +      }
 +    }
 +
 +    Buf_AddBytes(&accumFor->buf, strlen(line), line);
 +    Buf_AddByte(&accumFor->buf, '\n');
 +    return 1;
 +}
 +
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * For_Run --
 + *    Run the for loop, imitating the actions of an include file
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +static int
 +for_var_len(const char *var)
 +{
 +    char ch, var_start, var_end;
 +    int depth;
 +    int len;
 +
 +    var_start = *var;
 +    if (var_start == 0)
 +      /* just escape the $ */
 +      return 0;
 +
 +    if (var_start == '(')
 +      var_end = ')';
 +    else if (var_start == '{')
 +      var_end = '}';
 +    else
 +      /* Single char variable */
 +      return 1;
 +
 +    depth = 1;
 +    for (len = 1; (ch = var[len++]) != 0;) {
 +      if (ch == var_start)
 +          depth++;
 +      else if (ch == var_end && --depth == 0)
 +          return len;
 +    }
 +
 +    /* Variable end not found, escape the $ */
 +    return 0;
 +}
 +
 +static void
 +for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
 +{
 +    const char *item = strlist_str(items, item_no);
 +    int len;
 +    char ch;
 +
 +    /* If there were no escapes, or the only escape is the other variable
 +     * terminator, then just substitute the full string */
 +    if (!(strlist_info(items, item_no) &
 +          (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
 +      Buf_AddBytes(cmds, strlen(item), item);
 +      return;
 +    }
 +
 +    /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
 +    while ((ch = *item++) != 0) {
 +      if (ch == '$') {
 +          len = for_var_len(item);
 +          if (len != 0) {
 +              Buf_AddBytes(cmds, len + 1, item - 1);
 +              item += len;
 +              continue;
 +          }
 +          Buf_AddByte(cmds, '\\');
 +      } else if (ch == ':' || ch == '\\' || ch == ech)
 +          Buf_AddByte(cmds, '\\');
 +      Buf_AddByte(cmds, ch);
 +    }
 +}
 +
 +static char *
 +For_Iterate(void *v_arg, size_t *ret_len)
 +{
 +    For *arg = v_arg;
 +    int i, len;
 +    char *var;
 +    char *cp;
 +    char *cmd_cp;
 +    char *body_end;
 +    char ch;
 +    Buffer cmds;
 +
 +    if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
 +      /* No more iterations */
 +      For_Free(arg);
 +      return NULL;
 +    }
 +
 +    free(arg->parse_buf);
 +    arg->parse_buf = NULL;
 +
 +    /*
 +     * Scan the for loop body and replace references to the loop variables
 +     * with variable references that expand to the required text.
 +     * Using variable expansions ensures that the .for loop can't generate
 +     * syntax, and that the later parsing will still see a variable.
 +     * We assume that the null variable will never be defined.
 +     *
 +     * The detection of substitions of the loop control variable is naive.
 +     * Many of the modifiers use \ to escape $ (not $) so it is possible
 +     * to contrive a makefile where an unwanted substitution happens.
 +     */
 +
 +    cmd_cp = Buf_GetAll(&arg->buf, &len);
 +    body_end = cmd_cp + len;
 +    Buf_Init(&cmds, len + 256);
 +    for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
 +      char ech;
 +      ch = *++cp;
 +      if ((ch == '(' && (ech = ')')) || (ch == '{' && (ech = '}'))) {
 +          cp++;
 +          /* Check variable name against the .for loop variables */
 +          STRLIST_FOREACH(var, &arg->vars, i) {
 +              len = strlist_info(&arg->vars, i);
 +              if (memcmp(cp, var, len) != 0)
 +                  continue;
 +              if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
 +                  continue;
 +              /* Found a variable match. Replace with :U<value> */
 +              Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
 +              Buf_AddBytes(&cmds, 2, ":U");
 +              cp += len;
 +              cmd_cp = cp;
 +              for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
 +              break;
 +          }
 +          continue;
 +      }
 +      if (ch == 0)
 +          break;
 +      /* Probably a single character name, ignore $$ and stupid ones. {*/
 +      if (!arg->short_var || strchr("}):$", ch) != NULL) {
 +          cp++;
 +          continue;
 +      }
 +      STRLIST_FOREACH(var, &arg->vars, i) {
 +          if (var[0] != ch || var[1] != 0)
 +              continue;
 +          /* Found a variable match. Replace with ${:U<value>} */
 +          Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
 +          Buf_AddBytes(&cmds, 3, "{:U");
 +          cmd_cp = ++cp;
 +          for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
 +          Buf_AddBytes(&cmds, 1, "}");
 +          break;
 +      }
 +    }
 +    Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp);
 +
 +    cp = Buf_Destroy(&cmds, FALSE);
 +    if (DEBUG(FOR))
 +      (void)fprintf(debug_file, "For: loop body:\n%s", cp);
 +
 +    arg->sub_next += strlist_num(&arg->vars);
 +
 +    arg->parse_buf = cp;
 +    *ret_len = strlen(cp);
 +    return cp;
 +}
 +
 +void
 +For_Run(int lineno)
 +{ 
 +    For *arg;
 +  
 +    arg = accumFor;
 +    accumFor = NULL;
 +
 +    if (strlist_num(&arg->items) == 0) {
 +        /* Nothing to expand - possibly due to an earlier syntax error. */
 +        For_Free(arg);
 +        return;
 +    }
 + 
 +    Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
 +}
index 8a7a902e71159f851f5956953ec2183b66b82789,0000000000000000000000000000000000000000..20f693ab8618bca5755ae4c01e1556090e8f1607
mode 100644,000000..100644
--- /dev/null
@@@ -1,3083 -1,0 +1,3084 @@@
- /*    $NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $  */
++/*    $NetBSD: job.c,v 1.181 2015/10/11 04:51:24 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1988, 1989 by Adam de Boor
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $";
++static char rcsid[] = "$NetBSD: job.c,v 1.181 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)job.c     8.2 (Berkeley) 3/19/94";
 +#else
- __RCSID("$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $");
++__RCSID("$NetBSD: job.c,v 1.181 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * job.c --
 + *    handle the creation etc. of our child processes.
 + *
 + * Interface:
 + *    Job_Make                Start the creation of the given target.
 + *
 + *    Job_CatchChildren       Check for and handle the termination of any
 + *                            children. This must be called reasonably
 + *                            frequently to keep the whole make going at
 + *                            a decent clip, since job table entries aren't
 + *                            removed until their process is caught this way.
 + *
 + *    Job_CatchOutput         Print any output our children have produced.
 + *                            Should also be called fairly frequently to
 + *                            keep the user informed of what's going on.
 + *                            If no output is waiting, it will block for
 + *                            a time given by the SEL_* constants, below,
 + *                            or until output is ready.
 + *
 + *    Job_Init                Called to intialize this module. in addition,
 + *                            any commands attached to the .BEGIN target
 + *                            are executed before this function returns.
 + *                            Hence, the makefile must have been parsed
 + *                            before this function is called.
 + *
 + *    Job_End                 Cleanup any memory used.
 + *
 + *    Job_ParseShell          Given the line following a .SHELL target, parse
 + *                            the line as a shell specification. Returns
 + *                            FAILURE if the spec was incorrect.
 + *
 + *    Job_Finish              Perform any final processing which needs doing.
 + *                            This includes the execution of any commands
 + *                            which have been/were attached to the .END
 + *                            target. It should only be called when the
 + *                            job table is empty.
 + *
 + *    Job_AbortAll            Abort all currently running jobs. It doesn't
 + *                            handle output or do anything for the jobs,
 + *                            just kills them. It should only be called in
 + *                            an emergency, as it were.
 + *
 + *    Job_CheckCommands       Verify that the commands for a target are
 + *                            ok. Provide them if necessary and possible.
 + *
 + *    Job_Touch               Update a target without really updating it.
 + *
 + *    Job_Wait                Wait for all currently-running jobs to finish.
 + */
 +
 +#ifdef HAVE_CONFIG_H
 +# include "config.h"
 +#endif
 +#include <sys/types.h>
 +#include <sys/stat.h>
 +#include <sys/file.h>
 +#include <sys/time.h>
 +#include "wait.h"
 +
 +#include <assert.h>
 +#include <errno.h>
 +#include <fcntl.h>
 +#if !defined(USE_SELECT) && defined(HAVE_POLL_H)
 +#include <poll.h>
 +#else
 +#ifndef USE_SELECT                    /* no poll.h */
 +# define USE_SELECT
 +#endif
 +#if defined(HAVE_SYS_SELECT_H)
 +# include <sys/select.h>
 +#endif
 +#endif
 +#include <signal.h>
 +#include <stdio.h>
 +#include <string.h>
 +#include <utime.h>
 +#if defined(HAVE_SYS_SOCKET_H)
 +# include <sys/socket.h>
 +#endif
 +
 +#include "make.h"
 +#include "hash.h"
 +#include "dir.h"
 +#include "job.h"
 +#include "pathnames.h"
 +#include "trace.h"
 +# define STATIC static
 +
 +/*
 + * FreeBSD: traditionally .MAKE is not required to
 + * pass jobs queue to sub-makes.
 + * Use .MAKE.ALWAYS_PASS_JOB_QUEUE=no to disable.
 + */
 +#define MAKE_ALWAYS_PASS_JOB_QUEUE ".MAKE.ALWAYS_PASS_JOB_QUEUE"
 +static int Always_pass_job_queue = TRUE;
 +/*
 + * FreeBSD: aborting entire parallel make isn't always
 + * desired. When doing tinderbox for example, failure of
 + * one architecture should not stop all.
 + * We still want to bail on interrupt though.
 + */
 +#define MAKE_JOB_ERROR_TOKEN "MAKE_JOB_ERROR_TOKEN"
 +static int Job_error_token = TRUE;
 +
 +/*
 + * error handling variables
 + */
 +static int            errors = 0;         /* number of errors reported */
 +static int            aborting = 0;       /* why is the make aborting? */
 +#define ABORT_ERROR   1           /* Because of an error */
 +#define ABORT_INTERRUPT       2           /* Because it was interrupted */
 +#define ABORT_WAIT    3           /* Waiting for jobs to finish */
 +#define JOB_TOKENS    "+EI+"      /* Token to requeue for each abort state */
 +
 +/*
 + * this tracks the number of tokens currently "out" to build jobs.
 + */
 +int jobTokensRunning = 0;
 +int not_parallel = 0;             /* set if .NOT_PARALLEL */
 +
 +/*
 + * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file
 + * is a char! So when we go above 127 we turn negative!
 + */
 +#define FILENO(a) ((unsigned) fileno(a))
 +
 +/*
 + * post-make command processing. The node postCommands is really just the
 + * .END target but we keep it around to avoid having to search for it
 + * all the time.
 + */
 +static GNode            *postCommands = NULL;
 +                                  /* node containing commands to execute when
 +                                   * everything else is done */
 +static int              numCommands;      /* The number of commands actually printed
 +                                   * for a target. Should this number be
 +                                   * 0, no shell will be executed. */
 +
 +/*
 + * Return values from JobStart.
 + */
 +#define JOB_RUNNING   0       /* Job is running */
 +#define JOB_ERROR     1       /* Error in starting the job */
 +#define JOB_FINISHED  2       /* The job is already finished */
 +
 +/*
 + * Descriptions for various shells.
 + *
 + * The build environment may set DEFSHELL_INDEX to one of
 + * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to
 + * select one of the prefedined shells as the default shell.
 + *
 + * Alternatively, the build environment may set DEFSHELL_CUSTOM to the
 + * name or the full path of a sh-compatible shell, which will be used as
 + * the default shell.
 + *
 + * ".SHELL" lines in Makefiles can choose the default shell from the
 + # set defined here, or add additional shells.
 + */
 +
 +#ifdef DEFSHELL_CUSTOM
 +#define DEFSHELL_INDEX_CUSTOM 0
 +#define DEFSHELL_INDEX_SH     1
 +#define DEFSHELL_INDEX_KSH    2
 +#define DEFSHELL_INDEX_CSH    3
 +#else /* !DEFSHELL_CUSTOM */
 +#define DEFSHELL_INDEX_SH     0
 +#define DEFSHELL_INDEX_KSH    1
 +#define DEFSHELL_INDEX_CSH    2
 +#endif /* !DEFSHELL_CUSTOM */
 +
 +#ifndef DEFSHELL_INDEX
 +#define DEFSHELL_INDEX 0      /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */
 +#endif /* !DEFSHELL_INDEX */
 +
 +static Shell    shells[] = {
 +#ifdef DEFSHELL_CUSTOM
 +    /*
 +     * An sh-compatible shell with a non-standard name.
 +     *
 +     * Keep this in sync with the "sh" description below, but avoid
 +     * non-portable features that might not be supplied by all
 +     * sh-compatible shells.
 +     */
 +{
 +    DEFSHELL_CUSTOM,
 +    FALSE, "", "", "", 0,
 +    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
 +    "",
 +    "",
 +},
 +#endif /* DEFSHELL_CUSTOM */
 +    /*
 +     * SH description. Echo control is also possible and, under
 +     * sun UNIX anyway, one can even control error checking.
 +     */
 +{
 +    "sh",
 +    FALSE, "", "", "", 0,
 +    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
 +#if defined(MAKE_NATIVE) && defined(__NetBSD__)
 +    "q",
 +#else
 +    "",
 +#endif
 +    "",
 +},
 +    /*
 +     * KSH description. 
 +     */
 +{
 +    "ksh",
 +    TRUE, "set +v", "set -v", "set +v", 6,
 +    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
 +    "v",
 +    "",
 +},
 +    /*
 +     * CSH description. The csh can do echo control by playing
 +     * with the setting of the 'echo' shell variable. Sadly,
 +     * however, it is unable to do error control nicely.
 +     */
 +{
 +    "csh",
 +    TRUE, "unset verbose", "set verbose", "unset verbose", 10,
 +    FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
 +    "v", "e",
 +},
 +    /*
 +     * UNKNOWN.
 +     */
 +{
 +    NULL,
 +    FALSE, NULL, NULL, NULL, 0,
 +    FALSE, NULL, NULL, NULL, NULL, 0,
 +    NULL, NULL,
 +}
 +};
 +static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to
 +                                                 * which we pass all
 +                                                 * commands in the Makefile.
 +                                                 * It is set by the
 +                                                 * Job_ParseShell function */
 +const char *shellPath = NULL,                   /* full pathname of
 +                                                 * executable image */
 +           *shellName = NULL;                   /* last component of shell */
 +char *shellErrFlag = NULL;
 +static const char *shellArgv = NULL;            /* Custom shell args */
 +
 +
 +STATIC Job    *job_table;     /* The structures that describe them */
 +STATIC Job    *job_table_end; /* job_table + maxJobs */
 +static int    wantToken;      /* we want a token */
 +static int lurking_children = 0;
 +static int make_suspended = 0;        /* non-zero if we've seen a SIGTSTP (etc) */
 +
 +/*
 + * Set of descriptors of pipes connected to
 + * the output channels of children
 + */
 +static struct pollfd *fds = NULL;
 +static Job **jobfds = NULL;
 +static int nfds = 0;
 +static void watchfd(Job *);
 +static void clearfd(Job *);
 +static int readyfd(Job *);
 +
 +STATIC GNode          *lastNode;      /* The node for which output was most recently
 +                               * produced. */
 +static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */
 +static Job tokenWaitJob;      /* token wait pseudo-job */
 +
 +static Job childExitJob;      /* child exit pseudo-job */
 +#define       CHILD_EXIT      "."
 +#define       DO_JOB_RESUME   "R"
 +
 +#define TARG_FMT  "%s %s ---\n" /* Default format */
 +#define MESSAGE(fp, gn) \
 +      if (maxJobs != 1 && targPrefix && *targPrefix) \
 +          (void)fprintf(fp, TARG_FMT, targPrefix, gn->name)
 +
 +static sigset_t caught_signals;       /* Set of signals we handle */
 +#if defined(SYSV)
 +#define KILLPG(pid, sig)      kill(-(pid), (sig))
 +#else
 +#define KILLPG(pid, sig)      killpg((pid), (sig))
 +#endif
 +
 +static void JobChildSig(int);
 +static void JobContinueSig(int);
 +static Job *JobFindPid(int, int, Boolean);
 +static int JobPrintCommand(void *, void *);
 +static int JobSaveCommand(void *, void *);
 +static void JobClose(Job *);
 +static void JobExec(Job *, char **);
 +static void JobMakeArgv(Job *, char **);
 +static int JobStart(GNode *, int);
 +static char *JobOutput(Job *, char *, char *, int);
 +static void JobDoOutput(Job *, Boolean);
 +static Shell *JobMatchShell(const char *);
 +static void JobInterrupt(int, int) MAKE_ATTR_DEAD;
 +static void JobRestartJobs(void);
 +static void JobTokenAdd(void);
 +static void JobSigLock(sigset_t *);
 +static void JobSigUnlock(sigset_t *);
 +static void JobSigReset(void);
 +
 +const char *malloc_options="A";
 +
 +static void
 +job_table_dump(const char *where)
 +{
 +    Job *job;
 +
 +    fprintf(debug_file, "job table @ %s\n", where);
 +    for (job = job_table; job < job_table_end; job++) {
 +      fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n",
 +          (int)(job - job_table), job->job_state, job->flags, job->pid);
 +    }
 +}
 +
 +/*
 + * JobSigLock/JobSigUnlock
 + *
 + * Signal lock routines to get exclusive access. Currently used to
 + * protect `jobs' and `stoppedJobs' list manipulations.
 + */
 +static void JobSigLock(sigset_t *omaskp)
 +{
 +      if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) {
 +              Punt("JobSigLock: sigprocmask: %s", strerror(errno));
 +              sigemptyset(omaskp);
 +      }
 +}
 +
 +static void JobSigUnlock(sigset_t *omaskp)
 +{
 +      (void)sigprocmask(SIG_SETMASK, omaskp, NULL);
 +}
 +
 +static void
 +JobCreatePipe(Job *job, int minfd)
 +{
 +    int i, fd;
 +
 +    if (pipe(job->jobPipe) == -1)
 +      Punt("Cannot create pipe: %s", strerror(errno));
 +
 +    for (i = 0; i < 2; i++) {
 +       /* Avoid using low numbered fds */
 +       fd = fcntl(job->jobPipe[i], F_DUPFD, minfd);
 +       if (fd != -1) {
 +         close(job->jobPipe[i]);
 +         job->jobPipe[i] = fd;
 +       }
 +    }
 +    
 +    /* Set close-on-exec flag for both */
 +    (void)fcntl(job->jobPipe[0], F_SETFD, 1);
 +    (void)fcntl(job->jobPipe[1], F_SETFD, 1);
 +
 +    /*
 +     * We mark the input side of the pipe non-blocking; we poll(2) the
 +     * pipe when we're waiting for a job token, but we might lose the
 +     * race for the token when a new one becomes available, so the read 
 +     * from the pipe should not block.
 +     */
 +    fcntl(job->jobPipe[0], F_SETFL, 
 +      fcntl(job->jobPipe[0], F_GETFL, 0) | O_NONBLOCK);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobCondPassSig --
 + *    Pass a signal to a job
 + *
 + * Input:
 + *    signop          Signal to send it
 + *
 + * Side Effects:
 + *    None, except the job may bite it.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobCondPassSig(int signo)
 +{
 +    Job *job;
 +
 +    if (DEBUG(JOB)) {
 +      (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo);
 +    }
 +
 +    for (job = job_table; job < job_table_end; job++) {
 +      if (job->job_state != JOB_ST_RUNNING)
 +          continue;
 +      if (DEBUG(JOB)) {
 +          (void)fprintf(debug_file,
 +                         "JobCondPassSig passing signal %d to child %d.\n",
 +                         signo, job->pid);
 +      }
 +      KILLPG(job->pid, signo);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobChldSig --
 + *    SIGCHLD handler.
 + *
 + * Input:
 + *    signo           The signal number we've received
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Sends a token on the child exit pipe to wake us up from
 + *    select()/poll().
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobChildSig(int signo MAKE_ATTR_UNUSED)
 +{
 +    while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN)
 +      continue;
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobContinueSig --
 + *    Resume all stopped jobs.
 + *
 + * Input:
 + *    signo           The signal number we've received
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Jobs start running again.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobContinueSig(int signo MAKE_ATTR_UNUSED)
 +{
 +    /*
 +     * Defer sending to SIGCONT to our stopped children until we return
 +     * from the signal handler.
 +     */
 +    while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 &&
 +      errno == EAGAIN)
 +      continue;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobPassSig --
 + *    Pass a signal on to all jobs, then resend to ourselves.
 + *
 + * Input:
 + *    signo           The signal number we've received
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    We die by the same signal.
 + *
 + *-----------------------------------------------------------------------
 + */
 +MAKE_ATTR_DEAD static void
 +JobPassSig_int(int signo)
 +{
 +    /* Run .INTERRUPT target then exit */
 +    JobInterrupt(TRUE, signo);
 +}
 +
 +MAKE_ATTR_DEAD static void
 +JobPassSig_term(int signo)
 +{
 +    /* Dont run .INTERRUPT target then exit */
 +    JobInterrupt(FALSE, signo);
 +}
 +
 +static void
 +JobPassSig_suspend(int signo)
 +{
 +    sigset_t nmask, omask;
 +    struct sigaction act;
 +
 +    /* Suppress job started/continued messages */
 +    make_suspended = 1;
 +
 +    /* Pass the signal onto every job */
 +    JobCondPassSig(signo);
 +
 +    /*
 +     * Send ourselves the signal now we've given the message to everyone else.
 +     * Note we block everything else possible while we're getting the signal.
 +     * This ensures that all our jobs get continued when we wake up before
 +     * we take any other signal.
 +     */
 +    sigfillset(&nmask);
 +    sigdelset(&nmask, signo);
 +    (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
 +
 +    act.sa_handler = SIG_DFL;
 +    sigemptyset(&act.sa_mask);
 +    act.sa_flags = 0;
 +    (void)sigaction(signo, &act, NULL);
 +
 +    if (DEBUG(JOB)) {
 +      (void)fprintf(debug_file,
 +                     "JobPassSig passing signal %d to self.\n", signo);
 +    }
 +
 +    (void)kill(getpid(), signo);
 +
 +    /*
 +     * We've been continued.
 +     *
 +     * A whole host of signals continue to happen!
 +     * SIGCHLD for any processes that actually suspended themselves.
 +     * SIGCHLD for any processes that exited while we were alseep.
 +     * The SIGCONT that actually caused us to wakeup.
 +     *
 +     * Since we defer passing the SIGCONT on to our children until
 +     * the main processing loop, we can be sure that all the SIGCHLD
 +     * events will have happened by then - and that the waitpid() will
 +     * collect the child 'suspended' events.
 +     * For correct sequencing we just need to ensure we process the
 +     * waitpid() before passign on the SIGCONT.
 +     *
 +     * In any case nothing else is needed here.
 +     */
 +
 +    /* Restore handler and signal mask */
 +    act.sa_handler = JobPassSig_suspend;
 +    (void)sigaction(signo, &act, NULL);
 +    (void)sigprocmask(SIG_SETMASK, &omask, NULL);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobFindPid  --
 + *    Compare the pid of the job with the given pid and return 0 if they
 + *    are equal. This function is called from Job_CatchChildren
 + *    to find the job descriptor of the finished job.
 + *
 + * Input:
 + *    job             job to examine
 + *    pid             process id desired
 + *
 + * Results:
 + *    Job with matching pid
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static Job *
 +JobFindPid(int pid, int status, Boolean isJobs)
 +{
 +    Job *job;
 +
 +    for (job = job_table; job < job_table_end; job++) {
 +      if ((job->job_state == status) && job->pid == pid)
 +          return job;
 +    }
 +    if (DEBUG(JOB) && isJobs)
 +      job_table_dump("no pid");
 +    return NULL;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobPrintCommand  --
 + *    Put out another command for the given job. If the command starts
 + *    with an @ or a - we process it specially. In the former case,
 + *    so long as the -s and -n flags weren't given to make, we stick
 + *    a shell-specific echoOff command in the script. In the latter,
 + *    we ignore errors for the entire job, unless the shell has error
 + *    control.
 + *    If the command is just "..." we take all future commands for this
 + *    job to be commands to be executed once the entire graph has been
 + *    made and return non-zero to signal that the end of the commands
 + *    was reached. These commands are later attached to the postCommands
 + *    node and executed by Job_End when all things are done.
 + *    This function is called from JobStart via Lst_ForEach.
 + *
 + * Input:
 + *    cmdp            command string to print
 + *    jobp            job for which to print it
 + *
 + * Results:
 + *    Always 0, unless the command was "..."
 + *
 + * Side Effects:
 + *    If the command begins with a '-' and the shell has no error control,
 + *    the JOB_IGNERR flag is set in the job descriptor.
 + *    If the command is "..." and we're not ignoring such things,
 + *    tailCmds is set to the successor node of the cmd.
 + *    numCommands is incremented if the command is actually printed.
 + *-----------------------------------------------------------------------
 + */
 +static int
 +JobPrintCommand(void *cmdp, void *jobp)
 +{
 +    Boolean     noSpecials;       /* true if we shouldn't worry about
 +                                   * inserting special commands into
 +                                   * the input stream. */
 +    Boolean       shutUp = FALSE;   /* true if we put a no echo command
 +                                   * into the command file */
 +    Boolean     errOff = FALSE;   /* true if we turned error checking
 +                                   * off before printing the command
 +                                   * and need to turn it back on */
 +    const char    *cmdTemplate;           /* Template to use when printing the
 +                                   * command */
 +    char        *cmdStart;        /* Start of expanded command */
 +    char        *escCmd = NULL;    /* Command with quotes/backticks escaped */
 +    char        *cmd = (char *)cmdp;
 +    Job           *job = (Job *)jobp;
 +    int           i, j;
 +
 +    noSpecials = NoExecute(job->node);
 +
 +    if (strcmp(cmd, "...") == 0) {
 +      job->node->type |= OP_SAVE_CMDS;
 +      if ((job->flags & JOB_IGNDOTS) == 0) {
 +          job->tailCmds = Lst_Succ(Lst_Member(job->node->commands,
 +                                              cmd));
 +          return 1;
 +      }
 +      return 0;
 +    }
 +
 +#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) {  \
 +      (void)fprintf(debug_file, fmt, arg);    \
 +    }                                         \
 +   (void)fprintf(job->cmdFILE, fmt, arg);     \
 +   (void)fflush(job->cmdFILE);
 +
 +    numCommands += 1;
 +
-     cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE);
++    cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE, TRUE);
 +
 +    cmdTemplate = "%s\n";
 +
 +    /*
 +     * Check for leading @' and -'s to control echoing and error checking.
 +     */
 +    while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) {
 +      switch (*cmd) {
 +      case '@':
 +          shutUp = DEBUG(LOUD) ? FALSE : TRUE;
 +          break;
 +      case '-':
 +          errOff = TRUE;
 +          break;
 +      case '+':
 +          if (noSpecials) {
 +              /*
 +               * We're not actually executing anything...
 +               * but this one needs to be - use compat mode just for it.
 +               */
 +              CompatRunCommand(cmdp, job->node);
 +              return 0;
 +          }
 +          break;
 +      }
 +      cmd++;
 +    }
 +
 +    while (isspace((unsigned char) *cmd))
 +      cmd++;
 +
 +    /*
 +     * If the shell doesn't have error control the alternate echo'ing will
 +     * be done (to avoid showing additional error checking code) 
 +     * and this will need the characters '$ ` \ "' escaped
 +     */
 +
 +    if (!commandShell->hasErrCtl) {
 +      /* Worst that could happen is every char needs escaping. */
 +      escCmd = bmake_malloc((strlen(cmd) * 2) + 1);
 +      for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) {
 +              if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || 
 +                      cmd[i] == '"')
 +                      escCmd[j++] = '\\';
 +              escCmd[j] = cmd[i];     
 +      }
 +      escCmd[j] = 0;
 +    }
 +
 +    if (shutUp) {
 +      if (!(job->flags & JOB_SILENT) && !noSpecials &&
 +          commandShell->hasEchoCtl) {
 +              DBPRINTF("%s\n", commandShell->echoOff);
 +      } else {
 +          if (commandShell->hasErrCtl)
 +              shutUp = FALSE;
 +      }
 +    }
 +
 +    if (errOff) {
 +      if (!noSpecials) {
 +          if (commandShell->hasErrCtl) {
 +              /*
 +               * we don't want the error-control commands showing
 +               * up either, so we turn off echoing while executing
 +               * them. We could put another field in the shell
 +               * structure to tell JobDoOutput to look for this
 +               * string too, but why make it any more complex than
 +               * it already is?
 +               */
 +              if (!(job->flags & JOB_SILENT) && !shutUp &&
 +                  commandShell->hasEchoCtl) {
 +                      DBPRINTF("%s\n", commandShell->echoOff);
 +                      DBPRINTF("%s\n", commandShell->ignErr);
 +                      DBPRINTF("%s\n", commandShell->echoOn);
 +              } else {
 +                      DBPRINTF("%s\n", commandShell->ignErr);
 +              }
 +          } else if (commandShell->ignErr &&
 +                    (*commandShell->ignErr != '\0'))
 +          {
 +              /*
 +               * The shell has no error control, so we need to be
 +               * weird to get it to ignore any errors from the command.
 +               * If echoing is turned on, we turn it off and use the
 +               * errCheck template to echo the command. Leave echoing
 +               * off so the user doesn't see the weirdness we go through
 +               * to ignore errors. Set cmdTemplate to use the weirdness
 +               * instead of the simple "%s\n" template.
 +               */
 +              job->flags |= JOB_IGNERR;
 +              if (!(job->flags & JOB_SILENT) && !shutUp) {
 +                      if (commandShell->hasEchoCtl) {
 +                              DBPRINTF("%s\n", commandShell->echoOff);
 +                      }
 +                      DBPRINTF(commandShell->errCheck, escCmd);
 +                      shutUp = TRUE;
 +              } else {
 +                      if (!shutUp) {
 +                              DBPRINTF(commandShell->errCheck, escCmd);
 +                      }
 +              }
 +              cmdTemplate = commandShell->ignErr;
 +              /*
 +               * The error ignoration (hee hee) is already taken care
 +               * of by the ignErr template, so pretend error checking
 +               * is still on.
 +               */
 +              errOff = FALSE;
 +          } else {
 +              errOff = FALSE;
 +          }
 +      } else {
 +          errOff = FALSE;
 +      }
 +    } else {
 +
 +      /* 
 +       * If errors are being checked and the shell doesn't have error control
 +       * but does supply an errOut template, then setup commands to run
 +       * through it.
 +       */
 +
 +      if (!commandShell->hasErrCtl && commandShell->errOut && 
 +          (*commandShell->errOut != '\0')) {
 +              if (!(job->flags & JOB_SILENT) && !shutUp) {
 +                      if (commandShell->hasEchoCtl) {
 +                              DBPRINTF("%s\n", commandShell->echoOff);
 +                      }
 +                      DBPRINTF(commandShell->errCheck, escCmd);
 +                      shutUp = TRUE;
 +              }
 +              /* If it's a comment line or blank, treat as an ignored error */
 +              if ((escCmd[0] == commandShell->commentChar) ||
 +                  (escCmd[0] == 0))
 +                      cmdTemplate = commandShell->ignErr;
 +              else
 +                      cmdTemplate = commandShell->errOut;
 +              errOff = FALSE;
 +      }
 +    }
 +
 +    if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
 +      (job->flags & JOB_TRACED) == 0) {
 +          DBPRINTF("set -%s\n", "x");
 +          job->flags |= JOB_TRACED;
 +    }
 +    
 +    DBPRINTF(cmdTemplate, cmd);
 +    free(cmdStart);
 +    if (escCmd)
 +        free(escCmd);
 +    if (errOff) {
 +      /*
 +       * If echoing is already off, there's no point in issuing the
 +       * echoOff command. Otherwise we issue it and pretend it was on
 +       * for the whole command...
 +       */
 +      if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
 +          DBPRINTF("%s\n", commandShell->echoOff);
 +          shutUp = TRUE;
 +      }
 +      DBPRINTF("%s\n", commandShell->errCheck);
 +    }
 +    if (shutUp && commandShell->hasEchoCtl) {
 +      DBPRINTF("%s\n", commandShell->echoOn);
 +    }
 +    return 0;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobSaveCommand --
 + *    Save a command to be executed when everything else is done.
 + *    Callback function for JobFinish...
 + *
 + * Results:
 + *    Always returns 0
 + *
 + * Side Effects:
 + *    The command is tacked onto the end of postCommands's commands list.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +JobSaveCommand(void *cmd, void *gn)
 +{
-     cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, FALSE);
++    cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, FALSE, TRUE);
 +    (void)Lst_AtEnd(postCommands->commands, cmd);
 +    return(0);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobClose --
 + *    Called to close both input and output pipes when a job is finished.
 + *
 + * Results:
 + *    Nada
 + *
 + * Side Effects:
 + *    The file descriptors associated with the job are closed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobClose(Job *job)
 +{
 +    clearfd(job);
 +    (void)close(job->outPipe);
 +    job->outPipe = -1;
 +
 +    JobDoOutput(job, TRUE);
 +    (void)close(job->inPipe);
 +    job->inPipe = -1;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobFinish  --
 + *    Do final processing for the given job including updating
 + *    parents and starting new jobs as available/necessary. Note
 + *    that we pay no attention to the JOB_IGNERR flag here.
 + *    This is because when we're called because of a noexecute flag
 + *    or something, jstat.w_status is 0 and when called from
 + *    Job_CatchChildren, the status is zeroed if it s/b ignored.
 + *
 + * Input:
 + *    job             job to finish
 + *    status          sub-why job went away
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Final commands for the job are placed on postCommands.
 + *
 + *    If we got an error and are aborting (aborting == ABORT_ERROR) and
 + *    the job list is now empty, we are done for the day.
 + *    If we recognized an error (errors !=0), we set the aborting flag
 + *    to ABORT_ERROR so no more jobs will be started.
 + *-----------------------------------------------------------------------
 + */
 +/*ARGSUSED*/
 +static void
 +JobFinish (Job *job, WAIT_T status)
 +{
 +    Boolean    done, return_job_token;
 +
 +    if (DEBUG(JOB)) {
 +      fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
 +                              job->pid, job->node->name, status);
 +    }
 +
 +    if ((WIFEXITED(status) &&
 +       (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) ||
 +      WIFSIGNALED(status))
 +    {
 +      /*
 +       * If it exited non-zero and either we're doing things our
 +       * way or we're not ignoring errors, the job is finished.
 +       * Similarly, if the shell died because of a signal
 +       * the job is also finished. In these
 +       * cases, finish out the job's output before printing the exit
 +       * status...
 +       */
 +      JobClose(job);
 +      if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
 +         (void)fclose(job->cmdFILE);
 +         job->cmdFILE = NULL;
 +      }
 +      done = TRUE;
 +    } else if (WIFEXITED(status)) {
 +      /*
 +       * Deal with ignored errors in -B mode. We need to print a message
 +       * telling of the ignored error as well as setting status.w_status
 +       * to 0 so the next command gets run. To do this, we set done to be
 +       * TRUE if in -B mode and the job exited non-zero.
 +       */
 +      done = WEXITSTATUS(status) != 0;
 +      /*
 +       * Old comment said: "Note we don't
 +       * want to close down any of the streams until we know we're at the
 +       * end."
 +       * But we do. Otherwise when are we going to print the rest of the
 +       * stuff?
 +       */
 +      JobClose(job);
 +    } else {
 +      /*
 +       * No need to close things down or anything.
 +       */
 +      done = FALSE;
 +    }
 +
 +    if (done) {
 +      if (WIFEXITED(status)) {
 +          if (DEBUG(JOB)) {
 +              (void)fprintf(debug_file, "Process %d [%s] exited.\n",
 +                              job->pid, job->node->name);
 +          }
 +          if (WEXITSTATUS(status) != 0) {
 +              if (job->node != lastNode) {
 +                  MESSAGE(stdout, job->node);
 +                  lastNode = job->node;
 +              }
 +#ifdef USE_META
 +              if (useMeta) {
 +                  meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
 +              }
 +#endif
 +              (void)printf("*** [%s] Error code %d%s\n",
 +                              job->node->name,
 +                             WEXITSTATUS(status),
 +                             (job->flags & JOB_IGNERR) ? " (ignored)" : "");
 +              if (job->flags & JOB_IGNERR) {
 +                  WAIT_STATUS(status) = 0;
 +              } else {
 +                  PrintOnError(job->node, NULL);
 +              }
 +          } else if (DEBUG(JOB)) {
 +              if (job->node != lastNode) {
 +                  MESSAGE(stdout, job->node);
 +                  lastNode = job->node;
 +              }
 +              (void)printf("*** [%s] Completed successfully\n",
 +                              job->node->name);
 +          }
 +      } else {
 +          if (job->node != lastNode) {
 +              MESSAGE(stdout, job->node);
 +              lastNode = job->node;
 +          }
 +          (void)printf("*** [%s] Signal %d\n",
 +                      job->node->name, WTERMSIG(status));
 +      }
 +      (void)fflush(stdout);
 +    }
 +
 +#ifdef USE_META
 +    if (useMeta) {
 +      meta_job_finish(job);
 +    }
 +#endif
 +    
 +    return_job_token = FALSE;
 +
 +    Trace_Log(JOBEND, job);
 +    if (!(job->flags & JOB_SPECIAL)) {
 +      if ((WAIT_STATUS(status) != 0) ||
 +              (aborting == ABORT_ERROR) ||
 +              (aborting == ABORT_INTERRUPT))
 +          return_job_token = TRUE;
 +    }
 +
 +    if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) &&
 +      (WAIT_STATUS(status) == 0)) {
 +      /*
 +       * As long as we aren't aborting and the job didn't return a non-zero
 +       * status that we shouldn't ignore, we call Make_Update to update
 +       * the parents. In addition, any saved commands for the node are placed
 +       * on the .END target.
 +       */
 +      if (job->tailCmds != NULL) {
 +          Lst_ForEachFrom(job->node->commands, job->tailCmds,
 +                           JobSaveCommand,
 +                          job->node);
 +      }
 +      job->node->made = MADE;
 +      if (!(job->flags & JOB_SPECIAL))
 +          return_job_token = TRUE;
 +      Make_Update(job->node);
 +      job->job_state = JOB_ST_FREE;
 +    } else if (WAIT_STATUS(status)) {
 +      errors += 1;
 +      job->job_state = JOB_ST_FREE;
 +    }
 +
 +    /*
 +     * Set aborting if any error.
 +     */
 +    if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) {
 +      /*
 +       * If we found any errors in this batch of children and the -k flag
 +       * wasn't given, we set the aborting flag so no more jobs get
 +       * started.
 +       */
 +      aborting = ABORT_ERROR;
 +    }
 +
 +    if (return_job_token)
 +      Job_TokenReturn();
 +
 +    if (aborting == ABORT_ERROR && jobTokensRunning == 0) {
 +      /*
 +       * If we are aborting and the job table is now empty, we finish.
 +       */
 +      Finish(errors);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_Touch --
 + *    Touch the given target. Called by JobStart when the -t flag was
 + *    given
 + *
 + * Input:
 + *    gn              the node of the file to touch
 + *    silent          TRUE if should not print message
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The data modification of the file is changed. In addition, if the
 + *    file did not exist, it is created.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_Touch(GNode *gn, Boolean silent)
 +{
 +    int                 streamID;     /* ID of stream opened to do the touch */
 +    struct utimbuf times;     /* Times for utime() call */
 +
 +    if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
 +      OP_SPECIAL|OP_PHONY)) {
 +      /*
 +       * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
 +       * and, as such, shouldn't really be created.
 +       */
 +      return;
 +    }
 +
 +    if (!silent || NoExecute(gn)) {
 +      (void)fprintf(stdout, "touch %s\n", gn->name);
 +      (void)fflush(stdout);
 +    }
 +
 +    if (NoExecute(gn)) {
 +      return;
 +    }
 +
 +    if (gn->type & OP_ARCHV) {
 +      Arch_Touch(gn);
 +    } else if (gn->type & OP_LIB) {
 +      Arch_TouchLib(gn);
 +    } else {
 +      char    *file = gn->path ? gn->path : gn->name;
 +
 +      times.actime = times.modtime = now;
 +      if (utime(file, &times) < 0){
 +          streamID = open(file, O_RDWR | O_CREAT, 0666);
 +
 +          if (streamID >= 0) {
 +              char    c;
 +
 +              /*
 +               * Read and write a byte to the file to change the
 +               * modification time, then close the file.
 +               */
 +              if (read(streamID, &c, 1) == 1) {
 +                  (void)lseek(streamID, (off_t)0, SEEK_SET);
 +                  while (write(streamID, &c, 1) == -1 && errno == EAGAIN)
 +                      continue;
 +              }
 +
 +              (void)close(streamID);
 +          } else {
 +              (void)fprintf(stdout, "*** couldn't touch %s: %s",
 +                             file, strerror(errno));
 +              (void)fflush(stdout);
 +          }
 +      }
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_CheckCommands --
 + *    Make sure the given node has all the commands it needs.
 + *
 + * Input:
 + *    gn              The target whose commands need verifying
 + *    abortProc       Function to abort with message
 + *
 + * Results:
 + *    TRUE if the commands list is/was ok.
 + *
 + * Side Effects:
 + *    The node will have commands from the .DEFAULT rule added to it
 + *    if it needs them.
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
 +{
 +    if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) &&
 +      ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) {
 +      /*
 +       * No commands. Look for .DEFAULT rule from which we might infer
 +       * commands
 +       */
 +      if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) &&
 +              (gn->type & OP_SPECIAL) == 0) {
 +          char *p1;
 +          /*
 +           * Make only looks for a .DEFAULT if the node was never the
 +           * target of an operator, so that's what we do too. If
 +           * a .DEFAULT was given, we substitute its commands for gn's
 +           * commands and set the IMPSRC variable to be the target's name
 +           * The DEFAULT node acts like a transformation rule, in that
 +           * gn also inherits any attributes or sources attached to
 +           * .DEFAULT itself.
 +           */
 +          Make_HandleUse(DEFAULT, gn);
 +          Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0);
 +          if (p1)
 +              free(p1);
 +      } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) {
 +          /*
 +           * The node wasn't the target of an operator we have no .DEFAULT
 +           * rule to go on and the target doesn't already exist. There's
 +           * nothing more we can do for this branch. If the -k flag wasn't
 +           * given, we stop in our tracks, otherwise we just don't update
 +           * this node's parents so they never get examined.
 +           */
 +          static const char msg[] = ": don't know how to make";
 +
 +          if (gn->flags & FROM_DEPEND) {
 +              if (!Job_RunTarget(".STALE", gn->fname))
 +                  fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n",
 +                      progname, gn->fname, gn->lineno, makeDependfile,
 +                      gn->name);
 +              return TRUE;
 +          }
 +
 +          if (gn->type & OP_OPTIONAL) {
 +              (void)fprintf(stdout, "%s%s %s (ignored)\n", progname,
 +                  msg, gn->name);
 +              (void)fflush(stdout);
 +          } else if (keepgoing) {
 +              (void)fprintf(stdout, "%s%s %s (continuing)\n", progname,
 +                  msg, gn->name);
 +              (void)fflush(stdout);
 +              return FALSE;
 +          } else {
 +              (*abortProc)("%s%s %s. Stop", progname, msg, gn->name);
 +              return FALSE;
 +          }
 +      }
 +    }
 +    return TRUE;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobExec --
 + *    Execute the shell for the given job. Called from JobStart
 + *
 + * Input:
 + *    job             Job to execute
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    A shell is executed, outputs is altered and the Job structure added
 + *    to the job table.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobExec(Job *job, char **argv)
 +{
 +    int                 cpid;         /* ID of new child */
 +    sigset_t    mask;
 +
 +    job->flags &= ~JOB_TRACED;
 +
 +    if (DEBUG(JOB)) {
 +      int       i;
 +
 +      (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local");
 +      (void)fprintf(debug_file, "\tCommand: ");
 +      for (i = 0; argv[i] != NULL; i++) {
 +          (void)fprintf(debug_file, "%s ", argv[i]);
 +      }
 +      (void)fprintf(debug_file, "\n");
 +    }
 +
 +    /*
 +     * Some jobs produce no output and it's disconcerting to have
 +     * no feedback of their running (since they produce no output, the
 +     * banner with their name in it never appears). This is an attempt to
 +     * provide that feedback, even if nothing follows it.
 +     */
 +    if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) {
 +      MESSAGE(stdout, job->node);
 +      lastNode = job->node;
 +    }
 +
 +    /* No interruptions until this job is on the `jobs' list */
 +    JobSigLock(&mask);
 +
 +    /* Pre-emptively mark job running, pid still zero though */
 +    job->job_state = JOB_ST_RUNNING;
 +
 +    cpid = vFork();
 +    if (cpid == -1)
 +      Punt("Cannot vfork: %s", strerror(errno));
 +
 +    if (cpid == 0) {
 +      /* Child */
 +      sigset_t tmask;
 +
 +#ifdef USE_META
 +      if (useMeta) {
 +          meta_job_child(job);
 +      }
 +#endif
 +      /*
 +       * Reset all signal handlers; this is necessary because we also
 +       * need to unblock signals before we exec(2).
 +       */
 +      JobSigReset();
 +
 +      /* Now unblock signals */
 +      sigemptyset(&tmask);
 +      JobSigUnlock(&tmask);
 +
 +      /*
 +       * Must duplicate the input stream down to the child's input and
 +       * reset it to the beginning (again). Since the stream was marked
 +       * close-on-exec, we must clear that bit in the new input.
 +       */
 +      if (dup2(FILENO(job->cmdFILE), 0) == -1) {
 +          execError("dup2", "job->cmdFILE");
 +          _exit(1);
 +      }
 +      (void)fcntl(0, F_SETFD, 0);
 +      (void)lseek(0, (off_t)0, SEEK_SET);
 +
 +      if (Always_pass_job_queue ||
 +          (job->node->type & (OP_MAKE | OP_SUBMAKE))) {
 +              /*
 +               * Pass job token pipe to submakes.
 +               */
 +              fcntl(tokenWaitJob.inPipe, F_SETFD, 0);
 +              fcntl(tokenWaitJob.outPipe, F_SETFD, 0);                
 +      }
 +      
 +      /*
 +       * Set up the child's output to be routed through the pipe
 +       * we've created for it.
 +       */
 +      if (dup2(job->outPipe, 1) == -1) {
 +          execError("dup2", "job->outPipe");
 +          _exit(1);
 +      }
 +      /*
 +       * The output channels are marked close on exec. This bit was
 +       * duplicated by the dup2(on some systems), so we have to clear
 +       * it before routing the shell's error output to the same place as
 +       * its standard output.
 +       */
 +      (void)fcntl(1, F_SETFD, 0);
 +      if (dup2(1, 2) == -1) {
 +          execError("dup2", "1, 2");
 +          _exit(1);
 +      }
 +
 +      /*
 +       * We want to switch the child into a different process family so
 +       * we can kill it and all its descendants in one fell swoop,
 +       * by killing its process family, but not commit suicide.
 +       */
 +#if defined(HAVE_SETPGID)
 +      (void)setpgid(0, getpid());
 +#else
 +#if defined(HAVE_SETSID)
 +      /* XXX: dsl - I'm sure this should be setpgrp()... */
 +      (void)setsid();
 +#else
 +      (void)setpgrp(0, getpid());
 +#endif
 +#endif
 +
 +      Var_ExportVars();
 +
 +      (void)execv(shellPath, argv);
 +      execError("exec", shellPath);
 +      _exit(1);
 +    }
 +
 +    /* Parent, continuing after the child exec */
 +    job->pid = cpid;
 +
 +    Trace_Log(JOBSTART, job);
 +
 +    /*
 +     * Set the current position in the buffer to the beginning
 +     * and mark another stream to watch in the outputs mask
 +     */
 +    job->curPos = 0;
 +
 +    watchfd(job);
 +
 +    if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
 +      (void)fclose(job->cmdFILE);
 +      job->cmdFILE = NULL;
 +    }
 +
 +    /*
 +     * Now the job is actually running, add it to the table.
 +     */
 +    if (DEBUG(JOB)) {
 +      fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n",
 +              job->node->name, job->pid);
 +      job_table_dump("job started");
 +    }
 +    JobSigUnlock(&mask);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobMakeArgv --
 + *    Create the argv needed to execute the shell for a given job.
 + *
 + *
 + * Results:
 + *
 + * Side Effects:
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobMakeArgv(Job *job, char **argv)
 +{
 +    int                 argc;
 +    static char args[10];     /* For merged arguments */
 +
 +    argv[0] = UNCONST(shellName);
 +    argc = 1;
 +
 +    if ((commandShell->exit && (*commandShell->exit != '-')) ||
 +      (commandShell->echo && (*commandShell->echo != '-')))
 +    {
 +      /*
 +       * At least one of the flags doesn't have a minus before it, so
 +       * merge them together. Have to do this because the *(&(@*#*&#$#
 +       * Bourne shell thinks its second argument is a file to source.
 +       * Grrrr. Note the ten-character limitation on the combined arguments.
 +       */
 +      (void)snprintf(args, sizeof(args), "-%s%s",
 +                    ((job->flags & JOB_IGNERR) ? "" :
 +                     (commandShell->exit ? commandShell->exit : "")),
 +                    ((job->flags & JOB_SILENT) ? "" :
 +                     (commandShell->echo ? commandShell->echo : "")));
 +
 +      if (args[1]) {
 +          argv[argc] = args;
 +          argc++;
 +      }
 +    } else {
 +      if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
 +          argv[argc] = UNCONST(commandShell->exit);
 +          argc++;
 +      }
 +      if (!(job->flags & JOB_SILENT) && commandShell->echo) {
 +          argv[argc] = UNCONST(commandShell->echo);
 +          argc++;
 +      }
 +    }
 +    argv[argc] = NULL;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobStart  --
 + *    Start a target-creation process going for the target described
 + *    by the graph node gn.
 + *
 + * Input:
 + *    gn              target to create
 + *    flags           flags for the job to override normal ones.
 + *                    e.g. JOB_SPECIAL or JOB_IGNDOTS
 + *    previous        The previous Job structure for this node, if any.
 + *
 + * Results:
 + *    JOB_ERROR if there was an error in the commands, JOB_FINISHED
 + *    if there isn't actually anything left to do for the job and
 + *    JOB_RUNNING if the job has been started.
 + *
 + * Side Effects:
 + *    A new Job node is created and added to the list of running
 + *    jobs. PMake is forked and a child shell created.
 + *
 + * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set
 + *     JOB_IGNDOTS is never set (dsl)
 + *     Also the return value is ignored by everyone.
 + *-----------------------------------------------------------------------
 + */
 +static int
 +JobStart(GNode *gn, int flags)
 +{
 +    Job                 *job;       /* new job descriptor */
 +    char        *argv[10];  /* Argument vector to shell */
 +    Boolean     cmdsOK;     /* true if the nodes commands were all right */
 +    Boolean     noExec;     /* Set true if we decide not to run the job */
 +    int                 tfd;        /* File descriptor to the temp file */
 +
 +    for (job = job_table; job < job_table_end; job++) {
 +      if (job->job_state == JOB_ST_FREE)
 +          break;
 +    }
 +    if (job >= job_table_end)
 +      Punt("JobStart no job slots vacant");
 +
 +    memset(job, 0, sizeof *job);
 +    job->job_state = JOB_ST_SETUP;
 +    if (gn->type & OP_SPECIAL)
 +      flags |= JOB_SPECIAL;
 +
 +    job->node = gn;
 +    job->tailCmds = NULL;
 +
 +    /*
 +     * Set the initial value of the flags for this job based on the global
 +     * ones and the node's attributes... Any flags supplied by the caller
 +     * are also added to the field.
 +     */
 +    job->flags = 0;
 +    if (Targ_Ignore(gn)) {
 +      job->flags |= JOB_IGNERR;
 +    }
 +    if (Targ_Silent(gn)) {
 +      job->flags |= JOB_SILENT;
 +    }
 +    job->flags |= flags;
 +
 +    /*
 +     * Check the commands now so any attributes from .DEFAULT have a chance
 +     * to migrate to the node
 +     */
 +    cmdsOK = Job_CheckCommands(gn, Error);
 +
 +    job->inPollfd = NULL;
 +    /*
 +     * If the -n flag wasn't given, we open up OUR (not the child's)
 +     * temporary file to stuff commands in it. The thing is rd/wr so we don't
 +     * need to reopen it to feed it to the shell. If the -n flag *was* given,
 +     * we just set the file to be stdout. Cute, huh?
 +     */
 +    if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) ||
 +          (!noExecute && !touchFlag)) {
 +      /*
 +       * tfile is the name of a file into which all shell commands are
 +       * put. It is removed before the child shell is executed, unless
 +       * DEBUG(SCRIPT) is set.
 +       */
 +      char *tfile;
 +      sigset_t mask;
 +      /*
 +       * We're serious here, but if the commands were bogus, we're
 +       * also dead...
 +       */
 +      if (!cmdsOK) {
 +          PrintOnError(gn, NULL);     /* provide some clue */
 +          DieHorribly();
 +      }
 +
 +      JobSigLock(&mask);
 +      tfd = mkTempFile(TMPPAT, &tfile);
 +      if (!DEBUG(SCRIPT))
 +              (void)eunlink(tfile);
 +      JobSigUnlock(&mask);
 +
 +      job->cmdFILE = fdopen(tfd, "w+");
 +      if (job->cmdFILE == NULL) {
 +          Punt("Could not fdopen %s", tfile);
 +      }
 +      (void)fcntl(FILENO(job->cmdFILE), F_SETFD, 1);
 +      /*
 +       * Send the commands to the command file, flush all its buffers then
 +       * rewind and remove the thing.
 +       */
 +      noExec = FALSE;
 +
 +#ifdef USE_META
 +      if (useMeta) {
 +          meta_job_start(job, gn);
 +          if (Targ_Silent(gn)) {      /* might have changed */
 +              job->flags |= JOB_SILENT;
 +          }
 +      }
 +#endif
 +      /*
 +       * We can do all the commands at once. hooray for sanity
 +       */
 +      numCommands = 0;
 +      Lst_ForEach(gn->commands, JobPrintCommand, job);
 +
 +      /*
 +       * If we didn't print out any commands to the shell script,
 +       * there's not much point in executing the shell, is there?
 +       */
 +      if (numCommands == 0) {
 +          noExec = TRUE;
 +      }
 +
 +      free(tfile);
 +    } else if (NoExecute(gn)) {
 +      /*
 +       * Not executing anything -- just print all the commands to stdout
 +       * in one fell swoop. This will still set up job->tailCmds correctly.
 +       */
 +      if (lastNode != gn) {
 +          MESSAGE(stdout, gn);
 +          lastNode = gn;
 +      }
 +      job->cmdFILE = stdout;
 +      /*
 +       * Only print the commands if they're ok, but don't die if they're
 +       * not -- just let the user know they're bad and keep going. It
 +       * doesn't do any harm in this case and may do some good.
 +       */
 +      if (cmdsOK) {
 +          Lst_ForEach(gn->commands, JobPrintCommand, job);
 +      }
 +      /*
 +       * Don't execute the shell, thank you.
 +       */
 +      noExec = TRUE;
 +    } else {
 +      /*
 +       * Just touch the target and note that no shell should be executed.
 +       * Set cmdFILE to stdout to make life easier. Check the commands, too,
 +       * but don't die if they're no good -- it does no harm to keep working
 +       * up the graph.
 +       */
 +      job->cmdFILE = stdout;
 +      Job_Touch(gn, job->flags&JOB_SILENT);
 +      noExec = TRUE;
 +    }
 +    /* Just in case it isn't already... */
 +    (void)fflush(job->cmdFILE);
 +
 +    /*
 +     * If we're not supposed to execute a shell, don't.
 +     */
 +    if (noExec) {
 +      if (!(job->flags & JOB_SPECIAL))
 +          Job_TokenReturn();
 +      /*
 +       * Unlink and close the command file if we opened one
 +       */
 +      if (job->cmdFILE != stdout) {
 +          if (job->cmdFILE != NULL) {
 +              (void)fclose(job->cmdFILE);
 +              job->cmdFILE = NULL;
 +          }
 +      }
 +
 +      /*
 +       * We only want to work our way up the graph if we aren't here because
 +       * the commands for the job were no good.
 +       */
 +      if (cmdsOK && aborting == 0) {
 +          if (job->tailCmds != NULL) {
 +              Lst_ForEachFrom(job->node->commands, job->tailCmds,
 +                              JobSaveCommand,
 +                             job->node);
 +          }
 +          job->node->made = MADE;
 +          Make_Update(job->node);
 +      }
 +      job->job_state = JOB_ST_FREE;
 +      return cmdsOK ? JOB_FINISHED : JOB_ERROR;
 +    }
 +
 +    /*
 +     * Set up the control arguments to the shell. This is based on the flags
 +     * set earlier for this job.
 +     */
 +    JobMakeArgv(job, argv);
 +
 +    /* Create the pipe by which we'll get the shell's output.  */
 +    JobCreatePipe(job, 3);
 +
 +    JobExec(job, argv);
 +    return(JOB_RUNNING);
 +}
 +
 +static char *
 +JobOutput(Job *job, char *cp, char *endp, int msg)
 +{
 +    char *ecp;
 +
 +    if (commandShell->noPrint) {
 +      ecp = Str_FindSubstring(cp, commandShell->noPrint);
 +      while (ecp != NULL) {
 +          if (cp != ecp) {
 +              *ecp = '\0';
 +              if (!beSilent && msg && job->node != lastNode) {
 +                  MESSAGE(stdout, job->node);
 +                  lastNode = job->node;
 +              }
 +              /*
 +               * The only way there wouldn't be a newline after
 +               * this line is if it were the last in the buffer.
 +               * however, since the non-printable comes after it,
 +               * there must be a newline, so we don't print one.
 +               */
 +              (void)fprintf(stdout, "%s", cp);
 +              (void)fflush(stdout);
 +          }
 +          cp = ecp + commandShell->noPLen;
 +          if (cp != endp) {
 +              /*
 +               * Still more to print, look again after skipping
 +               * the whitespace following the non-printable
 +               * command....
 +               */
 +              cp++;
 +              while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
 +                  cp++;
 +              }
 +              ecp = Str_FindSubstring(cp, commandShell->noPrint);
 +          } else {
 +              return cp;
 +          }
 +      }
 +    }
 +    return cp;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobDoOutput  --
 + *    This function is called at different times depending on
 + *    whether the user has specified that output is to be collected
 + *    via pipes or temporary files. In the former case, we are called
 + *    whenever there is something to read on the pipe. We collect more
 + *    output from the given job and store it in the job's outBuf. If
 + *    this makes up a line, we print it tagged by the job's identifier,
 + *    as necessary.
 + *    If output has been collected in a temporary file, we open the
 + *    file and read it line by line, transfering it to our own
 + *    output channel until the file is empty. At which point we
 + *    remove the temporary file.
 + *    In both cases, however, we keep our figurative eye out for the
 + *    'noPrint' line for the shell from which the output came. If
 + *    we recognize a line, we don't print it. If the command is not
 + *    alone on the line (the character after it is not \0 or \n), we
 + *    do print whatever follows it.
 + *
 + * Input:
 + *    job             the job whose output needs printing
 + *    finish          TRUE if this is the last time we'll be called
 + *                    for this job
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    curPos may be shifted as may the contents of outBuf.
 + *-----------------------------------------------------------------------
 + */
 +STATIC void
 +JobDoOutput(Job *job, Boolean finish)
 +{
 +    Boolean       gotNL = FALSE;  /* true if got a newline */
 +    Boolean       fbuf;         /* true if our buffer filled up */
 +    int                 nr;             /* number of bytes read */
 +    int                 i;              /* auxiliary index into outBuf */
 +    int                 max;            /* limit for i (end of current data) */
 +    int                 nRead;          /* (Temporary) number of bytes read */
 +
 +    /*
 +     * Read as many bytes as will fit in the buffer.
 +     */
 +end_loop:
 +    gotNL = FALSE;
 +    fbuf = FALSE;
 +
 +    nRead = read(job->inPipe, &job->outBuf[job->curPos],
 +                   JOB_BUFSIZE - job->curPos);
 +    if (nRead < 0) {
 +      if (errno == EAGAIN)
 +          return;
 +      if (DEBUG(JOB)) {
 +          perror("JobDoOutput(piperead)");
 +      }
 +      nr = 0;
 +    } else {
 +      nr = nRead;
 +    }
 +
 +    /*
 +     * If we hit the end-of-file (the job is dead), we must flush its
 +     * remaining output, so pretend we read a newline if there's any
 +     * output remaining in the buffer.
 +     * Also clear the 'finish' flag so we stop looping.
 +     */
 +    if ((nr == 0) && (job->curPos != 0)) {
 +      job->outBuf[job->curPos] = '\n';
 +      nr = 1;
 +      finish = FALSE;
 +    } else if (nr == 0) {
 +      finish = FALSE;
 +    }
 +
 +    /*
 +     * Look for the last newline in the bytes we just got. If there is
 +     * one, break out of the loop with 'i' as its index and gotNL set
 +     * TRUE.
 +     */
 +    max = job->curPos + nr;
 +    for (i = job->curPos + nr - 1; i >= job->curPos; i--) {
 +      if (job->outBuf[i] == '\n') {
 +          gotNL = TRUE;
 +          break;
 +      } else if (job->outBuf[i] == '\0') {
 +          /*
 +           * Why?
 +           */
 +          job->outBuf[i] = ' ';
 +      }
 +    }
 +
 +    if (!gotNL) {
 +      job->curPos += nr;
 +      if (job->curPos == JOB_BUFSIZE) {
 +          /*
 +           * If we've run out of buffer space, we have no choice
 +           * but to print the stuff. sigh.
 +           */
 +          fbuf = TRUE;
 +          i = job->curPos;
 +      }
 +    }
 +    if (gotNL || fbuf) {
 +      /*
 +       * Need to send the output to the screen. Null terminate it
 +       * first, overwriting the newline character if there was one.
 +       * So long as the line isn't one we should filter (according
 +       * to the shell description), we print the line, preceded
 +       * by a target banner if this target isn't the same as the
 +       * one for which we last printed something.
 +       * The rest of the data in the buffer are then shifted down
 +       * to the start of the buffer and curPos is set accordingly.
 +       */
 +      job->outBuf[i] = '\0';
 +      if (i >= job->curPos) {
 +          char *cp;
 +
 +          cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
 +
 +          /*
 +           * There's still more in that thar buffer. This time, though,
 +           * we know there's no newline at the end, so we add one of
 +           * our own free will.
 +           */
 +          if (*cp != '\0') {
 +              if (!beSilent && job->node != lastNode) {
 +                  MESSAGE(stdout, job->node);
 +                  lastNode = job->node;
 +              }
 +#ifdef USE_META
 +              if (useMeta) {
 +                  meta_job_output(job, cp, gotNL ? "\n" : "");
 +              }
 +#endif
 +              (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
 +              (void)fflush(stdout);
 +          }
 +      }
 +      /*
 +       * max is the last offset still in the buffer. Move any remaining
 +       * characters to the start of the buffer and update the end marker
 +       * curPos.
 +       */
 +      if (i < max) {
 +          (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1));
 +          job->curPos = max - (i + 1);
 +      } else {
 +          assert(i == max);
 +          job->curPos = 0;
 +      }
 +    }
 +    if (finish) {
 +      /*
 +       * If the finish flag is true, we must loop until we hit
 +       * end-of-file on the pipe. This is guaranteed to happen
 +       * eventually since the other end of the pipe is now closed
 +       * (we closed it explicitly and the child has exited). When
 +       * we do get an EOF, finish will be set FALSE and we'll fall
 +       * through and out.
 +       */
 +      goto end_loop;
 +    }
 +}
 +
 +static void
 +JobRun(GNode *targ)
 +{
 +#ifdef notyet
 +    /*
 +     * Unfortunately it is too complicated to run .BEGIN, .END,
 +     * and .INTERRUPT job in the parallel job module. This has
 +     * the nice side effect that it avoids a lot of other problems.
 +     */
 +    Lst lst = Lst_Init(FALSE);
 +    Lst_AtEnd(lst, targ);
 +    (void)Make_Run(lst);
 +    Lst_Destroy(lst, NULL);
 +    JobStart(targ, JOB_SPECIAL);
 +    while (jobTokensRunning) {
 +      Job_CatchOutput();
 +    }
 +#else
 +    Compat_Make(targ, targ);
 +    if (targ->made == ERROR) {
 +      PrintOnError(targ, "\n\nStop.");
 +      exit(1);
 +    }
 +#endif
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_CatchChildren --
 + *    Handle the exit of a child. Called from Make_Make.
 + *
 + * Input:
 + *    block           TRUE if should block on the wait
 + *
 + * Results:
 + *    none.
 + *
 + * Side Effects:
 + *    The job descriptor is removed from the list of children.
 + *
 + * Notes:
 + *    We do waits, blocking or not, according to the wisdom of our
 + *    caller, until there are no more children to report. For each
 + *    job, call JobFinish to finish things off.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +void
 +Job_CatchChildren(void)
 +{
 +    int         pid;          /* pid of dead child */
 +    WAIT_T      status;       /* Exit/termination status */
 +
 +    /*
 +     * Don't even bother if we know there's no one around.
 +     */
 +    if (jobTokensRunning == 0)
 +      return;
 +
 +    while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) {
 +      if (DEBUG(JOB)) {
 +          (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid,
 +            WAIT_STATUS(status));
 +      }
 +      JobReapChild(pid, status, TRUE);
 +    }
 +}
 +
 +/*
 + * It is possible that wait[pid]() was called from elsewhere,
 + * this lets us reap jobs regardless.
 + */
 +void
 +JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs)
 +{
 +    Job                 *job;         /* job descriptor for dead child */
 +
 +    /*
 +     * Don't even bother if we know there's no one around.
 +     */
 +    if (jobTokensRunning == 0)
 +      return;
 +
 +    job = JobFindPid(pid, JOB_ST_RUNNING, isJobs);
 +    if (job == NULL) {
 +      if (isJobs) {
 +          if (!lurking_children)
 +              Error("Child (%d) status %x not in table?", pid, status);
 +      }
 +      return;                         /* not ours */
 +    }
 +    if (WIFSTOPPED(status)) {
 +      if (DEBUG(JOB)) {
 +          (void)fprintf(debug_file, "Process %d (%s) stopped.\n",
 +                        job->pid, job->node->name);
 +      }
 +      if (!make_suspended) {
 +          switch (WSTOPSIG(status)) {
 +          case SIGTSTP:
 +              (void)printf("*** [%s] Suspended\n", job->node->name);
 +              break;
 +          case SIGSTOP:
 +              (void)printf("*** [%s] Stopped\n", job->node->name);
 +              break;
 +          default:
 +              (void)printf("*** [%s] Stopped -- signal %d\n",
 +                           job->node->name, WSTOPSIG(status));
 +          }
 +          job->job_suspended = 1;
 +      }
 +      (void)fflush(stdout);
 +      return;
 +    }
 +
 +    job->job_state = JOB_ST_FINISHED;
 +    job->exit_status = WAIT_STATUS(status);
 +
 +    JobFinish(job, status);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_CatchOutput --
 + *    Catch the output from our children, if we're using
 + *    pipes do so. Otherwise just block time until we get a
 + *    signal(most likely a SIGCHLD) since there's no point in
 + *    just spinning when there's nothing to do and the reaping
 + *    of a child can wait for a while.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Output is read from pipes if we're piping.
 + * -----------------------------------------------------------------------
 + */
 +void
 +Job_CatchOutput(void)
 +{
 +    int nready;
 +    Job *job;
 +    int i;
 +
 +    (void)fflush(stdout);
 +
 +    /* The first fd in the list is the job token pipe */
 +    do {
 +      nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC);
 +    } while (nready < 0 && errno == EINTR);
 +
 +    if (nready < 0)
 +      Punt("poll: %s", strerror(errno));
 +
 +    if (nready > 0 && readyfd(&childExitJob)) {
 +      char token = 0;
 +      ssize_t count;
 +      count = read(childExitJob.inPipe, &token, 1);
 +      switch (count) {
 +      case 0:
 +          Punt("unexpected eof on token pipe");
 +      case -1:
 +          Punt("token pipe read: %s", strerror(errno));
 +      case 1:
 +          if (token == DO_JOB_RESUME[0])
 +              /* Complete relay requested from our SIGCONT handler */
 +              JobRestartJobs();
 +          break;
 +      default:
 +          abort();
 +      }
 +      --nready;
 +    }
 +
 +    Job_CatchChildren();
 +    if (nready == 0)
 +          return;
 +
 +    for (i = 2; i < nfds; i++) {
 +      if (!fds[i].revents)
 +          continue;
 +      job = jobfds[i];
 +      if (job->job_state == JOB_ST_RUNNING)
 +          JobDoOutput(job, FALSE);
 +      if (--nready == 0)
 +              return;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_Make --
 + *    Start the creation of a target. Basically a front-end for
 + *    JobStart used by the Make module.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Another job is started.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_Make(GNode *gn)
 +{
 +    (void)JobStart(gn, 0);
 +}
 +
 +void
 +Shell_Init(void)
 +{
 +    if (shellPath == NULL) {
 +      /*
 +       * We are using the default shell, which may be an absolute
 +       * path if DEFSHELL_CUSTOM is defined.
 +       */
 +      shellName = commandShell->name;
 +#ifdef DEFSHELL_CUSTOM
 +      if (*shellName == '/') {
 +          shellPath = shellName;
 +          shellName = strrchr(shellPath, '/');
 +          shellName++;
 +      } else
 +#endif
 +      shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
 +    }
 +    if (commandShell->exit == NULL) {
 +      commandShell->exit = "";
 +    }
 +    if (commandShell->echo == NULL) {
 +      commandShell->echo = "";
 +    }
 +    if (commandShell->hasErrCtl && *commandShell->exit) {
 +      if (shellErrFlag &&
 +          strcmp(commandShell->exit, &shellErrFlag[1]) != 0) {
 +          free(shellErrFlag);
 +          shellErrFlag = NULL;
 +      }
 +      if (!shellErrFlag) {
 +          int n = strlen(commandShell->exit) + 2;
 +
 +          shellErrFlag = bmake_malloc(n);
 +          if (shellErrFlag) {
 +              snprintf(shellErrFlag, n, "-%s", commandShell->exit);
 +          }
 +      }
 +    } else if (shellErrFlag) {
 +      free(shellErrFlag);
 +      shellErrFlag = NULL;
 +    }
 +}
 +
 +/*-
 + * Returns the string literal that is used in the current command shell
 + * to produce a newline character.
 + */
 +const char *
 +Shell_GetNewline(void)
 +{
 +
 +    return commandShell->newline;
 +}
 +
 +void
 +Job_SetPrefix(void)
 +{
 +    
 +    if (targPrefix) {
 +      free(targPrefix);
 +    } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
 +      Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0);
 +    }
 +
-     targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}", VAR_GLOBAL, 0);
++    targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}",
++                         VAR_GLOBAL, FALSE, TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_Init --
 + *    Initialize the process module
 + *
 + * Input:
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    lists and counters are initialized
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_Init(void)
 +{
 +    Job_SetPrefix();
 +    /* Allocate space for all the job info */
 +    job_table = bmake_malloc(maxJobs * sizeof *job_table);
 +    memset(job_table, 0, maxJobs * sizeof *job_table);
 +    job_table_end = job_table + maxJobs;
 +    wantToken =       0;
 +
 +    aborting =          0;
 +    errors =    0;
 +
 +    lastNode =          NULL;
 +
 +    Always_pass_job_queue = getBoolean(MAKE_ALWAYS_PASS_JOB_QUEUE,
 +                                     Always_pass_job_queue);
 +
 +    Job_error_token = getBoolean(MAKE_JOB_ERROR_TOKEN, Job_error_token);
 +
 +
 +    /*
 +     * There is a non-zero chance that we already have children.
 +     * eg after 'make -f- <<EOF'
 +     * Since their termination causes a 'Child (pid) not in table' message,
 +     * Collect the status of any that are already dead, and suppress the
 +     * error message if there are any undead ones.
 +     */
 +    for (;;) {
 +      int rval, status;
 +      rval = waitpid((pid_t) -1, &status, WNOHANG);
 +      if (rval > 0)
 +          continue;
 +      if (rval == 0)
 +          lurking_children = 1;
 +      break;
 +    }
 +
 +    Shell_Init();
 +
 +    JobCreatePipe(&childExitJob, 3);
 +
 +    /* We can only need to wait for tokens, children and output from each job */
 +    fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs));
 +    jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs));
 +
 +    /* These are permanent entries and take slots 0 and 1 */
 +    watchfd(&tokenWaitJob);
 +    watchfd(&childExitJob);
 +
 +    sigemptyset(&caught_signals);
 +    /*
 +     * Install a SIGCHLD handler.
 +     */
 +    (void)bmake_signal(SIGCHLD, JobChildSig);
 +    sigaddset(&caught_signals, SIGCHLD);
 +
 +#define ADDSIG(s,h)                           \
 +    if (bmake_signal(s, SIG_IGN) != SIG_IGN) {        \
 +      sigaddset(&caught_signals, s);          \
 +      (void)bmake_signal(s, h);                       \
 +    }
 +
 +    /*
 +     * Catch the four signals that POSIX specifies if they aren't ignored.
 +     * JobPassSig will take care of calling JobInterrupt if appropriate.
 +     */
 +    ADDSIG(SIGINT, JobPassSig_int)
 +    ADDSIG(SIGHUP, JobPassSig_term)
 +    ADDSIG(SIGTERM, JobPassSig_term)
 +    ADDSIG(SIGQUIT, JobPassSig_term)
 +
 +    /*
 +     * There are additional signals that need to be caught and passed if
 +     * either the export system wants to be told directly of signals or if
 +     * we're giving each job its own process group (since then it won't get
 +     * signals from the terminal driver as we own the terminal)
 +     */
 +    ADDSIG(SIGTSTP, JobPassSig_suspend)
 +    ADDSIG(SIGTTOU, JobPassSig_suspend)
 +    ADDSIG(SIGTTIN, JobPassSig_suspend)
 +    ADDSIG(SIGWINCH, JobCondPassSig)
 +    ADDSIG(SIGCONT, JobContinueSig)
 +#undef ADDSIG
 +
 +    (void)Job_RunTarget(".BEGIN", NULL);
 +    postCommands = Targ_FindNode(".END", TARG_CREATE);
 +}
 +
 +static void JobSigReset(void)
 +{
 +#define DELSIG(s)                                     \
 +    if (sigismember(&caught_signals, s)) {            \
 +      (void)bmake_signal(s, SIG_DFL);                 \
 +    }
 +
 +    DELSIG(SIGINT)
 +    DELSIG(SIGHUP)
 +    DELSIG(SIGQUIT)
 +    DELSIG(SIGTERM)
 +    DELSIG(SIGTSTP)
 +    DELSIG(SIGTTOU)
 +    DELSIG(SIGTTIN)
 +    DELSIG(SIGWINCH)
 +    DELSIG(SIGCONT)
 +#undef DELSIG
 +    (void)bmake_signal(SIGCHLD, SIG_DFL);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobMatchShell --
 + *    Find a shell in 'shells' given its name.
 + *
 + * Results:
 + *    A pointer to the Shell structure.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Shell *
 +JobMatchShell(const char *name)
 +{
 +    Shell     *sh;
 +
 +    for (sh = shells; sh->name != NULL; sh++) {
 +      if (strcmp(name, sh->name) == 0)
 +              return (sh);
 +    }
 +    return NULL;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_ParseShell --
 + *    Parse a shell specification and set up commandShell, shellPath
 + *    and shellName appropriately.
 + *
 + * Input:
 + *    line            The shell spec
 + *
 + * Results:
 + *    FAILURE if the specification was incorrect.
 + *
 + * Side Effects:
 + *    commandShell points to a Shell structure (either predefined or
 + *    created from the shell spec), shellPath is the full path of the
 + *    shell described by commandShell, while shellName is just the
 + *    final component of shellPath.
 + *
 + * Notes:
 + *    A shell specification consists of a .SHELL target, with dependency
 + *    operator, followed by a series of blank-separated words. Double
 + *    quotes can be used to use blanks in words. A backslash escapes
 + *    anything (most notably a double-quote and a space) and
 + *    provides the functionality it does in C. Each word consists of
 + *    keyword and value separated by an equal sign. There should be no
 + *    unnecessary spaces in the word. The keywords are as follows:
 + *        name            Name of shell.
 + *        path            Location of shell.
 + *        quiet           Command to turn off echoing.
 + *        echo            Command to turn echoing on
 + *        filter          Result of turning off echoing that shouldn't be
 + *                        printed.
 + *        echoFlag        Flag to turn echoing on at the start
 + *        errFlag         Flag to turn error checking on at the start
 + *        hasErrCtl       True if shell has error checking control
 + *        newline         String literal to represent a newline char
 + *        check           Command to turn on error checking if hasErrCtl
 + *                        is TRUE or template of command to echo a command
 + *                        for which error checking is off if hasErrCtl is
 + *                        FALSE.
 + *        ignore          Command to turn off error checking if hasErrCtl
 + *                        is TRUE or template of command to execute a
 + *                        command so as to ignore any errors it returns if
 + *                        hasErrCtl is FALSE.
 + *
 + *-----------------------------------------------------------------------
 + */
 +ReturnStatus
 +Job_ParseShell(char *line)
 +{
 +    char      **words;
 +    char      **argv;
 +    int               argc;
 +    char      *path;
 +    Shell     newShell;
 +    Boolean   fullSpec = FALSE;
 +    Shell     *sh;
 +
 +    while (isspace((unsigned char)*line)) {
 +      line++;
 +    }
 +
 +    if (shellArgv)
 +      free(UNCONST(shellArgv));
 +
 +    memset(&newShell, 0, sizeof(newShell));
 +
 +    /*
 +     * Parse the specification by keyword
 +     */
 +    words = brk_string(line, &argc, TRUE, &path);
 +    if (words == NULL) {
 +      Error("Unterminated quoted string [%s]", line);
 +      return FAILURE;
 +    }
 +    shellArgv = path;
 +
 +    for (path = NULL, argv = words; argc != 0; argc--, argv++) {
 +          if (strncmp(*argv, "path=", 5) == 0) {
 +              path = &argv[0][5];
 +          } else if (strncmp(*argv, "name=", 5) == 0) {
 +              newShell.name = &argv[0][5];
 +          } else {
 +              if (strncmp(*argv, "quiet=", 6) == 0) {
 +                  newShell.echoOff = &argv[0][6];
 +              } else if (strncmp(*argv, "echo=", 5) == 0) {
 +                  newShell.echoOn = &argv[0][5];
 +              } else if (strncmp(*argv, "filter=", 7) == 0) {
 +                  newShell.noPrint = &argv[0][7];
 +                  newShell.noPLen = strlen(newShell.noPrint);
 +              } else if (strncmp(*argv, "echoFlag=", 9) == 0) {
 +                  newShell.echo = &argv[0][9];
 +              } else if (strncmp(*argv, "errFlag=", 8) == 0) {
 +                  newShell.exit = &argv[0][8];
 +              } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) {
 +                  char c = argv[0][10];
 +                  newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
 +                                         (c != 'T') && (c != 't'));
 +              } else if (strncmp(*argv, "newline=", 8) == 0) {
 +                  newShell.newline = &argv[0][8];
 +              } else if (strncmp(*argv, "check=", 6) == 0) {
 +                  newShell.errCheck = &argv[0][6];
 +              } else if (strncmp(*argv, "ignore=", 7) == 0) {
 +                  newShell.ignErr = &argv[0][7];
 +              } else if (strncmp(*argv, "errout=", 7) == 0) {
 +                  newShell.errOut = &argv[0][7];
 +              } else if (strncmp(*argv, "comment=", 8) == 0) {
 +                  newShell.commentChar = argv[0][8];
 +              } else {
 +                  Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"",
 +                              *argv);
 +                  free(words);
 +                  return(FAILURE);
 +              }
 +              fullSpec = TRUE;
 +          }
 +    }
 +
 +    if (path == NULL) {
 +      /*
 +       * If no path was given, the user wants one of the pre-defined shells,
 +       * yes? So we find the one s/he wants with the help of JobMatchShell
 +       * and set things up the right way. shellPath will be set up by
 +       * Shell_Init.
 +       */
 +      if (newShell.name == NULL) {
 +          Parse_Error(PARSE_FATAL, "Neither path nor name specified");
 +          free(words);
 +          return(FAILURE);
 +      } else {
 +          if ((sh = JobMatchShell(newShell.name)) == NULL) {
 +                  Parse_Error(PARSE_WARNING, "%s: No matching shell",
 +                              newShell.name);
 +                  free(words);
 +                  return(FAILURE);
 +          }
 +          commandShell = sh;
 +          shellName = newShell.name;
 +          if (shellPath) {
 +              /* Shell_Init has already been called!  Do it again. */
 +              free(UNCONST(shellPath));
 +              shellPath = NULL;
 +              Shell_Init();
 +          }
 +      }
 +    } else {
 +      /*
 +       * The user provided a path. If s/he gave nothing else (fullSpec is
 +       * FALSE), try and find a matching shell in the ones we know of.
 +       * Else we just take the specification at its word and copy it
 +       * to a new location. In either case, we need to record the
 +       * path the user gave for the shell.
 +       */
 +      shellPath = path;
 +      path = strrchr(path, '/');
 +      if (path == NULL) {
 +          path = UNCONST(shellPath);
 +      } else {
 +          path += 1;
 +      }
 +      if (newShell.name != NULL) {
 +          shellName = newShell.name;
 +      } else {
 +          shellName = path;
 +      }
 +      if (!fullSpec) {
 +          if ((sh = JobMatchShell(shellName)) == NULL) {
 +                  Parse_Error(PARSE_WARNING, "%s: No matching shell",
 +                              shellName);
 +                  free(words);
 +                  return(FAILURE);
 +          }
 +          commandShell = sh;
 +      } else {
 +          commandShell = bmake_malloc(sizeof(Shell));
 +          *commandShell = newShell;
 +      }
 +      /* this will take care of shellErrFlag */
 +      Shell_Init();
 +    }
 +
 +    if (commandShell->echoOn && commandShell->echoOff) {
 +      commandShell->hasEchoCtl = TRUE;
 +    }
 +
 +    if (!commandShell->hasErrCtl) {
 +      if (commandShell->errCheck == NULL) {
 +          commandShell->errCheck = "";
 +      }
 +      if (commandShell->ignErr == NULL) {
 +          commandShell->ignErr = "%s\n";
 +      }
 +    }
 +
 +    /*
 +     * Do not free up the words themselves, since they might be in use by the
 +     * shell specification.
 +     */
 +    free(words);
 +    return SUCCESS;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobInterrupt --
 + *    Handle the receipt of an interrupt.
 + *
 + * Input:
 + *    runINTERRUPT    Non-zero if commands for the .INTERRUPT target
 + *                    should be executed
 + *    signo           signal received
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    All children are killed. Another job will be started if the
 + *    .INTERRUPT target was given.
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobInterrupt(int runINTERRUPT, int signo)
 +{
 +    Job               *job;           /* job descriptor in that element */
 +    GNode     *interrupt;     /* the node describing the .INTERRUPT target */
 +    sigset_t  mask;
 +    GNode     *gn;
 +
 +    aborting = ABORT_INTERRUPT;
 +
 +    JobSigLock(&mask);
 +
 +    for (job = job_table; job < job_table_end; job++) {
 +      if (job->job_state != JOB_ST_RUNNING)
 +          continue;
 +
 +      gn = job->node;
 +
 +      if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) {
 +          char *file = (gn->path == NULL ? gn->name : gn->path);
 +          if (!noExecute && eunlink(file) != -1) {
 +              Error("*** %s removed", file);
 +          }
 +      }
 +      if (job->pid) {
 +          if (DEBUG(JOB)) {
 +              (void)fprintf(debug_file,
 +                         "JobInterrupt passing signal %d to child %d.\n",
 +                         signo, job->pid);
 +          }
 +          KILLPG(job->pid, signo);
 +      }
 +    }
 +
 +    JobSigUnlock(&mask);
 +
 +    if (runINTERRUPT && !touchFlag) {
 +      interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
 +      if (interrupt != NULL) {
 +          ignoreErrors = FALSE;
 +          JobRun(interrupt);
 +      }
 +    }
 +    Trace_Log(MAKEINTR, 0);
 +    exit(signo);
 +}
 +
 +/*
 + *-----------------------------------------------------------------------
 + * Job_Finish --
 + *    Do final processing such as the running of the commands
 + *    attached to the .END target.
 + *
 + * Results:
 + *    Number of errors reported.
 + *
 + * Side Effects:
 + *    None.
 + *-----------------------------------------------------------------------
 + */
 +int
 +Job_Finish(void)
 +{
 +    if (postCommands != NULL &&
 +      (!Lst_IsEmpty(postCommands->commands) ||
 +       !Lst_IsEmpty(postCommands->children))) {
 +      if (errors) {
 +          Error("Errors reported so .END ignored");
 +      } else {
 +          JobRun(postCommands);
 +      }
 +    }
 +    return(errors);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_End --
 + *    Cleanup any memory used by the jobs module
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Memory is freed
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_End(void)
 +{
 +#ifdef CLEANUP
 +    if (shellArgv)
 +      free(shellArgv);
 +#endif
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_Wait --
 + *    Waits for all running jobs to finish and returns. Sets 'aborting'
 + *    to ABORT_WAIT to prevent other jobs from starting.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Currently running jobs finish.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_Wait(void)
 +{
 +    aborting = ABORT_WAIT;
 +    while (jobTokensRunning != 0) {
 +      Job_CatchOutput();
 +    }
 +    aborting = 0;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_AbortAll --
 + *    Abort all currently running jobs without handling output or anything.
 + *    This function is to be called only in the event of a major
 + *    error. Most definitely NOT to be called from JobInterrupt.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    All children are killed, not just the firstborn
 + *-----------------------------------------------------------------------
 + */
 +void
 +Job_AbortAll(void)
 +{
 +    Job               *job;   /* the job descriptor in that element */
 +    WAIT_T    foo;
 +
 +    aborting = ABORT_ERROR;
 +
 +    if (jobTokensRunning) {
 +      for (job = job_table; job < job_table_end; job++) {
 +          if (job->job_state != JOB_ST_RUNNING)
 +              continue;
 +          /*
 +           * kill the child process with increasingly drastic signals to make
 +           * darn sure it's dead.
 +           */
 +          KILLPG(job->pid, SIGINT);
 +          KILLPG(job->pid, SIGKILL);
 +      }
 +    }
 +
 +    /*
 +     * Catch as many children as want to report in at first, then give up
 +     */
 +    while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
 +      continue;
 +}
 +
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * JobRestartJobs --
 + *    Tries to restart stopped jobs if there are slots available.
 + *    Called in process context in response to a SIGCONT.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Resumes jobs.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +JobRestartJobs(void)
 +{
 +    Job *job;
 +
 +    for (job = job_table; job < job_table_end; job++) {
 +      if (job->job_state == JOB_ST_RUNNING &&
 +              (make_suspended || job->job_suspended)) {
 +          if (DEBUG(JOB)) {
 +              (void)fprintf(debug_file, "Restarting stopped job pid %d.\n",
 +                      job->pid);
 +          }
 +          if (job->job_suspended) {
 +                  (void)printf("*** [%s] Continued\n", job->node->name);
 +                  (void)fflush(stdout);
 +          }
 +          job->job_suspended = 0;
 +          if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
 +              fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid);
 +          }
 +      }
 +      if (job->job_state == JOB_ST_FINISHED)
 +          /* Job exit deferred after calling waitpid() in a signal handler */
 +          JobFinish(job, job->exit_status);
 +    }
 +    make_suspended = 0;
 +}
 +
 +static void
 +watchfd(Job *job)
 +{
 +    if (job->inPollfd != NULL)
 +      Punt("Watching watched job");
 +
 +    fds[nfds].fd = job->inPipe;
 +    fds[nfds].events = POLLIN;
 +    jobfds[nfds] = job;
 +    job->inPollfd = &fds[nfds];
 +    nfds++;
 +}
 +
 +static void
 +clearfd(Job *job)
 +{
 +    int i;
 +    if (job->inPollfd == NULL)
 +      Punt("Unwatching unwatched job");
 +    i = job->inPollfd - fds;
 +    nfds--;
 +    /*
 +     * Move last job in table into hole made by dead job.
 +     */
 +    if (nfds != i) {
 +      fds[i] = fds[nfds];
 +      jobfds[i] = jobfds[nfds];
 +      jobfds[i]->inPollfd = &fds[i];
 +    }
 +    job->inPollfd = NULL;
 +}
 +
 +static int
 +readyfd(Job *job)
 +{
 +    if (job->inPollfd == NULL)
 +      Punt("Polling unwatched job");
 +    return (job->inPollfd->revents & POLLIN) != 0;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * JobTokenAdd --
 + *    Put a token into the job pipe so that some make process can start
 + *    another job.
 + *
 + * Side Effects:
 + *    Allows more build jobs to be spawned somewhere.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +static void
 +JobTokenAdd(void)
 +{
 +    char tok = JOB_TOKENS[aborting], tok1;
 +
 +    if (!Job_error_token && aborting == ABORT_ERROR) {
 +      if (jobTokensRunning == 0)
 +          return;
 +      tok = '+';                      /* no error token */
 +    }
 +
 +    /* If we are depositing an error token flush everything else */
 +    while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
 +      continue;
 +
 +    if (DEBUG(JOB))
 +      fprintf(debug_file, "(%d) aborting %d, deposit token %c\n",
 +          getpid(), aborting, tok);
 +    while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
 +      continue;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_ServerStartTokenAdd --
 + *    Prep the job token pipe in the root make process.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +void
 +Job_ServerStart(int max_tokens, int jp_0, int jp_1)
 +{
 +    int i;
 +    char jobarg[64];
 +    
 +    if (jp_0 >= 0 && jp_1 >= 0) {
 +      /* Pipe passed in from parent */
 +      tokenWaitJob.inPipe = jp_0;
 +      tokenWaitJob.outPipe = jp_1;
 +      (void)fcntl(jp_0, F_SETFD, 1);
 +      (void)fcntl(jp_1, F_SETFD, 1);
 +      return;
 +    }
 +
 +    JobCreatePipe(&tokenWaitJob, 15);
 +
 +    snprintf(jobarg, sizeof(jobarg), "%d,%d",
 +          tokenWaitJob.inPipe, tokenWaitJob.outPipe);
 +
 +    Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
 +    Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);                        
 +
 +    /*
 +     * Preload the job pipe with one token per job, save the one
 +     * "extra" token for the primary job.
 +     * 
 +     * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
 +     * larger than the write buffer size of the pipe, we will
 +     * deadlock here.
 +     */
 +    for (i = 1; i < max_tokens; i++)
 +      JobTokenAdd();
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_TokenReturn --
 + *    Return a withdrawn token to the pool.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +void
 +Job_TokenReturn(void)
 +{
 +    jobTokensRunning--;
 +    if (jobTokensRunning < 0)
 +      Punt("token botch");
 +    if (jobTokensRunning || JOB_TOKENS[aborting] != '+')
 +      JobTokenAdd();
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_TokenWithdraw --
 + *    Attempt to withdraw a token from the pool.
 + *
 + * Results:
 + *    Returns TRUE if a token was withdrawn, and FALSE if the pool
 + *    is currently empty.
 + *
 + * Side Effects:
 + *    If pool is empty, set wantToken so that we wake up
 + *    when a token is released.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +
 +Boolean
 +Job_TokenWithdraw(void)
 +{
 +    char tok, tok1;
 +    int count;
 +
 +    wantToken = 0;
 +    if (DEBUG(JOB))
 +      fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
 +              getpid(), aborting, jobTokensRunning);
 +
 +    if (aborting || (jobTokensRunning >= maxJobs))
 +      return FALSE;
 +
 +    count = read(tokenWaitJob.inPipe, &tok, 1);
 +    if (count == 0)
 +      Fatal("eof on job pipe!");
 +    if (count < 0 && jobTokensRunning != 0) {
 +      if (errno != EAGAIN) {
 +          Fatal("job pipe read: %s", strerror(errno));
 +      }
 +      if (DEBUG(JOB))
 +          fprintf(debug_file, "(%d) blocked for token\n", getpid());
 +      wantToken = 1;
 +      return FALSE;
 +    }
 +
 +    if (count == 1 && tok != '+') {
 +      /* make being abvorted - remove any other job tokens */
 +      if (DEBUG(JOB))
 +          fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok);
 +      while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
 +          continue;
 +      /* And put the stopper back */
 +      while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
 +          continue;
 +      Fatal("A failure has been detected in another branch of the parallel make");
 +    }
 +
 +    if (count == 1 && jobTokensRunning == 0)
 +      /* We didn't want the token really */
 +      while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
 +          continue;
 +
 +    jobTokensRunning++;
 +    if (DEBUG(JOB))
 +      fprintf(debug_file, "(%d) withdrew token\n", getpid());
 +    return TRUE;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Job_RunTarget --
 + *    Run the named target if found. If a filename is specified, then
 + *    set that to the sources.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    exits if the target fails.
 + *
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Job_RunTarget(const char *target, const char *fname) {
 +    GNode *gn = Targ_FindNode(target, TARG_NOCREATE);
 +
 +    if (gn == NULL)
 +      return FALSE;
 +
 +    if (fname)
 +      Var_Set(ALLSRC, fname, gn, 0);
 +
 +    JobRun(gn);
 +    if (gn->made == ERROR) {
 +      PrintOnError(gn, "\n\nStop.");
 +      exit(1);
 +    }
 +    return TRUE;
 +}
 +
 +#ifdef USE_SELECT
 +int
 +emul_poll(struct pollfd *fd, int nfd, int timeout)
 +{
 +    fd_set rfds, wfds;
 +    int i, maxfd, nselect, npoll;
 +    struct timeval tv, *tvp;
 +    long usecs;
 +
 +    FD_ZERO(&rfds);
 +    FD_ZERO(&wfds);
 +
 +    maxfd = -1;
 +    for (i = 0; i < nfd; i++) {
 +      fd[i].revents = 0;
 +
 +      if (fd[i].events & POLLIN)
 +          FD_SET(fd[i].fd, &rfds);
 +
 +      if (fd[i].events & POLLOUT)
 +          FD_SET(fd[i].fd, &wfds);
 +
 +      if (fd[i].fd > maxfd)
 +          maxfd = fd[i].fd;
 +    }
 +    
 +    if (maxfd >= FD_SETSIZE) {
 +      Punt("Ran out of fd_set slots; " 
 +           "recompile with a larger FD_SETSIZE.");
 +    }
 +
 +    if (timeout < 0) {
 +      tvp = NULL;
 +    } else {
 +      usecs = timeout * 1000;
 +      tv.tv_sec = usecs / 1000000;
 +      tv.tv_usec = usecs % 1000000;
 +        tvp = &tv;
 +    }
 +
 +    nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp);
 +
 +    if (nselect <= 0)
 +      return nselect;
 +
 +    npoll = 0;
 +    for (i = 0; i < nfd; i++) {
 +      if (FD_ISSET(fd[i].fd, &rfds))
 +          fd[i].revents |= POLLIN;
 +
 +      if (FD_ISSET(fd[i].fd, &wfds))
 +          fd[i].revents |= POLLOUT;
 +
 +      if (fd[i].revents)
 +          npoll++;
 +    }
 +
 +    return npoll;
 +}
 +#endif /* USE_SELECT */
index 3287115cf8b20f5b7935fcc10529a6a7945e9be0,0000000000000000000000000000000000000000..d7fe5341290077a8ca40615207baeaeecc99c3c0
mode 100644,000000..100644
--- /dev/null
@@@ -1,2023 -1,0 +1,2035 @@@
- /*    $NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $   */
++/*    $NetBSD: main.c,v 1.234 2015/10/11 04:51:24 sjg Exp $   */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $";
++static char rcsid[] = "$NetBSD: main.c,v 1.234 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
 + The Regents of the University of California.  All rights reserved.");
 +#endif /* not lint */
 +
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)main.c    8.3 (Berkeley) 3/19/94";
 +#else
- __RCSID("$NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $");
++__RCSID("$NetBSD: main.c,v 1.234 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * main.c --
 + *    The main file for this entire program. Exit routines etc
 + *    reside here.
 + *
 + * Utility functions defined in this file:
 + *    Main_ParseArgLine       Takes a line of arguments, breaks them and
 + *                            treats them as if they were given when first
 + *                            invoked. Used by the parse module to implement
 + *                            the .MFLAGS target.
 + *
 + *    Error                   Print a tagged error message. The global
 + *                            MAKE variable must have been defined. This
 + *                            takes a format string and optional arguments
 + *                            for it.
 + *
 + *    Fatal                   Print an error message and exit. Also takes
 + *                            a format string and arguments for it.
 + *
 + *    Punt                    Aborts all jobs and exits with a message. Also
 + *                            takes a format string and arguments for it.
 + *
 + *    Finish                  Finish things up by printing the number of
 + *                            errors which occurred, as passed to it, and
 + *                            exiting.
 + */
 +
 +#include <sys/types.h>
 +#include <sys/time.h>
 +#include <sys/param.h>
 +#include <sys/resource.h>
 +#include <sys/stat.h>
 +#if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL)
 +#include <sys/sysctl.h>
 +#endif
 +#include <sys/utsname.h>
 +#include "wait.h"
 +
 +#include <errno.h>
 +#include <fcntl.h>
 +#include <signal.h>
 +#include <stdarg.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <time.h>
 +#include <ctype.h>
 +
 +#include "make.h"
 +#include "hash.h"
 +#include "dir.h"
 +#include "job.h"
 +#include "pathnames.h"
 +#include "trace.h"
 +
 +#ifdef USE_IOVEC
 +#include <sys/uio.h>
 +#endif
 +
 +#ifndef       DEFMAXLOCAL
 +#define       DEFMAXLOCAL DEFMAXJOBS
 +#endif        /* DEFMAXLOCAL */
 +
 +#ifndef __arraycount
 +# define __arraycount(__x)    (sizeof(__x) / sizeof(__x[0]))
 +#endif
 +
 +Lst                   create;         /* Targets to be made */
 +time_t                        now;            /* Time at start of make */
 +GNode                 *DEFAULT;       /* .DEFAULT node */
 +Boolean                       allPrecious;    /* .PRECIOUS given on line by itself */
 +
 +static Boolean                noBuiltins;     /* -r flag */
 +static Lst            makefiles;      /* ordered list of makefiles to read */
 +static Boolean                printVars;      /* print value of one or more vars */
 +static Lst            variables;      /* list of variables to print */
 +int                   maxJobs;        /* -j argument */
 +static int            maxJobTokens;   /* -j argument */
 +Boolean                       compatMake;     /* -B argument */
 +int                   debug;          /* -d argument */
 +Boolean                       debugVflag;     /* -dV */
 +Boolean                       noExecute;      /* -n flag */
 +Boolean                       noRecursiveExecute;     /* -N flag */
 +Boolean                       keepgoing;      /* -k flag */
 +Boolean                       queryFlag;      /* -q flag */
 +Boolean                       touchFlag;      /* -t flag */
 +Boolean                       enterFlag;      /* -w flag */
++Boolean                       enterFlagObj;   /* -w and objdir != srcdir */
 +Boolean                       ignoreErrors;   /* -i flag */
 +Boolean                       beSilent;       /* -s flag */
 +Boolean                       oldVars;        /* variable substitution style */
 +Boolean                       checkEnvFirst;  /* -e flag */
 +Boolean                       parseWarnFatal; /* -W flag */
 +Boolean                       jobServer;      /* -J flag */
 +static int jp_0 = -1, jp_1 = -1;      /* ends of parent job pipe */
 +Boolean                       varNoExportEnv; /* -X flag */
 +Boolean                       doing_depend;   /* Set while reading .depend */
 +static Boolean                jobsRunning;    /* TRUE if the jobs might be running */
 +static const char *   tracefile;
 +static void           MainParseArgs(int, char **);
 +static int            ReadMakefile(const void *, const void *);
 +static void           usage(void) MAKE_ATTR_DEAD;
 +
 +static Boolean                ignorePWD;      /* if we use -C, PWD is meaningless */
 +static char objdir[MAXPATHLEN + 1];   /* where we chdir'ed to */
 +char curdir[MAXPATHLEN + 1];          /* Startup directory */
 +char *progname;                               /* the program name */
 +char *makeDependfile;
 +pid_t myPid;
 +int makelevel;
 +
 +Boolean forceJobs = FALSE;
 +
 +/*
 + * On some systems MACHINE is defined as something other than
 + * what we want.
 + */
 +#ifdef FORCE_MACHINE
 +# undef MACHINE
 +# define MACHINE FORCE_MACHINE
 +#endif
 +
 +extern Lst parseIncPath;
 +
 +/*
 + * For compatibility with the POSIX version of MAKEFLAGS that includes
 + * all the options with out -, convert flags to -f -l -a -g -s.
 + */
 +static char *
 +explode(const char *flags)
 +{
 +    size_t len;
 +    char *nf, *st;
 +    const char *f;
 +
 +    if (flags == NULL)
 +      return NULL;
 +
 +    for (f = flags; *f; f++)
 +      if (!isalpha((unsigned char)*f))
 +          break;
 +
 +    if (*f)
 +      return bmake_strdup(flags);
 +
 +    len = strlen(flags);
 +    st = nf = bmake_malloc(len * 3 + 1);
 +    while (*flags) {
 +      *nf++ = '-';
 +      *nf++ = *flags++;
 +      *nf++ = ' ';
 +    }
 +    *nf = '\0';
 +    return st;
 +}
 +          
 +static void
 +parse_debug_options(const char *argvalue)
 +{
 +      const char *modules;
 +      const char *mode;
 +      char *fname;
 +      int len;
 +
 +      for (modules = argvalue; *modules; ++modules) {
 +              switch (*modules) {
 +              case 'A':
 +                      debug = ~0;
 +                      break;
 +              case 'a':
 +                      debug |= DEBUG_ARCH;
 +                      break;
 +              case 'C':
 +                      debug |= DEBUG_CWD;
 +                      break;
 +              case 'c':
 +                      debug |= DEBUG_COND;
 +                      break;
 +              case 'd':
 +                      debug |= DEBUG_DIR;
 +                      break;
 +              case 'e':
 +                      debug |= DEBUG_ERROR;
 +                      break;
 +              case 'f':
 +                      debug |= DEBUG_FOR;
 +                      break;
 +              case 'g':
 +                      if (modules[1] == '1') {
 +                              debug |= DEBUG_GRAPH1;
 +                              ++modules;
 +                      }
 +                      else if (modules[1] == '2') {
 +                              debug |= DEBUG_GRAPH2;
 +                              ++modules;
 +                      }
 +                      else if (modules[1] == '3') {
 +                              debug |= DEBUG_GRAPH3;
 +                              ++modules;
 +                      }
 +                      break;
 +              case 'j':
 +                      debug |= DEBUG_JOB;
 +                      break;
 +              case 'l':
 +                      debug |= DEBUG_LOUD;
 +                      break;
 +              case 'M':
 +                      debug |= DEBUG_META;
 +                      break;
 +              case 'm':
 +                      debug |= DEBUG_MAKE;
 +                      break;
 +              case 'n':
 +                      debug |= DEBUG_SCRIPT;
 +                      break;
 +              case 'p':
 +                      debug |= DEBUG_PARSE;
 +                      break;
 +              case 's':
 +                      debug |= DEBUG_SUFF;
 +                      break;
 +              case 't':
 +                      debug |= DEBUG_TARG;
 +                      break;
 +              case 'V':
 +                      debugVflag = TRUE;
 +                      break;
 +              case 'v':
 +                      debug |= DEBUG_VAR;
 +                      break;
 +              case 'x':
 +                      debug |= DEBUG_SHELL;
 +                      break;
 +              case 'F':
 +                      if (debug_file != stdout && debug_file != stderr)
 +                              fclose(debug_file);
 +                      if (*++modules == '+') {
 +                              modules++;
 +                              mode = "a";
 +                      } else
 +                              mode = "w";
 +                      if (strcmp(modules, "stdout") == 0) {
 +                              debug_file = stdout;
 +                              goto debug_setbuf;
 +                      }
 +                      if (strcmp(modules, "stderr") == 0) {
 +                              debug_file = stderr;
 +                              goto debug_setbuf;
 +                      }
 +                      len = strlen(modules);
 +                      fname = malloc(len + 20);
 +                      memcpy(fname, modules, len + 1);
 +                      /* Let the filename be modified by the pid */
 +                      if (strcmp(fname + len - 3, ".%d") == 0)
 +                              snprintf(fname + len - 2, 20, "%d", getpid());
 +                      debug_file = fopen(fname, mode);
 +                      if (!debug_file) {
 +                              fprintf(stderr, "Cannot open debug file %s\n",
 +                                  fname);
 +                              usage();
 +                      }
 +                      free(fname);
 +                      goto debug_setbuf;
 +              default:
 +                      (void)fprintf(stderr,
 +                          "%s: illegal argument to d option -- %c\n",
 +                          progname, *modules);
 +                      usage();
 +              }
 +      }
 +debug_setbuf:
 +      /*
 +       * Make the debug_file unbuffered, and make
 +       * stdout line buffered (unless debugfile == stdout).
 +       */
 +      setvbuf(debug_file, NULL, _IONBF, 0);
 +      if (debug_file != stdout) {
 +              setvbuf(stdout, NULL, _IOLBF, 0);
 +      }
 +}
 +
 +/*-
 + * MainParseArgs --
 + *    Parse a given argument vector. Called from main() and from
 + *    Main_ParseArgLine() when the .MAKEFLAGS target is used.
 + *
 + *    XXX: Deal with command line overriding .MAKEFLAGS in makefile
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Various global and local flags will be set depending on the flags
 + *    given
 + */
 +static void
 +MainParseArgs(int argc, char **argv)
 +{
 +      char *p;
 +      int c = '?';
 +      int arginc;
 +      char *argvalue;
 +      const char *getopt_def;
 +      char *optscan;
 +      Boolean inOption, dashDash = FALSE;
 +      char found_path[MAXPATHLEN + 1];        /* for searching for sys.mk */
 +
 +#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstw"
 +/* Can't actually use getopt(3) because rescanning is not portable */
 +
 +      getopt_def = OPTFLAGS;
 +rearg:        
 +      inOption = FALSE;
 +      optscan = NULL;
 +      while(argc > 1) {
 +              char *getopt_spec;
 +              if(!inOption)
 +                      optscan = argv[1];
 +              c = *optscan++;
 +              arginc = 0;
 +              if(inOption) {
 +                      if(c == '\0') {
 +                              ++argv;
 +                              --argc;
 +                              inOption = FALSE;
 +                              continue;
 +                      }
 +              } else {
 +                      if (c != '-' || dashDash)
 +                              break;
 +                      inOption = TRUE;
 +                      c = *optscan++;
 +              }
 +              /* '-' found at some earlier point */
 +              getopt_spec = strchr(getopt_def, c);
 +              if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') {
 +                      /* -<something> found, and <something> should have an arg */
 +                      inOption = FALSE;
 +                      arginc = 1;
 +                      argvalue = optscan;
 +                      if(*argvalue == '\0') {
 +                              if (argc < 3)
 +                                      goto noarg;
 +                              argvalue = argv[2];
 +                              arginc = 2;
 +                      }
 +              } else {
 +                      argvalue = NULL; 
 +              }
 +              switch(c) {
 +              case '\0':
 +                      arginc = 1;
 +                      inOption = FALSE;
 +                      break;
 +              case 'B':
 +                      compatMake = TRUE;
 +                      Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL);
 +                      Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0);
 +                      break;
 +              case 'C':
 +                      if (chdir(argvalue) == -1) {
 +                              (void)fprintf(stderr,
 +                                            "%s: chdir %s: %s\n",
 +                                            progname, argvalue,
 +                                            strerror(errno));
 +                              exit(1);
 +                      }
 +                      if (getcwd(curdir, MAXPATHLEN) == NULL) {
 +                              (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
 +                              exit(2);
 +                      }
 +                      ignorePWD = TRUE;
 +                      break;
 +              case 'D':
 +                      if (argvalue == NULL || argvalue[0] == 0) goto noarg;
 +                      Var_Set(argvalue, "1", VAR_GLOBAL, 0);
 +                      Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      break;
 +              case 'I':
 +                      if (argvalue == NULL) goto noarg;
 +                      Parse_AddIncludeDir(argvalue);
 +                      Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      break;
 +              case 'J':
 +                      if (argvalue == NULL) goto noarg;
 +                      if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) {
 +                          (void)fprintf(stderr,
 +                              "%s: internal error -- J option malformed (%s)\n",
 +                              progname, argvalue);
 +                              usage();
 +                      }
 +                      if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
 +                          (fcntl(jp_1, F_GETFD, 0) < 0)) {
 +#if 0
 +                          (void)fprintf(stderr,
 +                              "%s: ###### warning -- J descriptors were closed!\n",
 +                              progname);
 +                          exit(2);
 +#endif
 +                          jp_0 = -1;
 +                          jp_1 = -1;
 +                          compatMake = TRUE;
 +                      } else {
 +                          Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
 +                          Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                          jobServer = TRUE;
 +                      }
 +                      break;
 +              case 'N':
 +                      noExecute = TRUE;
 +                      noRecursiveExecute = TRUE;
 +                      Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL);
 +                      break;
 +              case 'S':
 +                      keepgoing = FALSE;
 +                      Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
 +                      break;
 +              case 'T':
 +                      if (argvalue == NULL) goto noarg;
 +                      tracefile = bmake_strdup(argvalue);
 +                      Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      break;
 +              case 'V':
 +                      if (argvalue == NULL) goto noarg;
 +                      printVars = TRUE;
 +                      (void)Lst_AtEnd(variables, argvalue);
 +                      Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      break;
 +              case 'W':
 +                      parseWarnFatal = TRUE;
 +                      break;
 +              case 'X':
 +                      varNoExportEnv = TRUE;
 +                      Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL);
 +                      break;
 +              case 'd':
 +                      if (argvalue == NULL) goto noarg;
 +                      /* If '-d-opts' don't pass to children */
 +                      if (argvalue[0] == '-')
 +                          argvalue++;
 +                      else {
 +                          Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
 +                          Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      }
 +                      parse_debug_options(argvalue);
 +                      break;
 +              case 'e':
 +                      checkEnvFirst = TRUE;
 +                      Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
 +                      break;
 +              case 'f':
 +                      if (argvalue == NULL) goto noarg;
 +                      (void)Lst_AtEnd(makefiles, argvalue);
 +                      break;
 +              case 'i':
 +                      ignoreErrors = TRUE;
 +                      Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
 +                      break;
 +              case 'j':
 +                      if (argvalue == NULL) goto noarg;
 +                      forceJobs = TRUE;
 +                      maxJobs = strtol(argvalue, &p, 0);
 +                      if (*p != '\0' || maxJobs < 1) {
 +                              (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n",
 +                                  progname);
 +                              exit(1);
 +                      }
 +                      Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0);
 +                      maxJobTokens = maxJobs;
 +                      break;
 +              case 'k':
 +                      keepgoing = TRUE;
 +                      Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
 +                      break;
 +              case 'm':
 +                      if (argvalue == NULL) goto noarg;
 +                      /* look for magic parent directory search string */
 +                      if (strncmp(".../", argvalue, 4) == 0) {
 +                              if (!Dir_FindHereOrAbove(curdir, argvalue+4,
 +                                  found_path, sizeof(found_path)))
 +                                      break;          /* nothing doing */
 +                              (void)Dir_AddDir(sysIncPath, found_path);
 +                      } else {
 +                              (void)Dir_AddDir(sysIncPath, argvalue);
 +                      }
 +                      Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL);
 +                      Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
 +                      break;
 +              case 'n':
 +                      noExecute = TRUE;
 +                      Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL);
 +                      break;
 +              case 'q':
 +                      queryFlag = TRUE;
 +                      /* Kind of nonsensical, wot? */
 +                      Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL);
 +                      break;
 +              case 'r':
 +                      noBuiltins = TRUE;
 +                      Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL);
 +                      break;
 +              case 's':
 +                      beSilent = TRUE;
 +                      Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL);
 +                      break;
 +              case 't':
 +                      touchFlag = TRUE;
 +                      Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL);
 +                      break;
 +              case 'w':
 +                      enterFlag = TRUE;
 +                      Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL);
 +                      break;
 +              case '-':
 +                      dashDash = TRUE;
 +                      break;
 +              default:
 +              case '?':
 +#ifndef MAKE_NATIVE
 +                      fprintf(stderr, "getopt(%s) -> %d (%c)\n",
 +                              OPTFLAGS, c, c);
 +#endif
 +                      usage();
 +              }
 +              argv += arginc;
 +              argc -= arginc;
 +      }
 +
 +      oldVars = TRUE;
 +
 +      /*
 +       * See if the rest of the arguments are variable assignments and
 +       * perform them if so. Else take them to be targets and stuff them
 +       * on the end of the "create" list.
 +       */
 +      for (; argc > 1; ++argv, --argc)
 +              if (Parse_IsVar(argv[1])) {
 +                      Parse_DoVar(argv[1], VAR_CMD);
 +              } else {
 +                      if (!*argv[1])
 +                              Punt("illegal (null) argument.");
 +                      if (*argv[1] == '-' && !dashDash)
 +                              goto rearg;
 +                      (void)Lst_AtEnd(create, bmake_strdup(argv[1]));
 +              }
 +
 +      return;
 +noarg:
 +      (void)fprintf(stderr, "%s: option requires an argument -- %c\n",
 +          progname, c);
 +      usage();
 +}
 +
 +/*-
 + * Main_ParseArgLine --
 + *    Used by the parse module when a .MFLAGS or .MAKEFLAGS target
 + *    is encountered and by main() when reading the .MAKEFLAGS envariable.
 + *    Takes a line of arguments and breaks it into its
 + *    component words and passes those words and the number of them to the
 + *    MainParseArgs function.
 + *    The line should have all its leading whitespace removed.
 + *
 + * Input:
 + *    line            Line to fracture
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Only those that come from the various arguments.
 + */
 +void
 +Main_ParseArgLine(const char *line)
 +{
 +      char **argv;                    /* Manufactured argument vector */
 +      int argc;                       /* Number of arguments in argv */
 +      char *args;                     /* Space used by the args */
 +      char *buf, *p1;
 +      char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1);
 +      size_t len;
 +
 +      if (line == NULL)
 +              return;
 +      for (; *line == ' '; ++line)
 +              continue;
 +      if (!*line)
 +              return;
 +
 +#ifndef POSIX
 +      {
 +              /*
 +               * $MAKE may simply be naming the make(1) binary
 +               */
 +              char *cp;
 +
 +              if (!(cp = strrchr(line, '/')))
 +                      cp = line;
 +              if ((cp = strstr(cp, "make")) &&
 +                  strcmp(cp, "make") == 0)
 +                      return;
 +      }
 +#endif
 +      buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2);
 +      (void)snprintf(buf, len, "%s %s", argv0, line);
 +      if (p1)
 +              free(p1);
 +
 +      argv = brk_string(buf, &argc, TRUE, &args);
 +      if (argv == NULL) {
 +              Error("Unterminated quoted string [%s]", buf);
 +              free(buf);
 +              return;
 +      }
 +      free(buf);
 +      MainParseArgs(argc, argv);
 +
 +      free(args);
 +      free(argv);
 +}
 +
 +Boolean
 +Main_SetObjdir(const char *path)
 +{
 +      struct stat sb;
 +      char *p = NULL;
 +      char buf[MAXPATHLEN + 1];
 +      Boolean rc = FALSE;
 +
 +      /* expand variable substitutions */
 +      if (strchr(path, '$') != 0) {
 +              snprintf(buf, MAXPATHLEN, "%s", path);
-               path = p = Var_Subst(NULL, buf, VAR_GLOBAL, 0);
++              path = p = Var_Subst(NULL, buf, VAR_GLOBAL, FALSE, TRUE);
 +      }
 +
 +      if (path[0] != '/') {
 +              snprintf(buf, MAXPATHLEN, "%s/%s", curdir, path);
 +              path = buf;
 +      }
 +
 +      /* look for the directory and try to chdir there */
 +      if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
 +              if (chdir(path)) {
 +                      (void)fprintf(stderr, "make warning: %s: %s.\n",
 +                                    path, strerror(errno));
 +              } else {
 +                      strncpy(objdir, path, MAXPATHLEN);
 +                      Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0);
 +                      setenv("PWD", objdir, 1);
 +                      Dir_InitDot();
 +                      rc = TRUE;
++                      if (enterFlag && strcmp(objdir, curdir) != 0)
++                              enterFlagObj = TRUE;
 +              }
 +      }
 +
 +      if (p)
 +              free(p);
 +      return rc;
 +}
 +
 +/*-
 + * ReadAllMakefiles --
 + *    wrapper around ReadMakefile() to read all.
 + *
 + * Results:
 + *    TRUE if ok, FALSE on error
 + */
 +static int
 +ReadAllMakefiles(const void *p, const void *q)
 +{
 +      return (ReadMakefile(p, q) == 0);
 +}
 +
 +int
 +str2Lst_Append(Lst lp, char *str, const char *sep)
 +{
 +    char *cp;
 +    int n;
 +
 +    if (!sep)
 +      sep = " \t";
 +
 +    for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
 +      (void)Lst_AtEnd(lp, cp);
 +      n++;
 +    }
 +    return (n);
 +}
 +
 +#ifdef SIGINFO
 +/*ARGSUSED*/
 +static void
 +siginfo(int signo MAKE_ATTR_UNUSED)
 +{
 +      char dir[MAXPATHLEN];
 +      char str[2 * MAXPATHLEN];
 +      int len;
 +      if (getcwd(dir, sizeof(dir)) == NULL)
 +              return;
 +      len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir);
 +      if (len > 0)
 +              (void)write(STDERR_FILENO, str, (size_t)len);
 +}
 +#endif
 +
 +/*
 + * Allow makefiles some control over the mode we run in.
 + */
 +void
 +MakeMode(const char *mode)
 +{
 +    char *mp = NULL;
 +
 +    if (!mode)
-       mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}", VAR_GLOBAL, 0);
++      mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}",
++                            VAR_GLOBAL, FALSE, TRUE);
 +
 +    if (mode && *mode) {
 +      if (strstr(mode, "compat")) {
 +          compatMake = TRUE;
 +          forceJobs = FALSE;
 +      }
 +#if USE_META
 +      if (strstr(mode, "meta"))
 +          meta_mode_init(mode);
 +#endif
 +    }
 +    if (mp)
 +      free(mp);
 +}
 +
 +/*-
 + * main --
 + *    The main function, for obvious reasons. Initializes variables
 + *    and a few modules, then parses the arguments give it in the
 + *    environment and on the command line. Reads the system makefile
 + *    followed by either Makefile, makefile or the file given by the
 + *    -f argument. Sets the .MAKEFLAGS PMake variable based on all the
 + *    flags it has received by then uses either the Make or the Compat
 + *    module to create the initial list of targets.
 + *
 + * Results:
 + *    If -q was given, exits -1 if anything was out-of-date. Else it exits
 + *    0.
 + *
 + * Side Effects:
 + *    The program exits when done. Targets are created. etc. etc. etc.
 + */
 +int
 +main(int argc, char **argv)
 +{
 +      Lst targs;      /* target nodes to create -- passed to Make_Init */
 +      Boolean outOfDate = FALSE;      /* FALSE if all targets up to date */
 +      struct stat sb, sa;
 +      char *p1, *path;
 +      char mdpath[MAXPATHLEN];
 +#ifdef FORCE_MACHINE
 +      const char *machine = FORCE_MACHINE;
 +#else
 +      const char *machine = getenv("MACHINE");
 +#endif
 +      const char *machine_arch = getenv("MACHINE_ARCH");
 +      char *syspath = getenv("MAKESYSPATH");
 +      Lst sysMkPath;                  /* Path of sys.mk */
 +      char *cp = NULL, *start;
 +                                      /* avoid faults on read-only strings */
 +      static char defsyspath[] = _PATH_DEFSYSPATH;
 +      char found_path[MAXPATHLEN + 1];        /* for searching for sys.mk */
 +      struct timeval rightnow;                /* to initialize random seed */
 +      struct utsname utsname;
 +
 +      /* default to writing debug to stderr */
 +      debug_file = stderr;
 +
 +#ifdef SIGINFO
 +      (void)bmake_signal(SIGINFO, siginfo);
 +#endif
 +      /*
 +       * Set the seed to produce a different random sequence
 +       * on each program execution.
 +       */
 +      gettimeofday(&rightnow, NULL);
 +      srandom(rightnow.tv_sec + rightnow.tv_usec);
 +      
 +      if ((progname = strrchr(argv[0], '/')) != NULL)
 +              progname++;
 +      else
 +              progname = argv[0];
 +#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE))
 +      /*
 +       * get rid of resource limit on file descriptors
 +       */
 +      {
 +              struct rlimit rl;
 +              if (getrlimit(RLIMIT_NOFILE, &rl) != -1 &&
 +                  rl.rlim_cur != rl.rlim_max) {
 +                      rl.rlim_cur = rl.rlim_max;
 +                      (void)setrlimit(RLIMIT_NOFILE, &rl);
 +              }
 +      }
 +#endif
 +
 +      if (uname(&utsname) == -1) {
 +          (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
 +              strerror(errno));
 +          exit(2);
 +      }
 +
 +      /*
 +       * Get the name of this type of MACHINE from utsname
 +       * so we can share an executable for similar machines.
 +       * (i.e. m68k: amiga hp300, mac68k, sun3, ...)
 +       *
 +       * Note that both MACHINE and MACHINE_ARCH are decided at
 +       * run-time.
 +       */
 +      if (!machine) {
 +#ifdef MAKE_NATIVE
 +          machine = utsname.machine;
 +#else
 +#ifdef MAKE_MACHINE
 +          machine = MAKE_MACHINE;
 +#else
 +          machine = "unknown";
 +#endif
 +#endif
 +      }
 +
 +      if (!machine_arch) {
 +#if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) && defined(CTL_HW) && defined(HW_MACHINE_ARCH)
 +          static char machine_arch_buf[sizeof(utsname.machine)];
 +          int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
 +          size_t len = sizeof(machine_arch_buf);
 +                
 +          if (sysctl(mib, __arraycount(mib), machine_arch_buf,
 +                  &len, NULL, 0) < 0) {
 +              (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
 +                  strerror(errno));
 +              exit(2);
 +          }
 +
 +          machine_arch = machine_arch_buf;
 +#else
 +#ifndef MACHINE_ARCH
 +#ifdef MAKE_MACHINE_ARCH
 +            machine_arch = MAKE_MACHINE_ARCH;
 +#else
 +          machine_arch = "unknown";
 +#endif
 +#else
 +          machine_arch = MACHINE_ARCH;
 +#endif
 +#endif
 +      }
 +
 +      myPid = getpid();               /* remember this for vFork() */
 +
 +      /*
 +       * Just in case MAKEOBJDIR wants us to do something tricky.
 +       */
 +      Var_Init();             /* Initialize the lists of variables for
 +                               * parsing arguments */
 +      Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0);
 +      Var_Set("MACHINE", machine, VAR_GLOBAL, 0);
 +      Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0);
 +#ifdef MAKE_VERSION
 +      Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0);
 +#endif
 +      Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */
 +      /*
 +       * This is the traditional preference for makefiles.
 +       */
 +#ifndef MAKEFILE_PREFERENCE_LIST
 +# define MAKEFILE_PREFERENCE_LIST "makefile Makefile"
 +#endif
 +      Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST,
 +              VAR_GLOBAL, 0);
 +      Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0);
 +
 +      create = Lst_Init(FALSE);
 +      makefiles = Lst_Init(FALSE);
 +      printVars = FALSE;
 +      debugVflag = FALSE;
 +      variables = Lst_Init(FALSE);
 +      beSilent = FALSE;               /* Print commands as executed */
 +      ignoreErrors = FALSE;           /* Pay attention to non-zero returns */
 +      noExecute = FALSE;              /* Execute all commands */
 +      noRecursiveExecute = FALSE;     /* Execute all .MAKE targets */
 +      keepgoing = FALSE;              /* Stop on error */
 +      allPrecious = FALSE;            /* Remove targets when interrupted */
 +      queryFlag = FALSE;              /* This is not just a check-run */
 +      noBuiltins = FALSE;             /* Read the built-in rules */
 +      touchFlag = FALSE;              /* Actually update targets */
 +      debug = 0;                      /* No debug verbosity, please. */
 +      jobsRunning = FALSE;
 +
 +      maxJobs = DEFMAXLOCAL;          /* Set default local max concurrency */
 +      maxJobTokens = maxJobs;
 +      compatMake = FALSE;             /* No compat mode */
 +      ignorePWD = FALSE;
 +
 +      /*
 +       * Initialize the parsing, directory and variable modules to prepare
 +       * for the reading of inclusion paths and variable settings on the
 +       * command line
 +       */
 +
 +      /*
 +       * Initialize various variables.
 +       *      MAKE also gets this name, for compatibility
 +       *      .MAKEFLAGS gets set to the empty string just in case.
 +       *      MFLAGS also gets initialized empty, for compatibility.
 +       */
 +      Parse_Init();
 +      if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) {
 +          /*
 +           * Leave alone if it is an absolute path, or if it does
 +           * not contain a '/' in which case we need to find it in
 +           * the path, like execvp(3) and the shells do.
 +           */
 +          p1 = argv[0];
 +      } else {
 +          /*
 +           * A relative path, canonicalize it.
 +           */
 +          p1 = realpath(argv[0], mdpath);
 +          if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) {
 +              p1 = argv[0];           /* realpath failed */
 +          }
 +      }
 +      Var_Set("MAKE", p1, VAR_GLOBAL, 0);
 +      Var_Set(".MAKE", p1, VAR_GLOBAL, 0);
 +      Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0);
 +      Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0);
 +      Var_Set("MFLAGS", "", VAR_GLOBAL, 0);
 +      Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0);
 +      /* some makefiles need to know this */
 +      Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0);
 +
 +      /*
 +       * Set some other useful macros
 +       */
 +      {
 +          char tmp[64], *ep;
 +
 +          makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0;
 +          if (makelevel < 0)
 +              makelevel = 0;
 +          snprintf(tmp, sizeof(tmp), "%d", makelevel);
 +          Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0);
 +          snprintf(tmp, sizeof(tmp), "%u", myPid);
 +          Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0);
 +          snprintf(tmp, sizeof(tmp), "%u", getppid());
 +          Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0);
 +      }
 +      if (makelevel > 0) {
 +              char pn[1024];
 +              snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel);
 +              progname = bmake_strdup(pn);
 +      }
 +
 +#ifdef USE_META
 +      meta_init();
 +#endif
 +      /*
 +       * First snag any flags out of the MAKE environment variable.
 +       * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
 +       * in a different format).
 +       */
 +#ifdef POSIX
 +      p1 = explode(getenv("MAKEFLAGS"));
 +      Main_ParseArgLine(p1);
 +      free(p1);
 +#else
 +      Main_ParseArgLine(getenv("MAKE"));
 +#endif
 +
 +      /*
 +       * Find where we are (now).
 +       * We take care of PWD for the automounter below...
 +       */
 +      if (getcwd(curdir, MAXPATHLEN) == NULL) {
 +              (void)fprintf(stderr, "%s: getcwd: %s.\n",
 +                  progname, strerror(errno));
 +              exit(2);
 +      }
 +
 +      MainParseArgs(argc, argv);
 +
 +      if (enterFlag)
 +              printf("%s: Entering directory `%s'\n", progname, curdir);
 +
 +      /*
 +       * Verify that cwd is sane.
 +       */
 +      if (stat(curdir, &sa) == -1) {
 +          (void)fprintf(stderr, "%s: %s: %s.\n",
 +               progname, curdir, strerror(errno));
 +          exit(2);
 +      }
 +
 +      /*
 +       * All this code is so that we know where we are when we start up
 +       * on a different machine with pmake.
 +       * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX
 +       * since the value of curdir can vary depending on how we got
 +       * here.  Ie sitting at a shell prompt (shell that provides $PWD)
 +       * or via subdir.mk in which case its likely a shell which does
 +       * not provide it.
 +       * So, to stop it breaking this case only, we ignore PWD if
 +       * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform.
 +       */
 +#ifndef NO_PWD_OVERRIDE
 +      if (!ignorePWD) {
 +              char *pwd, *ptmp1 = NULL, *ptmp2 = NULL;
 +
 +              if ((pwd = getenv("PWD")) != NULL &&
 +                  Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) {
 +                      const char *makeobjdir = Var_Value("MAKEOBJDIR",
 +                          VAR_CMD, &ptmp2);
 +
 +                      if (makeobjdir == NULL || !strchr(makeobjdir, '$')) {
 +                              if (stat(pwd, &sb) == 0 &&
 +                                  sa.st_ino == sb.st_ino &&
 +                                  sa.st_dev == sb.st_dev)
 +                                      (void)strncpy(curdir, pwd, MAXPATHLEN);
 +                      }
 +              }
 +              free(ptmp1);
 +              free(ptmp2);
 +      }
 +#endif
 +      Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0);
 +
 +      /*
 +       * Find the .OBJDIR.  If MAKEOBJDIRPREFIX, or failing that,
 +       * MAKEOBJDIR is set in the environment, try only that value
 +       * and fall back to .CURDIR if it does not exist.
 +       *
 +       * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and
 +       * finally _PATH_OBJDIRPREFIX`pwd`, in that order.  If none
 +       * of these paths exist, just use .CURDIR.
 +       */
 +      Dir_Init(curdir);
 +      (void)Main_SetObjdir(curdir);
 +
 +      if ((path = Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &p1)) != NULL) {
 +              (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir);
 +              (void)Main_SetObjdir(mdpath);
 +              free(p1);
 +      } else if ((path = Var_Value("MAKEOBJDIR", VAR_CMD, &p1)) != NULL) {
 +              (void)Main_SetObjdir(path);
 +              free(p1);
 +      } else {
 +              (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine);
 +              if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) {
 +                      (void)snprintf(mdpath, MAXPATHLEN, "%s%s", 
 +                                      _PATH_OBJDIRPREFIX, curdir);
 +                      (void)Main_SetObjdir(mdpath);
 +              }
 +      }
 +
 +      /*
 +       * Be compatible if user did not specify -j and did not explicitly
 +       * turned compatibility on
 +       */
 +      if (!compatMake && !forceJobs) {
 +              compatMake = TRUE;
 +      }
 +      
 +      /*
 +       * Initialize archive, target and suffix modules in preparation for
 +       * parsing the makefile(s)
 +       */
 +      Arch_Init();
 +      Targ_Init();
 +      Suff_Init();
 +      Trace_Init(tracefile);
 +
 +      DEFAULT = NULL;
 +      (void)time(&now);
 +
 +      Trace_Log(MAKESTART, NULL);
 +      
 +      /*
 +       * Set up the .TARGETS variable to contain the list of targets to be
 +       * created. If none specified, make the variable empty -- the parser
 +       * will fill the thing in with the default or .MAIN target.
 +       */
 +      if (!Lst_IsEmpty(create)) {
 +              LstNode ln;
 +
 +              for (ln = Lst_First(create); ln != NULL;
 +                  ln = Lst_Succ(ln)) {
 +                      char *name = (char *)Lst_Datum(ln);
 +
 +                      Var_Append(".TARGETS", name, VAR_GLOBAL);
 +              }
 +      } else
 +              Var_Set(".TARGETS", "", VAR_GLOBAL, 0);
 +
 +
 +      /*
 +       * If no user-supplied system path was given (through the -m option)
 +       * add the directories from the DEFSYSPATH (more than one may be given
 +       * as dir1:...:dirn) to the system include path.
 +       */
 +      if (syspath == NULL || *syspath == '\0')
 +              syspath = defsyspath;
 +      else
 +              syspath = bmake_strdup(syspath);
 +
 +      for (start = syspath; *start != '\0'; start = cp) {
 +              for (cp = start; *cp != '\0' && *cp != ':'; cp++)
 +                      continue;
 +              if (*cp == ':') {
 +                      *cp++ = '\0';
 +              }
 +              /* look for magic parent directory search string */
 +              if (strncmp(".../", start, 4) != 0) {
 +                      (void)Dir_AddDir(defIncPath, start);
 +              } else {
 +                      if (Dir_FindHereOrAbove(curdir, start+4, 
 +                          found_path, sizeof(found_path))) {
 +                              (void)Dir_AddDir(defIncPath, found_path);
 +                      }
 +              }
 +      }
 +      if (syspath != defsyspath)
 +              free(syspath);
 +
 +      /*
 +       * Read in the built-in rules first, followed by the specified
 +       * makefile, if it was (makefile != NULL), or the default
 +       * makefile and Makefile, in that order, if it wasn't.
 +       */
 +      if (!noBuiltins) {
 +              LstNode ln;
 +
 +              sysMkPath = Lst_Init(FALSE);
 +              Dir_Expand(_PATH_DEFSYSMK,
 +                         Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath,
 +                         sysMkPath);
 +              if (Lst_IsEmpty(sysMkPath))
 +                      Fatal("%s: no system rules (%s).", progname,
 +                          _PATH_DEFSYSMK);
 +              ln = Lst_Find(sysMkPath, NULL, ReadMakefile);
 +              if (ln == NULL)
 +                      Fatal("%s: cannot open %s.", progname,
 +                          (char *)Lst_Datum(ln));
 +      }
 +
 +      if (!Lst_IsEmpty(makefiles)) {
 +              LstNode ln;
 +
 +              ln = Lst_Find(makefiles, NULL, ReadAllMakefiles);
 +              if (ln != NULL)
 +                      Fatal("%s: cannot open %s.", progname, 
 +                          (char *)Lst_Datum(ln));
 +      } else {
 +          p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}",
-               VAR_CMD, 0);
++              VAR_CMD, FALSE, TRUE);
 +          if (p1) {
 +              (void)str2Lst_Append(makefiles, p1, NULL);
 +              (void)Lst_Find(makefiles, NULL, ReadMakefile);
 +              free(p1);
 +          }
 +      }
 +
 +      /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
 +      if (!noBuiltins || !printVars) {
 +          makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}",
-               VAR_CMD, 0);
++              VAR_CMD, FALSE, TRUE);
 +          doing_depend = TRUE;
 +          (void)ReadMakefile(makeDependfile, NULL);
 +          doing_depend = FALSE;
 +      }
 +
++      if (enterFlagObj)
++              printf("%s: Entering directory `%s'\n", progname, objdir);
++      
 +      MakeMode(NULL);
 +
 +      Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
 +      if (p1)
 +          free(p1);
 +
 +      if (!compatMake)
 +          Job_ServerStart(maxJobTokens, jp_0, jp_1);
 +      if (DEBUG(JOB))
 +          fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n",
 +              jp_0, jp_1, maxJobs, maxJobTokens, compatMake);
 +
 +      Main_ExportMAKEFLAGS(TRUE);     /* initial export */
 +
 +      /*
 +       * For compatibility, look at the directories in the VPATH variable
 +       * and add them to the search path, if the variable is defined. The
 +       * variable's value is in the same format as the PATH envariable, i.e.
 +       * <directory>:<directory>:<directory>...
 +       */
 +      if (Var_Exists("VPATH", VAR_CMD)) {
 +              char *vpath, savec;
 +              /*
 +               * GCC stores string constants in read-only memory, but
 +               * Var_Subst will want to write this thing, so store it
 +               * in an array
 +               */
 +              static char VPATH[] = "${VPATH}";
 +
-               vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE);
++              vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE, TRUE);
 +              path = vpath;
 +              do {
 +                      /* skip to end of directory */
 +                      for (cp = path; *cp != ':' && *cp != '\0'; cp++)
 +                              continue;
 +                      /* Save terminator character so know when to stop */
 +                      savec = *cp;
 +                      *cp = '\0';
 +                      /* Add directory to search path */
 +                      (void)Dir_AddDir(dirSearchPath, path);
 +                      *cp = savec;
 +                      path = cp + 1;
 +              } while (savec == ':');
 +              free(vpath);
 +      }
 +
 +      /*
 +       * Now that all search paths have been read for suffixes et al, it's
 +       * time to add the default search path to their lists...
 +       */
 +      Suff_DoPaths();
 +
 +      /*
 +       * Propagate attributes through :: dependency lists.
 +       */
 +      Targ_Propagate();
 +
 +      /* print the initial graph, if the user requested it */
 +      if (DEBUG(GRAPH1))
 +              Targ_PrintGraph(1);
 +
 +      /* print the values of any variables requested by the user */
 +      if (printVars) {
 +              LstNode ln;
 +              Boolean expandVars;
 +
 +              if (debugVflag)
 +                      expandVars = FALSE;
 +              else
 +                      expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE);
 +              for (ln = Lst_First(variables); ln != NULL;
 +                  ln = Lst_Succ(ln)) {
 +                      char *var = (char *)Lst_Datum(ln);
 +                      char *value;
 +                      
 +                      if (strchr(var, '$')) {
-                               value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, 0);
++                          value = p1 = Var_Subst(NULL, var, VAR_GLOBAL,
++                                                 FALSE, TRUE);
 +                      } else if (expandVars) {
 +                              char tmp[128];
 +                                                              
 +                              if (snprintf(tmp, sizeof(tmp), "${%s}", var) >= (int)(sizeof(tmp)))
 +                                      Fatal("%s: variable name too big: %s",
 +                                            progname, var);
-                               value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++                              value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL,
++                                                     FALSE, TRUE);
 +                      } else {
 +                              value = Var_Value(var, VAR_GLOBAL, &p1);
 +                      }
 +                      printf("%s\n", value ? value : "");
 +                      if (p1)
 +                              free(p1);
 +              }
 +      } else {
 +              /*
 +               * Have now read the entire graph and need to make a list of
 +               * targets to create. If none was given on the command line,
 +               * we consult the parsing module to find the main target(s)
 +               * to create.
 +               */
 +              if (Lst_IsEmpty(create))
 +                      targs = Parse_MainName();
 +              else
 +                      targs = Targ_FindList(create, TARG_CREATE);
 +
 +              if (!compatMake) {
 +                      /*
 +                       * Initialize job module before traversing the graph
 +                       * now that any .BEGIN and .END targets have been read.
 +                       * This is done only if the -q flag wasn't given
 +                       * (to prevent the .BEGIN from being executed should
 +                       * it exist).
 +                       */
 +                      if (!queryFlag) {
 +                              Job_Init();
 +                              jobsRunning = TRUE;
 +                      }
 +
 +                      /* Traverse the graph, checking on all the targets */
 +                      outOfDate = Make_Run(targs);
 +              } else {
 +                      /*
 +                       * Compat_Init will take care of creating all the
 +                       * targets as well as initializing the module.
 +                       */
 +                      Compat_Run(targs);
 +              }
 +      }
 +
 +#ifdef CLEANUP
 +      Lst_Destroy(targs, NULL);
 +      Lst_Destroy(variables, NULL);
 +      Lst_Destroy(makefiles, NULL);
 +      Lst_Destroy(create, (FreeProc *)free);
 +#endif
 +
 +      /* print the graph now it's been processed if the user requested it */
 +      if (DEBUG(GRAPH2))
 +              Targ_PrintGraph(2);
 +
 +      Trace_Log(MAKEEND, 0);
 +
++      if (enterFlagObj)
++              printf("%s: Leaving directory `%s'\n", progname, objdir);
 +      if (enterFlag)
 +              printf("%s: Leaving directory `%s'\n", progname, curdir);
 +
 +      Suff_End();
 +        Targ_End();
 +      Arch_End();
 +      Var_End();
 +      Parse_End();
 +      Dir_End();
 +      Job_End();
 +      Trace_End();
 +
 +      return outOfDate ? 1 : 0;
 +}
 +
 +/*-
 + * ReadMakefile  --
 + *    Open and parse the given makefile.
 + *
 + * Results:
 + *    0 if ok. -1 if couldn't open file.
 + *
 + * Side Effects:
 + *    lots
 + */
 +static int
 +ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED)
 +{
 +      const char *fname = p;          /* makefile to read */
 +      int fd;
 +      size_t len = MAXPATHLEN;
 +      char *name, *path = bmake_malloc(len);
 +
 +      if (!strcmp(fname, "-")) {
 +              Parse_File(NULL /*stdin*/, -1);
 +              Var_Set("MAKEFILE", "", VAR_INTERNAL, 0);
 +      } else {
 +              /* if we've chdir'd, rebuild the path name */
 +              if (strcmp(curdir, objdir) && *fname != '/') {
 +                      size_t plen = strlen(curdir) + strlen(fname) + 2;
 +                      if (len < plen)
 +                              path = bmake_realloc(path, len = 2 * plen);
 +                      
 +                      (void)snprintf(path, len, "%s/%s", curdir, fname);
 +                      fd = open(path, O_RDONLY);
 +                      if (fd != -1) {
 +                              fname = path;
 +                              goto found;
 +                      }
 +                      
 +                      /* If curdir failed, try objdir (ala .depend) */
 +                      plen = strlen(objdir) + strlen(fname) + 2;
 +                      if (len < plen)
 +                              path = bmake_realloc(path, len = 2 * plen);
 +                      (void)snprintf(path, len, "%s/%s", objdir, fname);
 +                      fd = open(path, O_RDONLY);
 +                      if (fd != -1) {
 +                              fname = path;
 +                              goto found;
 +                      }
 +              } else {
 +                      fd = open(fname, O_RDONLY);
 +                      if (fd != -1)
 +                              goto found;
 +              }
 +              /* look in -I and system include directories. */
 +              name = Dir_FindFile(fname, parseIncPath);
 +              if (!name)
 +                      name = Dir_FindFile(fname,
 +                              Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
 +              if (!name || (fd = open(name, O_RDONLY)) == -1) {
 +                      if (name)
 +                              free(name);
 +                      free(path);
 +                      return(-1);
 +              }
 +              fname = name;
 +              /*
 +               * set the MAKEFILE variable desired by System V fans -- the
 +               * placement of the setting here means it gets set to the last
 +               * makefile specified, as it is set by SysV make.
 +               */
 +found:
 +              if (!doing_depend)
 +                      Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0);
 +              Parse_File(fname, fd);
 +      }
 +      free(path);
 +      return(0);
 +}
 +
 +
 +
 +/*-
 + * Cmd_Exec --
 + *    Execute the command in cmd, and return the output of that command
 + *    in a string.
 + *
 + * Results:
 + *    A string containing the output of the command, or the empty string
 + *    If errnum is not NULL, it contains the reason for the command failure
 + *
 + * Side Effects:
 + *    The string must be freed by the caller.
 + */
 +char *
 +Cmd_Exec(const char *cmd, const char **errnum)
 +{
 +    const char        *args[4];       /* Args for invoking the shell */
 +    int       fds[2];         /* Pipe streams */
 +    int       cpid;           /* Child PID */
 +    int       pid;            /* PID from wait() */
 +    char      *res;           /* result */
 +    WAIT_T    status;         /* command exit status */
 +    Buffer    buf;            /* buffer to store the result */
 +    char      *cp;
 +    int               cc;             /* bytes read, or -1 */
 +    int               savederr;       /* saved errno */
 +
 +
 +    *errnum = NULL;
 +
 +    if (!shellName)
 +      Shell_Init();
 +    /*
 +     * Set up arguments for shell
 +     */
 +    args[0] = shellName;
 +    args[1] = "-c";
 +    args[2] = cmd;
 +    args[3] = NULL;
 +
 +    /*
 +     * Open a pipe for fetching its output
 +     */
 +    if (pipe(fds) == -1) {
 +      *errnum = "Couldn't create pipe for \"%s\"";
 +      goto bad;
 +    }
 +
 +    /*
 +     * Fork
 +     */
 +    switch (cpid = vFork()) {
 +    case 0:
 +      /*
 +       * Close input side of pipe
 +       */
 +      (void)close(fds[0]);
 +
 +      /*
 +       * Duplicate the output stream to the shell's output, then
 +       * shut the extra thing down. Note we don't fetch the error
 +       * stream...why not? Why?
 +       */
 +      (void)dup2(fds[1], 1);
 +      (void)close(fds[1]);
 +
 +      Var_ExportVars();
 +
 +      (void)execv(shellPath, UNCONST(args));
 +      _exit(1);
 +      /*NOTREACHED*/
 +
 +    case -1:
 +      *errnum = "Couldn't exec \"%s\"";
 +      goto bad;
 +
 +    default:
 +      /*
 +       * No need for the writing half
 +       */
 +      (void)close(fds[1]);
 +
 +      savederr = 0;
 +      Buf_Init(&buf, 0);
 +
 +      do {
 +          char   result[BUFSIZ];
 +          cc = read(fds[0], result, sizeof(result));
 +          if (cc > 0)
 +              Buf_AddBytes(&buf, cc, result);
 +      }
 +      while (cc > 0 || (cc == -1 && errno == EINTR));
 +      if (cc == -1)
 +          savederr = errno;
 +
 +      /*
 +       * Close the input side of the pipe.
 +       */
 +      (void)close(fds[0]);
 +
 +      /*
 +       * Wait for the process to exit.
 +       */
 +      while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) {
 +          JobReapChild(pid, status, FALSE);
 +          continue;
 +      }
 +      cc = Buf_Size(&buf);
 +      res = Buf_Destroy(&buf, FALSE);
 +
 +      if (savederr != 0)
 +          *errnum = "Couldn't read shell's output for \"%s\"";
 +
 +      if (WIFSIGNALED(status))
 +          *errnum = "\"%s\" exited on a signal";
 +      else if (WEXITSTATUS(status) != 0)
 +          *errnum = "\"%s\" returned non-zero status";
 +
 +      /*
 +       * Null-terminate the result, convert newlines to spaces and
 +       * install it in the variable.
 +       */
 +      res[cc] = '\0';
 +      cp = &res[cc];
 +
 +      if (cc > 0 && *--cp == '\n') {
 +          /*
 +           * A final newline is just stripped
 +           */
 +          *cp-- = '\0';
 +      }
 +      while (cp >= res) {
 +          if (*cp == '\n') {
 +              *cp = ' ';
 +          }
 +          cp--;
 +      }
 +      break;
 +    }
 +    return res;
 +bad:
 +    res = bmake_malloc(1);
 +    *res = '\0';
 +    return res;
 +}
 +
 +/*-
 + * Error --
 + *    Print an error message given its format.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The message is printed.
 + */
 +/* VARARGS */
 +void
 +Error(const char *fmt, ...)
 +{
 +      va_list ap;
 +      FILE *err_file;
 +
 +      err_file = debug_file;
 +      if (err_file == stdout)
 +              err_file = stderr;
 +      (void)fflush(stdout);
 +      for (;;) {
 +              va_start(ap, fmt);
 +              fprintf(err_file, "%s: ", progname);
 +              (void)vfprintf(err_file, fmt, ap);
 +              va_end(ap);
 +              (void)fprintf(err_file, "\n");
 +              (void)fflush(err_file);
 +              if (err_file == stderr)
 +                      break;
 +              err_file = stderr;
 +      }
 +}
 +
 +/*-
 + * Fatal --
 + *    Produce a Fatal error message. If jobs are running, waits for them
 + *    to finish.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The program exits
 + */
 +/* VARARGS */
 +void
 +Fatal(const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      if (jobsRunning)
 +              Job_Wait();
 +
 +      (void)fflush(stdout);
 +      (void)vfprintf(stderr, fmt, ap);
 +      va_end(ap);
 +      (void)fprintf(stderr, "\n");
 +      (void)fflush(stderr);
 +
 +      PrintOnError(NULL, NULL);
 +
 +      if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
 +              Targ_PrintGraph(2);
 +      Trace_Log(MAKEERROR, 0);
 +      exit(2);                /* Not 1 so -q can distinguish error */
 +}
 +
 +/*
 + * Punt --
 + *    Major exception once jobs are being created. Kills all jobs, prints
 + *    a message and exits.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    All children are killed indiscriminately and the program Lib_Exits
 + */
 +/* VARARGS */
 +void
 +Punt(const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      (void)fflush(stdout);
 +      (void)fprintf(stderr, "%s: ", progname);
 +      (void)vfprintf(stderr, fmt, ap);
 +      va_end(ap);
 +      (void)fprintf(stderr, "\n");
 +      (void)fflush(stderr);
 +
 +      PrintOnError(NULL, NULL);
 +
 +      DieHorribly();
 +}
 +
 +/*-
 + * DieHorribly --
 + *    Exit without giving a message.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    A big one...
 + */
 +void
 +DieHorribly(void)
 +{
 +      if (jobsRunning)
 +              Job_AbortAll();
 +      if (DEBUG(GRAPH2))
 +              Targ_PrintGraph(2);
 +      Trace_Log(MAKEERROR, 0);
 +      exit(2);                /* Not 1, so -q can distinguish error */
 +}
 +
 +/*
 + * Finish --
 + *    Called when aborting due to errors in child shell to signal
 + *    abnormal exit.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The program exits
 + */
 +void
 +Finish(int errors)
 +                      /* number of errors encountered in Make_Make */
 +{
 +      Fatal("%d error%s", errors, errors == 1 ? "" : "s");
 +}
 +
 +/*
 + * eunlink --
 + *    Remove a file carefully, avoiding directories.
 + */
 +int
 +eunlink(const char *file)
 +{
 +      struct stat st;
 +
 +      if (lstat(file, &st) == -1)
 +              return -1;
 +
 +      if (S_ISDIR(st.st_mode)) {
 +              errno = EISDIR;
 +              return -1;
 +      }
 +      return unlink(file);
 +}
 +
 +/*
 + * execError --
 + *    Print why exec failed, avoiding stdio.
 + */
 +void
 +execError(const char *af, const char *av)
 +{
 +#ifdef USE_IOVEC
 +      int i = 0;
 +      struct iovec iov[8];
 +#define IOADD(s) \
 +      (void)(iov[i].iov_base = UNCONST(s), \
 +          iov[i].iov_len = strlen(iov[i].iov_base), \
 +          i++)
 +#else
 +#define       IOADD(s) (void)write(2, s, strlen(s))
 +#endif
 +
 +      IOADD(progname);
 +      IOADD(": ");
 +      IOADD(af);
 +      IOADD("(");
 +      IOADD(av);
 +      IOADD(") failed (");
 +      IOADD(strerror(errno));
 +      IOADD(")\n");
 +
 +#ifdef USE_IOVEC
 +      while (writev(2, iov, 8) == -1 && errno == EAGAIN)
 +          continue;
 +#endif
 +}
 +
 +/*
 + * usage --
 + *    exit with usage message
 + */
 +static void
 +usage(void)
 +{
 +      char *p;
 +      if ((p = strchr(progname, '[')) != NULL)
 +          *p = '\0';
 +
 +      (void)fprintf(stderr,
 +"usage: %s [-BeikNnqrstWwX] \n\
 +            [-C directory] [-D variable] [-d flags] [-f makefile]\n\
 +            [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\
 +            [-V variable] [variable=value] [target ...]\n", progname);
 +      exit(2);
 +}
 +
 +
 +int
 +PrintAddr(void *a, void *b)
 +{
 +    printf("%lx ", (unsigned long) a);
 +    return b ? 0 : 0;
 +}
 +
 +
 +
 +void
 +PrintOnError(GNode *gn, const char *s)
 +{
 +    static GNode *en = NULL;
 +    char tmp[64];
 +    char *cp;
 +
 +    if (s)
 +      printf("%s", s);
 +      
 +    printf("\n%s: stopped in %s\n", progname, curdir);
 +
 +    if (en)
 +      return;                         /* we've been here! */
 +    if (gn) {
 +      /*
 +       * We can print this even if there is no .ERROR target.
 +       */
 +      Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0);
 +    }
 +    strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
 +          sizeof(tmp) - 1);
-     cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++    cp = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +    if (cp) {
 +      if (*cp)
 +          printf("%s", cp);
 +      free(cp);
 +    }
 +    /*
 +     * Finally, see if there is a .ERROR target, and run it if so.
 +     */
 +    en = Targ_FindNode(".ERROR", TARG_NOCREATE);
 +    if (en) {
 +      en->type |= OP_SPECIAL;
 +      Compat_Make(en, en);
 +    }
 +}
 +
 +void
 +Main_ExportMAKEFLAGS(Boolean first)
 +{
 +    static int once = 1;
 +    char tmp[64];
 +    char *s;
 +
 +    if (once != first)
 +      return;
 +    once = 0;
 +    
 +    strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
 +          sizeof(tmp));
-     s = Var_Subst(NULL, tmp, VAR_CMD, 0);
++    s = Var_Subst(NULL, tmp, VAR_CMD, FALSE, TRUE);
 +    if (s && *s) {
 +#ifdef POSIX
 +      setenv("MAKEFLAGS", s, 1);
 +#else
 +      setenv("MAKE", s, 1);
 +#endif
 +    }
 +}
 +
 +char *
 +getTmpdir(void)
 +{
 +    static char *tmpdir = NULL;
 +
 +    if (!tmpdir) {
 +      struct stat st;
 +
 +      /*
 +       * Honor $TMPDIR but only if it is valid.
 +       * Ensure it ends with /.
 +       */
-       tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, 0);
++      tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL,
++                         FALSE, TRUE);
 +      if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) {
 +          free(tmpdir);
 +          tmpdir = bmake_strdup(_PATH_TMP);
 +      }
 +    }
 +    return tmpdir;
 +}
 +
 +/*
 + * Create and open a temp file using "pattern".
 + * If "fnamep" is provided set it to a copy of the filename created.
 + * Otherwise unlink the file once open.
 + */
 +int
 +mkTempFile(const char *pattern, char **fnamep)
 +{
 +    static char *tmpdir = NULL;
 +    char tfile[MAXPATHLEN];
 +    int fd;
 +    
 +    if (!pattern)
 +      pattern = TMPPAT;
 +    if (!tmpdir)
 +      tmpdir = getTmpdir();
 +    if (pattern[0] == '/') {
 +      snprintf(tfile, sizeof(tfile), "%s", pattern);
 +    } else {
 +      snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern);
 +    }
 +    if ((fd = mkstemp(tfile)) < 0)
 +      Punt("Could not create temporary file %s: %s", tfile, strerror(errno));
 +    if (fnamep) {
 +      *fnamep = bmake_strdup(tfile);
 +    } else {
 +      unlink(tfile);                  /* we just want the descriptor */
 +    }
 +    return fd;
 +}
 +
 +
 +/*
 + * Return a Boolean based on setting of a knob.
 + *
 + * If the knob is not set, the supplied default is the return value.
 + * If set, anything that looks or smells like "No", "False", "Off", "0" etc,
 + * is FALSE, otherwise TRUE.
 + */
 +Boolean
 +getBoolean(const char *name, Boolean bf)
 +{
 +    char tmp[64];
 +    char *cp;
 +
 +    if (snprintf(tmp, sizeof(tmp), "${%s:tl}", name) < (int)(sizeof(tmp))) {
-       cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++      cp = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +
 +      if (cp) {
 +          switch(*cp) {
 +          case '\0':                  /* not set - the default wins */
 +              break;
 +          case '0':
 +          case 'f':
 +          case 'n':
 +              bf = FALSE;
 +              break;
 +          case 'o':
 +              switch (cp[1]) {
 +              case 'f':
 +                  bf = FALSE;
 +                  break;
 +              default:
 +                  bf = TRUE;
 +                  break;
 +              }
 +              break;
 +          default:
 +              bf = TRUE;
 +              break;
 +          }
 +          free(cp);
 +      }
 +    }
 +    return (bf);
 +}
index 2bb4c25a7058cf1f1eef01a9de3aaae83d78ccac,0000000000000000000000000000000000000000..638e29319ba2488db7b70e4b3d337bb9c2df7b6c
mode 100755,000000..100755
--- /dev/null
@@@ -1,84 -1,0 +1,84 @@@
- job.o make.o make_malloc.o parse.o sigcompat.o str.o strlist.o \
 +#!/bin/sh
 +
 +set -e
 +
 +srcdir=@srcdir@
 +
 +DEFAULT_SYS_PATH="@default_sys_path@"
 +
 +case "@use_meta@" in
 +yes) XDEFS="-DUSE_META ${XDEFS}";;
 +esac
 +
 +CC="@CC@"
 +CFLAGS="@CFLAGS@ -I. -I${srcdir} @DEFS@ @CPPFLAGS@ -DMAKE_NATIVE ${XDEFS} -DBMAKE_PATH_MAX=@bmake_path_max@"
 +
 +MAKE_VERSION=`sed -n '/^MAKE_VERSION=/s,.*=[^0-9]*,,p' $srcdir/Makefile`
 +
 +MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \
 +-D@force_machine@MACHINE=\"@machine@\" -DMACHINE_ARCH=\"@machine_arch@\" \
 +-D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\""
 +
 +
 +LDFLAGS="@LDFLAGS@"
 +LIBS="@LIBS@"
 +
 +do_compile2() {
 +      obj="$1"; shift
 +      src="$1"; shift
 +      echo ${CC} -c ${CFLAGS} "$@" -o "$obj" "$src"
 +      ${CC} -c ${CFLAGS} "$@" -o "$obj" "$src"
 +}
 +
 +do_compile() {
 +      obj="$1"; shift
 +      src=`basename "$obj" .o`.c
 +
 +      for d in "$srcdir" "$srcdir/lst.lib"
 +      do
 +              test -s "$d/$src" || continue
 +
 +              do_compile2 "$obj" "$d/$src" "$@" || exit 1
 +              return
 +      done
 +      echo "Unknown object file '$obj'" >&2
 +      exit 1
 +}
 +
 +do_link() {
 +      output="$1"; shift
 +      echo ${CC} ${LDSTATIC} ${LDFLAGS} -o "$output" "$@" ${LIBS}
 +      ${CC} ${LDSTATIC} ${LDFLAGS} -o "$output" "$@" ${LIBS}
 +}
 +
 +BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o for.o getopt hash.o \
++job.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \
 +suff.o targ.o trace.o var.o util.o"
 +
 +LST_OBJECTS="lstAppend.o lstDupl.o lstInit.o lstOpen.o \
 +lstAtEnd.o lstEnQueue.o lstInsert.o lstAtFront.o lstIsAtEnd.o \
 +lstClose.o lstFind.o lstIsEmpty.o lstRemove.o lstConcat.o \
 +lstFindFrom.o lstLast.o lstReplace.o lstFirst.o lstDatum.o \
 +lstForEach.o lstMember.o lstSucc.o lstDeQueue.o lstForEachFrom.o \
 +lstDestroy.o lstNext.o lstPrev.o"
 +
 +LIB_OBJECTS="@LIBOBJS@"
 +
 +do_compile main.o ${MDEFS}
 +
 +for o in ${BASE_OBJECTS} ${LST_OBJECTS} ${LIB_OBJECTS}
 +do
 +      do_compile "$o"
 +done
 +
 +case "@use_meta@" in
 +yes)
 +        case "@filemon_h@" in
 +      */filemon.h) FDEFS="-DHAVE_FILEMON_H -I`dirname @filemon_h@`";;
 +      esac
 +        do_compile meta.o ${FDEFS}
 +        BASE_OBJECTS="meta.o ${BASE_OBJECTS}"
 +        ;;
 +esac
 +
 +do_link bmake main.o ${BASE_OBJECTS} ${LST_OBJECTS} ${LIB_OBJECTS}
index fde185065fab67c68a32da408125b8a856b15c97,0000000000000000000000000000000000000000..b43bfd578cf0516bd115512335371c4f7622ac76
mode 100644,000000..100644
--- /dev/null
@@@ -1,1561 -1,0 +1,1561 @@@
- /*    $NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $    */
++/*    $NetBSD: make.c,v 1.92 2015/10/11 04:51:24 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $";
++static char rcsid[] = "$NetBSD: make.c,v 1.92 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)make.c    8.1 (Berkeley) 6/6/93";
 +#else
- __RCSID("$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $");
++__RCSID("$NetBSD: make.c,v 1.92 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * make.c --
 + *    The functions which perform the examination of targets and
 + *    their suitability for creation
 + *
 + * Interface:
 + *    Make_Run                Initialize things for the module and recreate
 + *                            whatever needs recreating. Returns TRUE if
 + *                            work was (or would have been) done and FALSE
 + *                            otherwise.
 + *
 + *    Make_Update             Update all parents of a given child. Performs
 + *                            various bookkeeping chores like the updating
 + *                            of the cmgn field of the parent, filling
 + *                            of the IMPSRC context variable, etc. It will
 + *                            place the parent on the toBeMade queue if it
 + *                            should be.
 + *
 + *    Make_TimeStamp          Function to set the parent's cmgn field
 + *                            based on a child's modification time.
 + *
 + *    Make_DoAllVar           Set up the various local variables for a
 + *                            target, including the .ALLSRC variable, making
 + *                            sure that any variable that needs to exist
 + *                            at the very least has the empty value.
 + *
 + *    Make_OODate             Determine if a target is out-of-date.
 + *
 + *    Make_HandleUse          See if a child is a .USE node for a parent
 + *                            and perform the .USE actions if so.
 + *
 + *    Make_ExpandUse          Expand .USE nodes
 + */
 +
 +#include    "make.h"
 +#include    "hash.h"
 +#include    "dir.h"
 +#include    "job.h"
 +
 +static unsigned int checked = 1;/* Sequence # to detect recursion */
 +static Lst            toBeMade;       /* The current fringe of the graph. These
 +                               * are nodes which await examination by
 +                               * MakeOODate. It is added to by
 +                               * Make_Update and subtracted from by
 +                               * MakeStartJobs */
 +
 +static int MakeAddChild(void *, void *);
 +static int MakeFindChild(void *, void *);
 +static int MakeUnmark(void *, void *);
 +static int MakeAddAllSrc(void *, void *);
 +static int MakeTimeStamp(void *, void *);
 +static int MakeHandleUse(void *, void *);
 +static Boolean MakeStartJobs(void);
 +static int MakePrintStatus(void *, void *);
 +static int MakeCheckOrder(void *, void *);
 +static int MakeBuildChild(void *, void *);
 +static int MakeBuildParent(void *, void *);
 +
 +MAKE_ATTR_DEAD static void
 +make_abort(GNode *gn, int line)
 +{
 +    static int two = 2;
 +
 +    fprintf(debug_file, "make_abort from line %d\n", line);
 +    Targ_PrintNode(gn, &two);
 +    Lst_ForEach(toBeMade, Targ_PrintNode, &two);
 +    Targ_PrintGraph(3);
 +    abort();
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_TimeStamp --
 + *    Set the cmgn field of a parent node based on the mtime stamp in its
 + *    child. Called from MakeOODate via Lst_ForEach.
 + *
 + * Input:
 + *    pgn             the current parent
 + *    cgn             the child we've just examined
 + *
 + * Results:
 + *    Always returns 0.
 + *
 + * Side Effects:
 + *    The cmgn of the parent node will be changed if the mtime
 + *    field of the child is greater than it.
 + *-----------------------------------------------------------------------
 + */
 +int
 +Make_TimeStamp(GNode *pgn, GNode *cgn)
 +{
 +    if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) {
 +      pgn->cmgn = cgn;
 +    }
 +    return (0);
 +}
 +
 +/*
 + * Input:
 + *    pgn             the current parent
 + *    cgn             the child we've just examined
 + *
 + */
 +static int
 +MakeTimeStamp(void *pgn, void *cgn)
 +{
 +    return Make_TimeStamp((GNode *)pgn, (GNode *)cgn);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_OODate --
 + *    See if a given node is out of date with respect to its sources.
 + *    Used by Make_Run when deciding which nodes to place on the
 + *    toBeMade queue initially and by Make_Update to screen out USE and
 + *    EXEC nodes. In the latter case, however, any other sort of node
 + *    must be considered out-of-date since at least one of its children
 + *    will have been recreated.
 + *
 + * Input:
 + *    gn              the node to check
 + *
 + * Results:
 + *    TRUE if the node is out of date. FALSE otherwise.
 + *
 + * Side Effects:
 + *    The mtime field of the node and the cmgn field of its parents
 + *    will/may be changed.
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Make_OODate(GNode *gn)
 +{
 +    Boolean         oodate;
 +
 +    /*
 +     * Certain types of targets needn't even be sought as their datedness
 +     * doesn't depend on their modification time...
 +     */
 +    if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) {
 +      (void)Dir_MTime(gn, 1);
 +      if (DEBUG(MAKE)) {
 +          if (gn->mtime != 0) {
 +              fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime));
 +          } else {
 +              fprintf(debug_file, "non-existent...");
 +          }
 +      }
 +    }
 +
 +    /*
 +     * A target is remade in one of the following circumstances:
 +     *        its modification time is smaller than that of its youngest child
 +     *            and it would actually be run (has commands or type OP_NOP)
 +     *        it's the object of a force operator
 +     *        it has no children, was on the lhs of an operator and doesn't exist
 +     *            already.
 +     *
 +     * Libraries are only considered out-of-date if the archive module says
 +     * they are.
 +     *
 +     * These weird rules are brought to you by Backward-Compatibility and
 +     * the strange people who wrote 'Make'.
 +     */
 +    if (gn->type & (OP_USE|OP_USEBEFORE)) {
 +      /*
 +       * If the node is a USE node it is *never* out of date
 +       * no matter *what*.
 +       */
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, ".USE node...");
 +      }
 +      oodate = FALSE;
 +    } else if ((gn->type & OP_LIB) &&
 +             ((gn->mtime==0) || Arch_IsLib(gn))) {
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, "library...");
 +      }
 +
 +      /*
 +       * always out of date if no children and :: target
 +       * or non-existent.
 +       */
 +      oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || 
 +                (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP)));
 +    } else if (gn->type & OP_JOIN) {
 +      /*
 +       * A target with the .JOIN attribute is only considered
 +       * out-of-date if any of its children was out-of-date.
 +       */
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, ".JOIN node...");
 +      }
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
 +      }
 +      oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE;
 +    } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
 +      /*
 +       * A node which is the object of the force (!) operator or which has
 +       * the .EXEC attribute is always considered out-of-date.
 +       */
 +      if (DEBUG(MAKE)) {
 +          if (gn->type & OP_FORCE) {
 +              fprintf(debug_file, "! operator...");
 +          } else if (gn->type & OP_PHONY) {
 +              fprintf(debug_file, ".PHONY node...");
 +          } else {
 +              fprintf(debug_file, ".EXEC node...");
 +          }
 +      }
 +      oodate = TRUE;
 +    } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) ||
 +             (gn->cmgn == NULL &&
 +              ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
 +                || gn->type & OP_DOUBLEDEP)))
 +    {
 +      /*
 +       * A node whose modification time is less than that of its
 +       * youngest child or that has no children (cmgn == NULL) and
 +       * either doesn't exist (mtime == 0) and it isn't optional
 +       * or was the object of a * :: operator is out-of-date.
 +       * Why? Because that's the way Make does it.
 +       */
 +      if (DEBUG(MAKE)) {
 +          if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) {
 +              fprintf(debug_file, "modified before source %s...",
 +                  gn->cmgn->path);
 +          } else if (gn->mtime == 0) {
 +              fprintf(debug_file, "non-existent and no sources...");
 +          } else {
 +              fprintf(debug_file, ":: operator and no sources...");
 +          }
 +      }
 +      oodate = TRUE;
 +    } else {
 +      /* 
 +       * When a non-existing child with no sources
 +       * (such as a typically used FORCE source) has been made and
 +       * the target of the child (usually a directory) has the same
 +       * timestamp as the timestamp just given to the non-existing child
 +       * after it was considered made.
 +       */
 +      if (DEBUG(MAKE)) {
 +          if (gn->flags & FORCE)
 +              fprintf(debug_file, "non existing child...");
 +      }
 +      oodate = (gn->flags & FORCE) ? TRUE : FALSE;
 +    }
 +
 +#ifdef USE_META
 +    if (useMeta) {
 +      oodate = meta_oodate(gn, oodate);
 +    }
 +#endif
 +
 +    /*
 +     * If the target isn't out-of-date, the parents need to know its
 +     * modification time. Note that targets that appear to be out-of-date
 +     * but aren't, because they have no commands and aren't of type OP_NOP,
 +     * have their mtime stay below their children's mtime to keep parents from
 +     * thinking they're out-of-date.
 +     */
 +    if (!oodate) {
 +      Lst_ForEach(gn->parents, MakeTimeStamp, gn);
 +    }
 +
 +    return (oodate);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * MakeAddChild  --
 + *    Function used by Make_Run to add a child to the list l.
 + *    It will only add the child if its make field is FALSE.
 + *
 + * Input:
 + *    gnp             the node to add
 + *    lp              the list to which to add it
 + *
 + * Results:
 + *    Always returns 0
 + *
 + * Side Effects:
 + *    The given list is extended
 + *-----------------------------------------------------------------------
 + */
 +static int
 +MakeAddChild(void *gnp, void *lp)
 +{
 +    GNode          *gn = (GNode *)gnp;
 +    Lst            l = (Lst) lp;
 +
 +    if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
 +      if (DEBUG(MAKE))
 +          fprintf(debug_file, "MakeAddChild: need to examine %s%s\n",
 +              gn->name, gn->cohort_num);
 +      (void)Lst_EnQueue(l, gn);
 +    }
 +    return (0);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * MakeFindChild  --
 + *    Function used by Make_Run to find the pathname of a child
 + *    that was already made.
 + *
 + * Input:
 + *    gnp             the node to find
 + *
 + * Results:
 + *    Always returns 0
 + *
 + * Side Effects:
 + *    The path and mtime of the node and the cmgn of the parent are
 + *    updated; the unmade children count of the parent is decremented.
 + *-----------------------------------------------------------------------
 + */
 +static int
 +MakeFindChild(void *gnp, void *pgnp)
 +{
 +    GNode          *gn = (GNode *)gnp;
 +    GNode          *pgn = (GNode *)pgnp;
 +
 +    (void)Dir_MTime(gn, 0);
 +    Make_TimeStamp(pgn, gn);
 +    pgn->unmade--;
 +
 +    return (0);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_HandleUse --
 + *    Function called by Make_Run and SuffApplyTransform on the downward
 + *    pass to handle .USE and transformation nodes. It implements the
 + *    .USE and transformation functionality by copying the node's commands,
 + *    type flags and children to the parent node.
 + *
 + *    A .USE node is much like an explicit transformation rule, except
 + *    its commands are always added to the target node, even if the
 + *    target already has commands.
 + *
 + * Input:
 + *    cgn             The .USE node
 + *    pgn             The target of the .USE node
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    Children and commands may be added to the parent and the parent's
 + *    type may be changed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Make_HandleUse(GNode *cgn, GNode *pgn)
 +{
 +    LstNode   ln;     /* An element in the children list */
 +
 +#ifdef DEBUG_SRC
 +    if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) {
 +      fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name);
 +      return;
 +    }
 +#endif
 +
 +    if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) {
 +          if (cgn->type & OP_USEBEFORE) {
 +              /*
 +               * .USEBEFORE --
 +               *      prepend the child's commands to the parent.
 +               */
 +              Lst cmds = pgn->commands;
 +              pgn->commands = Lst_Duplicate(cgn->commands, NULL);
 +              (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW);
 +              Lst_Destroy(cmds, NULL);
 +          } else {
 +              /*
 +               * .USE or target has no commands --
 +               *      append the child's commands to the parent.
 +               */
 +              (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW);
 +          }
 +    }
 +
 +    if (Lst_Open(cgn->children) == SUCCESS) {
 +      while ((ln = Lst_Next(cgn->children)) != NULL) {
 +          GNode *tgn, *gn = (GNode *)Lst_Datum(ln);
 +
 +          /*
 +           * Expand variables in the .USE node's name
 +           * and save the unexpanded form.
 +           * We don't need to do this for commands.
 +           * They get expanded properly when we execute.
 +           */
 +          if (gn->uname == NULL) {
 +              gn->uname = gn->name;
 +          } else {
 +              if (gn->name)
 +                  free(gn->name);
 +          }
-           gn->name = Var_Subst(NULL, gn->uname, pgn, FALSE);
++          gn->name = Var_Subst(NULL, gn->uname, pgn, FALSE, TRUE);
 +          if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) {
 +              /* See if we have a target for this node. */
 +              tgn = Targ_FindNode(gn->name, TARG_NOCREATE);
 +              if (tgn != NULL)
 +                  gn = tgn;
 +          }
 +
 +          (void)Lst_AtEnd(pgn->children, gn);
 +          (void)Lst_AtEnd(gn->parents, pgn);
 +          pgn->unmade += 1;
 +      }
 +      Lst_Close(cgn->children);
 +    }
 +
 +    pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * MakeHandleUse --
 + *    Callback function for Lst_ForEach, used by Make_Run on the downward
 + *    pass to handle .USE nodes. Should be called before the children
 + *    are enqueued to be looked at by MakeAddChild.
 + *    This function calls Make_HandleUse to copy the .USE node's commands,
 + *    type flags and children to the parent node.
 + *
 + * Input:
 + *    cgnp            the child we've just examined
 + *    pgnp            the current parent
 + *
 + * Results:
 + *    returns 0.
 + *
 + * Side Effects:
 + *    After expansion, .USE child nodes are removed from the parent
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +MakeHandleUse(void *cgnp, void *pgnp)
 +{
 +    GNode     *cgn = (GNode *)cgnp;
 +    GNode     *pgn = (GNode *)pgnp;
 +    LstNode   ln;     /* An element in the children list */
 +    int               unmarked;
 +
 +    unmarked = ((cgn->type & OP_MARK) == 0);
 +    cgn->type |= OP_MARK;
 +
 +    if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
 +      return (0);
 +
 +    if (unmarked)
 +      Make_HandleUse(cgn, pgn);
 +
 +    /*
 +     * This child node is now "made", so we decrement the count of
 +     * unmade children in the parent... We also remove the child
 +     * from the parent's list to accurately reflect the number of decent
 +     * children the parent has. This is used by Make_Run to decide
 +     * whether to queue the parent or examine its children...
 +     */
 +    if ((ln = Lst_Member(pgn->children, cgn)) != NULL) {
 +      Lst_Remove(pgn->children, ln);
 +      pgn->unmade--;
 +    }
 +    return (0);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_Recheck --
 + *    Check the modification time of a gnode, and update it as described
 + *    in the comments below.
 + *
 + * Results:
 + *    returns 0 if the gnode does not exist, or its filesystem
 + *    time if it does.
 + *
 + * Side Effects:
 + *    the gnode's modification time and path name are affected.
 + *
 + *-----------------------------------------------------------------------
 + */
 +time_t
 +Make_Recheck(GNode *gn)
 +{
 +    time_t mtime = Dir_MTime(gn, 1);
 +
 +#ifndef RECHECK
 +    /*
 +     * We can't re-stat the thing, but we can at least take care of rules
 +     * where a target depends on a source that actually creates the
 +     * target, but only if it has changed, e.g.
 +     *
 +     * parse.h : parse.o
 +     *
 +     * parse.o : parse.y
 +     *        yacc -d parse.y
 +     *        cc -c y.tab.c
 +     *        mv y.tab.o parse.o
 +     *        cmp -s y.tab.h parse.h || mv y.tab.h parse.h
 +     *
 +     * In this case, if the definitions produced by yacc haven't changed
 +     * from before, parse.h won't have been updated and gn->mtime will
 +     * reflect the current modification time for parse.h. This is
 +     * something of a kludge, I admit, but it's a useful one..
 +     * XXX: People like to use a rule like
 +     *
 +     * FRC:
 +     *
 +     * To force things that depend on FRC to be made, so we have to
 +     * check for gn->children being empty as well...
 +     */
 +    if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
 +      gn->mtime = now;
 +    }
 +#else
 +    /*
 +     * This is what Make does and it's actually a good thing, as it
 +     * allows rules like
 +     *
 +     *        cmp -s y.tab.h parse.h || cp y.tab.h parse.h
 +     *
 +     * to function as intended. Unfortunately, thanks to the stateless
 +     * nature of NFS (by which I mean the loose coupling of two clients
 +     * using the same file from a common server), there are times
 +     * when the modification time of a file created on a remote
 +     * machine will not be modified before the local stat() implied by
 +     * the Dir_MTime occurs, thus leading us to believe that the file
 +     * is unchanged, wreaking havoc with files that depend on this one.
 +     *
 +     * I have decided it is better to make too much than to make too
 +     * little, so this stuff is commented out unless you're sure it's ok.
 +     * -- ardeb 1/12/88
 +     */
 +    /*
 +     * Christos, 4/9/92: If we are  saving commands pretend that
 +     * the target is made now. Otherwise archives with ... rules
 +     * don't work!
 +     */
 +    if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
 +          (mtime == 0 && !(gn->type & OP_WAIT))) {
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, " recheck(%s): update time from %s to now\n",
 +                 gn->name, Targ_FmtTime(gn->mtime));
 +      }
 +      gn->mtime = now;
 +    }
 +    else {
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, " recheck(%s): current update time: %s\n",
 +                 gn->name, Targ_FmtTime(gn->mtime));
 +      }
 +    }
 +#endif
 +    return mtime;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_Update  --
 + *    Perform update on the parents of a node. Used by JobFinish once
 + *    a node has been dealt with and by MakeStartJobs if it finds an
 + *    up-to-date node.
 + *
 + * Input:
 + *    cgn             the child node
 + *
 + * Results:
 + *    Always returns 0
 + *
 + * Side Effects:
 + *    The unmade field of pgn is decremented and pgn may be placed on
 + *    the toBeMade queue if this field becomes 0.
 + *
 + *    If the child was made, the parent's flag CHILDMADE field will be
 + *    set true.
 + *
 + *    If the child is not up-to-date and still does not exist,
 + *    set the FORCE flag on the parents.
 + *
 + *    If the child wasn't made, the cmgn field of the parent will be
 + *    altered if the child's mtime is big enough.
 + *
 + *    Finally, if the child is the implied source for the parent, the
 + *    parent's IMPSRC variable is set appropriately.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Make_Update(GNode *cgn)
 +{
 +    GNode     *pgn;   /* the parent node */
 +    char      *cname; /* the child's name */
 +    LstNode   ln;     /* Element in parents and iParents lists */
 +    time_t    mtime = -1;
 +    char      *p1;
 +    Lst               parents;
 +    GNode     *centurion;
 +
 +    /* It is save to re-examine any nodes again */
 +    checked++;
 +
 +    cname = Var_Value(TARGET, cgn, &p1);
 +    if (p1)
 +      free(p1);
 +
 +    if (DEBUG(MAKE))
 +      fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
 +
 +    /*
 +     * If the child was actually made, see what its modification time is
 +     * now -- some rules won't actually update the file. If the file still
 +     * doesn't exist, make its mtime now.
 +     */
 +    if (cgn->made != UPTODATE) {
 +      mtime = Make_Recheck(cgn);
 +    }
 +
 +    /*
 +     * If this is a `::' node, we must consult its first instance
 +     * which is where all parents are linked.
 +     */
 +    if ((centurion = cgn->centurion) != NULL) {
 +      if (!Lst_IsEmpty(cgn->parents))
 +              Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num);
 +      centurion->unmade_cohorts -= 1;
 +      if (centurion->unmade_cohorts < 0)
 +          Error("Graph cycles through centurion %s", centurion->name);
 +    } else {
 +      centurion = cgn;
 +    }
 +    parents = centurion->parents;
 +
 +    /* If this was a .ORDER node, schedule the RHS */
 +    Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade));
 +
 +    /* Now mark all the parents as having one less unmade child */
 +    if (Lst_Open(parents) == SUCCESS) {
 +      while ((ln = Lst_Next(parents)) != NULL) {
 +          pgn = (GNode *)Lst_Datum(ln);
 +          if (DEBUG(MAKE))
 +              fprintf(debug_file, "inspect parent %s%s: flags %x, "
 +                          "type %x, made %d, unmade %d ",
 +                      pgn->name, pgn->cohort_num, pgn->flags,
 +                      pgn->type, pgn->made, pgn->unmade-1);
 +
 +          if (!(pgn->flags & REMAKE)) {
 +              /* This parent isn't needed */
 +              if (DEBUG(MAKE))
 +                  fprintf(debug_file, "- not needed\n");
 +              continue;
 +          }
 +          if (mtime == 0 && !(cgn->type & OP_WAIT))
 +              pgn->flags |= FORCE;
 +
 +          /*
 +           * If the parent has the .MADE attribute, its timestamp got
 +           * updated to that of its newest child, and its unmake
 +           * child count got set to zero in Make_ExpandUse().
 +           * However other things might cause us to build one of its
 +           * children - and so we mustn't do any processing here when
 +           * the child build finishes.
 +           */
 +          if (pgn->type & OP_MADE) {
 +              if (DEBUG(MAKE))
 +                  fprintf(debug_file, "- .MADE\n");
 +              continue;
 +          }
 +
 +          if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
 +              if (cgn->made == MADE)
 +                  pgn->flags |= CHILDMADE;
 +              (void)Make_TimeStamp(pgn, cgn);
 +          }
 +
 +          /*
 +           * A parent must wait for the completion of all instances
 +           * of a `::' dependency.
 +           */
 +          if (centurion->unmade_cohorts != 0 || centurion->made < MADE) {
 +              if (DEBUG(MAKE))
 +                  fprintf(debug_file,
 +                          "- centurion made %d, %d unmade cohorts\n",
 +                          centurion->made, centurion->unmade_cohorts);
 +              continue;
 +          }
 +
 +          /* One more child of this parent is now made */
 +          pgn->unmade -= 1;
 +          if (pgn->unmade < 0) {
 +              if (DEBUG(MAKE)) {
 +                  fprintf(debug_file, "Graph cycles through %s%s\n",
 +                      pgn->name, pgn->cohort_num);
 +                  Targ_PrintGraph(2);
 +              }
 +              Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num);
 +          }
 +
 +          /* We must always rescan the parents of .WAIT and .ORDER nodes. */
 +          if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
 +                  && !(centurion->flags & DONE_ORDER)) {
 +              if (DEBUG(MAKE))
 +                  fprintf(debug_file, "- unmade children\n");
 +              continue;
 +          }
 +          if (pgn->made != DEFERRED) {
 +              /*
 +               * Either this parent is on a different branch of the tree,
 +               * or it on the RHS of a .WAIT directive
 +               * or it is already on the toBeMade list.
 +               */
 +              if (DEBUG(MAKE))
 +                  fprintf(debug_file, "- not deferred\n");
 +              continue;
 +          }
 +          if (pgn->order_pred
 +                  && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) {
 +              /* A .ORDER rule stops us building this */
 +              continue;
 +          }
 +          if (DEBUG(MAKE)) {
 +              static int two = 2;
 +              fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n",
 +                      cgn->name, cgn->cohort_num,
 +                      pgn->name, pgn->cohort_num, pgn->made);
 +              Targ_PrintNode(pgn, &two);
 +          }
 +          /* Ok, we can schedule the parent again */
 +          pgn->made = REQUESTED;
 +          (void)Lst_EnQueue(toBeMade, pgn);
 +      }
 +      Lst_Close(parents);
 +    }
 +
 +    /*
 +     * Set the .PREFIX and .IMPSRC variables for all the implied parents
 +     * of this node.
 +     */
 +    if (Lst_Open(cgn->iParents) == SUCCESS) {
 +      char    *cpref = Var_Value(PREFIX, cgn, &p1);
 +
 +      while ((ln = Lst_Next(cgn->iParents)) != NULL) {
 +          pgn = (GNode *)Lst_Datum(ln);
 +          if (pgn->flags & REMAKE) {
 +              Var_Set(IMPSRC, cname, pgn, 0);
 +              if (cpref != NULL)
 +                  Var_Set(PREFIX, cpref, pgn, 0);
 +          }
 +      }
 +      if (p1)
 +          free(p1);
 +      Lst_Close(cgn->iParents);
 +    }
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * MakeAddAllSrc --
 + *    Add a child's name to the ALLSRC and OODATE variables of the given
 + *    node. Called from Make_DoAllVar via Lst_ForEach. A child is added only
 + *    if it has not been given the .EXEC, .USE or .INVISIBLE attributes.
 + *    .EXEC and .USE children are very rarely going to be files, so...
 + *    If the child is a .JOIN node, its ALLSRC is propagated to the parent.
 + *
 + *    A child is added to the OODATE variable if its modification time is
 + *    later than that of its parent, as defined by Make, except if the
 + *    parent is a .JOIN node. In that case, it is only added to the OODATE
 + *    variable if it was actually made (since .JOIN nodes don't have
 + *    modification times, the comparison is rather unfair...)..
 + *
 + * Results:
 + *    Always returns 0
 + *
 + * Side Effects:
 + *    The ALLSRC variable for the given node is extended.
 + *-----------------------------------------------------------------------
 + */
 +static int
 +MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED)
 +{
 +    GNode     *cgn = (GNode *)cgnp;
 +
 +    cgn->type &= ~OP_MARK;
 +    return (0);
 +}
 +
 +/*
 + * Input:
 + *    cgnp            The child to add
 + *    pgnp            The parent to whose ALLSRC variable it should
 + *                    be added
 + *
 + */
 +static int
 +MakeAddAllSrc(void *cgnp, void *pgnp)
 +{
 +    GNode     *cgn = (GNode *)cgnp;
 +    GNode     *pgn = (GNode *)pgnp;
 +
 +    if (cgn->type & OP_MARK)
 +      return (0);
 +    cgn->type |= OP_MARK;
 +
 +    if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) {
 +      char *child, *allsrc;
 +      char *p1 = NULL, *p2 = NULL;
 +
 +      if (cgn->type & OP_ARCHV)
 +          child = Var_Value(MEMBER, cgn, &p1);
 +      else
 +          child = cgn->path ? cgn->path : cgn->name;
 +      if (cgn->type & OP_JOIN) {
 +          allsrc = Var_Value(ALLSRC, cgn, &p2);
 +      } else {
 +          allsrc = child;
 +      }
 +      if (allsrc != NULL)
 +              Var_Append(ALLSRC, allsrc, pgn);
 +      if (p2)
 +          free(p2);
 +      if (pgn->type & OP_JOIN) {
 +          if (cgn->made == MADE) {
 +              Var_Append(OODATE, child, pgn);
 +          }
 +      } else if ((pgn->mtime < cgn->mtime) ||
 +                 (cgn->mtime >= now && cgn->made == MADE))
 +      {
 +          /*
 +           * It goes in the OODATE variable if the parent is younger than the
 +           * child or if the child has been modified more recently than
 +           * the start of the make. This is to keep pmake from getting
 +           * confused if something else updates the parent after the
 +           * make starts (shouldn't happen, I know, but sometimes it
 +           * does). In such a case, if we've updated the kid, the parent
 +           * is likely to have a modification time later than that of
 +           * the kid and anything that relies on the OODATE variable will
 +           * be hosed.
 +           *
 +           * XXX: This will cause all made children to go in the OODATE
 +           * variable, even if they're not touched, if RECHECK isn't defined,
 +           * since cgn->mtime is set to now in Make_Update. According to
 +           * some people, this is good...
 +           */
 +          Var_Append(OODATE, child, pgn);
 +      }
 +      if (p1)
 +          free(p1);
 +    }
 +    return (0);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_DoAllVar --
 + *    Set up the ALLSRC and OODATE variables. Sad to say, it must be
 + *    done separately, rather than while traversing the graph. This is
 + *    because Make defined OODATE to contain all sources whose modification
 + *    times were later than that of the target, *not* those sources that
 + *    were out-of-date. Since in both compatibility and native modes,
 + *    the modification time of the parent isn't found until the child
 + *    has been dealt with, we have to wait until now to fill in the
 + *    variable. As for ALLSRC, the ordering is important and not
 + *    guaranteed when in native mode, so it must be set here, too.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The ALLSRC and OODATE variables of the given node is filled in.
 + *    If the node is a .JOIN node, its TARGET variable will be set to
 + *    match its ALLSRC variable.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Make_DoAllVar(GNode *gn)
 +{
 +    if (gn->flags & DONE_ALLSRC)
 +      return;
 +    
 +    Lst_ForEach(gn->children, MakeUnmark, gn);
 +    Lst_ForEach(gn->children, MakeAddAllSrc, gn);
 +
 +    if (!Var_Exists (OODATE, gn)) {
 +      Var_Set(OODATE, "", gn, 0);
 +    }
 +    if (!Var_Exists (ALLSRC, gn)) {
 +      Var_Set(ALLSRC, "", gn, 0);
 +    }
 +
 +    if (gn->type & OP_JOIN) {
 +      char *p1;
 +      Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0);
 +      if (p1)
 +          free(p1);
 +    }
 +    gn->flags |= DONE_ALLSRC;
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * MakeStartJobs --
 + *    Start as many jobs as possible.
 + *
 + * Results:
 + *    If the query flag was given to pmake, no job will be started,
 + *    but as soon as an out-of-date target is found, this function
 + *    returns TRUE. At all other times, this function returns FALSE.
 + *
 + * Side Effects:
 + *    Nodes are removed from the toBeMade queue and job table slots
 + *    are filled.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +static int
 +MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED)
 +{
 +    GNode *bn = v_bn;
 +
 +    if (bn->made >= MADE || !(bn->flags & REMAKE))
 +      return 0;
 +    if (DEBUG(MAKE))
 +      fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
 +              bn->name, bn->cohort_num);
 +    return 1;
 +}
 +
 +static int
 +MakeBuildChild(void *v_cn, void *toBeMade_next)
 +{
 +    GNode *cn = v_cn;
 +
 +    if (DEBUG(MAKE))
 +      fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
 +          cn->name, cn->cohort_num, cn->made, cn->type);
 +    if (cn->made > DEFERRED)
 +      return 0;
 +
 +    /* If this node is on the RHS of a .ORDER, check LHSs. */
 +    if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) {
 +      /* Can't build this (or anything else in this child list) yet */
 +      cn->made = DEFERRED;
 +      return 0;                       /* but keep looking */
 +    }
 +
 +    if (DEBUG(MAKE))
 +      fprintf(debug_file, "MakeBuildChild: schedule %s%s\n",
 +              cn->name, cn->cohort_num);
 +
 +    cn->made = REQUESTED;
 +    if (toBeMade_next == NULL)
 +      Lst_AtEnd(toBeMade, cn);
 +    else
 +      Lst_InsertBefore(toBeMade, toBeMade_next, cn);
 +
 +    if (cn->unmade_cohorts != 0)
 +      Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next);
 +
 +    /*
 +     * If this node is a .WAIT node with unmade chlidren
 +     * then don't add the next sibling.
 +     */
 +    return cn->type & OP_WAIT && cn->unmade > 0;
 +}
 +
 +/* When a .ORDER LHS node completes we do this on each RHS */
 +static int
 +MakeBuildParent(void *v_pn, void *toBeMade_next)
 +{
 +    GNode *pn = v_pn;
 +
 +    if (pn->made != DEFERRED)
 +      return 0;
 +
 +    if (MakeBuildChild(pn, toBeMade_next) == 0) {
 +      /* Mark so that when this node is built we reschedule its parents */
 +      pn->flags |= DONE_ORDER;
 +    }
 +
 +    return 0;
 +}
 +
 +static Boolean
 +MakeStartJobs(void)
 +{
 +    GNode     *gn;
 +    int               have_token = 0;
 +
 +    while (!Lst_IsEmpty (toBeMade)) {
 +      /* Get token now to avoid cycling job-list when we only have 1 token */
 +      if (!have_token && !Job_TokenWithdraw())
 +          break;
 +      have_token = 1;
 +
 +      gn = (GNode *)Lst_DeQueue(toBeMade);
 +      if (DEBUG(MAKE))
 +          fprintf(debug_file, "Examining %s%s...\n",
 +                  gn->name, gn->cohort_num);
 +
 +      if (gn->made != REQUESTED) {
 +          if (DEBUG(MAKE))
 +              fprintf(debug_file, "state %d\n", gn->made);
 +
 +          make_abort(gn, __LINE__);
 +      }
 +
 +      if (gn->checked == checked) {
 +          /* We've already looked at this node since a job finished... */
 +          if (DEBUG(MAKE))
 +              fprintf(debug_file, "already checked %s%s\n",
 +                      gn->name, gn->cohort_num);
 +          gn->made = DEFERRED;
 +          continue;
 +      }
 +      gn->checked = checked;
 +
 +      if (gn->unmade != 0) {
 +          /*
 +           * We can't build this yet, add all unmade children to toBeMade,
 +           * just before the current first element.
 +           */
 +          gn->made = DEFERRED;
 +          Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade));
 +          /* and drop this node on the floor */
 +          if (DEBUG(MAKE))
 +              fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num);
 +          continue;
 +      }
 +
 +      gn->made = BEINGMADE;
 +      if (Make_OODate(gn)) {
 +          if (DEBUG(MAKE)) {
 +              fprintf(debug_file, "out-of-date\n");
 +          }
 +          if (queryFlag) {
 +              return (TRUE);
 +          }
 +          Make_DoAllVar(gn);
 +          Job_Make(gn);
 +          have_token = 0;
 +      } else {
 +          if (DEBUG(MAKE)) {
 +              fprintf(debug_file, "up-to-date\n");
 +          }
 +          gn->made = UPTODATE;
 +          if (gn->type & OP_JOIN) {
 +              /*
 +               * Even for an up-to-date .JOIN node, we need it to have its
 +               * context variables so references to it get the correct
 +               * value for .TARGET when building up the context variables
 +               * of its parent(s)...
 +               */
 +              Make_DoAllVar(gn);
 +          }
 +          Make_Update(gn);
 +      }
 +    }
 +
 +    if (have_token)
 +      Job_TokenReturn();
 +
 +    return (FALSE);
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * MakePrintStatus --
 + *    Print the status of a top-level node, viz. it being up-to-date
 + *    already or not created due to an error in a lower level.
 + *    Callback function for Make_Run via Lst_ForEach.
 + *
 + * Input:
 + *    gnp             Node to examine
 + *    cyclep          True if gn->unmade being non-zero implies a
 + *                    cycle in the graph, not an error in an
 + *                    inferior.
 + *
 + * Results:
 + *    Always returns 0.
 + *
 + * Side Effects:
 + *    A message may be printed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +MakePrintStatusOrder(void *ognp, void *gnp)
 +{
 +    GNode *ogn = ognp;
 +    GNode *gn = gnp;
 +
 +    if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED)
 +      /* not waiting for this one */
 +      return 0;
 +
 +    printf("    `%s%s' has .ORDER dependency against %s%s "
 +              "(made %d, flags %x, type %x)\n",
 +          gn->name, gn->cohort_num,
 +          ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
 +    if (DEBUG(MAKE) && debug_file != stdout)
 +      fprintf(debug_file, "    `%s%s' has .ORDER dependency against %s%s "
 +                  "(made %d, flags %x, type %x)\n",
 +              gn->name, gn->cohort_num,
 +              ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
 +    return 0;
 +}
 +
 +static int
 +MakePrintStatus(void *gnp, void *v_errors)
 +{
 +    GNode     *gn = (GNode *)gnp;
 +    int       *errors = v_errors;
 +
 +    if (gn->flags & DONECYCLE)
 +      /* We've completely processed this node before, don't do it again. */
 +      return 0;
 +
 +    if (gn->unmade == 0) {
 +      gn->flags |= DONECYCLE;
 +      switch (gn->made) {
 +      case UPTODATE:
 +          printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num);
 +          break;
 +      case MADE:
 +          break;
 +      case UNMADE:
 +      case DEFERRED:
 +      case REQUESTED:
 +      case BEINGMADE:
 +          (*errors)++;
 +          printf("`%s%s' was not built (made %d, flags %x, type %x)!\n",
 +                  gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
 +          if (DEBUG(MAKE) && debug_file != stdout)
 +              fprintf(debug_file,
 +                      "`%s%s' was not built (made %d, flags %x, type %x)!\n",
 +                      gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
 +          /* Most likely problem is actually caused by .ORDER */
 +          Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn);
 +          break;
 +      default:
 +          /* Errors - already counted */
 +          printf("`%s%s' not remade because of errors.\n",
 +                  gn->name, gn->cohort_num);
 +          if (DEBUG(MAKE) && debug_file != stdout)
 +              fprintf(debug_file, "`%s%s' not remade because of errors.\n",
 +                      gn->name, gn->cohort_num);
 +          break;
 +      }
 +      return 0;
 +    }
 +
 +    if (DEBUG(MAKE))
 +      fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n",
 +              gn->name, gn->cohort_num, gn->unmade);
 +    /*
 +     * If printing cycles and came to one that has unmade children,
 +     * print out the cycle by recursing on its children.
 +     */
 +    if (!(gn->flags & CYCLE)) {
 +      /* Fist time we've seen this node, check all children */
 +      gn->flags |= CYCLE;
 +      Lst_ForEach(gn->children, MakePrintStatus, errors);
 +      /* Mark that this node needn't be processed again */
 +      gn->flags |= DONECYCLE;
 +      return 0;
 +    }
 +
 +    /* Only output the error once per node */
 +    gn->flags |= DONECYCLE;
 +    Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
 +    if ((*errors)++ > 100)
 +      /* Abandon the whole error report */
 +      return 1;
 +
 +    /* Reporting for our children will give the rest of the loop */
 +    Lst_ForEach(gn->children, MakePrintStatus, errors);
 +    return 0;
 +}
 +\f
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_ExpandUse --
 + *    Expand .USE nodes and create a new targets list
 + *
 + * Input:
 + *    targs           the initial list of targets
 + *
 + * Side Effects:
 + *-----------------------------------------------------------------------
 + */
 +void
 +Make_ExpandUse(Lst targs)
 +{
 +    GNode  *gn;               /* a temporary pointer */
 +    Lst    examine;   /* List of targets to examine */
 +
 +    examine = Lst_Duplicate(targs, NULL);
 +
 +    /*
 +     * Make an initial downward pass over the graph, marking nodes to be made
 +     * as we go down. We call Suff_FindDeps to find where a node is and
 +     * to get some children for it if it has none and also has no commands.
 +     * If the node is a leaf, we stick it on the toBeMade queue to
 +     * be looked at in a minute, otherwise we add its children to our queue
 +     * and go on about our business.
 +     */
 +    while (!Lst_IsEmpty (examine)) {
 +      gn = (GNode *)Lst_DeQueue(examine);
 +    
 +      if (gn->flags & REMAKE)
 +          /* We've looked at this one already */
 +          continue;
 +      gn->flags |= REMAKE;
 +      if (DEBUG(MAKE))
 +          fprintf(debug_file, "Make_ExpandUse: examine %s%s\n",
 +                  gn->name, gn->cohort_num);
 +
 +      if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) {
 +          /* Append all the 'cohorts' to the list of things to examine */
 +          Lst new;
 +          new = Lst_Duplicate(gn->cohorts, NULL);
 +          Lst_Concat(new, examine, LST_CONCLINK);
 +          examine = new;
 +      }
 +
 +      /*
 +       * Apply any .USE rules before looking for implicit dependencies
 +       * to make sure everything has commands that should...
 +       * Make sure that the TARGET is set, so that we can make
 +       * expansions.
 +       */
 +      if (gn->type & OP_ARCHV) {
 +          char *eoa, *eon;
 +          eoa = strchr(gn->name, '(');
 +          eon = strchr(gn->name, ')');
 +          if (eoa == NULL || eon == NULL)
 +              continue;
 +          *eoa = '\0';
 +          *eon = '\0';
 +          Var_Set(MEMBER, eoa + 1, gn, 0);
 +          Var_Set(ARCHIVE, gn->name, gn, 0);
 +          *eoa = '(';
 +          *eon = ')';
 +      }
 +
 +      (void)Dir_MTime(gn, 0);
 +      Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
 +      Lst_ForEach(gn->children, MakeUnmark, gn);
 +      Lst_ForEach(gn->children, MakeHandleUse, gn);
 +
 +      if ((gn->type & OP_MADE) == 0)
 +          Suff_FindDeps(gn);
 +      else {
 +          /* Pretend we made all this node's children */
 +          Lst_ForEach(gn->children, MakeFindChild, gn);
 +          if (gn->unmade != 0)
 +                  printf("Warning: %s%s still has %d unmade children\n",
 +                          gn->name, gn->cohort_num, gn->unmade);
 +      }
 +
 +      if (gn->unmade != 0)
 +          Lst_ForEach(gn->children, MakeAddChild, examine);
 +    }
 +
 +    Lst_Destroy(examine, NULL);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_ProcessWait --
 + *    Convert .WAIT nodes into dependencies
 + *
 + * Input:
 + *    targs           the initial list of targets
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +static int
 +link_parent(void *cnp, void *pnp)
 +{
 +    GNode *cn = cnp;
 +    GNode *pn = pnp;
 +
 +    Lst_AtEnd(pn->children, cn);
 +    Lst_AtEnd(cn->parents, pn);
 +    pn->unmade++;
 +    return 0;
 +}
 +
 +static int
 +add_wait_dep(void *v_cn, void *v_wn)
 +{
 +    GNode *cn = v_cn;
 +    GNode *wn = v_wn;
 +
 +    if (cn == wn)
 +      return 1;
 +
 +    if (cn == NULL || wn == NULL) {
 +      printf("bad wait dep %p %p\n", cn, wn);
 +      exit(4);
 +    }
 +    if (DEBUG(MAKE))
 +       fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n",
 +              cn->name, cn->cohort_num, wn->name);
 +
 +    Lst_AtEnd(wn->children, cn);
 +    wn->unmade++;
 +    Lst_AtEnd(cn->parents, wn);
 +    return 0;
 +}
 +
 +static void
 +Make_ProcessWait(Lst targs)
 +{
 +    GNode  *pgn;      /* 'parent' node we are examining */
 +    GNode  *cgn;      /* Each child in turn */
 +    LstNode owln;     /* Previous .WAIT node */
 +    Lst    examine;   /* List of targets to examine */
 +    LstNode ln;
 +
 +    /*
 +     * We need all the nodes to have a common parent in order for the
 +     * .WAIT and .ORDER scheduling to work.
 +     * Perhaps this should be done earlier...
 +     */
 +
 +    pgn = Targ_NewGN(".MAIN");
 +    pgn->flags = REMAKE;
 +    pgn->type = OP_PHONY | OP_DEPENDS;
 +    /* Get it displayed in the diag dumps */
 +    Lst_AtFront(Targ_List(), pgn);
 +
 +    Lst_ForEach(targs, link_parent, pgn);
 +
 +    /* Start building with the 'dummy' .MAIN' node */
 +    MakeBuildChild(pgn, NULL);
 +
 +    examine = Lst_Init(FALSE);
 +    Lst_AtEnd(examine, pgn);
 +
 +    while (!Lst_IsEmpty (examine)) {
 +      pgn = Lst_DeQueue(examine);
 +   
 +      /* We only want to process each child-list once */
 +      if (pgn->flags & DONE_WAIT)
 +          continue;
 +      pgn->flags |= DONE_WAIT;
 +      if (DEBUG(MAKE))
 +          fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name);
 +
 +      if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) {
 +          /* Append all the 'cohorts' to the list of things to examine */
 +          Lst new;
 +          new = Lst_Duplicate(pgn->cohorts, NULL);
 +          Lst_Concat(new, examine, LST_CONCLINK);
 +          examine = new;
 +      }
 +
 +      owln = Lst_First(pgn->children);
 +      Lst_Open(pgn->children);
 +      for (; (ln = Lst_Next(pgn->children)) != NULL; ) {
 +          cgn = Lst_Datum(ln);
 +          if (cgn->type & OP_WAIT) {
 +              /* Make the .WAIT node depend on the previous children */
 +              Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn);
 +              owln = ln;
 +          } else {
 +              Lst_AtEnd(examine, cgn);
 +          }
 +      }
 +      Lst_Close(pgn->children);
 +    }
 +
 +    Lst_Destroy(examine, NULL);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Make_Run --
 + *    Initialize the nodes to remake and the list of nodes which are
 + *    ready to be made by doing a breadth-first traversal of the graph
 + *    starting from the nodes in the given list. Once this traversal
 + *    is finished, all the 'leaves' of the graph are in the toBeMade
 + *    queue.
 + *    Using this queue and the Job module, work back up the graph,
 + *    calling on MakeStartJobs to keep the job table as full as
 + *    possible.
 + *
 + * Input:
 + *    targs           the initial list of targets
 + *
 + * Results:
 + *    TRUE if work was done. FALSE otherwise.
 + *
 + * Side Effects:
 + *    The make field of all nodes involved in the creation of the given
 + *    targets is set to 1. The toBeMade list is set to contain all the
 + *    'leaves' of these subgraphs.
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Make_Run(Lst targs)
 +{
 +    int                   errors;     /* Number of errors the Job module reports */
 +
 +    /* Start trying to make the current targets... */
 +    toBeMade = Lst_Init(FALSE);
 +
 +    Make_ExpandUse(targs);
 +    Make_ProcessWait(targs);
 +
 +    if (DEBUG(MAKE)) {
 +       fprintf(debug_file, "#***# full graph\n");
 +       Targ_PrintGraph(1);
 +    }
 +
 +    if (queryFlag) {
 +      /*
 +       * We wouldn't do any work unless we could start some jobs in the
 +       * next loop... (we won't actually start any, of course, this is just
 +       * to see if any of the targets was out of date)
 +       */
 +      return (MakeStartJobs());
 +    }
 +    /*
 +     * Initialization. At the moment, no jobs are running and until some
 +     * get started, nothing will happen since the remaining upward
 +     * traversal of the graph is performed by the routines in job.c upon
 +     * the finishing of a job. So we fill the Job table as much as we can
 +     * before going into our loop.
 +     */
 +    (void)MakeStartJobs();
 +
 +    /*
 +     * Main Loop: The idea here is that the ending of jobs will take
 +     * care of the maintenance of data structures and the waiting for output
 +     * will cause us to be idle most of the time while our children run as
 +     * much as possible. Because the job table is kept as full as possible,
 +     * the only time when it will be empty is when all the jobs which need
 +     * running have been run, so that is the end condition of this loop.
 +     * Note that the Job module will exit if there were any errors unless the
 +     * keepgoing flag was given.
 +     */
 +    while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) {
 +      Job_CatchOutput();
 +      (void)MakeStartJobs();
 +    }
 +
 +    errors = Job_Finish();
 +
 +    /*
 +     * Print the final status of each target. E.g. if it wasn't made
 +     * because some inferior reported an error.
 +     */
 +    if (DEBUG(MAKE))
 +       fprintf(debug_file, "done: errors %d\n", errors);
 +    if (errors == 0) {
 +      Lst_ForEach(targs, MakePrintStatus, &errors);
 +      if (DEBUG(MAKE)) {
 +          fprintf(debug_file, "done: errors %d\n", errors);
 +          if (errors)
 +              Targ_PrintGraph(4);
 +      }
 +    }
 +    return errors != 0;
 +}
index 9cf7243b58c967d134c26e7c8fdaa63cf6eaaf56,0000000000000000000000000000000000000000..7cf34d82e0a68d1587bcf4be5cf52b32d7a82087
mode 100644,000000..100644
--- /dev/null
@@@ -1,526 -1,0 +1,533 @@@
- /*    $NetBSD: make.h,v 1.95 2014/09/07 20:55:34 joerg Exp $  */
++/*    $NetBSD: make.h,v 1.96 2015/09/21 21:50:16 pooka Exp $  */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + *
 + *    from: @(#)make.h        8.3 (Berkeley) 6/13/95
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + *
 + *    from: @(#)make.h        8.3 (Berkeley) 6/13/95
 + */
 +
 +/*-
 + * make.h --
 + *    The global definitions for pmake
 + */
 +
 +#ifndef _MAKE_H_
 +#define _MAKE_H_
 +
 +#ifdef HAVE_CONFIG_H
 +# include "config.h"
 +#endif
 +
 +#include <sys/types.h>
 +#include <sys/param.h>
 +
 +#include <ctype.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#ifdef HAVE_STRING_H
 +#include <string.h>
 +#else
 +#include <strings.h>
 +#endif
 +#include <unistd.h>
 +#include <sys/cdefs.h>
 +
 +#if defined(__GNUC__)
 +#define       MAKE_GNUC_PREREQ(x, y)                                          \
 +      ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) ||                  \
 +       (__GNUC__ > (x)))
 +#else /* defined(__GNUC__) */
 +#define       MAKE_GNUC_PREREQ(x, y)  0
 +#endif /* defined(__GNUC__) */
 +
 +#if MAKE_GNUC_PREREQ(2, 7)
 +#define       MAKE_ATTR_UNUSED        __attribute__((__unused__))
 +#else
 +#define       MAKE_ATTR_UNUSED        /* delete */
 +#endif
 +
 +#if MAKE_GNUC_PREREQ(2, 5)
 +#define       MAKE_ATTR_DEAD          __attribute__((__noreturn__))
 +#elif defined(__GNUC__)
 +#define       MAKE_ATTR_DEAD          __volatile
 +#else
 +#define       MAKE_ATTR_DEAD          /* delete */
 +#endif
 +
 +#if MAKE_GNUC_PREREQ(2, 7)
 +#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg)     \
 +          __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
 +#else
 +#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg)     /* delete */
 +#endif
 +
 +#include "sprite.h"
 +#include "lst.h"
 +#include "hash.h"
 +#include "make-conf.h"
 +#include "buf.h"
 +#include "make_malloc.h"
 +
 +/*
 + * some vendors don't have this --sjg
 + */
 +#if defined(S_IFDIR) && !defined(S_ISDIR)
 +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
 +#endif
 +
 +#if defined(sun) && (defined(__svr4__) || defined(__SVR4))
 +#define POSIX_SIGNALS
 +#endif
 +
 +/*-
 + * The structure for an individual graph node. Each node has several
 + * pieces of data associated with it.
 + *    1) the name of the target it describes
 + *    2) the location of the target file in the file system.
 + *    3) the type of operator used to define its sources (qv. parse.c)
 + *    4) whether it is involved in this invocation of make
 + *    5) whether the target has been remade
 + *    6) whether any of its children has been remade
 + *    7) the number of its children that are, as yet, unmade
 + *    8) its modification time
 + *    9) the modification time of its youngest child (qv. make.c)
 + *    10) a list of nodes for which this is a source (parents)
 + *    11) a list of nodes on which this depends (children)
 + *    12) a list of nodes that depend on this, as gleaned from the
 + *        transformation rules (iParents)
 + *    13) a list of ancestor nodes, which includes parents, iParents,
 + *        and recursive parents of parents
 + *    14) a list of nodes of the same name created by the :: operator
 + *    15) a list of nodes that must be made (if they're made) before
 + *        this node can be, but that do not enter into the datedness of
 + *        this node.
 + *    16) a list of nodes that must be made (if they're made) before
 + *        this node or any child of this node can be, but that do not
 + *        enter into the datedness of this node.
 + *    17) a list of nodes that must be made (if they're made) after
 + *        this node is, but that do not depend on this node, in the
 + *        normal sense.
 + *    18) a Lst of ``local'' variables that are specific to this target
 + *       and this target only (qv. var.c [$@ $< $?, etc.])
 + *    19) a Lst of strings that are commands to be given to a shell
 + *       to create this target.
 + */
 +typedef struct GNode {
 +    char            *name;            /* The target's name */
 +    char            *uname;           /* The unexpanded name of a .USE node */
 +    char          *path;      /* The full pathname of the file */
 +    int             type;             /* Its type (see the OP flags, below) */
 +
 +    int             flags;
 +#define REMAKE                0x1     /* this target needs to be (re)made */
 +#define       CHILDMADE       0x2     /* children of this target were made */
 +#define FORCE         0x4     /* children don't exist, and we pretend made */
 +#define DONE_WAIT     0x8     /* Set by Make_ProcessWait() */
 +#define DONE_ORDER    0x10    /* Build requested by .ORDER processing */
 +#define FROM_DEPEND   0x20    /* Node created from .depend */
 +#define DONE_ALLSRC   0x40    /* We do it once only */
 +#define CYCLE         0x1000  /* Used by MakePrintStatus */
 +#define DONECYCLE     0x2000  /* Used by MakePrintStatus */
 +    enum enum_made {
 +      UNMADE, DEFERRED, REQUESTED, BEINGMADE,
 +      MADE, UPTODATE, ERROR, ABORTED
 +    }             made;       /* Set to reflect the state of processing
 +                               * on this node:
 +                               *  UNMADE - Not examined yet
 +                               *  DEFERRED - Examined once (building child)
 +                               *  REQUESTED - on toBeMade list
 +                               *  BEINGMADE - Target is already being made.
 +                               *      Indicates a cycle in the graph.
 +                               *  MADE - Was out-of-date and has been made
 +                               *  UPTODATE - Was already up-to-date
 +                               *  ERROR - An error occurred while it was being
 +                               *      made (used only in compat mode)
 +                               *  ABORTED - The target was aborted due to
 +                               *      an error making an inferior (compat).
 +                               */
 +    int             unmade;           /* The number of unmade children */
 +
 +    time_t          mtime;            /* Its modification time */
 +    struct GNode    *cmgn;            /* The youngest child */
 +
 +    Lst           iParents;   /* Links to parents for which this is an
 +                               * implied source, if any */
 +    Lst                   cohorts;    /* Other nodes for the :: operator */
 +    Lst             parents;          /* Nodes that depend on this one */
 +    Lst             children;         /* Nodes on which this one depends */
 +    Lst             order_pred;       /* .ORDER nodes we need made */
 +    Lst             order_succ;       /* .ORDER nodes who need us */
 +
 +    char          cohort_num[8]; /* #n for this cohort */
 +    int                   unmade_cohorts;/* # of unmade instances on the
 +                                    cohorts list */
 +    struct GNode    *centurion;       /* Pointer to the first instance of a ::
 +                                 node; only set when on a cohorts list */
 +    unsigned int    checked;    /* Last time we tried to makle this node */
 +
 +    Hash_Table      context;  /* The local variables */
 +    Lst             commands;         /* Creation commands */
 +
 +    struct _Suff    *suffix;  /* Suffix for the node (determined by
 +                               * Suff_FindDeps and opaque to everyone
 +                               * but the Suff module) */
 +    const char            *fname;     /* filename where the GNode got defined */
 +    int                    lineno;    /* line number where the GNode got defined */
 +} GNode;
 +
 +/*
 + * The OP_ constants are used when parsing a dependency line as a way of
 + * communicating to other parts of the program the way in which a target
 + * should be made. These constants are bitwise-OR'ed together and
 + * placed in the 'type' field of each node. Any node that has
 + * a 'type' field which satisfies the OP_NOP function was never never on
 + * the lefthand side of an operator, though it may have been on the
 + * righthand side...
 + */
 +#define OP_DEPENDS    0x00000001  /* Execution of commands depends on
 +                                   * kids (:) */
 +#define OP_FORCE      0x00000002  /* Always execute commands (!) */
 +#define OP_DOUBLEDEP  0x00000004  /* Execution of commands depends on kids
 +                                   * per line (::) */
 +#define OP_OPMASK     (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP)
 +
 +#define OP_OPTIONAL   0x00000008  /* Don't care if the target doesn't
 +                                   * exist and can't be created */
 +#define OP_USE                0x00000010  /* Use associated commands for parents */
 +#define OP_EXEC               0x00000020  /* Target is never out of date, but always
 +                                   * execute commands anyway. Its time
 +                                   * doesn't matter, so it has none...sort
 +                                   * of */
 +#define OP_IGNORE     0x00000040  /* Ignore errors when creating the node */
 +#define OP_PRECIOUS   0x00000080  /* Don't remove the target when
 +                                   * interrupted */
 +#define OP_SILENT     0x00000100  /* Don't echo commands when executed */
 +#define OP_MAKE               0x00000200  /* Target is a recursive make so its
 +                                   * commands should always be executed when
 +                                   * it is out of date, regardless of the
 +                                   * state of the -n or -t flags */
 +#define OP_JOIN       0x00000400  /* Target is out-of-date only if any of its
 +                                   * children was out-of-date */
 +#define       OP_MADE         0x00000800  /* Assume the children of the node have
 +                                   * been already made */
 +#define OP_SPECIAL    0x00001000  /* Special .BEGIN, .END, .INTERRUPT */
 +#define       OP_USEBEFORE    0x00002000  /* Like .USE, only prepend commands */
 +#define OP_INVISIBLE  0x00004000  /* The node is invisible to its parents.
 +                                   * I.e. it doesn't show up in the parents's
 +                                   * local variables. */
 +#define OP_NOTMAIN    0x00008000  /* The node is exempt from normal 'main
 +                                   * target' processing in parse.c */
 +#define OP_PHONY      0x00010000  /* Not a file target; run always */
 +#define OP_NOPATH     0x00020000  /* Don't search for file in the path */
 +#define OP_WAIT       0x00040000  /* .WAIT phony node */
 +#define OP_NOMETA     0x00080000  /* .NOMETA do not create a .meta file */
 +#define OP_META               0x00100000  /* .META we _do_ want a .meta file */
 +#define OP_NOMETA_CMP 0x00200000  /* Do not compare commands in .meta file */
 +#define OP_SUBMAKE    0x00400000  /* Possibly a submake node */
 +/* Attributes applied by PMake */
 +#define OP_TRANSFORM  0x80000000  /* The node is a transformation rule */
 +#define OP_MEMBER     0x40000000  /* Target is a member of an archive */
 +#define OP_LIB                0x20000000  /* Target is a library */
 +#define OP_ARCHV      0x10000000  /* Target is an archive construct */
 +#define OP_HAS_COMMANDS       0x08000000  /* Target has all the commands it should.
 +                                   * Used when parsing to catch multiple
 +                                   * commands for a target */
 +#define OP_SAVE_CMDS  0x04000000  /* Saving commands on .END (Compat) */
 +#define OP_DEPS_FOUND 0x02000000  /* Already processed by Suff_FindDeps */
 +#define       OP_MARK         0x01000000  /* Node found while expanding .ALLSRC */
 +
 +#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute)
 +/*
 + * OP_NOP will return TRUE if the node with the given type was not the
 + * object of a dependency operator
 + */
 +#define OP_NOP(t)     (((t) & OP_OPMASK) == 0x00000000)
 +
 +#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)
 +
 +/*
 + * The TARG_ constants are used when calling the Targ_FindNode and
 + * Targ_FindList functions in targ.c. They simply tell the functions what to
 + * do if the desired node(s) is (are) not found. If the TARG_CREATE constant
 + * is given, a new, empty node will be created for the target, placed in the
 + * table of all targets and its address returned. If TARG_NOCREATE is given,
 + * a NULL pointer will be returned.
 + */
 +#define TARG_NOCREATE 0x00      /* don't create it */
 +#define TARG_CREATE   0x01      /* create node if not found */
 +#define TARG_NOHASH   0x02      /* don't look in/add to hash table */
 +
 +/*
 + * These constants are all used by the Str_Concat function to decide how the
 + * final string should look. If STR_ADDSPACE is given, a space will be
 + * placed between the two strings. If STR_ADDSLASH is given, a '/' will
 + * be used instead of a space. If neither is given, no intervening characters
 + * will be placed between the two strings in the final output. If the
 + * STR_DOFREE bit is set, the two input strings will be freed before
 + * Str_Concat returns.
 + */
 +#define STR_ADDSPACE  0x01    /* add a space when Str_Concat'ing */
 +#define STR_ADDSLASH  0x02    /* add a slash when Str_Concat'ing */
 +
 +/*
 + * Error levels for parsing. PARSE_FATAL means the process cannot continue
 + * once the makefile has been parsed. PARSE_WARNING means it can. Passed
 + * as the first argument to Parse_Error.
 + */
 +#define PARSE_WARNING 2
 +#define PARSE_FATAL   1
 +
 +/*
 + * Values returned by Cond_Eval.
 + */
 +#define COND_PARSE    0       /* Parse the next lines */
 +#define COND_SKIP     1       /* Skip the next lines */
 +#define COND_INVALID  2       /* Not a conditional statement */
 +
 +/*
 + * Definitions for the "local" variables. Used only for clarity.
 + */
 +#define TARGET                  "@"   /* Target of dependency */
 +#define OODATE                  "?"   /* All out-of-date sources */
 +#define ALLSRC                  ">"   /* All sources */
 +#define IMPSRC                  "<"   /* Source implied by transformation */
 +#define PREFIX                  "*"   /* Common prefix */
 +#define ARCHIVE                 "!"   /* Archive in "archive(member)" syntax */
 +#define MEMBER                  "%"   /* Member in "archive(member)" syntax */
 +
 +#define FTARGET           "@F"  /* file part of TARGET */
 +#define DTARGET           "@D"  /* directory part of TARGET */
 +#define FIMPSRC           "<F"  /* file part of IMPSRC */
 +#define DIMPSRC           "<D"  /* directory part of IMPSRC */
 +#define FPREFIX           "*F"  /* file part of PREFIX */
 +#define DPREFIX           "*D"  /* directory part of PREFIX */
 +
 +/*
 + * Global Variables
 + */
 +extern Lst    create;         /* The list of target names specified on the
 +                               * command line. used to resolve #if
 +                               * make(...) statements */
 +extern Lst            dirSearchPath;  /* The list of directories to search when
 +                               * looking for targets */
 +
 +extern Boolean        compatMake;     /* True if we are make compatible */
 +extern Boolean        ignoreErrors;   /* True if should ignore all errors */
 +extern Boolean  beSilent;     /* True if should print no commands */
 +extern Boolean  noExecute;            /* True if should execute nothing */
 +extern Boolean  noRecursiveExecute;           /* True if should execute nothing */
 +extern Boolean  allPrecious;          /* True if every target is precious */
 +extern Boolean  keepgoing;            /* True if should continue on unaffected
 +                               * portions of the graph when have an error
 +                               * in one portion */
 +extern Boolean        touchFlag;      /* TRUE if targets should just be 'touched'
 +                               * if out of date. Set by the -t flag */
 +extern Boolean        queryFlag;      /* TRUE if we aren't supposed to really make
 +                               * anything, just see if the targets are out-
 +                               * of-date */
 +extern Boolean        doing_depend;   /* TRUE if processing .depend */
 +
 +extern Boolean        checkEnvFirst;  /* TRUE if environment should be searched for
 +                               * variables before the global context */
 +extern Boolean        jobServer;      /* a jobServer already exists */
 +
 +extern Boolean        parseWarnFatal; /* TRUE if makefile parsing warnings are
 +                               * treated as errors */
 +
 +extern Boolean        varNoExportEnv; /* TRUE if we should not export variables
 +                               * set on the command line to the env. */
 +
 +extern GNode    *DEFAULT;     /* .DEFAULT rule */
 +
 +extern GNode  *VAR_INTERNAL;  /* Variables defined internally by make
 +                               * which should not override those set by
 +                               * makefiles.
 +                               */
 +extern GNode    *VAR_GLOBAL;          /* Variables defined in a global context, e.g
 +                               * in the Makefile itself */
 +extern GNode    *VAR_CMD;     /* Variables defined on the command line */
 +extern GNode  *VAR_FOR;       /* Iteration variables */
 +extern char           var_Error[];    /* Value returned by Var_Parse when an error
 +                               * is encountered. It actually points to
 +                               * an empty string, so naive callers needn't
 +                               * worry about it. */
 +
 +extern time_t         now;            /* The time at the start of this whole
 +                               * process */
 +
 +extern Boolean        oldVars;        /* Do old-style variable substitution */
 +
 +extern Lst    sysIncPath;     /* The system include path. */
 +extern Lst    defIncPath;     /* The default include path. */
 +
 +extern char   curdir[];       /* Startup directory */
 +extern char   *progname;      /* The program name */
 +extern char   *makeDependfile; /* .depend */
 +extern char   **savedEnv;      /* if we replaced environ this will be non-NULL */
 +
 +/*
 + * We cannot vfork() in a child of vfork().
 + * Most systems do not enforce this but some do.
 + */
 +#define vFork() ((getpid() == myPid) ? vfork() : fork())
 +extern pid_t  myPid;
 +
 +#define       MAKEFLAGS       ".MAKEFLAGS"
 +#define       MAKEOVERRIDES   ".MAKEOVERRIDES"
 +#define       MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */
 +#define       MAKE_EXPORTED   ".MAKE.EXPORTED"   /* variables we export */
 +#define       MAKE_MAKEFILES  ".MAKE.MAKEFILES"  /* all the makefiles we read */
 +#define       MAKE_LEVEL      ".MAKE.LEVEL"      /* recursion level */
 +#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
 +#define MAKE_DEPENDFILE       ".MAKE.DEPENDFILE" /* .depend */
 +#define MAKE_MODE     ".MAKE.MODE"
 +#ifndef MAKE_LEVEL_ENV
 +# define MAKE_LEVEL_ENV       "MAKELEVEL"
 +#endif
 +
 +/*
 + * debug control:
 + *    There is one bit per module.  It is up to the module what debug
 + *    information to print.
 + */
 +FILE *debug_file;             /* Output written here - default stdout */
 +extern int debug;
 +#define       DEBUG_ARCH      0x00001
 +#define       DEBUG_COND      0x00002
 +#define       DEBUG_DIR       0x00004
 +#define       DEBUG_GRAPH1    0x00008
 +#define       DEBUG_GRAPH2    0x00010
 +#define       DEBUG_JOB       0x00020
 +#define       DEBUG_MAKE      0x00040
 +#define       DEBUG_SUFF      0x00080
 +#define       DEBUG_TARG      0x00100
 +#define       DEBUG_VAR       0x00200
 +#define DEBUG_FOR     0x00400
 +#define DEBUG_SHELL   0x00800
 +#define DEBUG_ERROR   0x01000
 +#define DEBUG_LOUD    0x02000
 +#define DEBUG_META    0x04000
 +
 +#define DEBUG_GRAPH3  0x10000
 +#define DEBUG_SCRIPT  0x20000
 +#define DEBUG_PARSE   0x40000
 +#define DEBUG_CWD     0x80000
 +
 +#define CONCAT(a,b)   a##b
 +
 +#define       DEBUG(module)   (debug & CONCAT(DEBUG_,module))
 +
 +#include "nonints.h"
 +
 +int Make_TimeStamp(GNode *, GNode *);
 +Boolean Make_OODate(GNode *);
 +void Make_ExpandUse(Lst);
 +time_t Make_Recheck(GNode *);
 +void Make_HandleUse(GNode *, GNode *);
 +void Make_Update(GNode *);
 +void Make_DoAllVar(GNode *);
 +Boolean Make_Run(Lst);
 +char * Check_Cwd_Cmd(const char *);
 +void Check_Cwd(const char **);
 +void PrintOnError(GNode *, const char *);
 +void Main_ExportMAKEFLAGS(Boolean);
 +Boolean Main_SetObjdir(const char *);
 +int mkTempFile(const char *, char **);
 +int str2Lst_Append(Lst, char *, const char *);
 +
 +#ifdef __GNUC__
 +#define UNCONST(ptr)  ({              \
 +    union __unconst {                 \
 +      const void *__cp;               \
 +      void *__p;                      \
 +    } __d;                            \
 +    __d.__cp = ptr, __d.__p; })
 +#else
 +#define UNCONST(ptr)  (void *)(ptr)
 +#endif
 +
 +#ifndef MIN
 +#define MIN(a, b) ((a < b) ? a : b)
 +#endif
 +#ifndef MAX
 +#define MAX(a, b) ((a > b) ? a : b)
 +#endif
 +
++/* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */
++#ifdef HAVE_LIMITS_H
++#include <limits.h>
++#endif
 +#ifndef MAXPATHLEN
- #define MAXPATHLEN BMAKE_PATH_MAX
++#define MAXPATHLEN    BMAKE_PATH_MAX
++#endif
++#ifndef PATH_MAX
++#define PATH_MAX      MAXPATHLEN
 +#endif
 +
 +#endif /* _MAKE_H_ */
index 5600a772bed436380e77153c27b77e3b788f04d3,0000000000000000000000000000000000000000..09febfc4c1fd4b671007a6323758f7036d018e02
mode 100644,000000..100644
--- /dev/null
@@@ -1,1462 -1,0 +1,1455 @@@
- /*      $NetBSD: meta.c,v 1.38 2015/04/11 05:24:30 sjg Exp $ */
++/*      $NetBSD: meta.c,v 1.40 2015/10/11 04:51:24 sjg Exp $ */
 +
 +/*
 + * Implement 'meta' mode.
 + * Adapted from John Birrell's patches to FreeBSD make.
 + * --sjg
 + */
 +/*
 + * Copyright (c) 2009-2010, Juniper Networks, Inc.
 + * Portions Copyright (c) 2009, John Birrell.
 + * 
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions 
 + * are met: 
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer. 
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.  
 + * 
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 + */
 +#if defined(USE_META)
 +
 +#ifdef HAVE_CONFIG_H
 +# include "config.h"
 +#endif
 +#include <sys/stat.h>
 +#include <sys/ioctl.h>
 +#include <fcntl.h>
 +#include <libgen.h>
 +#include <errno.h>
 +#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
 +#include <err.h>
 +#endif
 +
 +#include "make.h"
 +#include "job.h"
 +
 +#ifdef HAVE_FILEMON_H
 +# include <filemon.h>
 +#endif
 +#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
 +# define USE_FILEMON
 +#endif
 +
 +static BuildMon Mybm;                 /* for compat */
 +static Lst metaBailiwick;             /* our scope of control */
 +static Lst metaIgnorePaths;           /* paths we deliberately ignore */
 +
 +#ifndef MAKE_META_IGNORE_PATHS
 +#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS"
 +#endif
 +
 +Boolean useMeta = FALSE;
 +static Boolean useFilemon = FALSE;
 +static Boolean writeMeta = FALSE;
 +static Boolean metaEnv = FALSE;               /* don't save env unless asked */
 +static Boolean metaVerbose = FALSE;
 +static Boolean metaIgnoreCMDs = FALSE;        /* ignore CMDs in .meta files */
 +static Boolean metaCurdirOk = FALSE;  /* write .meta in .CURDIR Ok? */
 +static Boolean metaSilent = FALSE;    /* if we have a .meta be SILENT */
 +
 +extern Boolean forceJobs;
 +extern Boolean comatMake;
 +extern char    **environ;
 +
 +#define       MAKE_META_PREFIX        ".MAKE.META.PREFIX"
 +
 +#ifndef N2U
 +# define N2U(n, u)   (((n) + ((u) - 1)) / (u))
 +#endif
 +#ifndef ROUNDUP
 +# define ROUNDUP(n, u)   (N2U((n), (u)) * (u))
 +#endif
 +
 +#if !defined(HAVE_STRSEP)
 +# define strsep(s, d) stresep((s), (d), 0)
 +#endif
 +
 +/*
 + * Filemon is a kernel module which snoops certain syscalls.
 + *
 + * C chdir
 + * E exec
 + * F [v]fork
 + * L [sym]link
 + * M rename
 + * R read
 + * W write
 + * S stat
 + *
 + * See meta_oodate below - we mainly care about 'E' and 'R'.
 + *
 + * We can still use meta mode without filemon, but 
 + * the benefits are more limited.
 + */
 +#ifdef USE_FILEMON
 +# ifndef _PATH_FILEMON
 +#   define _PATH_FILEMON "/dev/filemon"
 +# endif
 +
 +/*
 + * Open the filemon device.
 + */
 +static void
 +filemon_open(BuildMon *pbm)
 +{
 +    int retry;
 +    
 +    pbm->mon_fd = pbm->filemon_fd = -1;
 +    if (!useFilemon)
 +      return;
 +
 +    for (retry = 5; retry >= 0; retry--) {
 +      if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
 +          break;
 +    }
 +
 +    if (pbm->filemon_fd < 0) {
 +      useFilemon = FALSE;
 +      warn("Could not open %s", _PATH_FILEMON);
 +      return;
 +    }
 +
 +    /*
 +     * We use a file outside of '.'
 +     * to avoid a FreeBSD kernel bug where unlink invalidates
 +     * cwd causing getcwd to do a lot more work.
 +     * We only care about the descriptor.
 +     */
 +    pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
 +    if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
 +      err(1, "Could not set filemon file descriptor!");
 +    }
 +    /* we don't need these once we exec */
 +    (void)fcntl(pbm->mon_fd, F_SETFD, 1);
 +    (void)fcntl(pbm->filemon_fd, F_SETFD, 1);
 +}
 +
 +/*
 + * Read the build monitor output file and write records to the target's
 + * metadata file.
 + */
 +static void
 +filemon_read(FILE *mfp, int fd)
 +{
 +    char buf[BUFSIZ];
 +    int n;
 +
 +    /* Check if we're not writing to a meta data file.*/
 +    if (mfp == NULL) {
 +      if (fd >= 0)
 +          close(fd);                  /* not interested */
 +      return;
 +    }
 +    /* rewind */
 +    (void)lseek(fd, (off_t)0, SEEK_SET);
 +
 +    fprintf(mfp, "\n-- filemon acquired metadata --\n");
 +
 +    while ((n = read(fd, buf, sizeof(buf))) > 0) {
 +      fwrite(buf, 1, n, mfp);
 +    }
 +    fflush(mfp);
 +    close(fd);
 +}
 +#endif
 +
 +/*
 + * when realpath() fails,
 + * we use this, to clean up ./ and ../
 + */
 +static void
 +eat_dots(char *buf, size_t bufsz, int dots)
 +{
 +    char *cp;
 +    char *cp2;
 +    const char *eat;
 +    size_t eatlen;
 +
 +    switch (dots) {
 +    case 1:
 +      eat = "/./";
 +      eatlen = 2;
 +      break;
 +    case 2:
 +      eat = "/../";
 +      eatlen = 3;
 +      break;
 +    default:
 +      return;
 +    }
 +    
 +    do {
 +      cp = strstr(buf, eat);
 +      if (cp) {
 +          cp2 = cp + eatlen;
 +          if (dots == 2 && cp > buf) {
 +              do {
 +                  cp--;
 +              } while (cp > buf && *cp != '/');
 +          }
 +          if (*cp == '/') {
 +              strlcpy(cp, cp2, bufsz - (cp - buf));
 +          } else {
 +              return;                 /* can't happen? */
 +          }
 +      }
 +    } while (cp);
 +}
 +
 +static char *
 +meta_name(struct GNode *gn, char *mname, size_t mnamelen,
 +        const char *dname,
 +        const char *tname)
 +{
 +    char buf[MAXPATHLEN];
 +    char cwd[MAXPATHLEN];
 +    char *rp;
 +    char *cp;
 +    char *tp;
 +    char *p[4];                               /* >= number of possible uses */
 +    int i;
 +
 +    i = 0;
 +    if (!dname)
 +      dname = Var_Value(".OBJDIR", gn, &p[i++]);
 +    if (!tname)
 +      tname = Var_Value(TARGET, gn, &p[i++]);
 +
 +    if (realpath(dname, cwd))
 +      dname = cwd;
 +
 +    /*
 +     * Weed out relative paths from the target file name.
 +     * We have to be careful though since if target is a
 +     * symlink, the result will be unstable.
 +     * So we use realpath() just to get the dirname, and leave the
 +     * basename as given to us.
 +     */
 +    if ((cp = strrchr(tname, '/'))) {
 +      if (realpath(tname, buf)) {
 +          if ((rp = strrchr(buf, '/'))) {
 +              rp++;
 +              cp++;
 +              if (strcmp(cp, rp) != 0)
 +                  strlcpy(rp, cp, sizeof(buf) - (rp - buf));
 +          }
 +          tname = buf;
 +      } else {
 +          /*
 +           * We likely have a directory which is about to be made.
 +           * We pretend realpath() succeeded, to have a chance
 +           * of generating the same meta file name that we will
 +           * next time through.
 +           */
 +          if (tname[0] == '/') {
 +              strlcpy(buf, tname, sizeof(buf));
 +          } else {
 +              snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
 +          }
 +          eat_dots(buf, sizeof(buf), 1);      /* ./ */
 +          eat_dots(buf, sizeof(buf), 2);      /* ../ */
 +          tname = buf;
 +      }
 +    }
 +    /* on some systems dirname may modify its arg */
 +    tp = bmake_strdup(tname);
 +    if (strcmp(dname, dirname(tp)) == 0)
 +      snprintf(mname, mnamelen, "%s.meta", tname);
 +    else {
 +      snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
 +
 +      /*
 +       * Replace path separators in the file name after the
 +       * current object directory path.
 +       */
 +      cp = mname + strlen(dname) + 1;
 +
 +      while (*cp != '\0') {
 +          if (*cp == '/')
 +              *cp = '_';
 +          cp++;
 +      }
 +    }
 +    free(tp);
 +    for (i--; i >= 0; i--) {
 +      if (p[i])
 +          free(p[i]);
 +    }
 +    return (mname);
 +}
 +
 +/*
 + * Return true if running ${.MAKE}
 + * Bypassed if target is flagged .MAKE
 + */
 +static int
 +is_submake(void *cmdp, void *gnp)
 +{
 +    static char *p_make = NULL;
 +    static int p_len;
 +    char  *cmd = cmdp;
 +    GNode *gn = gnp;
 +    char *mp = NULL;
 +    char *cp;
 +    char *cp2;
 +    int rc = 0;                               /* keep looking */
 +
 +    if (!p_make) {
 +      p_make = Var_Value(".MAKE", gn, &cp);
 +      p_len = strlen(p_make);
 +    }
 +    cp = strchr(cmd, '$');
 +    if ((cp)) {
-       mp = Var_Subst(NULL, cmd, gn, FALSE);
++      mp = Var_Subst(NULL, cmd, gn, FALSE, TRUE);
 +      cmd = mp;
 +    }
 +    cp2 = strstr(cmd, p_make);
 +    if ((cp2)) {
 +      switch (cp2[p_len]) {
 +      case '\0':
 +      case ' ':
 +      case '\t':
 +      case '\n':
 +          rc = 1;
 +          break;
 +      }
 +      if (cp2 > cmd && rc > 0) {
 +          switch (cp2[-1]) {
 +          case ' ':
 +          case '\t':
 +          case '\n':
 +              break;
 +          default:
 +              rc = 0;                 /* no match */
 +              break;
 +          }
 +      }
 +    }
 +    if (mp)
 +      free(mp);
 +    return (rc);
 +}
 +
 +typedef struct meta_file_s {
 +    FILE *fp;
 +    GNode *gn;
 +} meta_file_t;
 +
 +static int
 +printCMD(void *cmdp, void *mfpp)
 +{
 +    meta_file_t *mfp = mfpp;
 +    char *cmd = cmdp;
 +    char *cp = NULL;
 +
 +    if (strchr(cmd, '$')) {
-       cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE);
++      cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE, TRUE);
 +    }
 +    fprintf(mfp->fp, "CMD %s\n", cmd);
 +    if (cp)
 +      free(cp);
 +    return 0;
 +}
 +
 +/*
 + * Certain node types never get a .meta file
 + */
 +#define SKIP_META_TYPE(_type) do { \
 +    if ((gn->type & __CONCAT(OP_, _type))) {  \
 +      if (DEBUG(META)) { \
 +          fprintf(debug_file, "Skipping meta for %s: .%s\n", \
 +                  gn->name, __STRING(_type));                \
 +      } \
 +      return (NULL); \
 +    } \
 +} while (0)
 +
 +static FILE *
 +meta_create(BuildMon *pbm, GNode *gn)
 +{
 +    meta_file_t mf;
 +    char buf[MAXPATHLEN];
 +    char objdir[MAXPATHLEN];
 +    char **ptr;
 +    const char *dname;
 +    const char *tname;
 +    char *fname;
 +    const char *cp;
 +    char *p[4];                               /* >= possible uses */
 +    int i;
 +    struct stat fs;
 +
 +    
 +    /* This may be a phony node which we don't want meta data for... */
 +    /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
 +    /* Or it may be explicitly flagged as .NOMETA */
 +    SKIP_META_TYPE(NOMETA);
 +    /* Unless it is explicitly flagged as .META */
 +    if (!(gn->type & OP_META)) {
 +      SKIP_META_TYPE(PHONY);
 +      SKIP_META_TYPE(SPECIAL);
 +      SKIP_META_TYPE(MAKE);
 +    }
 +
 +    mf.fp = NULL;
 +    
 +    i = 0;
 +    
 +    dname = Var_Value(".OBJDIR", gn, &p[i++]);
 +    tname = Var_Value(TARGET, gn, &p[i++]);
 +    
 +    /* The object directory may not exist. Check it.. */
 +    if (stat(dname, &fs) != 0) {
 +      if (DEBUG(META))
 +          fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
 +                  gn->name);
 +      goto out;
 +    }
 +    /* Check if there are no commands to execute. */
 +    if (Lst_IsEmpty(gn->commands)) {
 +      if (DEBUG(META))
 +          fprintf(debug_file, "Skipping meta for %s: no commands\n",
 +                  gn->name);
 +      goto out;
 +    }
 +
 +    /* make sure these are canonical */
 +    if (realpath(dname, objdir))
 +      dname = objdir;
 +
 +    /* If we aren't in the object directory, don't create a meta file. */
 +    if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
 +      if (DEBUG(META))
 +          fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
 +                  gn->name);
 +      goto out;
 +    }
 +    if (!(gn->type & OP_META)) {
 +      /* We do not generate .meta files for sub-makes */
 +      if (Lst_ForEach(gn->commands, is_submake, gn)) {
 +          if (DEBUG(META))
 +              fprintf(debug_file, "Skipping meta for %s: .MAKE\n",
 +                      gn->name);
 +          goto out;
 +      }
 +    }
 +
 +    if (metaVerbose) {
 +      char *mp;
 +
 +      /* Describe the target we are building */
-       mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0);
++      mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, FALSE, TRUE);
 +      if (*mp)
 +          fprintf(stdout, "%s\n", mp);
 +      free(mp);
 +    }
 +    /* Get the basename of the target */
 +    if ((cp = strrchr(tname, '/')) == NULL) {
 +      cp = tname;
 +    } else {
 +      cp++;
 +    }
 +
 +    fflush(stdout);
 +
 +    if (strcmp(cp, makeDependfile) == 0)
 +      goto out;
 +
 +    if (!writeMeta)
 +      /* Don't create meta data. */
 +      goto out;
 +
 +    fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
 +                    dname, tname);
 +
 +#ifdef DEBUG_META_MODE
 +    if (DEBUG(META))
 +      fprintf(debug_file, "meta_create: %s\n", fname);
 +#endif
 +
 +    if ((mf.fp = fopen(fname, "w")) == NULL)
 +      err(1, "Could not open meta file '%s'", fname);
 +
 +    fprintf(mf.fp, "# Meta data file %s\n", fname);
 +
 +    mf.gn = gn;
 +
 +    Lst_ForEach(gn->commands, printCMD, &mf);
 +
 +    fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
 +    fprintf(mf.fp, "TARGET %s\n", tname);
 +
 +    if (metaEnv) {
 +      for (ptr = environ; *ptr != NULL; ptr++)
 +          fprintf(mf.fp, "ENV %s\n", *ptr);
 +    }
 +
 +    fprintf(mf.fp, "-- command output --\n");
 +    fflush(mf.fp);
 +
 +    Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
 +    Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
 +
 +    gn->type |= OP_META;              /* in case anyone wants to know */
 +    if (metaSilent) {
 +          gn->type |= OP_SILENT;
 +    }
 + out:
 +    for (i--; i >= 0; i--) {
 +      if (p[i])
 +          free(p[i]);
 +    }
 +
 +    return (mf.fp);
 +}
 +
 +static Boolean
 +boolValue(char *s)
 +{
 +    switch(*s) {
 +    case '0':
 +    case 'N':
 +    case 'n':
 +    case 'F':
 +    case 'f':
 +      return FALSE;
 +    }
 +    return TRUE;
 +}
 +
 +/*
 + * Initialization we need before reading makefiles.
 + */
 +void
 +meta_init(void)
 +{
 +#ifdef USE_FILEMON
 +      /* this allows makefiles to test if we have filemon support */
 +      Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
 +#endif
 +}
 +
 +
 +/*
 + * Initialization we need after reading makefiles.
 + */
 +void
 +meta_mode_init(const char *make_mode)
 +{
 +    static int once = 0;
 +    char *cp;
 +
 +    useMeta = TRUE;
 +    useFilemon = TRUE;
 +    writeMeta = TRUE;
 +
 +    if (make_mode) {
 +      if (strstr(make_mode, "env"))
 +          metaEnv = TRUE;
 +      if (strstr(make_mode, "verb"))
 +          metaVerbose = TRUE;
 +      if (strstr(make_mode, "read"))
 +          writeMeta = FALSE;
 +      if (strstr(make_mode, "nofilemon"))
 +          useFilemon = FALSE;
 +      if ((cp = strstr(make_mode, "curdirok="))) {
 +          metaCurdirOk = boolValue(&cp[9]);
 +      }
 +      if ((cp = strstr(make_mode, "silent="))) {
 +          metaSilent = boolValue(&cp[7]);
 +      }
 +      if (strstr(make_mode, "ignore-cmd"))
 +          metaIgnoreCMDs = TRUE;
 +      /* for backwards compatability */
 +      Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0);
 +      Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0);
 +    }
 +    if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
 +      /*
 +       * The default value for MAKE_META_PREFIX
 +       * prints the absolute path of the target.
 +       * This works be cause :H will generate '.' if there is no /
 +       * and :tA will resolve that to cwd.
 +       */
 +      Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
 +    }
 +    if (once)
 +      return;
 +    once = 1;
 +    memset(&Mybm, 0, sizeof(Mybm));
 +    /*
 +     * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
 +     */
 +    metaBailiwick = Lst_Init(FALSE);
-     cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, 0);
++    cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL,
++                 FALSE, TRUE);
 +    if (cp) {
 +      str2Lst_Append(metaBailiwick, cp, NULL);
 +    }
 +    /*
 +     * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
 +     */
 +    metaIgnorePaths = Lst_Init(FALSE);
 +    Var_Append(MAKE_META_IGNORE_PATHS,
 +             "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
 +    cp = Var_Subst(NULL,
-                  "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 0);
++                 "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL,
++                 FALSE, TRUE);
 +    if (cp) {
 +      str2Lst_Append(metaIgnorePaths, cp, NULL);
 +    }
 +}
 +
 +/*
 + * In each case below we allow for job==NULL
 + */
 +void
 +meta_job_start(Job *job, GNode *gn)
 +{
 +    BuildMon *pbm;
 +
 +    if (job != NULL) {
 +      pbm = &job->bm;
 +    } else {
 +      pbm = &Mybm;
 +    }
 +    pbm->mfp = meta_create(pbm, gn);
 +#ifdef USE_FILEMON_ONCE
 +    /* compat mode we open the filemon dev once per command */
 +    if (job == NULL)
 +      return;
 +#endif
 +#ifdef USE_FILEMON
 +    if (pbm->mfp != NULL && useFilemon) {
 +      filemon_open(pbm);
 +    } else {
 +      pbm->mon_fd = pbm->filemon_fd = -1;
 +    }
 +#endif
 +}
 +
 +/*
 + * The child calls this before doing anything.
 + * It does not disturb our state.
 + */
 +void
 +meta_job_child(Job *job)
 +{
 +#ifdef USE_FILEMON
 +    BuildMon *pbm;
 +
 +    if (job != NULL) {
 +      pbm = &job->bm;
 +    } else {
 +      pbm = &Mybm;
 +    }
 +    if (pbm->mfp != NULL) {
 +      close(fileno(pbm->mfp));
 +      if (useFilemon) {
 +          pid_t pid;
 +
 +          pid = getpid();
 +          if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
 +              err(1, "Could not set filemon pid!");
 +          }
 +      }
 +    }
 +#endif
 +}
 +
 +void
 +meta_job_error(Job *job, GNode *gn, int flags, int status)
 +{
 +    char cwd[MAXPATHLEN];
 +    BuildMon *pbm;
 +
 +    if (job != NULL) {
 +      pbm = &job->bm;
 +    } else {
 +      if (!gn)
 +          gn = job->node;
 +      pbm = &Mybm;
 +    }
 +    if (pbm->mfp != NULL) {
 +      fprintf(pbm->mfp, "*** Error code %d%s\n",
 +              status,
 +              (flags & JOB_IGNERR) ?
 +              "(ignored)" : "");
 +    }
 +    if (gn) {
 +      Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
 +    }
 +    getcwd(cwd, sizeof(cwd));
 +    Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
 +    if (pbm && pbm->meta_fname[0]) {
 +      Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
 +    }
 +    meta_job_finish(job);
 +}
 +
 +void
 +meta_job_output(Job *job, char *cp, const char *nl)
 +{
 +    BuildMon *pbm;
 +    
 +    if (job != NULL) {
 +      pbm = &job->bm;
 +    } else {
 +      pbm = &Mybm;
 +    }
 +    if (pbm->mfp != NULL) {
 +      if (metaVerbose) {
 +          static char *meta_prefix = NULL;
 +          static int meta_prefix_len;
 +
 +          if (!meta_prefix) {
 +              char *cp2;
 +
-               meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0);
++              meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}",
++                                      VAR_GLOBAL, FALSE, TRUE);
 +              if ((cp2 = strchr(meta_prefix, '$')))
 +                  meta_prefix_len = cp2 - meta_prefix;
 +              else
 +                  meta_prefix_len = strlen(meta_prefix);
 +          }
 +          if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
 +              cp = strchr(cp+1, '\n');
 +              if (!cp++)
 +                  return;
 +          }
 +      }
 +      fprintf(pbm->mfp, "%s%s", cp, nl);
 +    }
 +}
 +
 +void
 +meta_cmd_finish(void *pbmp)
 +{
 +#ifdef USE_FILEMON
 +    BuildMon *pbm = pbmp;
 +
 +    if (!pbm)
 +      pbm = &Mybm;
 +
 +    if (pbm->filemon_fd >= 0) {
 +      close(pbm->filemon_fd);
 +      filemon_read(pbm->mfp, pbm->mon_fd);
 +      pbm->filemon_fd = pbm->mon_fd = -1;
 +    }
 +#endif
 +}
 +
 +void
 +meta_job_finish(Job *job)
 +{
 +    BuildMon *pbm;
 +
 +    if (job != NULL) {
 +      pbm = &job->bm;
 +    } else {
 +      pbm = &Mybm;
 +    }
 +    if (pbm->mfp != NULL) {
 +      meta_cmd_finish(pbm);
 +      fclose(pbm->mfp);
 +      pbm->mfp = NULL;
 +      pbm->meta_fname[0] = '\0';
 +    }
 +}
 +
 +/*
 + * Fetch a full line from fp - growing bufp if needed
 + * Return length in bufp.
 + */
 +static int 
 +fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
 +{
 +    char *buf = *bufp;
 +    size_t bufsz = *szp;
 +    struct stat fs;
 +    int x;
 +
 +    if (fgets(&buf[o], bufsz - o, fp) != NULL) {
 +    check_newline:
 +      x = o + strlen(&buf[o]);
 +      if (buf[x - 1] == '\n')
 +          return x;
 +      /*
 +       * We need to grow the buffer.
 +       * The meta file can give us a clue.
 +       */
 +      if (fstat(fileno(fp), &fs) == 0) {
 +          size_t newsz;
 +          char *p;
 +
 +          newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
 +          if (newsz <= bufsz)
 +              newsz = ROUNDUP(fs.st_size, BUFSIZ);
 +          if (DEBUG(META)) 
 +              fprintf(debug_file, "growing buffer %u -> %u\n",
 +                      (unsigned)bufsz, (unsigned)newsz);
 +          p = bmake_realloc(buf, newsz);
 +          if (p) {
 +              *bufp = buf = p;
 +              *szp = bufsz = newsz;
 +              /* fetch the rest */
 +              if (!fgets(&buf[x], bufsz - x, fp))
 +                  return x;           /* truncated! */
 +              goto check_newline;
 +          }
 +      }
 +    }
 +    return 0;
 +}
 +
 +static int
 +prefix_match(void *p, void *q)
 +{
 +    const char *prefix = p;
 +    const char *path = q;
 +    size_t n = strlen(prefix);
 +
 +    return (0 == strncmp(path, prefix, n));
 +}
 +
 +static int
 +string_match(const void *p, const void *q)
 +{
 +    const char *p1 = p;
 +    const char *p2 = q;
 +
 +    return strcmp(p1, p2);
 +}
 +
 +
 +/*
 + * When running with 'meta' functionality, a target can be out-of-date
 + * if any of the references in its meta data file is more recent.
 + * We have to track the latestdir on a per-process basis.
 + */
 +#define LCWD_VNAME_FMT ".meta.%d.lcwd"
 +#define LDIR_VNAME_FMT ".meta.%d.ldir"
 +
 +/*
 + * It is possible that a .meta file is corrupted,
 + * if we detect this we want to reproduce it.
 + * Setting oodate TRUE will have that effect.
 + */
 +#define CHECK_VALID_META(p) if (!(p && *p)) { \
 +    warnx("%s: %d: malformed", fname, lineno); \
 +    oodate = TRUE; \
 +    continue; \
 +    }
 +
 +#define DEQUOTE(p) if (*p == '\'') {  \
 +    char *ep; \
 +    p++; \
 +    if ((ep = strchr(p, '\''))) \
 +      *ep = '\0'; \
 +    }
 +
 +Boolean
 +meta_oodate(GNode *gn, Boolean oodate)
 +{
 +    static char *tmpdir = NULL;
 +    static char cwd[MAXPATHLEN];
 +    char lcwd_vname[64];
 +    char ldir_vname[64];
 +    char lcwd[MAXPATHLEN];
 +    char latestdir[MAXPATHLEN];
 +    char fname[MAXPATHLEN];
 +    char fname1[MAXPATHLEN];
 +    char fname2[MAXPATHLEN];
 +    char fname3[MAXPATHLEN];
 +    char *p;
 +    char *cp;
 +    char *link_src;
 +    char *move_target;
 +    static size_t cwdlen = 0;
 +    static size_t tmplen = 0;
 +    FILE *fp;
 +    Boolean needOODATE = FALSE;
 +    Lst missingFiles;
 +    
 +    if (oodate)
 +      return oodate;          /* we're done */
 +
 +    missingFiles = Lst_Init(FALSE);
 +
 +    /*
 +     * We need to check if the target is out-of-date. This includes
 +     * checking if the expanded command has changed. This in turn
 +     * requires that all variables are set in the same way that they
 +     * would be if the target needs to be re-built.
 +     */
 +    Make_DoAllVar(gn);
 +
 +    meta_name(gn, fname, sizeof(fname), NULL, NULL);
 +
 +#ifdef DEBUG_META_MODE
 +    if (DEBUG(META))
 +      fprintf(debug_file, "meta_oodate: %s\n", fname);
 +#endif
 +
 +    if ((fp = fopen(fname, "r")) != NULL) {
 +      static char *buf = NULL;
 +      static size_t bufsz;
 +      int lineno = 0;
 +      int lastpid = 0;
 +      int pid;
 +      int f = 0;
 +      int x;
 +      LstNode ln;
 +      struct stat fs;
 +
 +      if (!buf) {
 +          bufsz = 8 * BUFSIZ;
 +          buf = bmake_malloc(bufsz);
 +      }
 +
 +      if (!cwdlen) {
 +          if (getcwd(cwd, sizeof(cwd)) == NULL)
 +              err(1, "Could not get current working directory");
 +          cwdlen = strlen(cwd);
 +      }
 +      strlcpy(lcwd, cwd, sizeof(lcwd));
 +      strlcpy(latestdir, cwd, sizeof(latestdir));
 +
 +      if (!tmpdir) {
 +          tmpdir = getTmpdir();
 +          tmplen = strlen(tmpdir);
 +      }
 +
 +      /* we want to track all the .meta we read */
 +      Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
 +
 +      ln = Lst_First(gn->commands);
 +      while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
 +          lineno++;
 +          if (buf[x - 1] == '\n')
 +              buf[x - 1] = '\0';
 +          else {
 +              warnx("%s: %d: line truncated at %u", fname, lineno, x);
 +              oodate = TRUE;
 +              break;
 +          }
 +          link_src = NULL;
 +          move_target = NULL;
 +          /* Find the start of the build monitor section. */
 +          if (!f) {
 +              if (strncmp(buf, "-- filemon", 10) == 0) {
 +                  f = 1;
 +                  continue;
 +              }
 +              if (strncmp(buf, "# buildmon", 10) == 0) {
 +                  f = 1;
 +                  continue;
 +              }
 +          }               
 +
 +          /* Delimit the record type. */
 +          p = buf;
 +#ifdef DEBUG_META_MODE
 +          if (DEBUG(META))
 +              fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf);
 +#endif
 +          strsep(&p, " ");
 +          if (f) {
 +              /*
 +               * We are in the 'filemon' output section.
 +               * Each record from filemon follows the general form:
 +               *
 +               * <key> <pid> <data>
 +               *
 +               * Where:
 +               * <key> is a single letter, denoting the syscall.
 +               * <pid> is the process that made the syscall.
 +               * <data> is the arguments (of interest).
 +               */
 +              switch(buf[0]) {
 +              case '#':               /* comment */
 +              case 'V':               /* version */
 +                  break;
 +              default:
 +                  /*
 +                   * We need to track pathnames per-process.
 +                   *
 +                   * Each process run by make, starts off in the 'CWD'
 +                   * recorded in the .meta file, if it chdirs ('C')
 +                   * elsewhere we need to track that - but only for
 +                   * that process.  If it forks ('F'), we initialize
 +                   * the child to have the same cwd as its parent.
 +                   *
 +                   * We also need to track the 'latestdir' of
 +                   * interest.  This is usually the same as cwd, but
 +                   * not if a process is reading directories.
 +                   *
 +                   * Each time we spot a different process ('pid')
 +                   * we save the current value of 'latestdir' in a
 +                   * variable qualified by 'lastpid', and
 +                   * re-initialize 'latestdir' to any pre-saved
 +                   * value for the current 'pid' and 'CWD' if none.
 +                   */
 +                  CHECK_VALID_META(p);
 +                  pid = atoi(p);
 +                  if (pid > 0 && pid != lastpid) {
 +                      char *ldir;
 +                      char *tp;
 +                  
 +                      if (lastpid > 0) {
 +                          /* We need to remember these. */
 +                          Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
 +                          Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0);
 +                      }
 +                      snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid);
 +                      snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
 +                      lastpid = pid;
 +                      ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
 +                      if (ldir) {
 +                          strlcpy(latestdir, ldir, sizeof(latestdir));
 +                          if (tp)
 +                              free(tp);
 +                      }
 +                      ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
 +                      if (ldir) {
 +                          strlcpy(lcwd, ldir, sizeof(lcwd));
 +                          if (tp)
 +                              free(tp);
 +                      }
 +                  }
 +                  /* Skip past the pid. */
 +                  if (strsep(&p, " ") == NULL)
 +                      continue;
 +#ifdef DEBUG_META_MODE
 +                  if (DEBUG(META))
 +                          fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
 +                                  fname, lineno,
 +                                  pid, buf[0], cwd, lcwd, latestdir);
 +#endif
 +                  break;
 +              }
 +
 +              CHECK_VALID_META(p);
 +
 +              /* Process according to record type. */
 +              switch (buf[0]) {
 +              case 'X':               /* eXit */
 +                  Var_Delete(lcwd_vname, VAR_GLOBAL);
 +                  Var_Delete(ldir_vname, VAR_GLOBAL);
 +                  lastpid = 0;        /* no need to save ldir_vname */
 +                  break;
 +
 +              case 'F':               /* [v]Fork */
 +                  {
 +                      char cldir[64];
 +                      int child;
 +
 +                      child = atoi(p);
 +                      if (child > 0) {
 +                          snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child);
 +                          Var_Set(cldir, lcwd, VAR_GLOBAL, 0);
 +                          snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
 +                          Var_Set(cldir, latestdir, VAR_GLOBAL, 0);
 +#ifdef DEBUG_META_MODE
 +                          if (DEBUG(META))
 +                                  fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n",
 +                                          fname, lineno,
 +                                          child, cwd, lcwd, latestdir);
 +#endif
 +                      }
 +                  }
 +                  break;
 +
 +              case 'C':               /* Chdir */
 +                  /* Update lcwd and latest directory. */
 +                  strlcpy(latestdir, p, sizeof(latestdir));   
 +                  strlcpy(lcwd, p, sizeof(lcwd));
 +                  Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
 +                  Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0);
 +#ifdef DEBUG_META_MODE
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd);
 +#endif
 +                  break;
 +
 +              case 'M':               /* renaMe */
 +                  /*
 +                   * For 'M'oves we want to check
 +                   * the src as for 'R'ead
 +                   * and the target as for 'W'rite.
 +                   */
 +                  cp = p;             /* save this for a second */
 +                  /* now get target */
 +                  if (strsep(&p, " ") == NULL)
 +                      continue;
 +                  CHECK_VALID_META(p);
 +                  move_target = p;
 +                  p = cp;
 +                  /* 'L' and 'M' put single quotes around the args */
 +                  DEQUOTE(p);
 +                  DEQUOTE(move_target);
 +                  /* FALLTHROUGH */
 +              case 'D':               /* unlink */
 +                  if (*p == '/' && !Lst_IsEmpty(missingFiles)) {
 +                      /* remove p from the missingFiles list if present */
 +                      if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) {
 +                          char *tp = Lst_Datum(ln);
 +                          Lst_Remove(missingFiles, ln);
 +                          free(tp);
 +                          ln = NULL;  /* we're done with it */
 +                      }
 +                  }
 +                  if (buf[0] == 'M') {
 +                      /* the target of the mv is a file 'W'ritten */
 +#ifdef DEBUG_META_MODE
 +                      if (DEBUG(META))
 +                          fprintf(debug_file, "meta_oodate: M %s -> %s\n",
 +                                  p, move_target);
 +#endif
 +                      p = move_target;
 +                      goto check_write;
 +                  }
 +                  break;
 +              case 'L':               /* Link */
 +                  /*
 +                   * For 'L'inks check
 +                   * the src as for 'R'ead
 +                   * and the target as for 'W'rite.
 +                   */
 +                  link_src = p;
 +                  /* now get target */
 +                  if (strsep(&p, " ") == NULL)
 +                      continue;
 +                  CHECK_VALID_META(p);
 +                  /* 'L' and 'M' put single quotes around the args */
 +                  DEQUOTE(p);
 +                  DEQUOTE(link_src);
 +#ifdef DEBUG_META_MODE
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "meta_oodate: L %s -> %s\n",
 +                              link_src, p);
 +#endif
 +                  /* FALLTHROUGH */
 +              case 'W':               /* Write */
 +              check_write:
 +                  /*
 +                   * If a file we generated within our bailiwick
 +                   * but outside of .OBJDIR is missing,
 +                   * we need to do it again. 
 +                   */
 +                  /* ignore non-absolute paths */
 +                  if (*p != '/')
 +                      break;
 +
 +                  if (Lst_IsEmpty(metaBailiwick))
 +                      break;
 +
 +                  /* ignore cwd - normal dependencies handle those */
 +                  if (strncmp(p, cwd, cwdlen) == 0)
 +                      break;
 +
 +                  if (!Lst_ForEach(metaBailiwick, prefix_match, p))
 +                      break;
 +
 +                  /* tmpdir might be within */
 +                  if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)
 +                      break;
 +
 +                  /* ignore anything containing the string "tmp" */
 +                  if ((strstr("tmp", p)))
 +                      break;
 +
 +                  if (stat(p, &fs) < 0) {
 +                      Lst_AtEnd(missingFiles, bmake_strdup(p));
 +                  }
 +                  break;
 +              check_link_src:
 +                  p = link_src;
 +                  link_src = NULL;
 +#ifdef DEBUG_META_MODE
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "meta_oodate: L src %s\n", p);
 +#endif
 +                  /* FALLTHROUGH */
 +              case 'R':               /* Read */
 +              case 'E':               /* Exec */
 +                  /*
 +                   * Check for runtime files that can't
 +                   * be part of the dependencies because
 +                   * they are _expected_ to change.
 +                   */
 +                  if (*p == '/' &&
 +                      Lst_ForEach(metaIgnorePaths, prefix_match, p)) {
 +#ifdef DEBUG_META_MODE
 +                      if (DEBUG(META))
 +                          fprintf(debug_file, "meta_oodate: ignoring: %s\n",
 +                                  p);
 +#endif
 +                      break;
 +                  }
 +
-                   if ((cp = strrchr(p, '/'))) {
-                       cp++;
-                       /*
-                        * We don't normally expect to see this,
-                        * but we do expect it to change.
-                        */
-                       if (strcmp(cp, makeDependfile) == 0)
-                           break;
-                   }
 +                  /*
 +                   * The rest of the record is the file name.
 +                   * Check if it's not an absolute path.
 +                   */
 +                  {
 +                      char *sdirs[4];
 +                      char **sdp;
 +                      int sdx = 0;
 +                      int found = 0;
 +
 +                      if (*p == '/') {
 +                          sdirs[sdx++] = p; /* done */
 +                      } else {
 +                          if (strcmp(".", p) == 0)
 +                              continue;  /* no point */
 +
 +                          /* Check vs latestdir */
 +                          snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
 +                          sdirs[sdx++] = fname1;
 +
 +                          if (strcmp(latestdir, lcwd) != 0) {
 +                              /* Check vs lcwd */
 +                              snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p);
 +                              sdirs[sdx++] = fname2;
 +                          }
 +                          if (strcmp(lcwd, cwd) != 0) {
 +                              /* Check vs cwd */
 +                              snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p);
 +                              sdirs[sdx++] = fname3;
 +                          }
 +                      }
 +                      sdirs[sdx++] = NULL;
 +
 +                      for (sdp = sdirs; *sdp && !found; sdp++) {
 +#ifdef DEBUG_META_MODE
 +                          if (DEBUG(META))
 +                              fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp);
 +#endif
 +                          if (stat(*sdp, &fs) == 0) {
 +                              found = 1;
 +                              p = *sdp;
 +                          }
 +                      }
 +                      if (found) {
 +#ifdef DEBUG_META_MODE
 +                          if (DEBUG(META))
 +                              fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p);
 +#endif
 +                          if (!S_ISDIR(fs.st_mode) &&
 +                              fs.st_mtime > gn->mtime) {
 +                              if (DEBUG(META))
 +                                  fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
 +                              oodate = TRUE;
 +                          } else if (S_ISDIR(fs.st_mode)) {
 +                              /* Update the latest directory. */
 +                              realpath(p, latestdir);
 +                          }
 +                      } else if (errno == ENOENT && *p == '/' &&
 +                                 strncmp(p, cwd, cwdlen) != 0) {
 +                          /*
 +                           * A referenced file outside of CWD is missing.
 +                           * We cannot catch every eventuality here...
 +                           */
 +                          if (DEBUG(META))
 +                              fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p);
 +                          oodate = TRUE;
 +                      }
 +                  }
 +                  if (buf[0] == 'E') {
 +                      /* previous latestdir is no longer relevant */
 +                      strlcpy(latestdir, lcwd, sizeof(latestdir));
 +                  }
 +                  break;
 +              default:
 +                  break;
 +              }
 +              if (!oodate && buf[0] == 'L' && link_src != NULL)
 +                  goto check_link_src;
 +          } else if (strcmp(buf, "CMD") == 0) {
 +              /*
 +               * Compare the current command with the one in the
 +               * meta data file.
 +               */
 +              if (ln == NULL) {
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
 +                  oodate = TRUE;
 +              } else {
 +                  char *cmd = (char *)Lst_Datum(ln);
 +                  Boolean hasOODATE = FALSE;
 +
 +                  if (strstr(cmd, "$?"))
 +                      hasOODATE = TRUE;
 +                  else if ((cp = strstr(cmd, ".OODATE"))) {
 +                      /* check for $[{(].OODATE[:)}] */
 +                      if (cp > cmd + 2 && cp[-2] == '$')
 +                          hasOODATE = TRUE;
 +                  }
 +                  if (hasOODATE) {
 +                      needOODATE = TRUE;
 +                      if (DEBUG(META))
 +                          fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno);
 +                  }
-                   cmd = Var_Subst(NULL, cmd, gn, TRUE);
++                  cmd = Var_Subst(NULL, cmd, gn, TRUE, TRUE);
 +
 +                  if ((cp = strchr(cmd, '\n'))) {
 +                      int n;
 +
 +                      /*
 +                       * This command contains newlines, we need to
 +                       * fetch more from the .meta file before we
 +                       * attempt a comparison.
 +                       */
 +                      /* first put the newline back at buf[x - 1] */
 +                      buf[x - 1] = '\n';
 +                      do {
 +                          /* now fetch the next line */
 +                          if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
 +                              break;
 +                          x = n;
 +                          lineno++;
 +                          if (buf[x - 1] != '\n') {
 +                              warnx("%s: %d: line truncated at %u", fname, lineno, x);
 +                              break;
 +                          }
 +                          cp = strchr(++cp, '\n');
 +                      } while (cp);
 +                      if (buf[x - 1] == '\n')
 +                          buf[x - 1] = '\0';
 +                  }
 +                  if (!hasOODATE &&
 +                      !(gn->type & OP_NOMETA_CMP) &&
 +                      strcmp(p, cmd) != 0) {
 +                      if (DEBUG(META))
 +                          fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
 +                      if (!metaIgnoreCMDs)
 +                          oodate = TRUE;
 +                  }
 +                  free(cmd);
 +                  ln = Lst_Succ(ln);
 +              }
 +          } else if (strcmp(buf, "CWD") == 0) {
 +              /*
 +               * Check if there are extra commands now
 +               * that weren't in the meta data file.
 +               */
 +              if (!oodate && ln != NULL) {
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
 +                  oodate = TRUE;
 +              }
 +              if (strcmp(p, cwd) != 0) {
 +                  if (DEBUG(META))
 +                      fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
 +                  oodate = TRUE;
 +              }
 +          }
 +      }
 +
 +      fclose(fp);
 +      if (!Lst_IsEmpty(missingFiles)) {
 +          if (DEBUG(META))
 +              fprintf(debug_file, "%s: missing files: %s...\n",
 +                      fname, (char *)Lst_Datum(Lst_First(missingFiles)));
 +          oodate = TRUE;
 +          Lst_Destroy(missingFiles, (FreeProc *)free);
 +      }
 +    } else {
 +      if ((gn->type & OP_META)) {
 +          if (DEBUG(META))
 +              fprintf(debug_file, "%s: required but missing\n", fname);
 +          oodate = TRUE;
 +      }
 +    }
 +    if (oodate && needOODATE) {
 +      /*
 +       * Target uses .OODATE which is empty; or we wouldn't be here.
 +       * We have decided it is oodate, so .OODATE needs to be set.
 +       * All we can sanely do is set it to .ALLSRC.
 +       */
 +      Var_Delete(OODATE, gn);
 +      Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0);
 +      if (cp)
 +          free(cp);
 +    }
 +    return oodate;
 +}
 +
 +/* support for compat mode */
 +
 +static int childPipe[2];
 +
 +void
 +meta_compat_start(void)
 +{
 +#ifdef USE_FILEMON_ONCE
 +    /*
 +     * We need to re-open filemon for each cmd.
 +     */
 +    BuildMon *pbm = &Mybm;
 +    
 +    if (pbm->mfp != NULL && useFilemon) {
 +      filemon_open(pbm);
 +    } else {
 +      pbm->mon_fd = pbm->filemon_fd = -1;
 +    }
 +#endif
 +    if (pipe(childPipe) < 0)
 +      Punt("Cannot create pipe: %s", strerror(errno));
 +    /* Set close-on-exec flag for both */
 +    (void)fcntl(childPipe[0], F_SETFD, 1);
 +    (void)fcntl(childPipe[1], F_SETFD, 1);
 +}
 +
 +void
 +meta_compat_child(void)
 +{
 +    meta_job_child(NULL);
 +    if (dup2(childPipe[1], 1) < 0 ||
 +      dup2(1, 2) < 0) {
 +      execError("dup2", "pipe");
 +      _exit(1);
 +    }
 +}
 +
 +void
 +meta_compat_parent(void)
 +{
 +    FILE *fp;
 +    char buf[BUFSIZ];
 +    
 +    close(childPipe[1]);                      /* child side */
 +    fp = fdopen(childPipe[0], "r");
 +    while (fgets(buf, sizeof(buf), fp)) {
 +      meta_job_output(NULL, buf, "");
 +      printf("%s", buf);
 +    }
 +    fclose(fp);
 +}
 +
 +#endif        /* USE_META */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..49603383e660cae886da7889369fd880f7ba1d64
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,88 @@@
++/*    $NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $     */
++
++/*-
++ * Copyright (c) 2015 The NetBSD Foundation, Inc.
++ * All rights reserved.
++ *
++ * This code is derived from software contributed to The NetBSD Foundation
++ * by Christos Zoulas.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
++ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++ * POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#if HAVE_NBTOOL_CONFIG_H
++#include "nbtool_config.h"
++#endif
++
++#if defined(MAKE_NATIVE) || defined(HAVE_NBTOOL_CONFIG_H)
++#include <sys/cdefs.h>
++#endif
++
++#if defined(__RCSID) && !defined(lint)
++__RCSID("$NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $");
++#endif
++
++#include "metachar.h"
++/*
++ * The following array is used to make a fast determination of which
++ * characters are interpreted specially by the shell.  If a command
++ * contains any of these characters, it is executed by the shell, not
++ * directly by us.
++ *
++ * perhaps move it to ctype?
++ */
++
++unsigned char _metachar[128] = {
++//    nul   soh   stx   etx   eot   enq   ack   bel
++      1,    0,    0,    0,    0,    0,    0,    0,
++//     bs    ht    nl    vt    np    cr    so    si
++      0,    0,    1,    0,    0,    0,    0,    0,
++//    dle   dc1   dc2   dc3   dc4   nak   syn   etb
++      0,    0,    0,    0,    0,    0,    0,    0,
++//    can    em   sub   esc    fs    gs    rs    us
++      0,    0,    0,    0,    0,    0,    0,    0,
++//     sp     !     "     #     $     %     &     '
++      0,    1,    1,    1,    1,    0,    1,    1,
++//      (     )     *     +     ,     -     .     /
++      1,    1,    1,    0,    0,    0,    0,    0,
++//      0     1     2     3     4     5     6     7
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      8     9     :     ;     <     =     >     ?
++      0,    0,    0,    1,    1,    0,    1,    1,
++//      @     A     B     C     D     E     F     G
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      H     I     J     K     L     M     N     O
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      P     Q     R     S     T     U     V     W
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      X     Y     Z     [     \     ]     ^     _
++      0,    0,    0,    1,    1,    1,    1,    0,
++//      `     a     b     c     d     e     f     g
++      1,    0,    0,    0,    0,    0,    0,    0,
++//      h     i     j     k     l     m     n     o
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      p     q     r     s     t     u     v     w
++      0,    0,    0,    0,    0,    0,    0,    0,
++//      x     y     z     {     |     }     ~   del
++      0,    0,    0,    1,    1,    1,    1,    0,
++};
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..db88d671067e4c4cab774c80979b20a230bd4a68
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,61 @@@
++/*    $NetBSD: metachar.h,v 1.4 2015/06/21 20:26:02 christos Exp $    */
++
++/*-
++ * Copyright (c) 2015 The NetBSD Foundation, Inc.
++ * All rights reserved.
++ *
++ * This code is derived from software contributed to The NetBSD Foundation
++ * by Christos Zoulas.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
++ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
++ * POSSIBILITY OF SUCH DAMAGE.
++ */
++#ifndef _METACHAR_H
++#define _METACHAR_H
++
++#include <ctype.h>
++
++extern unsigned char _metachar[];
++
++#define ismeta(c)     _metachar[(c) & 0x7f]
++
++static inline int
++hasmeta(const char *cmd)
++{
++      while (!ismeta(*cmd))
++              cmd++;
++
++      return *cmd != '\0';
++}
++
++static inline int
++needshell(const char *cmd, int white)
++{
++      while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') {
++              if (white && isspace((unsigned char)*cmd))
++                      break;
++              cmd++;
++      }
++
++      return *cmd != '\0';
++}
++
++#endif /* _METACHAR_H */
index 85c8ae1715b2543510dc606e3fdf751c4e0be7a2,0000000000000000000000000000000000000000..e7f71b5ffb400cc091d4744cf2cf8213283809a6
mode 100644,000000..100644
--- /dev/null
@@@ -1,974 -1,0 +1,1019 @@@
++2015-10-20  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * install-mk (MK_VERSION): 20151020
++      
++      * dirdeps.mk: Add logic for 
++        make -f dirdeps.mk some/dir.${TARGET_SPEC}
++
++2015-10-14  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * install-mk (MK_VERSION): 20151010
++
++2015-10-02  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * meta.stage.mk: use staging: ${STAGE_TARGETS:...
++        to have stage_lins run last in non-jobs mode.
++        Use .ORDER only for jobs mode.
++
++2015-09-02  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * rst2htm.mk: allow for per target flags etc.
++
++2015-09-01  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * install-mk (MK_VERSION): 20150901
++
++      * doc.mk: create dir if needed use DOC_INSTALL_OWN
++
++2015-06-15  Simon J. Gerraty  <sjg@bad.crufty.net>
++
++      * install-mk (MK_VERSION): 20150615
++      
++      * auto.obj.mk: allow use of MAKEOBJDIRPREFIX too.
++        Follow make's normal precedence rules.
++      
++      * gendirdeps.mk: allow customization of the header.
++        eg. for FreeBSD: 
++        GENDIRDEPS_HEADER= echo '\# ${FreeBSD:L:@v@$$$v$$ @:M*F*}';
++
++      * meta.autodep.mk: ignore dirdeps.cache*
++      
++      * meta.stage.mk: when bootstrapping options it can be handy to
++        throw warnings rather than errors for staging conflicts.
++
++      * meta.sys.mk: include local.meta.sys.mk for customization
++
 +2015-06-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150606
 +      
 +      * dirdeps.mk: don't rely on manually maintained Makefile.depend
 +        to set DEP_RELDIR and reset DIRDEPS.
 +        By setting DEP_RELDIR ourselves we can skip :tA
 +      
 +      * gendirdeps.mk: skip setting DEP_RELDIR.
 +
 +2015-05-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dirdeps.mk: avoid wildcards like make(bootstrap*)
 +
 +2015-05-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150520
 +
 +      * dirdeps.mk: when we are building dirdeps cache file we *want*
 +        meta_oodate to look at all the Makefile.depend files, so
 +        set .MAKE.DEPENDFILE to something that won't match.
 +
 +      * meta.stage.mk: for STAGE_AS_* basename of file may not be unique
 +        so first use absolute path as key.
 +        Also skip staging at level 0. 
 +
 +2015-04-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150430
 +      
 +      * dirdeps.mk: fix _count_dirdeps for non-cache case.
 +
 +2015-04-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150411
 +        bump version
 +      
 +      * own.mk: put AUTO_OBJ in OPTIONS_DEFAULT_NO rather than YES.
 +        it is here mainly for documentation purposes, since
 +        if using auto.obj.mk it is better done via sys.mk
 +
 +2015-04-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150401
 +      
 +      * meta2deps.sh: support @list
 +      
 +      * meta2deps.py: updates from Juniper
 +        o add EXCLUDES 
 +        o skip bogus input files.
 +        o treat 'M' and 'L' as both an 'R' and a 'W'
 +
 +2015-03-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20150303
 +      
 +      * dirdeps.mk: if MK_DIRDEPS_CACHE is yes, use dirdeps-cache
 +        which is built via sub-make so we have a .meta file to tell if
 +        it is out-of-date. 
 +        The dirdeps-cache contains the same dependency rules that we
 +        normaly construct on the fly.
 +        This adds a few seconds overhead when the cache is out of date,
 +        but for a large target, the savings can be significant (10-20min).
 +
 +2014-11-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20141118
 +      
 +      * meta.stage.mk: add stale_staged
 +      
 +      * dirdeps.mk (_DIRDEP_USE_LEVEL): allow this to be tweaked
 +        only useful under very rare conditions such as
 +        FreeBSD's make universe.
 +
 +      * auto.obj.mk: Allow MK_AUTO_OBJ to set MKOBJDIRS=auto
 +      
 +2014-11-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20141111
 +
 +      * mkopt.sh: use consistent semantics for _mk_opt and _mk_opts
 +
 +2014-11-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * FILES: include mkopt.sh which allows handling options in shell
 +        scripts in a manner compatible with options.mk
 +
 +2014-10-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: ensure only _STAGED_DIRS under objroot are used
 +        for GENDIRDEPS_FILTER to avoid surprises.
 +
 +2014-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +      
 +      * dirdeps.mk (NSkipHostDir): this needs SRCTOP prepended since by
 +        the time it is applied to __depdirs they have.
 +      
 +      * dirdeps.mk fix filtering of _machines since M_dep_qual_fixes
 +        expects patterns like *.${MACHINE}
 +      
 +      * cython.mk (pyprefix?): use pyprefix to find python bits
 +        since prefix might be something else (where we install our
 +        stuff)
 +      
 +2014-09-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20140911
 +      
 +      * dirdeps.mk: add bootstrap target to simplify adding support for
 +        new MACHINE.
 +      
 +2014-09-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * gendirdeps.mk: Add handling of GENDIRDEPS_FILTER_DIR_VARS and
 +        GENDIRDEPS_FILTER_VARS to make it easier to produce sharable
 +        Makefile.depend files.
 +
 +2014-08-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20140828
 +      
 +      * cython.mk: capture logic for building python extension modules
 +        with Cython.
 +
 +2014-08-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk (_STAGE_AS_BASENAME_USE): Add StageAs variant
 +
 +2014-08-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20140801
 +
 +      * dep.mk: use explicit MKDEP_MK rather than overload MKDEP to
 +      identify the autodep.mk variant. 
 +      
 +      * sys.dependfile.mk: delete .MAKE.DEPENDFILE if its
 +      initial value does not match .MAKE.DEPENDFILE_PREFIX
 +
 +      * meta.autodep.mk: if _bootstrap_dirdeps add RELDIR to DIRDEPS
 +
 +2014-05-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20140522
 +
 +      * lib.mk: use CC to link shlib for linux too
 +        patch from Brendan MacDonell
 +
 +2014-05-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.autodep.mk: add _reldir_{finish,failed} for gathering stats
 +        if WITH_META_STATS is defined.
 +
 +2014-05-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dirdeps.mk: accept -DWITHOUT_DIRDEPS (same a as -DNO_DIRDEPS)
 +        to supress dirdeps outside of .CURDIR.
 +
 +2014-04-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * Fix spelling errors - patch from Pedro Giffuni
 +
 +2014-03-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20140314
 +
 +      * dirdeps.mk (beforedirdeps): a handy hook
 +
 +      * dirdeps.mk (DIRDEP_MAKE): allow the actual command we run
 +        to visit leaf dirs to be intercepted (eg. for distributed
 +        build).
 +
 +      * dirdeps.mk (__depdirs): ensure // don't sneak in
 +      
 +      * gendirdeps.mk (DIRDEPS): ensure // don't sneak in
 +
 +
 +2014-02-21  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * rst2htm.mk (RST2PDF): add support for rst2pdf
 +
 +2014-02-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * dirdeps.mk (_last_dependfile): use .INCLUDEDFROMFILE if
 +        available.
 +
 +2014-02-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * options.mk: avoid :U so this isn't bmake dependent
 +
 +2014-02-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * options.mk: cleanup and simplify semanitcs 
 +        NO_* dominates all, if both WITH_* and WITHOUT_*
 +        are defined then result is DOMINATE_* which defaults to "no".
 +        Ie. WITHOUT_ normally wins.
 +
 +2013-12-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * meta2deps.py: convert to print function for python3 compat.
 +        we also need to open files with mode 'r' rather than 'rb'
 +        otherwise we get bytes instead of strings.
 +
 +2013-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +
 +      * dirdeps.mk: when TARGET_SPEC_VARS is more than just MACHINE
 +        apply the same filtering (M_dep_qual_fixes) when setting _machines
 +        as _build_dirs.
 +        Also fix the filtering of Makefile.depend files - for reporting
 +        what we are looking for (M_dep_qual_fixes can get confused by
 +        Makefile.depend) 
 +        Add some more debug info.
 +
 +2013-09-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * gendirdeps.mk (_objtops): fix typo also
 +        while processing M2D_OBJROOTS to gather qualdir_list
 +        qualify $ql with loop iterator to ensure correct results.
 +
 +2013-08-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20130801
 +      * libs.mk: update to match progs.mk
 +      
 +2013-07-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20130726
 +        some updates from Juniper and FreeBSD
 +        o meta2deps.py: indicate file and line number when we hit parse
 +          errors
 +          also allow @file to provide huge list of .meta files.
 +      * meta2deps.py: add try_parse() to cleanup the above.
 +      
 +2013-07-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20130716
 +      * own.mk: add GPROG as an option
 +      * prog.mk: honor MK_GPROF==yes
 +      
 +2013-05-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20130505
 +      * gendirdeps.mk, meta2deps.py, meta2deps.sh: handle $TARGET_SPEC
 +        for when $MACHINE isn't enough for objdir distinction.
 +        Bring meta2deps.sh closer to par with meta2deps.py.
 +
 +2013-04-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: set INSTALL to STAGE_INSTALL when making 'all'
 +        also if the target 'beforeinstall' exists, make it depend on
 +        .dirdep (incase it uses STAGE_INSTALL).
 +
 +2013-04-17  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20130401 ;-)
 +      * meta.stage.mk (STAGE_INSTALL_SH): add stage-install.sh as
 +        wrapper around install(1).
 +      * options.mk (OPTION_PREFIX): Allow a prefix other than MK_
 +
 +2013-03-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta2deps.py (MetaFile.__init__): ensure self.cwd is initialized.
 +      * install-mk (MK_VERSION): bump version
 +
 +2013-03-21  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * gendirdeps.mk: do not apply :tA to DPADD entries, since we lose
 +        any trailing /., rather apply :tA only when needed.
 +      * gendirdeps.mk: better mimic meta2deps handling of .dirdep files.
 +      * meta.stage.mk (LN_CP_SCRIPT): Add LnCp to do the ln||cp dance
 +        consistently.
 +      * dirdeps.mk: better describe the dance in sys.mk for TARGET_SPEC.
 +      
 +2013-03-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * gendirdeps.mk: revert the dance around .MAKE.DEPENDFILE_DEFAULT
 +        it is simpler to just not update when say building for "host"
 +        (where we know we apply filters to DIRDEPS), and using a
 +        non-machine qualified dependfile.
 +
 +2013-03-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dirdeps.mk: improve DIRDEPS filtering by allowing DEP_SKIP_DIR
 +        and DEP_DIRDEPS_FILTER to vary by DEP_MACHINE and DEP_TARGET_SPEC
 +      * gendirdeps.mk: ensure _objroot has trailing / if it needs it.
 +      * meta2deps.py: if machine is "host", then also trim
 +        self.host_target from any OBJROOTS.
 +      
 +
 +2013-03-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * gendirdeps.mk: if .MAKE.DEPENDFILE_DEFAULT is not machine
 +        qualified but _DEPENDFILE is, and .MAKE.DEPENDFILE_DEFAULT exists
 +        but _DEPENDFILE does not, compare the new _DEPENDFILE against
 +        .MAKE.DEPENDFILE_DEFAULT and discard if the same.
 +
 +2013-03-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: use STAGE_TARGETS to control .ORDER
 +        and hook to all: via staging:
 +
 +2013-03-07  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.dependfile.mk (.MAKE.DEPENDFILE_DEFAULT): 
 +        use a separate variable for the default .MAKE.DEPENDFILE value
 +        so that it can be controlled independently of
 +        .MAKE.DEPENDFILE_PREFERENCE
 +
 +      * meta.stage.mk: throw error if cp fails etc.
 +        Stage*() return early if passed no args.
 +        .ORDER stage_*
 +
 +2013-03-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * gendirdeps.mk: handle multiple M2D_OBJROOTS better.
 +
 +2013-02-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20130210
 +      * import latest dirdeps.mk, gendirdeps.mk and meta2deps.py 
 +        from Juniper. 
 +        o dirdeps.mk now fully supports TARGET_SPEC consisting of more
 +          than just MACHINE.
 +        o no longer use DEP_MACHINE from Makefile.depend* so remove it.
 +      
 +2013-01-23  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20130123
 +      * meta.stage.mk: add stage_links (hard links).
 +        if doing hard links, we add dest to link as well.
 +        Default the stage dir for [sym]links to STAGE_OBJTOP since
 +        these are typically specified as absolute paths.
 +        Add -m "mode" flag to StageFiles and StageAs.
 +
 +2012-11-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20121111
 +      * autoconf.mk: avoid meta mode seeing changed commands for config.status
 +      * meta.autodep.mk: pass resolved MAKESYSPATH to gendirdeps
 +        in case we were found via .../mk
 +      * sys.clean-env.mk: move it from examples, we and others use it
 +        "as is".
 +      * FILES: add srctop.mk and options.mk
 +      * own.mk: convert to using options.mk
 +        which is modeled after FreeBSD's handling of MK_*
 +        but more flexible.
 +        This allows MK_* for boolean knobs to not be confused
 +        with MK* which can be commands.
 +
 +      * examples/sys.clean-env.mk: add WITH[OUT]_ to
 +        MAKE_ENV_SAVE_PREFIX_LIST.
 +        Mention that HOME=/var/empty might be a good idea.
 +
 +2012-11-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.dependfile.mk: if not depend file exists, $MACHINE
 +        specific ones are supported but not the default,
 +        check if any exist and follow suit.
 +
 +2012-11-06  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20121106
 +
 +2012-11-05  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * import latest dirdeps.mk and meta2deps.py from Juniper.
 +      * progs.mk: add MAN and CXXFLAGS to PROG_VARS
 +        also add PROGS_TARGETS and pass on PROG_CXX if it seems
 +        appropriate.
 +      
 +2012-11-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: update CLEANFILES
 +        remove redundant cp of .dirdep from STAGE_AS_SCRIPT.
 +      * progs.mk: Add LDADD to PROG_VARS
 +      
 +2012-10-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk (STAGE_DIR_FILTER): track dirs we stage to in
 +        _STAGED_DIRS so that these can be turned into filters for
 +        GENDIRDEPS_FILTER.
 +
 +2012-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20121010
 +      * meta.stage.mk (STAGE_DIRDEP_SCRIPT): check that an existing
 +      target.dirdep matches .dirdep
 +
 +2012-08-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120808
 +      * import latest meta2deps.py from Juniper.
 +
 +2012-07-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120711
 +      * dep.mk: add explicit dependencies on SRCS after applying
 +        SRCS_DEP_FILTER 
 +      * meta.autodep.mk: add explicit dependencies on SRCS after
 +        applying SRCS_DEP_FILTER
 +      * meta.autodep.mk: ensure GENDIRDEPS_FILTER is exported if needed.
 +      
 +2012-06-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120626
 +      * meta.sys.mk: ignore PYTHON if it does not exist
 +        compare ${.MAKE.DEPENDFILE:E} against ${MACHINE} is more reliable.
 +      * meta.stage.mk: examine .MAKE.DEPENDFILE_PREFERENCE for any
 +        entries ending in .${MACHINE} to decide if qualified _dirdep is
 +        needed.
 +      * gendirdeps.mk: only produce unqualified deps if no
 +        .MAKE.DEPENDFILE_PREFERENCE ends in .${MACHINE}
 +      * meta.subdir.mk: apply SUBDIREPS_FILTER
 +      
 +2012-04-20  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120420
 +      * add sys.dependfile.mk so we can experiment with
 +        .MAKE.DEPENDFILE_PREFERENCE 
 +      * meta.autodep.mk: _DEPENDFILE is precious!
 +      
 +2012-03-15  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120315
 +      * install-new.mk: avoid being interrupted
 +
 +2012-02-26  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * man.mk: MAN might have multiple values so be careful with exists().
 +
 +2012-01-19  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20120112
 +      * fix examples/sys.clean-env.mk so that MAKEOBJDIR is handled
 +        as: MAKEOBJDIR='${.CURDIR:S,${SRCTOP},${OBJTOP},}'
 +
 +2011-12-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION):  bump version to 20111201
 +      * import dirdeps.mk from Juniper sjg@
 +        o more consistent handling of DEP_MACHINE, especially when
 +          dealing with an odd Makefile.depend, when normally using
 +          Makefile.depend.${MACHINE}
 +
 +2011-11-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20111122
 +      * meta.autodep.mk: add some debug output, be more crisp about
 +        updating.  Use ${.ALLTARGETS:M*.o} as a clue for .depend
 +
 +2011-11-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20111111
 +        it's too cool to miss
 +      * import meta* updates from Juniper sjg@
 +        o dirdeps.mk set DEP_MACHINE for Makefile.depend (when we are
 +          normally using Makefile.depend.${MACHINE}), handy for
 +          read-only manually maintained dependencies.
 +        o meta2deps.py add a clear 'ERROR:' token if an exception is raised.
 +        o gendirdeps.mk if ERROR: from meta2deps.py do not update
 +          anything.
 +      
 +2011-10-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-new.mk separate the cmp and copy logic to its own function.
 +      
 +2011-10-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20111028
 +      * sys.mk: include auto.obj.mk if MKOBJDIRS is set to auto
 +      * subdir.mk: ensure _SUBDIRUSE is provided
 +      * meta.autodep.mk: remove dependency of gendirdeps.mk on auto.obj.mk 
 +      * meta.subdir.mk: always allow for Makefile.depend
 +      
 +2011-10-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20111010
 +        o minor tweak to *dirdeps.mk from Juniper sjg@
 +      
 +2011-10-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20111001
 +        o add meta2deps.py from Juniper sjg@
 +        o tweak gendirdeps.mk to work with meta2deps.py when not
 +          cross-building 
 +      * autoconf.mk: add autoconf-input as a hook for regenerating 
 +        AUTOCONF_INPUTS (configure).
 +
 +2011-08-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.autodep.mk: if we do not have OBJS, .depend isn't a useful
 +        trigger for updating Makefile.depend* 
 +
 +2011-08-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110808
 +      * obj.mk: minor cleanup
 +      * auto.obj.mk: improve description of Mkdirs and honor NO_OBJ too.
 +
 +2011-08-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * auto.obj.mk (.OBJDIR): throw an error if we cannot use the
 +        specified dir.
 +
 +2011-06-28  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.autodep.mk: if XMAKE_META_FILE is set
 +        the makefile uses a foreign make, and so dependencies
 +        can only be gathered from a clean tree build.
 +
 +2011-06-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110622
 +      * meta.autodep.mk: improve bootstraping
 +
 +2011-06-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * yacc.mk: handle the corner case of .c being removed
 +        while .h remains.
 +
 +2011-06-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * yacc.mk: do .y.h and .y.c separately
 +
 +2011-06-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110606
 +      * don't store SRC_DIRDEPS in Makefile.depend* by default
 +        not everyone needs it.
 +
 +2011-05-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110505
 +        first release including meta mode makefiles
 +
 +2011-05-02  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: add STAGE_AS_SETS and stage_as
 +        for things that need to be staged with different names.
 +
 +2011-05-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: add notion of STAGE_SETS
 +        so a makefile can stage to multiple dirs
 +
 +2011-04-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * rst2htm.mk: convert rst to s5 (slides) or plain html depending
 +        on target name. 
 +
 +2011-03-30  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110330
 +
 +2011-03-29  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.mk (_DEBUG_MAKE_FLAGS): use indirection so that DEBUG_MAKE_FLAGS0
 +        can be used to debug level 0 only and DEBUG_MAKE_FLAGS for the rest.
 +      * sys.mk: re-define M_whence in terms of M_type.
 +        M_type is useful for checking if something is a builtin.
 +      
 +2011-03-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.stage.mk: add stage_symlinks and leverage StageLinks for
 +        stage_libs 
 +
 +2011-03-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dirdeps.mk: correct value for _depdir_files depends on
 +        .MAKE.DEPENDFILE 
 +        Add our copyright - just to make it clear we have frobbed this
 +        quite a bit.
 +        DEP_MACHINE needs to be set to MACHINE each time, if using only
 +        Makefile.depend (cf. Makefile.depend.${MACHINE})
 +
 +      * meta.stage.mk: meta mode version of staging
 +
 +      * init.mk, final.mk: include local.*.mk to simplify customization
 +
 +2011-03-03  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * auto.obj.mk: just because we are doing mk destroy, we should
 +        still set .OBJDIR correctly if it exists.
 +
 +      * install-mk (mksrc): do not exclude meta.sys.mk
 +
 +2011-03-01  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * host-target.mk: set/export _HOST_ARCH etc separately,
 +        catch junk resulting from uname -p, so we can find sys/Linux.mk
 +        correctly.
 +
 +2011-02-18  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.sys.mk: throw an error if /dev/filemon is missing and we
 +        expected to be updating Makefile.depend*
 +
 +2011-02-14  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20110214
 +      * meta.subdir.mk: add support for -DBOOTSTRAP_DEPENDFILES
 +
 +2010-09-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * meta.sys.mk: not valid for older bmake
 +
 +2010-09-24  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100919
 +      include dirdeps.mk et al from Juniper Networks, 
 +      for meta mode - requires filemon(9).
 +      * sys.mk, subdir.mk: Add hooks for meta mode.
 +      we do this as meta.sys.mk, meta.autodep.mk and meta.subdir.mk
 +      to make turning it on/off simple.
 +
 +2010-06-16  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100616
 +      * fix typo in sys.mk
 +
 +2010-06-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100612
 +      * lib.mk: remove duplicate addition to SOBJS
 +
 +2010-06-10  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.mk: Add a means of selectively turning on debug flags.
 +        Eg. DEBUG_MAKE_FLAGS=-dv DEBUG_MAKE_DIRS="*lib/sjg"
 +        will act as if we did make -dv if .CURDIR ends in lib/sjg
 +        DEBUG_MAKE_SYS_DIRS does the same thing, but we set the flags at
 +        the start of sys.mk rather than the end.
 +        This only makes sense for leaf dirs, so we check that
 +        .MAKE.LEVEL > 0
 +
 +2010-06-09  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100608
 +      * sys.mk: include sys.env.mk later so it can use M_ListToSkip et al.
 +      * examples/sys.clean-env.mk: require MAKE_VERIONS >= 20100606
 +        also make it easier for folk to tweak
 +
 +2010-06-08  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100606
 +        do not install examples/*
 +      * FILES: add examples/sys.clean-env.mk
 +      * examples/sys.clean-env.mk: use .export-env to handle MAKEOBJDIR
 +        this requires bmake-20100606 or later to work.
 +
 +2010-05-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.mk (M_tA): better simulate the result of :tA if not available.
 +
 +2010-05-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * sys.mk: canonicalize MAKE_VERSION
 +        old versions reported bmake-<src-date> build-<build-date>
 +        whereas we only care about <src-date>
 +
 +2010-04-25  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk: just warn about FORCE_{BSD,SYS}_MK being ignored
 +      * lib.mk: we only build the shared lib if SHLIB_FULLVERSION
 +        is !empty
 +
 +2010-04-22  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dpadd.mk: use LDADD_* if defined.
 +
 +2010-04-21  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100420
 +      * sys/NetBSD.mk: add MACHINE_CPU to keep netbsd makefiles happy
 +      * autoconf.mk allow AUTO_AUTOCONF
 +      
 +2010-04-19  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * obj.mk: add objwarn to keep freebsd makefiles happy
 +      * auto.obj.mk: ensure Mkdirs is available.
 +      * FILES: add auto.dep.mk - a simpler version of autodep.mk
 +      * dep.mk: auto.dep.mk does not do 'make depend' so ignore it if
 +        asked to do that.
 +        fix/simplify the tests for when to run mkdep.
 +      * auto.dep.mk: add some explanation of how/what we do.
 +      * autodep.mk: skip the .OPTIONAL frobbing of .depend
 +        bmake's FROM_DEPEND flag makes it redundant.
 +      
 +2010-04-13  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100404
 +      * subdir.mk: protect from multiple inclusion using _SUBDIRUSE.
 +      * obj.mk: protect from multiple inclusion even as bsd.obj.mk
 +      Also create a target _SUBDIRUSE so that we can  be used without
 +      subdir.mk
 +
 +2010-04-12  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * dep.mk: use <> when .including so can override.
 +
 +2010-01-11  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * lib.mk (SHLIB_LINKS): ensure a string comparison.
 +
 +2010-01-04  Simon J. Gerraty  <sjg@bad.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20100102
 +      * own.mk: ensure PRINTOBJDIR works
 +      * autoconf.mk: pass on CONFIGURE_ARGS
 +      * init.mk: handle COPTS.${.IMPSRC:T} etc.
 +      * lib.mk: allow sys.mk to control SHLIB_FULLVERSION
 +        fix handling of symlinks for darwin
 +      * libnames.mk: add DSHLIBEXT for libs which only exist as shared.
 +      * man.mk: suppress chown when not root.
 +      * rst2htm.mk: allow srcs from multiple locations.
 +      * sys.mk: M_whence, stop after 1st line of output.
 +      * sys/Darwin.mk: Use .dylib for DSHLIBEXT and HOST_LIBEXT
 +      * sys/SunOS.mk: we need to export PATH
 +
 +2009-12-23  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +        include rst2htm.mk
 +
 +2009-12-17  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * sys.mk,libnames.mk add .-include <local.*>
 +        this allows local customization without the need to edit the
 +        distributed files. 
 +
 +2009-12-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * dpadd.mk (__dpadd_libdirs): order -L's to avoid picking up
 +        older versions already installed.
 +
 +2009-12-13  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * stage.mk (.stage-install): generalize lib.mk's .libinstall
 +      * rules.mk rules for generic Makefile.
 +      * inc.mk install for includes.
 +
 +2009-12-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * sys/NetBSD.mk (MAKE_VERSION): some of our *.mk want to check
 +        this, so provide it if using native make.
 +
 +2009-12-10  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * FILES: move all the platform *.sys.mk files to sys/*.mk
 +      * Rename Generic.sys.mk to sys.mk - we always want it.
 +
 +2009-11-17  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * host-target.mk: only export the expensive stuff 
 +      * Generic.sys.mk (sys_mk): for SunOS we need to look for
 +        ${HOST_OS}.${HOST_OSMAJOR} too!
 +
 +2009-11-07  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * lib.mk: if sys.mk doesn't give us an lorder, don't use it.
 +        based on patch from Greg Olszewski.
 +      * Generic.sys.mk: if we have nothing to work with
 +      set LORDER etc only if we can find it.
 +
 +2009-09-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * man.mk: cleanman: remove CLEANMAN if defined.
 +
 +2009-09-04  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * SunOS.5.sys.mk (CC): Use ?= like the other *sys.mk
 +
 +2009-07-17  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      include auto.obj.mk
 +
 +
 +2009-03-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * prog.mk,lib.mk: ensure test of USE_DPADD_MK doesn't fail.
 +
 +2008-11-11  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      man.mk: ensure we generate *.cat1 etc in .
 +
 +2008-07-16  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      add prlist.mk
 +
 +2007-11-25  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Generic.sys.mk: Allow os specific sys.mk to be in a
 +      subdir of ${.PARSEDIR}
 +
 +2007-11-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * general cleanup
 +      * dpadd.mk introduce DPMAGIC_LIBS_* 
 +
 +2007-04-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +
 +      * libs.mk, progs.mk, autodep.mk: allow for per lib/prog
 +      depend files and ensure clean is called for each lib/prog.
 +
 +2007-03-27  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * autodep.mk (.depend): delete lines that do not start with
 +      space and do not contain ':'
 +
 +2007-02-16  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * autodep.mk (.depend): gcc may wrap lines if pathnames are long
 +      so make sure the transform for .OPTIONAL copes.
 +
 +2007-02-03  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +
 +      * own.mk: make sure RM and LN are defined.
 +
 +      * obj.mk: fix a typo, and objlink target.
 +
 +2006-12-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version
 +      * added libs.mk - analogous to progs.mk
 +        make both of them always inlcude {lib,prog}.mk
 +
 +2006-12-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * progs.mk: add a means of building multiple apps in one dir.
 +
 +2006-11-26  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20061126
 +
 +      * warnings.mk: detect invalid WARNINGS_SET
 +      
 +      * warnings.mk: use ${.TARGET:T:R}.o when looking for target
 +      specific warnings. 
 +      
 +      * For .cc sources, turn off warnings that g++ vomits on.
 +
 +2006-11-08  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * own.mk: if __initialized__ target doesn't exist and we are
 +      FreeBSD we got here directly from sys.mk
 +
 +2006-11-06  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20061106
 +      add scripts.mk
 +
 +2006-03-18  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20060318
 +
 +      * autodep.mk: avoid := when modifying OBJS into __dependsrcs
 +
 +2006-03-02  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20060302
 +      * autodep.mk: use -MF et al to help gcc+ccache DTRT.
 +
 +2006-03-01  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20060301
 +      * autodep.mk (.depend): 
 +      if MAKE_VERSION is newer than  20050530 we can make .END depend on
 +      .depend and make .depend depend on __depsrcs that exist.
 +      * dpadd.mk: add SRC_PATHADD
 +      
 +2005-11-04  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20051104
 +      * prog.mk: remove all the LIBC?= junk, use
 +      .-include libnames.mk instead (none by default).
 +      also if USE_DPADD_MK is set, include that.
 +
 +2005-10-09  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20051001
 +      Add UnixWare.sys.mk from Klaus Heinz.
 +
 +2005-04-05  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk: always install *.sys.mk and if need be symlink one
 +      to sys.mk
 +
 +2005-03-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * subdir.mk, own.mk: use .MAKE rather than MAKE
 +
 +2004-02-15  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * own.mk: don't use NetBSD's _SRC_TOP_ it can
 +      cause confusion.  Also don't take just 'mk' as a 
 +      srctop indicator.
 +
 +2004-02-14  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * warnings.mk: overhauled, now very powerful.
 +
 +2004-02-03  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * Generic.sys.mk: need to use ${.PARSEDIR} with exists().
 +
 +2004-02-01  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): bump version to 20040201
 +      * extract HOST_TARGET stuff to host-target.mk so own.mk and
 +      Generic.sys.mk can share.
 +      * fix typo in autodep.mk _SUBDIRUSE not _SUBDIR.
 +
 +2003-09-30  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20030930
 +      * rename generic.sys.mk to Generic.sys.mk
 +      so that it does not get installed (unless being used as sys.mk)
 +      * set OS and ROOT_GROUP for those that we know the value.
 +      for others (eg. Generic.sys.mk) wrap the != in an .ifndef so
 +      we don't do it again for each sub-make.
 +      
 +2003-09-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk (MK_VERSION): 20030928
 +      Add some extra *.sys.mk from bootstrap-pkgsrc
 +      some of these likely still need work.
 +      Make everything default to root:wheel ownership,
 +      sys.mk can set ROOT_GROUP accordingly.
 +
 +2003-08-07  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk: if FORCE_BSD_MK={cp,ln} use the ones in SYS_MK_DIR
 +      not the portable ones.
 +
 +2003-07-31  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk: add ability to use cp -f when updating 
 +      destination .mk files.  Also now possible to play games with 
 +      FORCE_SYS_MK=ln etc on *BSD machines to link /usr/share/mk/sys.mk
 +      into dest - not recommended unless you seriously want to.
 +
 +2003-07-28  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * own.mk (IMPFLAGS): add support for COPTS.${IMPSRC:T} etc
 +      for semi-compatability with NetBSD.
 +
 +2003-07-23  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * install-mk: add a version indicator
 +
 +2003-07-22  Simon J. Gerraty  <sjg@void.crufty.net>
 +
 +      * prog.mk: don't try and use ${LIBCRT0} if its /dev/null
 +
 +      * install-mk: Allow FORCE_SYS_MK to come from env
 +
 +
 +      
index e25a721cdf69b49cc4394fd4004ef435a148bf61,0000000000000000000000000000000000000000..fee3de29b7d5057465e26db0f326e48bbc31bf8b
mode 100644,000000..100644
--- /dev/null
@@@ -1,61 -1,0 +1,64 @@@
- # $Id: auto.obj.mk,v 1.10 2015/04/16 16:59:00 sjg Exp $
++# $Id: auto.obj.mk,v 1.11 2015/06/16 06:28:21 sjg Exp $
 +#
 +#     @(#) Copyright (c) 2004, Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +ECHO_TRACE ?= echo
 +
 +.ifndef Mkdirs
 +# A race condition in some versions of mkdir, means that it can bail 
 +# if another process made a dir that mkdir expected to.
 +# We repeat the mkdir -p a number of times to try and work around this.
 +# We stop looping as soon as the dir exists.
 +# If we get to the end of the loop, a plain mkdir will issue an error.
 +Mkdirs= Mkdirs() { \
 +      for d in $$*; do \
 +              for i in 1 2 3 4 5 6; do \
 +                      mkdir -p $$d; \
 +                      test -d $$d && return 0; \
 +              done > /dev/null 2>&1; \
 +              mkdir $$d || exit $$?; \
 +      done; }
 +.endif
 +
 +# if MKOBJDIRS is set to auto (and NOOBJ isn't defined) do some magic...
 +# This will automatically create objdirs as needed.
 +# Skip it if we are just doing 'clean'.
 +.if ${MK_AUTO_OBJ:Uno} == "yes"
 +MKOBJDIRS= auto
 +.endif
 +.if !defined(NOOBJ) && !defined(NO_OBJ) && ${MKOBJDIRS:Uno} == auto
 +# Use __objdir here so it is easier to tweak without impacting
 +# the logic.
- __objdir?= ${MAKEOBJDIR}
++.if !empty(MAKEOBJDIRPREFIX) && exists(${MAKEOBJDIRPREFIX})
++__objdir?= ${MAKEOBJDIRPREFIX}${.CURDIR}
++.endif
++__objdir?= ${MAKEOBJDIR:Uobj}
 +__objdir:= ${__objdir:tA}
 +.if ${.OBJDIR} != ${__objdir}
 +# We need to chdir, make the directory if needed
 +.if !exists(${__objdir}/) && \
 +      (${.TARGETS} == "" || ${.TARGETS:Nclean*:N*clean:Ndestroy*} != "")
 +# This will actually make it... 
 +__objdir_made != echo ${__objdir}/; umask ${OBJDIR_UMASK:U002}; \
 +        ${ECHO_TRACE} "[Creating objdir ${__objdir}...]" >&2; \
 +        ${Mkdirs}; Mkdirs ${__objdir}
 +__objdir:= ${__objdir:tA}
 +.endif
 +# This causes make to use the specified directory as .OBJDIR
 +.OBJDIR: ${__objdir}
 +.if ${.OBJDIR} != ${__objdir} && ${__objdir_made:Uno:M${__objdir}/*} != ""
 +.error could not use ${__objdir}: .OBJDIR=${.OBJDIR}
 +.endif
 +.endif
 +.endif
index 4aa101fbd8d06bde896d2163e046fcc56d7900b9,0000000000000000000000000000000000000000..823115d7a8ce6f4a2efdb2729a4d281084a07a3d
mode 100644,000000..100644
--- /dev/null
@@@ -1,651 -1,0 +1,666 @@@
- # $Id: dirdeps.mk,v 1.54 2015/06/08 20:55:11 sjg Exp $
++# $Id: dirdeps.mk,v 1.55 2015/10/20 22:04:53 sjg Exp $
 +
 +# Copyright (c) 2010-2013, Juniper Networks, Inc.
 +# All rights reserved.
 +# 
 +# Redistribution and use in source and binary forms, with or without
 +# modification, are permitted provided that the following conditions 
 +# are met: 
 +# 1. Redistributions of source code must retain the above copyright
 +#    notice, this list of conditions and the following disclaimer. 
 +# 2. Redistributions in binary form must reproduce the above copyright
 +#    notice, this list of conditions and the following disclaimer in the
 +#    documentation and/or other materials provided with the distribution.  
 +# 
 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 +
 +# Much of the complexity here is for supporting cross-building.
 +# If a tree does not support that, simply using plain Makefile.depend
 +# should provide sufficient clue.
 +# Otherwise the recommendation is to use Makefile.depend.${MACHINE}
 +# as expected below.
 +
 +# Note: this file gets multiply included.
 +# This is what we do with DIRDEPS
 +
 +# DIRDEPS:
 +#     This is a list of directories - relative to SRCTOP, it is
 +#     normally only of interest to .MAKE.LEVEL 0.
 +#     In some cases the entry may be qualified with a .<machine>
 +#     or .<target_spec> suffix (see TARGET_SPEC_VARS below),
 +#     for example to force building something for the pseudo
 +#     machines "host" or "common" regardless of current ${MACHINE}.
 +#     
 +#     All unqualified entries end up being qualified with .${TARGET_SPEC}
 +#     and partially qualified (if TARGET_SPEC_VARS has multiple
 +#     entries) are also expanded to a full .<target_spec>.
 +#     The  _DIRDEP_USE target uses the suffix to set TARGET_SPEC
 +#     correctly when visiting each entry.
 +#
 +#     The fully qualified directory entries are used to construct a
 +#     dependency graph that will drive the build later.
 +#     
 +#     Also, for each fully qualified directory target, we will search
 +#     using ${.MAKE.DEPENDFILE_PREFERENCE} to find additional
 +#     dependencies.  We use Makefile.depend (default value for
 +#     .MAKE.DEPENDFILE_PREFIX) to refer to these makefiles to
 +#     distinguish them from others.
 +#     
 +#     Each Makefile.depend file sets DEP_RELDIR to be the
 +#     the RELDIR (path relative to SRCTOP) for its directory, and
 +#     since each Makefile.depend file includes dirdeps.mk, this
 +#     processing is recursive and results in .MAKE.LEVEL 0 learning the
 +#     dependencies of the tree wrt the initial directory (_DEP_RELDIR).
 +#
 +# BUILD_AT_LEVEL0
 +#     Indicates whether .MAKE.LEVEL 0 builds anything:
 +#     if "no" sub-makes are used to build everything,
 +#     if "yes" sub-makes are only used to build for other machines.
 +#     It is best to use "no", but this can require fixing some
 +#     makefiles to not do anything at .MAKE.LEVEL 0.
 +#
 +# TARGET_SPEC_VARS
 +#     The default value is just MACHINE, and for most environments
 +#     this is sufficient.  The _DIRDEP_USE target actually sets
 +#     both MACHINE and TARGET_SPEC to the suffix of the current
 +#     target so that in the general case TARGET_SPEC can be ignored.
 +#
 +#     If more than MACHINE is needed then sys.mk needs to decompose
 +#     TARGET_SPEC and set the relevant variables accordingly.
 +#     It is important that MACHINE be included in and actually be
 +#     the first member of TARGET_SPEC_VARS.  This allows other
 +#     variables to be considered optional, and some of the treatment
 +#     below relies on MACHINE being the first entry.
 +#     Note: TARGET_SPEC cannot contain any '.'s so the target
 +#     triple used by compiler folk won't work (directly anyway).
 +#
 +#     For example:
 +#
 +#             # Always list MACHINE first, 
 +#             # other variables might be optional.
 +#             TARGET_SPEC_VARS = MACHINE TARGET_OS
 +#             .if ${TARGET_SPEC:Uno:M*,*} != ""
 +#             _tspec := ${TARGET_SPEC:S/,/ /g}
 +#             MACHINE := ${_tspec:[1]}
 +#             TARGET_OS := ${_tspec:[2]}
 +#             # etc.
 +#             # We need to stop that TARGET_SPEC affecting any submakes
 +#             # and deal with MACHINE=${TARGET_SPEC} in the environment.
 +#             TARGET_SPEC =
 +#             # export but do not track
 +#             .export-env TARGET_SPEC 
 +#             .export ${TARGET_SPEC_VARS}
 +#             .for v in ${TARGET_SPEC_VARS:O:u}
 +#             .if empty($v)
 +#             .undef $v
 +#             .endif
 +#             .endfor
 +#             .endif
 +#             # make sure we know what TARGET_SPEC is
 +#             # as we may need it to find Makefile.depend*
 +#             TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,}
 +#     
 +
 +# touch this at your peril
 +_DIRDEP_USE_LEVEL?= 0
 +.if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL}
 +# only the first instance is interested in all this
 +
 +# First off, we want to know what ${MACHINE} to build for.
 +# This can be complicated if we are using a mixture of ${MACHINE} specific
 +# and non-specific Makefile.depend*
 +
 +.if !target(_DIRDEP_USE)
 +# do some setup we only need once
 +_CURDIR ?= ${.CURDIR}
 +_OBJDIR ?= ${.OBJDIR}
 +
 +now_utc = ${%s:L:gmtime}
 +.if !defined(start_utc)
 +start_utc := ${now_utc}
 +.endif
 +
 +# make sure these are empty to start with
 +_DEP_TARGET_SPEC =
 +_DIRDEP_CHECKED =
 +
 +# If TARGET_SPEC_VARS is other than just MACHINE
 +# it should be set by sys.mk or similar by now.
 +# TARGET_SPEC must not contain any '.'s.
 +TARGET_SPEC_VARS ?= MACHINE
 +# this is what we started with
 +TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,}
 +# this is what we mostly use below
 +DEP_TARGET_SPEC = ${TARGET_SPEC_VARS:S,^,DEP_,:@v@${$v:U}@:ts,}
 +# make sure we have defaults
 +.for v in ${TARGET_SPEC_VARS}
 +DEP_$v ?= ${$v}
 +.endfor
 +
 +.if ${TARGET_SPEC_VARS:[#]} > 1
 +# Ok, this gets more complex (putting it mildly).
 +# In order to stay sane, we need to ensure that all the build_dirs
 +# we compute below are fully qualified wrt DEP_TARGET_SPEC.
 +# The makefiles may only partially specify (eg. MACHINE only),
 +# so we need to construct a set of modifiers to fill in the gaps.
 +# jot 10 should output 1 2 3 .. 10
 +JOT ?= jot
 +_tspec_x := ${${JOT} ${TARGET_SPEC_VARS:[#]}:L:sh}
 +# this handles unqualified entries
 +M_dep_qual_fixes = C;(/[^/.,]+)$$;\1.$${DEP_TARGET_SPEC};
 +# there needs to be at least one item missing for these to make sense
 +.for i in ${_tspec_x:[2..-1]}
 +_tspec_m$i := ${TARGET_SPEC_VARS:[2..$i]:@w@[^,]+@:ts,}
 +_tspec_a$i := ,${TARGET_SPEC_VARS:[$i..-1]:@v@$$$${DEP_$v}@:ts,}
 +M_dep_qual_fixes += C;(\.${_tspec_m$i})$$;\1${_tspec_a$i};
 +.endfor
 +.else
 +# A harmless? default.
 +M_dep_qual_fixes = U
 +.endif
 +
 +.if !defined(.MAKE.DEPENDFILE_PREFERENCE)
 +# .MAKE.DEPENDFILE_PREFERENCE makes the logic below neater?
 +# you really want this set by sys.mk or similar
 +.MAKE.DEPENDFILE_PREFERENCE = ${_CURDIR}/${.MAKE.DEPENDFILE:T}
 +.if ${.MAKE.DEPENDFILE:E} == "${TARGET_SPEC}"
 +.if ${TARGET_SPEC} != ${MACHINE}
 +.MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R}.$${MACHINE}
 +.endif
 +.MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R}
 +.endif
 +.endif
 +
 +_default_dependfile := ${.MAKE.DEPENDFILE_PREFERENCE:[1]:T}
 +_machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*}
 +
 +# for machine specific dependfiles we require ${MACHINE} to be at the end
 +# also for the sake of sanity we require a common prefix
 +.if !defined(.MAKE.DEPENDFILE_PREFIX)
 +# knowing .MAKE.DEPENDFILE_PREFIX helps
 +.if !empty(_machine_dependfiles)
 +.MAKE.DEPENDFILE_PREFIX := ${_machine_dependfiles:[1]:T:R}
 +.else
 +.MAKE.DEPENDFILE_PREFIX := ${_default_dependfile:T}
 +.endif
 +.endif
 +
 +
 +# this is how we identify non-machine specific dependfiles
 +N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}}
 +
 +.endif                                # !target(_DIRDEP_USE)
 +
 +# if we were included recursively _DEP_TARGET_SPEC should be valid.
 +.if empty(_DEP_TARGET_SPEC)
 +# we may or may not have included a dependfile yet
 +.if defined(.INCLUDEDFROMFILE)
 +_last_dependfile := ${.INCLUDEDFROMFILE:M${.MAKE.DEPENDFILE_PREFIX}*}
 +.else
 +_last_dependfile := ${.MAKE.MAKEFILES:M*/${.MAKE.DEPENDFILE_PREFIX}*:[-1]}
 +.endif
 +.if ${_debug_reldir:U0}
 +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _last_dependfile='${_last_dependfile}'
 +.endif
 +
 +.if empty(_last_dependfile) || ${_last_dependfile:E:${N_notmachine}} == ""
 +# this is all we have to work with
 +DEP_MACHINE = ${TARGET_MACHINE:U${MACHINE}}
 +_DEP_TARGET_SPEC := ${DEP_TARGET_SPEC}
 +.else
 +_DEP_TARGET_SPEC = ${_last_dependfile:${M_dep_qual_fixes:ts:}:E}
 +.endif
 +.if !empty(_last_dependfile)
 +# record that we've read dependfile for this
 +_DIRDEP_CHECKED += ${_CURDIR}.${TARGET_SPEC}
 +.endif
 +.endif
 +
 +# by now _DEP_TARGET_SPEC should be set, parse it.
 +.if ${TARGET_SPEC_VARS:[#]} > 1
 +# we need to parse DEP_MACHINE may or may not contain more info
 +_tspec := ${_DEP_TARGET_SPEC:S/,/ /g}
 +.for i in ${_tspec_x}
 +DEP_${TARGET_SPEC_VARS:[$i]} := ${_tspec:[$i]}
 +.endfor
 +.for v in ${TARGET_SPEC_VARS:O:u}
 +.if empty(DEP_$v)
 +.undef DEP_$v
 +.endif
 +.endfor
 +.else
 +DEP_MACHINE := ${_DEP_TARGET_SPEC}
 +.endif
 +
++.if ${MAKEFILE:T} == ${.PARSEFILE} && empty(DIRDEPS) && ${.TARGETS:Uall:M*/*} != ""
++# This little trick let's us do
++#
++# mk -f dirdeps.mk some/dir.${TARGET_SPEC}
++#
++all:
++${.TARGETS:Nall}: all
++DIRDEPS := ${.TARGETS:M*/*}
++# so that -DNO_DIRDEPS works
++DEP_RELDIR := ${DIRDEPS:R:[1]}
++# disable DIRDEPS_CACHE as it does not like this trick
++MK_DIRDEPS_CACHE = no
++.endif
++
++
 +# pickup customizations
 +# as below you can use !target(_DIRDEP_USE) to protect things
 +# which should only be done once.
 +.-include "local.dirdeps.mk"
 +
 +# the first time we are included the _DIRDEP_USE target will not be defined
 +# we can use this as a clue to do initialization and other one time things.
 +.if !target(_DIRDEP_USE)
 +# make sure this target exists
 +dirdeps: beforedirdeps .WAIT
 +beforedirdeps:
 +
 +# We normally expect to be included by Makefile.depend.*
 +# which sets the DEP_* macros below.
 +DEP_RELDIR ?= ${RELDIR}
 +
 +# this can cause lots of output!
 +# set to a set of glob expressions that might match RELDIR
 +DEBUG_DIRDEPS ?= no
 +
 +# remember the initial value of DEP_RELDIR - we test for it below.
 +_DEP_RELDIR := ${DEP_RELDIR}
 +
 +# things we skip for host tools
 +SKIP_HOSTDIR ?=
 +
 +NSkipHostDir = ${SKIP_HOSTDIR:N*.host*:S,$,.host*,:N.host*:S,^,${SRCTOP}/,:${M_ListToSkip}}
 +
 +# things we always skip
 +# SKIP_DIRDEPS allows for adding entries on command line.
 +SKIP_DIR += .host *.WAIT ${SKIP_DIRDEPS}
 +SKIP_DIR.host += ${SKIP_HOSTDIR}
 +
 +DEP_SKIP_DIR = ${SKIP_DIR} \
 +      ${SKIP_DIR.${DEP_TARGET_SPEC}:U} \
 +      ${SKIP_DIR.${DEP_MACHINE}:U} \
 +      ${SKIP_DIRDEPS.${DEP_MACHINE}:U}
 +
 +NSkipDir = ${DEP_SKIP_DIR:${M_ListToSkip}}
 +
 +.if defined(NO_DIRDEPS) || defined(NODIRDEPS) || defined(WITHOUT_DIRDEPS)
 +# confine ourselves to the original dir
 +DIRDEPS_FILTER += M${_DEP_RELDIR}*
 +.endif
 +
 +# this is what we run below
 +DIRDEP_MAKE?= ${.MAKE}
 +
 +# we suppress SUBDIR when visiting the leaves
 +# we assume sys.mk will set MACHINE_ARCH
 +# you can add extras to DIRDEP_USE_ENV
 +# if there is no makefile in the target directory, we skip it.
 +_DIRDEP_USE:  .USE .MAKE
 +      @for m in ${.MAKE.MAKEFILE_PREFERENCE}; do \
 +              test -s ${.TARGET:R}/$$m || continue; \
 +              echo "${TRACER}Checking ${.TARGET:R} for ${.TARGET:E} ..."; \
 +              MACHINE_ARCH= NO_SUBDIR=1 ${DIRDEP_USE_ENV} \
 +              TARGET_SPEC=${.TARGET:E} \
 +              MACHINE=${.TARGET:E} \
 +              ${DIRDEP_MAKE} -C ${.TARGET:R} || exit 1; \
 +              break; \
 +      done
 +
 +.ifdef ALL_MACHINES
 +# this is how you limit it to only the machines we have been built for
 +# previously.
 +.if empty(ONLY_MACHINE_LIST)
 +.if !empty(ALL_MACHINE_LIST)
 +# ALL_MACHINE_LIST is the list of all legal machines - ignore anything else
 +_machine_list != cd ${_CURDIR} && 'ls' -1 ${ALL_MACHINE_LIST:O:u:@m@${.MAKE.DEPENDFILE:T:R}.$m@} 2> /dev/null; echo
 +.else
 +_machine_list != 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.* 2> /dev/null; echo
 +.endif
 +_only_machines := ${_machine_list:${NIgnoreFiles:UN*.bak}:E:O:u}
 +.else
 +_only_machines := ${ONLY_MACHINE_LIST}
 +.endif
 +
 +.if empty(_only_machines)
 +# we must be boot-strapping
 +_only_machines := ${TARGET_MACHINE:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}}
 +.endif
 +
 +.else                         # ! ALL_MACHINES
 +# if ONLY_MACHINE_LIST is set, we are limited to that
 +# if TARGET_MACHINE is set - it is really the same as ONLY_MACHINE_LIST
 +# otherwise DEP_MACHINE is it - so DEP_MACHINE will match.
 +_only_machines := ${ONLY_MACHINE_LIST:U${TARGET_MACHINE:U${DEP_MACHINE}}:M${DEP_MACHINE}}
 +.endif
 +
 +.if !empty(NOT_MACHINE_LIST)
 +_only_machines := ${_only_machines:${NOT_MACHINE_LIST:${M_ListToSkip}}}
 +.endif
 +
 +# make sure we have a starting place?
 +DIRDEPS ?= ${RELDIR}
 +.endif                                # target 
 +
 +# if repeatedly building the same target, 
 +# we can avoid the overhead of re-computing the tree dependencies.
 +MK_DIRDEPS_CACHE ?= no
 +BUILD_DIRDEPS_CACHE ?= no
 +BUILD_DIRDEPS ?= yes
 +
 +.if !defined(NO_DIRDEPS)
 +.if ${MK_DIRDEPS_CACHE} == "yes"
 +# this is where we will cache all our work
 +DIRDEPS_CACHE?= ${_OBJDIR}/dirdeps.cache${.TARGETS:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.}
 +
 +# just ensure this exists
 +build-dirdeps:
 +
 +M_oneperline = @x@\\${.newline}       $$x@
 +
 +.if ${BUILD_DIRDEPS_CACHE} == "no" 
 +.if !target(dirdeps-cached)
 +# we do this via sub-make
 +BUILD_DIRDEPS = no
 +
 +dirdeps: dirdeps-cached
 +dirdeps-cached:       ${DIRDEPS_CACHE} .MAKE
 +      @echo "${TRACER}Using ${DIRDEPS_CACHE}"
 +      @MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \
 +              dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no
 +
 +# these should generally do
 +BUILD_DIRDEPS_MAKEFILE ?= ${MAKEFILE}
 +BUILD_DIRDEPS_TARGETS ?= ${.TARGETS}
 +
 +# we need the .meta file to ensure we update if 
 +# any of the Makefile.depend* changed.
 +# We do not want to compare the command line though.
 +${DIRDEPS_CACHE}:     .META .NOMETA_CMP
 +      +@{ echo '# Autogenerated - do NOT edit!'; echo; \
 +      echo 'BUILD_DIRDEPS=no'; echo; \
 +      echo '.include <dirdeps.mk>'; \
 +      } > ${.TARGET}.new
 +      +@MAKELEVEL=${.MAKE.LEVEL} DIRDEPS_CACHE=${DIRDEPS_CACHE} \
 +      DIRDEPS="${DIRDEPS}" \
 +      MAKEFLAGS= ${.MAKE} -C ${_CURDIR} -f ${BUILD_DIRDEPS_MAKEFILE} \
 +      ${BUILD_DIRDEPS_TARGETS} BUILD_DIRDEPS_CACHE=yes \
 +      .MAKE.DEPENDFILE=.none \
 +      3>&1 1>&2 | sed 's,${SRCTOP},$${SRCTOP},g' >> ${.TARGET}.new && \
 +      mv ${.TARGET}.new ${.TARGET}
 +
 +.endif
 +.elif !target(_count_dirdeps)
 +# we want to capture the dirdeps count in the cache
 +.END: _count_dirdeps
 +_count_dirdeps: .NOMETA
 +      @echo '.info $${.newline}$${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]}' >&3
 +
 +.endif
 +.elif !make(dirdeps) && !target(_count_dirdeps)
 +beforedirdeps: _count_dirdeps
 +_count_dirdeps: .NOMETA
 +      @echo "${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} seconds=`expr ${now_utc} - ${start_utc}`"
 +
 +.endif
 +.endif
 +
 +.if ${BUILD_DIRDEPS} == "yes"
 +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != ""
 +_debug_reldir = 1
 +.else
 +_debug_reldir = 0
 +.endif
 +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != ""
 +_debug_search = 1
 +.else
 +_debug_search = 0
 +.endif
 +
 +# the rest is done repeatedly for every Makefile.depend we read.
 +# if we are anything but the original dir we care only about the
 +# machine type we were included for..
 +
 +.if ${DEP_RELDIR} == "."
 +_this_dir := ${SRCTOP}
 +.else
 +_this_dir := ${SRCTOP}/${DEP_RELDIR}
 +.endif
 +
 +# on rare occasions, there can be a need for extra help
 +_dep_hack := ${_this_dir}/${.MAKE.DEPENDFILE_PREFIX}.inc
 +.-include "${_dep_hack}"
 +
 +.if ${DEP_RELDIR} != ${_DEP_RELDIR} || ${DEP_TARGET_SPEC} != ${TARGET_SPEC}
 +# this should be all
 +_machines := ${DEP_MACHINE}
 +.else
 +# this is the machine list we actually use below
 +_machines := ${_only_machines}
 +
 +.if defined(HOSTPROG) || ${DEP_MACHINE} == "host"
 +# we need to build this guy's dependencies for host as well.
 +_machines += host
 +.endif
 +
 +_machines := ${_machines:O:u}
 +.endif
 +
 +.if ${TARGET_SPEC_VARS:[#]} > 1
 +# we need to tweak _machines
 +_dm := ${DEP_MACHINE}
 +# apply the same filtering that we do when qualifying DIRDEPS.
 +# M_dep_qual_fixes expects .${MACHINE}* so add (and remove) '.'
 +_machines := ${_machines:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:${M_dep_qual_fixes:ts:}:O:u:S,^.,,}
 +DEP_MACHINE := ${_dm}
 +.endif
 +
 +# reset each time through
 +_build_dirs =
 +
 +.if ${DEP_RELDIR} == ${_DEP_RELDIR}
 +# pickup other machines for this dir if necessary
 +.if ${BUILD_AT_LEVEL0:Uyes} == "no"
 +_build_dirs += ${_machines:@m@${_CURDIR}.$m@}
 +.else
 +_build_dirs += ${_machines:N${DEP_TARGET_SPEC}:@m@${_CURDIR}.$m@}
 +.if ${DEP_TARGET_SPEC} == ${TARGET_SPEC}
 +# pickup local dependencies now
 +.-include <.depend>
 +.endif
 +.endif
 +.endif
 +
 +.if ${_debug_reldir}
 +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: DIRDEPS='${DIRDEPS}'
 +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _machines='${_machines}' 
 +.endif
 +
 +.if !empty(DIRDEPS)
 +# these we reset each time through as they can depend on DEP_MACHINE
 +DEP_DIRDEPS_FILTER = \
 +      ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \
 +      ${DIRDEPS_FILTER.${DEP_MACHINE}:U} \
 +      ${DIRDEPS_FILTER:U} 
 +.if empty(DEP_DIRDEPS_FILTER)
 +# something harmless
 +DEP_DIRDEPS_FILTER = U
 +.endif
 +
 +# this is what we start with
 +__depdirs := ${DIRDEPS:${NSkipDir}:${DEP_DIRDEPS_FILTER:ts:}:C,//+,/,g:O:u:@d@${SRCTOP}/$d@}
 +
 +# some entries may be qualified with .<machine> 
 +# the :M*/*/*.* just tries to limit the dirs we check to likely ones.
 +# the ${d:E:M*/*} ensures we don't consider junos/usr.sbin/mgd
 +__qual_depdirs := ${__depdirs:M*/*/*.*:@d@${exists($d):?:${"${d:E:M*/*}":?:${exists(${d:R}):?$d:}}}@}
 +__unqual_depdirs := ${__depdirs:${__qual_depdirs:Uno:${M_ListToSkip}}}
 +
 +.if ${DEP_RELDIR} == ${_DEP_RELDIR}
 +# if it was called out - we likely need it.
 +__hostdpadd := ${DPADD:U.:M${HOST_OBJTOP}/*:S,${HOST_OBJTOP}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host,:N.*:@d@${SRCTOP}/$d@}
 +__qual_depdirs += ${__hostdpadd}
 +.endif
 +
 +.if ${_debug_reldir}
 +.info depdirs=${__depdirs}
 +.info qualified=${__qual_depdirs}
 +.info unqualified=${__unqual_depdirs}
 +.endif
 +
 +# _build_dirs is what we will feed to _DIRDEP_USE
 +_build_dirs += \
 +      ${__qual_depdirs:M*.host:${NSkipHostDir}:N.host} \
 +      ${__qual_depdirs:N*.host} \
 +      ${_machines:Mhost*:@m@${__unqual_depdirs:@d@$d.$m@}@:${NSkipHostDir}:N.host} \
 +      ${_machines:Nhost*:@m@${__unqual_depdirs:@d@$d.$m@}@}
 +
 +# qualify everything now
 +_build_dirs := ${_build_dirs:${M_dep_qual_fixes:ts:}:O:u}
 +
 +.endif                                # empty DIRDEPS
 +
 +# Normally if doing make -V something,
 +# we do not want to waste time chasing DIRDEPS
 +# but if we want to count the number of Makefile.depend* read, we do.
 +.if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS}} == ""
 +.if !empty(_build_dirs)
 +.if ${BUILD_DIRDEPS_CACHE} == "yes"
 +x!= { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; \
 +      echo 'dirdeps: ${_build_dirs:${M_oneperline}}'; echo; } >&3; echo
 +x!= { ${_build_dirs:@x@${target($x):?:echo '$x: _DIRDEP_USE';}@} echo; } >&3; echo
 +.else
 +# this makes it all happen
 +dirdeps: ${_build_dirs}
 +.endif
 +${_build_dirs}:       _DIRDEP_USE
 +
 +.if ${_debug_reldir}
 +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs}
 +.endif
 +
 +# this builds the dependency graph
 +.for m in ${_machines}
 +# it would be nice to do :N${.TARGET}
 +.if !empty(__qual_depdirs)
 +.for q in ${__qual_depdirs:${M_dep_qual_fixes:ts:}:E:O:u:N$m}
 +.if ${_debug_reldir} || ${DEBUG_DIRDEPS:@x@${${DEP_RELDIR}.$m:L:M$x}${${DEP_RELDIR}.$q:L:M$x}@} != ""
 +.info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$q}
 +.endif
 +.if ${BUILD_DIRDEPS_CACHE} == "yes"
 +x!= { echo; echo '${_this_dir}.$m: ${_build_dirs:M*.$q:${M_oneperline}}'; echo; } >&3; echo
 +.else
 +${_this_dir}.$m: ${_build_dirs:M*.$q}
 +.endif
 +.endfor
 +.endif
 +.if ${_debug_reldir}
 +.info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$m:N${_this_dir}.$m}
 +.endif
 +.if ${BUILD_DIRDEPS_CACHE} == "yes"
 +x!= { echo; echo '${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m:${M_oneperline}}'; echo; } >&3; echo
 +.else
 +${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m}
 +.endif
 +.endfor
 +
 +.endif
 +
 +# Now find more dependencies - and recurse.
 +.for d in ${_build_dirs}
 +.if ${_DIRDEP_CHECKED:M$d} == ""
 +# once only
 +_DIRDEP_CHECKED += $d
 +.if ${_debug_search}
 +.info checking $d
 +.endif
 +# Note: _build_dirs is fully qualifed so d:R is always the directory
 +.if exists(${d:R})
 +# Warning: there is an assumption here that MACHINE is always 
 +# the first entry in TARGET_SPEC_VARS.
 +# If TARGET_SPEC and MACHINE are insufficient, you have a problem.
 +_m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:S;${MACHINE};${d:E:C/,.*//};:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]}
 +.if !empty(_m)
 +# M_dep_qual_fixes isn't geared to Makefile.depend
 +_qm := ${_m:C;(\.depend)$;\1.${d:E};:${M_dep_qual_fixes:ts:}}
 +.if ${_debug_search}
 +.info Looking for ${_qm}
 +.endif
 +# we pass _DEP_TARGET_SPEC to tell the next step what we want
 +_DEP_TARGET_SPEC := ${d:E}
 +# some makefiles may still look at this
 +_DEP_MACHINE := ${d:E:C/,.*//}
 +# set this "just in case" 
 +# we can skip :tA since we computed the path above
 +DEP_RELDIR := ${_m:H:S,${SRCTOP}/,,}
 +# and reset this
 +DIRDEPS =
 +.if ${_debug_reldir} && ${_qm} != ${_m}
 +.info loading ${_m} for ${d:E}
 +.endif
 +.include <${_m}>
 +.endif
 +.endif
 +.endif
 +.endfor
 +
 +.endif                                # -V
 +.endif                                # BUILD_DIRDEPS
 +
 +.elif ${.MAKE.LEVEL} > 42
 +.error You should have stopped recursing by now.
 +.else
 +# we are building something
 +DEP_RELDIR := ${RELDIR}
 +_DEP_RELDIR := ${RELDIR}
 +# pickup local dependencies
 +.-include <.depend>
 +.endif
 +
 +# bootstrapping new dependencies made easy?
 +.if (make(bootstrap) || make(bootstrap-recurse)) && !target(bootstrap)
 +
 +.if exists(${.CURDIR}/${.MAKE.DEPENDFILE:T})
 +# stop here
 +${.TARGETS:Mboot*}:
 +.else
 +# find a Makefile.depend to use as _src
 +_src != cd ${.CURDIR} && for m in ${.MAKE.DEPENDFILE_PREFERENCE:T:S,${MACHINE},*,}; do test -s $$m || continue; echo $$m; break; done; echo
 +.if empty(_src)
 +.error cannot find any of ${.MAKE.DEPENDFILE_PREFERENCE:T}
 +.endif
 +
 +_src?= ${.MAKE.DEPENDFILE:T}
 +
 +bootstrap-this:       .NOTMAIN
 +      @echo Bootstrapping ${RELDIR}/${.MAKE.DEPENDFILE:T} from ${_src:T}
 +      (cd ${.CURDIR} && sed 's,${_src:E},${MACHINE},g' ${_src} > ${.MAKE.DEPENDFILE:T})
 +
 +bootstrap: bootstrap-recurse
 +bootstrap-recurse: bootstrap-this
 +
 +_mf := ${.PARSEFILE}
 +bootstrap-recurse:    .NOTMAIN .MAKE
 +      @cd ${SRCTOP} && \
 +      for d in `cd ${RELDIR} && ${.MAKE} -B -f ${"${.MAKEFLAGS:M-n}":?${_src}:${.MAKE.DEPENDFILE:T}} -V DIRDEPS`; do \
 +              test -d $$d || d=$${d%.*}; \
 +              test -d $$d || continue; \
 +              echo "Checking $$d for bootstrap ..."; \
 +              (cd $$d && ${.MAKE} -f ${_mf} bootstrap-recurse); \
 +      done
 +
 +.endif
 +.endif
index d496c42aeefb32fc85ad558c2988839c6ad5350c,0000000000000000000000000000000000000000..d1385e9a4e82320ed85dd2a93dc206e35f596f63
mode 100644,000000..100644
--- /dev/null
@@@ -1,63 -1,0 +1,65 @@@
- # $Id: doc.mk,v 1.4 2012/11/11 22:37:02 sjg Exp $
++# $Id: doc.mk,v 1.5 2015/09/08 06:15:31 sjg Exp $
 +
 +.if !target(__${.PARSEFILE}__)
 +__${.PARSEFILE}__:
 +
 +BIB?=         bib
 +EQN?=         eqn
 +GREMLIN?=     grn
 +GRIND?=               vgrind -f
 +INDXBIB?=     indxbib
 +PIC?=         pic
 +REFER?=               refer
 +ROFF?=                groff -M/usr/share/tmac ${MACROS} ${PAGES}
 +SOELIM?=      soelim
 +TBL?=         tbl
 +
 +.PATH: ${.CURDIR}
 +
 +.if !target(all)
 +.MAIN: all
 +all: paper.ps
 +.endif
 +
 +.if !target(paper.ps)
 +paper.ps: ${SRCS}
 +      ${ROFF} ${SRCS} > ${.TARGET}
 +.endif
 +
 +.if !target(print)
 +print: paper.ps
 +      lpr -P${PRINTER} paper.ps
 +.endif
 +
 +.if !target(manpages)
 +manpages:
 +.endif
 +
 +.if !target(obj)
 +obj:
 +.endif
 +
 +clean cleandir:
 +      rm -f paper.* [eE]rrs mklog ${CLEANFILES}
 +
 +.if ${MK_DOC} == "no"
 +install:
 +.else
 +FILES?=       ${SRCS}
 +install:
-       ${INSTALL} -c -o ${BINOWN} -g ${BINGRP} -m 444 \
-           Makefile ${FILES} ${EXTRA} ${DESTDIR}${BINDIR}/${DIR}
++      test -d ${DESTDIR}${DOCDIR}/${DIR} || \
++          ${INSTALL} -d ${DOC_INSTALL_OWN} -m ${DIRMODE} ${DESTDIR}${DOCDIR}/${DIR}
++      ${INSTALL} ${COPY} ${DOC_INSTALL_OWN} -m ${DOCMODE} \
++          Makefile ${FILES} ${EXTRA} ${DESTDIR}${DOCDIR}/${DIR}
 +.endif
 +
 +spell: ${SRCS}
 +      spell ${SRCS} | sort | comm -23 - spell.ok > paper.spell
 +
- BINDIR?=      /usr/share/doc
- BINGRP?=      bin
- BINOWN?=      bin
- BINMODE?=     444
 +.include <own.mk>
++
++.if !empty(DOCOWN)
++DOC_INSTALL_OWN?= -o ${DOCOWN} -g ${DOGGRP}
++.endif
++
 +.endif
index 614a20e3d451fbb133103e74ba44ece15dc470bf,0000000000000000000000000000000000000000..2497f853c3b63229656d712d95f923bd685b59e0
mode 100644,000000..100644
--- /dev/null
@@@ -1,346 -1,0 +1,346 @@@
- # $Id: gendirdeps.mk,v 1.27 2015/06/08 20:55:11 sjg Exp $
++# $Id: gendirdeps.mk,v 1.29 2015/10/03 05:00:46 sjg Exp $
 +
 +# Copyright (c) 2010-2013, Juniper Networks, Inc.
 +# All rights reserved.
 +# 
 +# Redistribution and use in source and binary forms, with or without
 +# modification, are permitted provided that the following conditions 
 +# are met: 
 +# 1. Redistributions of source code must retain the above copyright
 +#    notice, this list of conditions and the following disclaimer. 
 +# 2. Redistributions in binary form must reproduce the above copyright
 +#    notice, this list of conditions and the following disclaimer in the
 +#    documentation and/or other materials provided with the distribution.  
 +# 
 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 +
 +#
 +# This makefile [re]generates ${.MAKE.DEPENDFILE}
 +#
 +
 +.include <install-new.mk>
 +
 +# Assumptions:
 +#     RELDIR is the relative path from ${SRCTOP} to ${_CURDIR}
 +#             (SRCTOP is ${SB}/src)
 +#     _CURDIR is the absolute version of ${.CURDIR}
 +#     _OBJDIR is the absolute version of ${.OBJDIR}
 +#     _objroot is realpath of ${_OBJTOP} without ${MACHINE}
 +#             this may be different from _OBJROOT if $SB/obj is a
 +#             symlink to another filesystem.
 +#             _objroot must be a prefix match for _objtop
 +
 +.MAIN: all
 +
 +# keep this simple
 +.MAKE.MODE = compat
 +
 +all:
 +
 +_CURDIR ?= ${.CURDIR}
 +_OBJDIR ?= ${.OBJDIR}
 +_OBJTOP ?= ${OBJTOP}
 +_OBJROOT ?= ${OBJROOT:U${_OBJTOP}}
 +.if ${_OBJROOT:M*/}
 +_slash=/
 +.else
 +_slash=
 +.endif
 +_objroot ?= ${_OBJROOT:tA}${_slash}
 +
 +_this = ${.PARSEDIR}/${.PARSEFILE}
 +
 +# remember what to make
 +_DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T}
 +
 +# We do _not_ want to read our own output!
 +.MAKE.DEPENDFILE = /dev/null
 +
 +# caller should have set this
 +META_FILES ?= ${.MAKE.META.FILES}
 +
 +.if !empty(META_FILES)
 +
 +.if ${.MAKE.LEVEL} > 0 && !empty(GENDIRDEPS_FILTER)
 +# so we can compare below
 +.-include <${_DEPENDFILE}>
 +# yes, I mean :U with no value
 +_DIRDEPS := ${DIRDEPS:U:O:u}
 +.endif
 +
 +META_FILES := ${META_FILES:T:O:u}
 +.export META_FILES
 +
 +# pickup customizations
 +.-include "local.gendirdeps.mk"
 +
 +# these are actually prefixes that we'll skip
 +# they should all be absolute paths
 +SKIP_GENDIRDEPS ?=
 +.if !empty(SKIP_GENDIRDEPS)
 +_skip_gendirdeps = egrep -v '^(${SKIP_GENDIRDEPS:O:u:ts|})' |
 +.else
 +_skip_gendirdeps =
 +.endif
 +
 +# Below we will turn _{VAR} into ${VAR} which keeps this simple
 +# GENDIRDEPS_FILTER_DIR_VARS is a list of dirs to be substiuted for.
 +# GENDIRDEPS_FILTER_VARS is more general.
 +# In each case order matters.
 +.if !empty(GENDIRDEPS_FILTER_DIR_VARS)
 +GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_DIR_VARS:@v@S,${$v},_{${v}},@}
 +.endif
 +.if !empty(GENDIRDEPS_FILTER_VARS)
 +GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_VARS:@v@S,/${$v}/,/_{${v}}/,@:NS,//,*:u}
 +.endif
 +
 +# this (*should* be set in meta.sys.mk) 
 +# is the script that extracts what we want.
 +META2DEPS ?= ${.PARSEDIR}/meta2deps.sh
 +META2DEPS := ${META2DEPS}
 +
 +.if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" && ${DEBUG_GENDIRDEPS:Uno:Mmeta2d*} != ""
 +_time = time
 +_sh_x = sh -x
 +_py_d = -ddd
 +.else
 +_time =
 +_sh_x =
 +_py_d =
 +.endif
 +
 +.if ${META2DEPS:E} == "py"
 +# we can afford to do this all the time.
 +DPDEPS ?= no
 +META2DEPS_CMD = ${_time} ${PYTHON} ${META2DEPS} ${_py_d} 
 +.if ${DPDEPS:tl} != "no"
 +META2DEPS_CMD += -D ${DPDEPS}
 +.endif
 +META2DEPS_FILTER = sed 's,^src:,${SRCTOP}/,;s,^\([^/]\),${OBJTOP}/\1,' |
 +.elif ${META2DEPS:E} == "sh"
 +META2DEPS_CMD = ${_time} ${_sh_x} ${META2DEPS} OBJTOP=${_OBJTOP}
 +.else
 +META2DEPS_CMD ?= ${META2DEPS}
 +.endif
 +
 +.if ${TARGET_OBJ_SPEC:U${MACHINE}} != ${MACHINE}
 +META2DEPS_CMD += -T ${TARGET_OBJ_SPEC}
 +.endif
 +META2DEPS_CMD += \
 +      -R ${RELDIR} -H ${HOST_TARGET} \
 +      ${M2D_OBJROOTS:O:u:@o@-O $o@}
 +
 +
 +M2D_OBJROOTS += ${OBJTOP} ${_OBJROOT} ${_objroot}
 +.if defined(SB_OBJROOT)
 +M2D_OBJROOTS += ${SB_OBJROOT}
 +.endif
 +.if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} == ""
 +# meta2deps.py only groks objroot
 +# so we need to give it what it expects
 +# and tell it not to add machine qualifiers
 +META2DEPS_ARGS += MACHINE=none
 +.endif
 +.if defined(SB_BACKING_SB) 
 +META2DEPS_CMD += -S ${SB_BACKING_SB}/src 
 +M2D_OBJROOTS += ${SB_BACKING_SB}/${SB_OBJPREFIX}
 +.endif
 +
 +# we are only interested in the dirs
- # sepecifically those we read something from.
++# specifically those we read something from.
 +# we canonicalize them to keep things simple
 +# if we are using a split-fs sandbox, it gets a little messier.
 +_objtop := ${_OBJTOP:tA}
 +dir_list != cd ${_OBJDIR} && \
 +      ${META2DEPS_CMD} MACHINE=${MACHINE} \
 +      SRCTOP=${SRCTOP} RELDIR=${RELDIR} CURDIR=${_CURDIR} \
 +      ${META2DEPS_ARGS} \
 +      ${META_FILES:O:u} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \
 +      sed 's,//*$$,,;s,\.${HOST_TARGET}$$,.host,'
 +
 +.if ${dir_list:M*ERROR\:*} != ""
 +.warning ${dir_list:tW:C,.*(ERROR),\1,}
 +.warning Skipping ${_DEPENDFILE:S,${SRCTOP}/,,}
 +# we are not going to update anything
 +.else
 +dpadd_dir_list=
 +.if !empty(DPADD)
 +_nonlibs := ${DPADD:T:Nlib*:N*include}
 +.if !empty(_nonlibs)
 +ddep_list =
 +.for f in ${_nonlibs:@x@${DPADD:M*/$x}@}
 +.if exists($f.dirdep)
 +ddep_list += $f.dirdep
 +.elif exists(${f:H}.dirdep)
 +ddep_list += ${f:H}.dirdep
 +.else
 +dir_list += ${f:H:tA}
 +dpadd_dir_list += ${f:H:tA}
 +.endif
 +.endfor
 +.if !empty(ddep_list)
 +ddeps != cat ${ddep_list:O:u} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \
 +        sed 's,//*$$,,;s,\.${HOST_TARGET}$$,.host,;s,\.${MACHINE}$$,,'
 +
 +.if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != ""
 +.info ${RELDIR}: raw_dir_list='${dir_list}'
 +.info ${RELDIR}: ddeps='${ddeps}'
 +.endif
 +dir_list += ${ddeps}
 +.endif
 +.endif
 +.endif
 +
 +# DIRDEPS represent things that had to have been built first
 +# so they should all be undir OBJTOP.
 +# Note that ${_OBJTOP}/bsd/include/machine will get reported 
 +# to us as $SRCTOP/bsd/sys/$MACHINE_ARCH/include meaning we
 +# will want to visit bsd/include
 +# so we add 
 +# ${"${dir_list:M*bsd/sys/${MACHINE_ARCH}/include}":?bsd/include:}
 +# to GENDIRDEPS_DIR_LIST_XTRAS
 +_objtops = ${OBJTOP} ${_OBJTOP} ${_objtop}
 +_objtops := ${_objtops:O:u}
 +dirdep_list = \
 +      ${_objtops:@o@${dir_list:M$o*/*:C,$o[^/]*/,,}@} \
 +      ${GENDIRDEPS_DIR_LIST_XTRAS}
 +
 +# sort longest first
 +M2D_OBJROOTS := ${M2D_OBJROOTS:O:u:[-1..1]}
 +
 +# anything we use from an object dir other than ours
 +# needs to be qualified with its .<machine> suffix
 +# (we used the pseudo machine "host" for the HOST_TARGET).
 +skip_ql= ${SRCTOP}* ${_objtops:@o@$o*@}
 +.for o in ${M2D_OBJROOTS:${skip_ql:${M_ListToSkip}}}
 +# we need := so only skip_ql to this point applies
 +ql.$o := ${dir_list:${skip_ql:${M_ListToSkip}}:M$o*/*/*:C,$o([^/]+)/(.*),\2.\1,:S,.${HOST_TARGET},.host,}
 +qualdir_list += ${ql.$o}
 +.if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != ""
 +.info ${RELDIR}: o=$o ${ql.$o qualdir_list:L:@v@$v=${$v}@}
 +.endif
 +skip_ql+= $o*
 +.endfor
 +
 +dirdep_list := ${dirdep_list:O:u}
 +qualdir_list := ${qualdir_list:N*.${MACHINE}:O:u}
 +
 +DIRDEPS = \
 +      ${dirdep_list:N${RELDIR}:N${RELDIR}/*} \
 +      ${qualdir_list:N${RELDIR}.*:N${RELDIR}/*}
 +
 +# We only consider things below $RELDIR/ if they have a makefile.
 +# This is the same test that _DIRDEP_USE applies.
 +# We have do a double test with dirdep_list as it _may_ contain 
 +# qualified dirs - if we got anything from a stage dir.
 +# qualdir_list we know are all qualified.
 +# It would be nice do peform this check for all of DIRDEPS,
 +# but we cannot assume that all of the tree is present, 
 +# in fact we can only assume that RELDIR is.
 +DIRDEPS += \
 +      ${dirdep_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/$d/$m):?$d:${exists(${SRCTOP}/${d:R}/$m):?$d:}}@}@} \
 +      ${qualdir_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/${d:R}/$m):?$d:}@}@}
 +
 +DIRDEPS := ${DIRDEPS:${GENDIRDEPS_FILTER:UNno:ts:}:C,//+,/,g:O:u}
 +
 +.if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != ""
 +.info ${RELDIR}: M2D_OBJROOTS=${M2D_OBJROOTS}
 +.info ${RELDIR}: dir_list='${dir_list}'
 +.info ${RELDIR}: dpadd_dir_list='${dpadd_dir_list}'
 +.info ${RELDIR}: dirdep_list='${dirdep_list}'
 +.info ${RELDIR}: qualdir_list='${qualdir_list}'
 +.info ${RELDIR}: SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS}'
 +.info ${RELDIR}: GENDIRDEPS_FILTER='${GENDIRDEPS_FILTER}'
 +.info ${RELDIR}: FORCE_DPADD='${DPADD}'
 +.info ${RELDIR}: DIRDEPS='${DIRDEPS}'
 +.endif
 +
 +# SRC_DIRDEPS is for checkout logic
 +src_dirdep_list = \
 +      ${dir_list:M${SRCTOP}/*:S,${SRCTOP}/,,}
 +
 +SRC_DIRDEPS = \
 +      ${src_dirdep_list:N${RELDIR}:N${RELDIR}/*:C,(/h)/.*,,}
 +
 +SRC_DIRDEPS := ${SRC_DIRDEPS:${GENDIRDEPS_SRC_FILTER:UN/*:ts:}:C,//+,/,g:O:u}
 +
 +# if you want to capture SRC_DIRDEPS in .MAKE.DEPENDFILE put
 +# SRC_DIRDEPS_FILE = ${_DEPENDFILE} 
 +# in local.gendirdeps.mk
 +.if ${SRC_DIRDEPS_FILE:Uno:tl} != "no"
 +ECHO_SRC_DIRDEPS = echo 'SRC_DIRDEPS = \'; echo '${SRC_DIRDEPS:@d@    $d \\${.newline}@}'; echo;
 +
 +.if ${SRC_DIRDEPS_FILE:T} == ${_DEPENDFILE:T}
 +_include_src_dirdeps = ${ECHO_SRC_DIRDEPS}
 +.else
 +all: ${SRC_DIRDEPS_FILE}
 +.if !target(${SRC_DIRDEPS_FILE})
 +${SRC_DIRDEPS_FILE}: ${META_FILES} ${_this} ${META2DEPS}
 +      @(${ECHO_SRC_DIRDEPS}) > $@
 +.endif
 +.endif
 +.endif
 +_include_src_dirdeps ?= 
 +
 +all:  ${_DEPENDFILE}
 +
 +# if this is going to exist it would be there by now
 +.if !exists(.depend)
 +CAT_DEPEND = /dev/null
 +.endif
 +CAT_DEPEND ?= .depend
 +
 +.if !empty(_DIRDEPS) && ${DIRDEPS} != ${_DIRDEPS}
 +# we may have changed a filter
 +.PHONY: ${_DEPENDFILE}
 +.endif
 +
 +# 'cat .depend' should suffice, but if we are mixing build modes
 +# .depend may contain things we don't want.
 +# The sed command at the end of the stream, allows for the filters
 +# to output _{VAR} tokens which we will turn into proper ${VAR} references.
 +${_DEPENDFILE}: ${CAT_DEPEND:M.depend} ${META_FILES:O:u:@m@${exists($m):?$m:}@} ${_this} ${META2DEPS}
-       @(echo '# Autogenerated - do NOT edit!'; echo; \
++      @(${GENDIRDEPS_HEADER} echo '# Autogenerated - do NOT edit!'; echo; \
 +      echo 'DIRDEPS = \'; \
 +      echo '${DIRDEPS:@d@     $d \\${.newline}@}'; echo; \
 +      ${_include_src_dirdeps} \
 +      echo '.include <dirdeps.mk>'; \
 +      echo; \
 +      echo '.if $${DEP_RELDIR} == $${_DEP_RELDIR}'; \
 +      echo '# local dependencies - needed for -jN in clean tree'; \
 +      [ -s ${CAT_DEPEND} ] && { grep : ${CAT_DEPEND} | grep -v '[/\\]'; }; \
 +      echo '.endif' ) | sed 's,_\([{(]\),$$\1,g' > $@.new${.MAKE.PID}
 +      @${InstallNew}; InstallNew -s $@.new${.MAKE.PID}
 +
 +.endif                                # meta2deps failed
 +.elif !empty(SUBDIR)
 +
 +DIRDEPS := ${SUBDIR:S,^,${RELDIR}/,:O:u}
 +
 +all:  ${_DEPENDFILE}
 +
 +${_DEPENDFILE}: ${MAKEFILE} ${_this}
-       @(echo '# Autogenerated - do NOT edit!'; echo; \
++      @(${GENDIRDEPS_HEADER} echo '# Autogenerated - do NOT edit!'; echo; \
 +      echo 'DIRDEPS = \'; \
 +      echo '${DIRDEPS:@d@     $d \\${.newline}@}'; echo; \
 +      echo '.include <dirdeps.mk>'; \
 +      echo ) | sed 's,_\([{(]\),$$\1,g' > $@.new
 +      @${InstallNew}; InstallNew $@.new
 +
 +.else
 +
 +# nothing to do
 +all ${_DEPENDFILE}:
 +
 +.endif
 +${_DEPENDFILE}: .PRECIOUS
index eacdf1d95111b6821faa2896f16216c901e6b715,0000000000000000000000000000000000000000..b67c34d2523ca7c32ce4acc21f8abfa4fba48565
mode 100644,000000..100644
--- /dev/null
@@@ -1,35 -1,0 +1,44 @@@
- #     $Id: host-target.mk,v 1.7 2014/05/16 17:54:52 sjg Exp $
 +# RCSid:
- _HOST_ARCH   !=       uname -p 2>/dev/null || uname -m
++#     $Id: host-target.mk,v 1.9 2015/09/10 18:42:57 sjg Exp $
 +
 +# Host platform information; may be overridden
 +.if !defined(_HOST_OSNAME)
 +_HOST_OSNAME !=       uname -s
 +.export _HOST_OSNAME
 +.endif
 +.if !defined(_HOST_OSREL)
 +_HOST_OSREL  !=       uname -r
 +.export _HOST_OSREL
 +.endif
++.if !defined(_HOST_MACHINE)
++_HOST_MACHINE != uname -m
++.export _HOST_MACHINE
++.endif
 +.if !defined(_HOST_ARCH)
- _HOST_ARCH != uname -m
++# for NetBSD prefer $MACHINE (amd64 rather than x86_64)
++.if ${_HOST_OSNAME:NNetBSD} == ""
++_HOST_ARCH := ${_HOST_MACHINE}
++.else
++_HOST_ARCH != uname -p 2> /dev/null || uname -m
 +# uname -p may produce garbage on linux
 +.if ${_HOST_ARCH:[\#]} > 1
- HOST_MACHINE != uname -m
++_HOST_ARCH := ${_HOST_MACHINE}
++.endif
 +.endif
 +.export _HOST_ARCH
 +.endif
 +.if !defined(HOST_MACHINE)
++HOST_MACHINE := ${_HOST_MACHINE}
 +.export HOST_MACHINE
 +.endif
 +
 +HOST_OSMAJOR := ${_HOST_OSREL:C/[^0-9].*//}
 +HOST_OSTYPE  :=       ${_HOST_OSNAME}-${_HOST_OSREL:C/\([^\)]*\)//}-${_HOST_ARCH}
 +HOST_OS      :=       ${_HOST_OSNAME}
 +host_os      :=       ${_HOST_OSNAME:tl}
 +HOST_TARGET  := ${host_os}${HOST_OSMAJOR}-${_HOST_ARCH}
 +
 +# tr is insanely non-portable, accommodate the lowest common denominator
 +TR ?= tr
 +toLower = ${TR} 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'
 +toUpper = ${TR} 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
index 05facd549ee9cbaa38e5260857aefa4fe223e3d3,0000000000000000000000000000000000000000..eca75e00df66803877489561b9df3c8da9c80443
mode 100644,000000..100644
--- /dev/null
@@@ -1,185 -1,0 +1,185 @@@
- #     $Id: install-mk,v 1.112 2015/06/08 20:55:11 sjg Exp $
 +:
 +# NAME:
 +#     install-mk - install mk files
 +#
 +# SYNOPSIS:
 +#     install-mk [options] [var=val] [dest]
 +#
 +# DESCRIPTION:
 +#     This tool installs mk files in a semi-intelligent manner into
 +#     "dest".
 +#
 +#     Options:
 +#
 +#     -n      just say what we want to do, but don't touch anything.
 +#
 +#     -f      use -f when copying sys,mk.
 +#
 +#     -v      be verbose
 +#
 +#     -q      be quiet
 +#
 +#     -m "mode"
 +#             Use "mode" for installed files (444).
 +#
 +#     -o "owner"
 +#             Use "owner" for installed files.
 +#
 +#     -g "group"
 +#             Use "group" for installed files.
 +#
 +#     var=val
 +#             Set "var" to "val".  See below.
 +#
 +#     All our *.mk files are copied to "dest" with appropriate
 +#     ownership and permissions.
 +#     
 +#     By default if a sys.mk can be found in a standard location
 +#     (that bmake will find) then no sys.mk will be put in "dest".
 +#
 +#     SKIP_SYS_MK:
 +#             If set, we will avoid installing our 'sys.mk'
 +#             This is probably a bad idea.
 +#
 +#     SKIP_BSD_MK:
 +#             If set, we will skip making bsd.*.mk links to *.mk
 +#
 +#     sys.mk:
 +#
 +#     By default (and provided we are not installing to the system
 +#     mk dir - '/usr/share/mk') we install our own 'sys.mk' which
 +#     includes a sys specific file, or a generic one.
 +#
 +#
 +# AUTHOR:
 +#       Simon J. Gerraty <sjg@crufty.net>
 +
 +# RCSid:
- MK_VERSION=20150606
++#     $Id: install-mk,v 1.115 2015/10/20 22:04:53 sjg Exp $
 +#
 +#     @(#) Copyright (c) 1994 Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
++MK_VERSION=20151020
 +OWNER=
 +GROUP=
 +MODE=444
 +BINMODE=555
 +ECHO=:
 +SKIP=
 +cp_f=-f
 +
 +while :
 +do
 +      case "$1" in
 +      *=*) eval "$1"; shift;;
 +      +f) cp_f=; shift;;
 +      -f) cp_f=-f; shift;;
 +      -m) MODE=$2; shift 2;;
 +      -o) OWNER=$2; shift 2;;
 +      -g) GROUP=$2; shift 2;;
 +      -v) ECHO=echo; shift;;
 +      -q) ECHO=:; shift;;
 +      -n) ECHO=echo SKIP=:; shift;;
 +      --) shift; break;;
 +      *) break;;
 +      esac
 +done
 +
 +case $# in
 +0)    echo "$0 [options] <destination> [<os>]"
 +      echo "eg."
 +      echo "$0 -o bin -g bin -m 444 /usr/local/share/mk"
 +      exit 1
 +      ;;
 +esac
 +dest=$1
 +os=${2:-`uname`}
 +osrel=${3:-`uname -r`}
 +
 +Do() {
 +      $ECHO "$@"
 +      $SKIP "$@"
 +}
 +
 +Error() {
 +      echo "ERROR: $@" >&2
 +      exit 1
 +}
 +
 +Warning() {
 +      echo "WARNING: $@" >&2
 +}
 +
 +[ "$FORCE_SYS_MK" ] && Warning "ignoring: FORCE_{BSD,SYS}_MK (no longer supported)"
 +
 +SYS_MK_DIR=${SYS_MK_DIR:-/usr/share/mk}
 +SYS_MK=${SYS_MK:-$SYS_MK_DIR/sys.mk}
 +
 +realpath() {
 +      [ -d $1 ] && cd $1 && 'pwd' && return
 +      echo $1
 +}
 +
 +if [ -s $SYS_MK -a -d $dest ]; then
 +      # if this is a BSD system we don't want to touch $SYS_MK
 +      dest=`realpath $dest`
 +      sys_mk_dir=`realpath $SYS_MK_DIR`
 +      if [ $dest = $sys_mk_dir ]; then
 +              case "$os" in
 +              *BSD*)  SKIP_SYS_MK=: 
 +                      SKIP_BSD_MK=:
 +                      ;;
 +              *)      # could be fake?
 +                      if [ ! -d $dest/sys -a ! -s $dest/Generic.sys.mk ]; then
 +                              SKIP_SYS_MK=: # play safe
 +                              SKIP_BSD_MK=:
 +                      fi
 +                      ;;
 +              esac
 +      fi
 +fi
 +
 +[ -d $dest/sys ] || Do mkdir -p $dest/sys
 +[ -d $dest/sys ] || Do mkdir $dest/sys || exit 1
 +[ -z "$SKIP" ] && dest=`realpath $dest`
 +
 +cd `dirname $0`
 +mksrc=`'pwd'`
 +if [ $mksrc = $dest ]; then
 +      SKIP_MKFILES=:
 +else
 +      # we do not install the examples
 +      mk_files=`grep '^[a-z].*\.mk' FILES | egrep -v '(examples/|^sys\.mk|sys/)'`
 +      mk_scripts=`egrep '^[a-z].*\.(sh|py)' FILES | egrep -v '/'`
 +      sys_mk_files=`grep 'sys/.*\.mk' FILES`
 +      SKIP_MKFILES=
 +      [ -z "$SKIP_SYS_MK" ] && mk_files="sys.mk $mk_files"
 +fi
 +$SKIP_MKFILES Do cp $cp_f $mk_files $dest
 +$SKIP_MKFILES Do cp $cp_f $sys_mk_files $dest/sys
 +$SKIP_MKFILES Do cp $cp_f $mk_scripts $dest
 +$SKIP cd $dest
 +$SKIP_MKFILES Do chmod $MODE $mk_files $sys_mk_files
 +$SKIP_MKFILES Do chmod $BINMODE $mk_scripts
 +[ "$GROUP" ] && $SKIP_MKFILES Do chgrp $GROUP $mk_files $sys_mk_files
 +[ "$OWNER" ] && $SKIP_MKFILES Do chown $OWNER $mk_files $sys_mk_files
 +# if this is a BSD system the bsd.*.mk should exist and be used.
 +if [ -z "$SKIP_BSD_MK" ]; then
 +      for f in dep doc init lib links man nls obj own prog subdir
 +      do
 +              b=bsd.$f.mk
 +              [ -s $b ] || Do ln -s $f.mk $b
 +      done
 +fi
 +exit 0
index 64bc30bd475768006fd9d3089ccaf6df477b54a6,0000000000000000000000000000000000000000..a062e47f92c8fa5b7df7473cf412645e7bb9227e
mode 100644,000000..100644
--- /dev/null
@@@ -1,291 -1,0 +1,293 @@@
- # $Id: meta.autodep.mk,v 1.36 2014/08/02 23:10:29 sjg Exp $
++# $Id: meta.autodep.mk,v 1.37 2015/06/16 06:29:17 sjg Exp $
 +
 +#
 +#     @(#) Copyright (c) 2010, Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +_this ?= ${.PARSEFILE}
 +.if !target(__${_this}__)
 +__${_this}__: .NOTMAIN
 +
 +.-include "local.autodep.mk"
 +
 +.if defined(SRCS)
 +# it would be nice to be able to query .SUFFIXES
 +OBJ_EXTENSIONS+= .o .po .lo .So
 +
 +# explicit dependencies help short-circuit .SUFFIX searches
 +SRCS_DEP_FILTER+= N*.[hly]
 +.for s in ${SRCS:${SRCS_DEP_FILTER:O:u:ts:}}
 +.for e in ${OBJ_EXTENSIONS:O:u}
 +.if !target(${s:T:R}$e)
 +${s:T:R}$e: $s
 +.endif
 +.endfor
 +.endfor
 +.endif
 +
 +.if make(gendirdeps)
 +# you are supposed to know what you are doing!
 +UPDATE_DEPENDFILE = yes
 +.elif !empty(.TARGETS) && !make(all)
 +# do not update the *depend* files 
 +# unless we are building the entire directory or the default target.
 +# NO means don't update .depend - or Makefile.depend*
 +# no means update .depend but not Makefile.depend*
 +UPDATE_DEPENDFILE = NO
 +.elif ${.MAKEFLAGS:M-k} != ""
 +# it is a bad idea to update anything
 +UPDATE_DEPENDFILE = NO
 +.endif
 +
 +_CURDIR ?= ${.CURDIR}
 +_DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T}
 +
 +.if ${.MAKE.LEVEL} == 0
 +.if ${BUILD_AT_LEVEL0:Uyes:tl} == "no"
 +UPDATE_DEPENDFILE = NO
 +.endif
 +.endif
 +.if !exists(${_DEPENDFILE})
 +_bootstrap_dirdeps = yes
 +.endif
 +_bootstrap_dirdeps ?= no
 +UPDATE_DEPENDFILE ?= yes
 +
 +.if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != ""
 +.info ${_DEPENDFILE:S,${SRCTOP}/,,} update=${UPDATE_DEPENDFILE}
 +.endif
 +
 +.if !empty(XMAKE_META_FILE)
 +.if exists(${.OBJDIR}/${XMAKE_META_FILE})
 +# we cannot get accurate dependencies from an update build
 +UPDATE_DEPENDFILE = NO
 +.else
 +META_XTRAS += ${XMAKE_META_FILE}
 +.endif
 +.endif
 +
 +.if ${_bootstrap_dirdeps} == "yes" || exists(${_DEPENDFILE})
 +# if it isn't supposed to be touched by us the Makefile should have
 +# UPDATE_DEPENDFILE = no
 +WANT_UPDATE_DEPENDFILE ?= yes
 +.endif
 +
 +.if ${WANT_UPDATE_DEPENDFILE:Uno:tl} != "no"
 +.if ${.MAKE.MODE:Mmeta*} == "" || ${.MAKE.MODE:M*read*} != ""
 +UPDATE_DEPENDFILE = no
 +.endif
 +
 +.if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != ""
 +.info ${_DEPENDFILE:S,${SRCTOP}/,,} update=${UPDATE_DEPENDFILE}
 +.endif
 +
 +.if ${UPDATE_DEPENDFILE:tl} == "yes"
 +# sometimes we want .meta files generated to aid debugging/error detection
 +# but do not want to consider them for dependencies
 +# for example the result of running configure
 +# just make sure this is not empty
 +META_FILE_FILTER ?= N.meta
++# never consider these
++META_FILE_FILTER += Ndirdeps.cache*
 +
 +.if !empty(DPADD)
 +# if we have any non-libs in DPADD, 
 +# they probably need to be paid attention to
 +.if !empty(DPLIBS)
 +FORCE_DPADD = ${DPADD:${DPLIBS:${M_ListToSkip}}:${DPADD_LAST:${M_ListToSkip}}}
 +.else
 +_nonlibs := ${DPADD:T:Nlib*:N*include}
 +.if !empty(_nonlibs)
 +FORCE_DPADD += ${_nonlibs:@x@${DPADD:M*/$x}@}
 +.endif
 +.endif
 +.endif
 +
 +.if !make(gendirdeps)
 +.END: gendirdeps
 +.endif
 +
 +# if we don't have OBJS, then .depend isn't useful
 +.if !target(.depend) && (!empty(OBJS) || ${.ALLTARGETS:M*.o} != "")
 +# some makefiles and/or targets contain
 +# circular dependencies if you dig too deep 
 +# (as meta mode is apt to do) 
 +# so we provide a means of suppressing them.
 +# the input to the loop below is target: dependency
 +# with just one dependency per line.
 +# Also some targets are not really local, or use random names.
 +# Use local.autodep.mk to provide local additions!
 +SUPPRESS_DEPEND += \
 +      ${SB:S,/,_,g}* \
 +      *:y.tab.c \
 +      *.c:*.c \
 +      *.h:*.h
 +
 +.NOPATH:      .depend
 +# we use ${.MAKE.META.CREATED} to trigger an update but
 +# we process using ${.MAKE.META.FILES}
 +# the double $$ defers initial evaluation
 +# if necessary, we fake .po dependencies, just so the result 
 +# in Makefile.depend* is stable
 +# The current objdir may be referred to in various ways
 +OBJDIR_REFS += ${.OBJDIR} ${.OBJDIR:tA} ${_OBJDIR} ${RELOBJTOP}/${RELDIR}
 +_depend = .depend
 +# it would be nice to be able to get .SUFFIXES as ${.SUFFIXES}
 +# we actually only care about the .SUFFIXES of files that might be 
 +# generated by tools like yacc.
 +DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh
 +.depend: .NOMETA $${.MAKE.META.CREATED} ${_this}
 +      @echo "Updating $@: ${.OODATE:T:[1..8]}"
 +      @egrep -i '^R .*\.(${DEPEND_SUFFIXES:tl:O:u:S,^.,,:ts|})$$' /dev/null ${.MAKE.META.FILES:T:O:u:${META_FILE_FILTER:ts:}:M*o.meta} | \
 +      sed -e 's, \./, ,${OBJDIR_REFS:O:u:@d@;s, $d/, ,@};/\//d' \
 +              -e 's,^\([^/][^/]*\).meta...[0-9]* ,\1: ,' | \
 +      sort -u | \
 +      while read t d; do \
 +              case "$$d:" in $$t) continue;; esac; \
 +              case "$$t$$d" in ${SUPPRESS_DEPEND:U.:O:u:ts|}) continue;; esac; \
 +              echo $$t $$d; \
 +      done > $@.${.MAKE.PID}
 +      @case "${.MAKE.META.FILES:T:M*.po.*}" in \
 +      *.po.*) mv $@.${.MAKE.PID} $@;; \
 +      *) { cat $@.${.MAKE.PID}; \
 +      sed 's,\.So:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \
 +      rm -f $@.${.MAKE.PID};; \
 +      esac
 +.else
 +# make sure this exists
 +.depend:
 +# do _not_ assume that .depend is in any fit state for us to use
 +CAT_DEPEND = /dev/null
 +.if ${.MAKE.LEVEL} > 0
 +.export CAT_DEPEND
 +.endif
 +_depend =
 +.endif
 +
 +.if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != ""
 +.info ${_DEPENDFILE:S,${SRCTOP}/,,} _depend=${_depend}
 +.endif
 +
 +.if ${UPDATE_DEPENDFILE} == "yes"
 +gendirdeps:   ${_DEPENDFILE}
 +.endif
 +
 +.if !target(${_DEPENDFILE})
 +.if ${_bootstrap_dirdeps} == "yes"
 +# We are boot-strapping a new directory
 +# Use DPADD to seed DIRDEPS
 +.if !empty(DPADD)
 +# anything which matches ${_OBJROOT}* but not ${_OBJTOP}*
 +# needs to be qualified in DIRDEPS
 +# The pseudo machine "host" is used for HOST_TARGET
 +DIRDEPS = \
 +      ${DPADD:M${_OBJTOP}*:H:C,${_OBJTOP}[^/]*/,,:N.:O:u} \
 +      ${DPADD:M${_OBJROOT}*:N${_OBJTOP}*:H:S,${_OBJROOT},,:C,^([^/]+)/(.*),\2.\1,:S,${HOST_TARGET}$,host,:N.*:O:u}
 +
 +.endif
 +.endif
 +
 +_gendirdeps_mutex =
 +.if defined(NEED_GENDIRDEPS_MUTEX)
 +# If a src dir gets built with multiple object dirs,
 +# we need a mutex.  Obviously, this is best avoided.
 +# Note if .MAKE.DEPENDFILE is common for all ${MACHINE}
 +# you either need to mutex, or ensure only one machine builds at a time!
 +# lockf is an example of a suitable tool
 +LOCKF ?= /usr/bin/lockf
 +.if exists(${LOCKF})
 +GENDIRDEPS_MUTEXER ?= ${LOCKF} -k
 +.endif
 +.if empty(GENDIRDEPS_MUTEXER)
 +.error NEED_GENDIRDEPS_MUTEX defined, but GENDIRDEPS_MUTEXER not set
 +.else
 +_gendirdeps_mutex = ${GENDIRDEPS_MUTEXER} ${GENDIRDEPS_MUTEX:U${_CURDIR}/Makefile}
 +.endif
 +.endif
 +
 +# If we have META_XTRAS we most likely did not create them
 +# but we need to behave as if we did.
 +# Avoid adding glob patterns to .MAKE.META.CREATED though.
 +.MAKE.META.CREATED += ${META_XTRAS:N*\**:O:u}
 +
 +.if make(gendirdeps)
 +META_FILES = *.meta
 +.elif ${OPTIMIZE_OBJECT_META_FILES:Uno:tl} == "no"
 +META_FILES = ${.MAKE.META.FILES:T:N.depend*:O:u}
 +.else
 +# if we have 1000's of .o.meta, .So.meta etc we need only look at one set
 +# it is left as an exercise for the reader to work out what this does
 +META_FILES = ${.MAKE.META.FILES:T:N.depend*:N*o.meta:O:u} \
 +      ${.MAKE.META.FILES:T:M*.${.MAKE.META.FILES:M*o.meta:R:E:O:u:[1]}.meta:O:u}
 +.endif
 +
 +.if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != ""
 +.info ${_DEPENDFILE:S,${SRCTOP}/,,}: ${_depend} ${.PARSEDIR}/gendirdeps.mk ${META2DEPS} xtras=${META_XTRAS}
 +.endif
 +
 +.if ${.MAKE.LEVEL} > 0 && !empty(GENDIRDEPS_FILTER)
 +.export GENDIRDEPS_FILTER
 +.endif
 +
 +# we might have .../ in MAKESYSPATH
 +_makesyspath:= ${_PARSEDIR}
 +${_DEPENDFILE}: ${_depend} ${.PARSEDIR}/gendirdeps.mk  ${META2DEPS} $${.MAKE.META.CREATED}
 +      @echo Checking $@: ${.OODATE:T:[1..8]}
 +      @(cd . && \
 +      SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS:O:u}' \
 +      DPADD='${FORCE_DPADD:O:u}' ${_gendirdeps_mutex} \
 +      MAKESYSPATH=${_makesyspath} \
 +      ${.MAKE} -f gendirdeps.mk RELDIR=${RELDIR} _DEPENDFILE=${_DEPENDFILE} \
 +      META_FILES='${META_XTRAS:T:O:u} ${META_FILES:T:O:u:${META_FILE_FILTER:ts:}}')
 +      @test -s $@ && touch $@; :
 +.endif
 +
 +.endif
 +.endif
 +
 +.if ${_bootstrap_dirdeps} == "yes"
 +.if ${BUILD_AT_LEVEL0:Uno} == "no"
 +DIRDEPS+= ${RELDIR}.${TARGET_SPEC:U${MACHINE}}
 +.endif
 +# make sure this is included at least once
 +.include <dirdeps.mk>
 +.else
 +${_DEPENDFILE}: .PRECIOUS
 +.endif
 +
 +CLEANFILES += *.meta filemon.* *.db
 +
 +# these make it easy to gather some stats
 +now_utc = ${%s:L:gmtime}
 +start_utc := ${now_utc}
 +
 +meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \
 +      created=${empty(.MAKE.META.CREATED):?0:${.MAKE.META.CREATED:[#]}}
 +
 +#.END: _reldir_finish
 +.if target(gendirdeps)
 +_reldir_finish: gendirdeps
 +.endif
 +_reldir_finish: .NOMETA
 +      @echo "${TIME_STAMP} Finished ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}"
 +
 +#.ERROR: _reldir_failed
 +_reldir_failed: .NOMETA
 +      @echo "${TIME_STAMP} Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}"
 +
 +.if defined(WITH_META_STATS) && ${.MAKE.LEVEL} > 0
 +.END: _reldir_finish
 +.ERROR: _reldir_failed
 +.endif
 +
 +.endif
index 588f50e982048151aef8e7fa243f3d7d98ae14de,0000000000000000000000000000000000000000..4337c0eff5d711079011f69ddc42d4327b0c2621
mode 100644,000000..100644
--- /dev/null
@@@ -1,279 -1,0 +1,292 @@@
- # $Id: meta.stage.mk,v 1.35 2015/05/20 06:40:33 sjg Exp $
++# $Id: meta.stage.mk,v 1.40 2015/10/04 17:36:54 sjg Exp $
 +#
 +#     @(#) Copyright (c) 2011, Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +.if !target(__${.PARSEFILE}__)
 +__${.PARSEFILE}__:
 +
 +.if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} != ""
 +# this is generally safer anyway
 +_dirdep = ${RELDIR}.${MACHINE}
 +.else
 +_dirdep = ${RELDIR}
 +.endif
 +
++CLEANFILES+= .dirdep
++
 +# this allows us to trace dependencies back to their src dir
 +.dirdep:
 +      @echo '${_dirdep}' > $@
 +
 +.if defined(NO_POSIX_SHELL) || ${type printf:L:sh:Mbuiltin} == ""
 +_stage_file_basename = `basename $$f`
 +_stage_target_dirname = `dirname $$t`
 +.else
 +_stage_file_basename = $${f\#\#*/}
 +_stage_target_dirname = $${t%/*}
 +.endif
 +
 +_OBJROOT ?= ${OBJROOT:U${OBJTOP:H}}
 +.if ${_OBJROOT:M*/} != ""
 +_objroot ?= ${_OBJROOT:tA}/
 +.else
 +_objroot ?= ${_OBJROOT:tA}
 +.endif
 +
 +# make sure this is global
 +_STAGED_DIRS ?=
 +.export _STAGED_DIRS
 +# add each dir we stage to to _STAGED_DIRS
 +# and make sure we have absolute paths so that bmake
 +# will match against .MAKE.META.BAILIWICK
 +STAGE_DIR_FILTER = tA:@d@$${_STAGED_DIRS::+=$$d}$$d@
 +# convert _STAGED_DIRS into suitable filters
 +GENDIRDEPS_FILTER += Nnot-empty-is-important \
 +      ${_STAGED_DIRS:O:u:M${OBJTOP}*:S,${OBJTOP}/,N,} \
 +      ${_STAGED_DIRS:O:u:M${_objroot}*:N${OBJTOP}*:S,${_objroot},,:C,^([^/]+)/(.*),N\2.\1,:S,${HOST_TARGET},.host,}
 +
 +LN_CP_SCRIPT = LnCp() { \
 +  rm -f $$2 2> /dev/null; \
 +  ln $$1 $$2 2> /dev/null || \
 +  cp -p $$1 $$2; }
 +
++# a staging conflict should cause an error
++# a warning is handy when bootstapping different options.
++STAGE_CONFLICT?= ERROR
++.if ${STAGE_CONFLICT:tl} == "error"
++STAGE_CONFLICT_ACTION= exit 1;
++.else
++STAGE_CONFLICT_ACTION=
++.endif
++
 +# it is an error for more than one src dir to try and stage
 +# the same file
 +STAGE_DIRDEP_SCRIPT = ${LN_CP_SCRIPT}; StageDirdep() { \
 +  t=$$1; \
 +  if [ -s $$t.dirdep ]; then \
 +      cmp -s .dirdep $$t.dirdep && return; \
-       echo "ERROR: $$t installed by `cat $$t.dirdep` not ${_dirdep}" >&2; \
-       exit 1; \
++      echo "${STAGE_CONFLICT}: $$t installed by `cat $$t.dirdep` not ${_dirdep}" >&2; \
++      ${STAGE_CONFLICT_ACTION} \
 +  fi; \
 +  LnCp .dirdep $$t.dirdep || exit 1; }
 +
 +# common logic for staging files
 +# this all relies on RELDIR being set to a subdir of SRCTOP
 +# we use ln(1) if we can, else cp(1)
 +STAGE_FILE_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageFiles() { \
 +  case "$$1" in "") return;; -m) mode=$$2; shift 2;; *) mode=;; esac; \
 +  dest=$$1; shift; \
 +  mkdir -p $$dest; \
 +  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
 +  for f in "$$@"; do \
 +      case "$$f" in */*) t=$$dest/${_stage_file_basename};; *) t=$$dest/$$f;; esac; \
 +      StageDirdep $$t; \
 +      LnCp $$f $$t || exit 1; \
 +      [ -z "$$mode" ] || chmod $$mode $$t; \
 +  done; :; }
 +
 +STAGE_LINKS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageLinks() { \
 +  case "$$1" in "") return;; --) shift;; -*) ldest= lnf=$$1; shift;; /*) ldest=$$1/;; esac; \
 +  dest=$$1; shift; \
 +  mkdir -p $$dest; \
 +  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
 +  while test $$\# -ge 2; do \
 +      l=$$ldest$$1; shift; \
 +      t=$$dest/$$1; \
 +      case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \
 +      shift; \
 +      StageDirdep $$t; \
 +      rm -f $$t 2>/dev/null; \
 +      ln $$lnf $$l $$t || exit 1; \
 +  done; :; }
 +
 +STAGE_AS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageAs() { \
 +  case "$$1" in "") return;; -m) mode=$$2; shift 2;; *) mode=;; esac; \
 +  dest=$$1; shift; \
 +  mkdir -p $$dest; \
 +  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
 +  while test $$\# -ge 2; do \
 +      s=$$1; shift; \
 +      t=$$dest/$$1; \
 +      case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \
 +      shift; \
 +      StageDirdep $$t; \
 +      LnCp $$s $$t || exit 1; \
 +      [ -z "$$mode" ] || chmod $$mode $$t; \
 +  done; :; }
 +
 +# this is simple, a list of the "staged" files depends on this,
 +_STAGE_BASENAME_USE:  .USE ${.TARGET:T}
 +      @${STAGE_FILE_SCRIPT}; StageFiles ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T}
 +
 +_STAGE_AS_BASENAME_USE:        .USE ${.TARGET:T}
 +      @${STAGE_AS_SCRIPT}; StageAs ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T} ${STAGE_AS_${.TARGET:T}:U${.TARGET:T}}
 +
 +.if !empty(STAGE_INCSDIR)
 +STAGE_TARGETS += stage_incs
- STAGE_INCS ?= ${.ALLSRC:N.dirdep}
++STAGE_INCS ?= ${.ALLSRC:N.dirdep:Nstage_*}
 +
 +stage_includes: stage_incs
 +stage_incs:   .dirdep
 +      @${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_INCSDIR:${STAGE_DIR_FILTER}} ${STAGE_INCS}
 +      @touch $@
 +.endif
 +
 +.if !empty(STAGE_LIBDIR)
 +STAGE_TARGETS += stage_libs
 +
- STAGE_LIBS ?= ${.ALLSRC:N.dirdep}
++STAGE_LIBS ?= ${.ALLSRC:N.dirdep:Nstage_*}
 +
 +stage_libs:   .dirdep
 +      @${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${STAGE_LIBS}
 +.if !defined(NO_SHLIB_LINKS)
 +.if !empty(SHLIB_LINKS)
 +      @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} \
 +      ${SHLIB_LINKS:@t@${STAGE_LIBS:T:M$t.*} $t@}
 +.elif !empty(SHLIB_LINK) && !empty(SHLIB_NAME)
 +      @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${SHLIB_NAME} ${SHLIB_LINK} ${SYMLINKS:T}
 +.endif
 +.endif
 +      @touch $@
 +.endif
 +
 +.if !empty(STAGE_DIR)
 +STAGE_SETS += _default
 +STAGE_DIR._default = ${STAGE_DIR}
 +STAGE_LINKS_DIR._default = ${STAGE_LINKS_DIR:U${STAGE_OBJTOP}}
 +STAGE_SYMLINKS_DIR._default = ${STAGE_SYMLINKS_DIR:U${STAGE_OBJTOP}}
 +STAGE_FILES._default = ${STAGE_FILES}
 +STAGE_LINKS._default = ${STAGE_LINKS}
 +STAGE_SYMLINKS._default = ${STAGE_SYMLINKS}
 +STAGE_FILES ?= ${.ALLSRC:N.dirdep:Nstage_*}
 +STAGE_SYMLINKS ?= ${.ALLSRC:T:N.dirdep:Nstage_*}
 +.endif
 +
 +.if !empty(STAGE_SETS)
 +CLEANFILES += ${STAGE_SETS:@s@stage*$s@}
 +
 +# some makefiles need to populate multiple directories
 +.for s in ${STAGE_SETS:O:u}
- STAGE_FILES.$s ?= ${.ALLSRC:N.dirdep}
- STAGE_SYMLINKS.$s ?= ${.ALLSRC:N.dirdep}
++STAGE_FILES.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
++STAGE_SYMLINKS.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
 +STAGE_LINKS_DIR.$s ?= ${STAGE_OBJTOP}
 +STAGE_SYMLINKS_DIR.$s ?= ${STAGE_OBJTOP}
 +
 +STAGE_TARGETS += stage_files
 +.if $s != "_default"
 +stage_files:  stage_files.$s
 +stage_files.$s:       .dirdep
 +.else
 +stage_files:  .dirdep
 +.endif
 +      @${STAGE_FILE_SCRIPT}; StageFiles ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_FILES.$s}
 +      @touch $@
 +
 +STAGE_TARGETS += stage_links
 +.if $s != "_default"
 +stage_links:  stage_links.$s
 +stage_links.$s:       .dirdep
 +.else
 +stage_links:  .dirdep
 +.endif
 +      @${STAGE_LINKS_SCRIPT}; StageLinks ${STAGE_LINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_LINKS.$s}
 +      @touch $@
 +
 +STAGE_TARGETS += stage_symlinks
 +.if $s != "_default"
 +stage_symlinks:       stage_symlinks.$s
 +stage_symlinks.$s:    .dirdep
 +.else
 +stage_symlinks:       .dirdep
 +.endif
 +      @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_SYMLINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_SYMLINKS.$s}
 +      @touch $@
 +
 +.endfor
 +.endif
 +
 +.if !empty(STAGE_AS_SETS)
 +CLEANFILES += ${STAGE_AS_SETS:@s@stage*$s@}
 +
 +STAGE_TARGETS += stage_as
 +
 +# sometimes things need to be renamed as they are staged
 +# each ${file} will be staged as ${STAGE_AS_${file:T}}
 +# one could achieve the same with SYMLINKS
 +.for s in ${STAGE_AS_SETS:O:u}
- STAGE_AS.$s ?= ${.ALLSRC:N.dirdep}
++STAGE_AS.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
 +
 +stage_as:     stage_as.$s
 +stage_as.$s:  .dirdep
 +      @${STAGE_AS_SCRIPT}; StageAs ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_AS.$s:@f@$f ${STAGE_AS_${f:tA}:U${STAGE_AS_${f:T}:U${f:T}}}@}
 +      @touch $@
 +
 +.endfor
 +.endif
 +
 +CLEANFILES += ${STAGE_TARGETS} stage_incs stage_includes
 +
 +# stage_*links usually needs to follow any others.
++# for non-jobs mode the order here matters
++staging: ${STAGE_TARGETS:N*_links} ${STAGE_TARGETS:M*_links}
++
++.if ${.MAKE.JOBS:U0} > 0 && ${STAGE_TARGETS:M*_links} != ""
++# the above isn't sufficient
 +.for t in ${STAGE_TARGETS:N*links:O:u}
 +.ORDER: $t stage_links
- .ORDER: $t stage_symlinks
 +.endfor
- # make sure this exists
- staging:
++.endif
 +
 +# generally we want staging to wait until everything else is done
 +STAGING_WAIT ?= .WAIT
 +
 +.if ${.MAKE.LEVEL} > 0
 +all: ${STAGING_WAIT} staging
 +.endif
 +
 +.if exists(${.PARSEDIR}/stage-install.sh) && !defined(STAGE_INSTALL)
 +# this will run install(1) and then followup with .dirdep files.
 +STAGE_INSTALL := sh ${.PARSEDIR:tA}/stage-install.sh INSTALL="${INSTALL}" OBJDIR=${.OBJDIR:tA}
 +.endif
 +
 +# if ${INSTALL} gets run during 'all' assume it is for staging?
 +.if ${.TARGETS:Nall} == "" && defined(STAGE_INSTALL)
 +INSTALL := ${STAGE_INSTALL}
 +.if target(beforeinstall)
 +beforeinstall: .dirdep
 +.endif
 +.endif
 +.NOPATH: ${STAGE_FILES}
 +
 +.if !empty(STAGE_TARGETS)
 +MK_STALE_STAGED?= no
 +.if ${MK_STALE_STAGED} == "yes"
 +all: stale_staged
 +# get a list of paths that we have just staged
 +# get a list of paths that we have previously staged to those same dirs
 +# anything in the 2nd list but not the first is stale - remove it.
 +stale_staged: staging .NOMETA
 +      @egrep '^[WL] .*${STAGE_OBJTOP}' /dev/null ${.MAKE.META.FILES:M*stage_*} | \
 +      sed "/\.dirdep/d;s,.* '*\(${STAGE_OBJTOP}/[^ '][^ ']*\).*,\1," | \
 +      sort > ${.TARGET}.staged1
 +      @grep -l '${_dirdep}' /dev/null ${_STAGED_DIRS:M${STAGE_OBJTOP}*:O:u:@d@$d/*.dirdep@} | \
 +      sed 's,\.dirdep,,' | sort > ${.TARGET}.staged2
 +      @comm -13 ${.TARGET}.staged1 ${.TARGET}.staged2 > ${.TARGET}.stale
 +      @test ! -s ${.TARGET}.stale || { \
 +              echo "Removing stale staged files..."; \
 +              sed 's,.*,& &.dirdep,' ${.TARGET}.stale | xargs rm -f; }
 +
 +.endif
 +.endif
 +.endif
index 1f7ac49dae0798045e538a08e9c30440cc1f7835,0000000000000000000000000000000000000000..1d1ba5338a106c2af40a824e33ec6c59151f441d
mode 100644,000000..100644
--- /dev/null
@@@ -1,142 -1,0 +1,147 @@@
- # $Id: meta.sys.mk,v 1.21 2015/06/01 22:43:49 sjg Exp $
++# $Id: meta.sys.mk,v 1.22 2015/06/16 06:31:05 sjg Exp $
 +
 +#
 +#     @(#) Copyright (c) 2010, Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +# include this if you want to enable meta mode
 +# for maximum benefit, requires filemon(4) driver.
 +
 +.if ${MAKE_VERSION:U0} > 20100901
 +.if !target(.ERROR)
 +
++.-include "local.meta.sys.mk"
++
++# absoulte path to what we are reading.
++_PARSEDIR = ${.PARSEDIR:tA}
 +
 +META_MODE += meta verbose
 +.MAKE.MODE ?= ${META_MODE}
 +
 +.if ${.MAKE.LEVEL} == 0
 +_make_mode := ${.MAKE.MODE} ${META_MODE}
 +.if ${_make_mode:M*read*} != "" || ${_make_mode:M*nofilemon*} != ""
 +# tell everyone we are not updating Makefile.depend*
 +UPDATE_DEPENDFILE = NO
 +.export UPDATE_DEPENDFILE
 +.endif
 +.if ${UPDATE_DEPENDFILE:Uyes:tl} == "no" && !exists(/dev/filemon)
 +# we should not get upset
 +META_MODE += nofilemon
 +.export META_MODE
 +.endif
 +.endif
 +
 +.if !defined(NO_SILENT)
 +.if ${MAKE_VERSION} > 20110818
 +# only be silent when we have a .meta file
 +META_MODE += silent=yes
 +.else
 +.SILENT:
 +.endif
 +.endif
 +
 +# make defaults .MAKE.DEPENDFILE to .depend
 +# that won't work for us.
 +.if ${.MAKE.DEPENDFILE} == ".depend"
 +.undef .MAKE.DEPENDFILE
 +.endif
 +
 +# if you don't cross build for multiple MACHINEs concurrently, then
 +# .MAKE.DEPENDFILE = Makefile.depend
 +# probably makes sense - you can set that in local.sys.mk 
 +.MAKE.DEPENDFILE ?= Makefile.depend.${MACHINE}
 +
 +# we use the pseudo machine "host" for the build host.
 +# this should be taken care of before we get here
 +.if ${OBJTOP:Ua} == ${HOST_OBJTOP:Ub}
 +MACHINE = host
 +.endif
 +
 +.if ${.MAKE.LEVEL} == 0
 +# it can be handy to know which MACHINE kicked off the build
 +# for example, if using Makefild.depend for multiple machines,
 +# allowing only MACHINE0 to update can keep things simple.
 +MACHINE0 := ${MACHINE}
++.export MACHINE0
 +
 +.if defined(PYTHON) && exists(${PYTHON})
 +# we prefer the python version of this - it is much faster
 +META2DEPS ?= ${.PARSEDIR}/meta2deps.py
 +.else
 +META2DEPS ?= ${.PARSEDIR}/meta2deps.sh
 +.endif
 +META2DEPS := ${META2DEPS}
 +.export META2DEPS
 +.endif
 +
 +MAKE_PRINT_VAR_ON_ERROR += \
 +      .ERROR_TARGET \
 +      .ERROR_META_FILE \
 +      .MAKE.LEVEL \
 +      MAKEFILE \
 +      .MAKE.MODE
 +
 +.if !defined(SB) && defined(SRCTOP)
 +SB = ${SRCTOP:H}
 +.endif
 +ERROR_LOGDIR ?= ${SB}/error
 +meta_error_log = ${ERROR_LOGDIR}/meta-${.MAKE.PID}.log
 +
 +# we are not interested in make telling us a failure happened elsewhere
 +.ERROR: _metaError
 +_metaError: .NOMETA .NOTMAIN
 +      -@[ "${.ERROR_META_FILE}" ] && { \
 +      grep -q 'failure has been detected in another branch' ${.ERROR_META_FILE} && exit 0; \
 +      mkdir -p ${meta_error_log:H}; \
 +      cp ${.ERROR_META_FILE} ${meta_error_log}; \
 +      echo "ERROR: log ${meta_error_log}" >&2; }; :
 +
 +.endif
 +
 +# Are we, after all, in meta mode?
 +.if ${.MAKE.MODE:Mmeta*} != ""
 +MKDEP_MK = meta.autodep.mk
 +
 +.if ${UPDATE_DEPENDFILE:Uyes:tl} != "no"
 +.if ${.MAKEFLAGS:Uno:M-k} != ""
 +# make this more obvious
 +.warning Setting UPDATE_DEPENDFILE=NO due to -k
 +UPDATE_DEPENDFILE= NO
 +.export UPDATE_DEPENDFILE
 +.elif !exists(/dev/filemon)
 +.error ${.newline}ERROR: The filemon module (/dev/filemon) is not loaded.
 +.endif
 +.endif
 +
 +.if ${.MAKE.LEVEL} == 0
 +# make sure dirdeps target exists and do it first
 +all: dirdeps .WAIT
 +dirdeps:
 +.NOPATH: dirdeps
 +
 +.if defined(ALL_MACHINES)
 +# the first .MAIN: is what counts
 +# by default dirdeps is all we want at level0
 +.MAIN: dirdeps
 +# tell dirdeps.mk what we want
 +BUILD_AT_LEVEL0 = no
 +.endif
 +.if ${.TARGETS:Nall} == "" 
 +# it works best if we do everything via sub-makes
 +BUILD_AT_LEVEL0 ?= no
 +.endif
 +
 +.endif
 +.endif
 +.endif
index f090bbe8e537292a81d75d18275364151205f15b,0000000000000000000000000000000000000000..3fdb16bcfc9a98439156f9ccb8fb4849e90b9a98
mode 100644,000000..100644
--- /dev/null
@@@ -1,245 -1,0 +1,248 @@@
- # $Id: own.mk,v 1.28 2015/04/16 16:59:00 sjg Exp $
++# $Id: own.mk,v 1.29 2015/09/08 06:15:31 sjg Exp $
 +
 +.if !target(__${.PARSEFILE}__)
 +__${.PARSEFILE}__:
 +
 +.if !target(__init.mk__)
 +.include "init.mk"
 +.endif
 +
 +.ifndef NOMAKECONF
 +MAKECONF?=    /etc/mk.conf
 +.-include "${MAKECONF}"
 +.endif
 +
 +.include <host-target.mk>
 +
 +TARGET_OSNAME?= ${_HOST_OSNAME}
 +TARGET_OSREL?= ${_HOST_OSREL}
 +TARGET_OSTYPE?= ${HOST_OSTYPE}
 +TARGET_HOST?= ${HOST_TARGET}
 +
 +# these may or may not exist
 +.-include "${TARGET_HOST}.mk"
 +.-include "config.mk"
 +
 +RM?= rm
 +LN?= ln
 +INSTALL?= install
 +
 +prefix?=      /usr
 +.if exists(${prefix}/lib)
 +libprefix?=   ${prefix}
 +.else
 +libprefix?=   /usr
 +.endif
 +
 +# FreeBSD at least does not set this
 +MACHINE_ARCH?=${MACHINE}
 +# we need to make sure these are defined too in case sys.mk fails to.
 +COMPILE.s?=   ${CC} ${AFLAGS} -c
 +LINK.s?=      ${CC} ${AFLAGS} ${LDFLAGS}
 +COMPILE.S?=   ${CC} ${AFLAGS} ${CPPFLAGS} -c -traditional-cpp
 +LINK.S?=      ${CC} ${AFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +COMPILE.c?=   ${CC} ${CFLAGS} ${CPPFLAGS} -c
 +LINK.c?=      ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +CXXFLAGS?=    ${CFLAGS}
 +COMPILE.cc?=  ${CXX} ${CXXFLAGS} ${CPPFLAGS} -c
 +LINK.cc?=     ${CXX} ${CXXFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +COMPILE.m?=   ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} -c
 +LINK.m?=      ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +COMPILE.f?=   ${FC} ${FFLAGS} -c
 +LINK.f?=      ${FC} ${FFLAGS} ${LDFLAGS}
 +COMPILE.F?=   ${FC} ${FFLAGS} ${CPPFLAGS} -c
 +LINK.F?=      ${FC} ${FFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +COMPILE.r?=   ${FC} ${FFLAGS} ${RFLAGS} -c
 +LINK.r?=      ${FC} ${FFLAGS} ${RFLAGS} ${LDFLAGS}
 +LEX.l?=               ${LEX} ${LFLAGS}
 +COMPILE.p?=   ${PC} ${PFLAGS} ${CPPFLAGS} -c
 +LINK.p?=      ${PC} ${PFLAGS} ${CPPFLAGS} ${LDFLAGS}
 +YACC.y?=      ${YACC} ${YFLAGS}
 +
 +# for suffix rules
 +IMPFLAGS?=    ${COPTS.${.IMPSRC:T}} ${CPUFLAGS.${.IMPSRC:T}} ${CPPFLAGS.${.IMPSRC:T}}
 +.for s in .c .cc 
 +COMPILE.$s += ${IMPFLAGS}
 +LINK.$s +=  ${IMPFLAGS}
 +.endfor
 +
 +PRINT.VAR.MAKE = MAKESYSPATH=${MAKESYSPATH:U${.PARSEDIR}} ${.MAKE}
 +.if empty(.MAKEFLAGS:M-V*)
 +.if defined(MAKEOBJDIRPREFIX) || defined(MAKEOBJDIR)
 +PRINTOBJDIR=  ${PRINT.VAR.MAKE} -r -V .OBJDIR -f /dev/null xxx
 +.else
 +PRINTOBJDIR=  ${PRINT.VAR.MAKE} -V .OBJDIR
 +.endif
 +.else
 +PRINTOBJDIR=  echo # prevent infinite recursion
 +.endif
 +
 +# we really like to have SRCTOP and OBJTOP defined...
 +.if !defined(SRCTOP) || !defined(OBJTOP)
 +.-include "srctop.mk"
 +.endif
 +
 +.if !defined(SRCTOP) || !defined(OBJTOP)
 +# dpadd.mk is rather pointless without these
 +OPTIONS_DEFAULT_NO+= DPADD_MK
 +.endif
 +
 +# process options
 +OPTIONS_DEFAULT_NO+= \
 +      AUTO_OBJ \
 +      INSTALL_AS_USER \
 +      GPROF \
 +      LIBTOOL \
 +      LINT \
 +      META_MODE \
 +
 +OPTIONS_DEFAULT_YES+= \
 +      ARCHIVE \
 +      AUTODEP \
 +      CRYPTO \
 +      DOC \
 +      DPADD_MK \
 +      GDB \
 +      KERBEROS \
 +      LINKLIB \
 +      MAN \
 +      NLS \
 +      OBJ \
 +      PIC \
 +      SHARE \
 +      SKEY \
 +      YP \
 +
 +OPTIONS_DEFAULT_DEPENDENT+= \
 +      CATPAGES/MAN \
 +      OBJDIRS/OBJ \
 +      PICINSTALL/LINKLIB \
 +      PICLIB/PIC \
 +      PROFILE/LINKLIB \
 +
 +.include <options.mk>
 +
 +.if ${MK_INSTALL_AS_USER} == "yes"
 +# We ignore this if user is root.
 +_uid!=  id -u
 +.if ${_uid} != 0
 +.if !defined(USERGRP)
 +USERGRP!=  id -g
 +.export USERGRP
 +.endif
 +.for x in BIN CONF DOC INFO KMOD LIB MAN NLS SHARE
 +$xOWN=  ${USER}
 +$xGRP=  ${USERGRP}
++$x_INSTALL_OWN=
 +.endfor
++PROG_INSTALL_OWN=
 +.endif
 +.endif
 +
 +# override this in sys.mk
 +ROOT_GROUP?=  wheel
 +BINGRP?=      ${ROOT_GROUP}
 +BINOWN?=      root
 +BINMODE?=     555
 +NONBINMODE?=  444
++DIRMODE?=     755
 +
 +# Define MANZ to have the man pages compressed (gzip)
 +#MANZ=                1
 +
 +MANTARGET?= cat
 +MANDIR?=      ${prefix}/share/man/${MANTARGET}
 +MANGRP?=      ${BINGRP}
 +MANOWN?=      ${BINOWN}
 +MANMODE?=     ${NONBINMODE}
 +
 +LIBDIR?=      ${libprefix}/lib
 +SHLIBDIR?=    ${libprefix}/lib
 +.if ${USE_SHLIBDIR:Uno} == "yes"
 +_LIBSODIR?=   ${SHLIBDIR}
 +.else
 +_LIBSODIR?=   ${LIBDIR}
 +.endif
 +# this is where ld.*so lives
 +SHLINKDIR?=   /usr/libexec
 +LINTLIBDIR?=  ${libprefix}/libdata/lint
 +LIBGRP?=      ${BINGRP}
 +LIBOWN?=      ${BINOWN}
 +LIBMODE?=     ${NONBINMODE}
 +
 +DOCDIR?=        ${prefix}/share/doc
 +DOCGRP?=      ${BINGRP}
 +DOCOWN?=      ${BINOWN}
 +DOCMODE?=       ${NONBINMODE}
 +
 +NLSDIR?=      ${prefix}/share/nls
 +NLSGRP?=      ${BINGRP}
 +NLSOWN?=      ${BINOWN}
 +NLSMODE?=     ${NONBINMODE}
 +
 +KMODDIR?=     ${prefix}/lkm
 +KMODGRP?=     ${BINGRP}
 +KMODOWN?=     ${BINOWN}
 +KMODMODE?=    ${NONBINMODE}
 +
 +COPY?=                -c
 +STRIP_FLAG?=  -s
 +
 +.if ${TARGET_OSNAME} == "NetBSD"
 +.if exists(/usr/libexec/ld.elf_so)
 +OBJECT_FMT=ELF
 +.endif
 +OBJECT_FMT?=a.out
 +.endif
 +# sys.mk should set something appropriate if need be.
 +OBJECT_FMT?=ELF
 +
 +.if (${_HOST_OSNAME} == "FreeBSD")
 +CFLAGS+= ${CPPFLAGS}
 +.endif
 +
 +# allow for per target flags
 +# apply the :T:R first, so the more specific :T can override if needed
 +CPPFLAGS += ${CPPFLAGS_${.TARGET:T:R}} ${CPPFLAGS_${.TARGET:T}} 
 +CFLAGS += ${CFLAGS_${.TARGET:T:R}} ${CFLAGS_${.TARGET:T}} 
 +
 +# Define SYS_INCLUDE to indicate whether you want symbolic links to the system
 +# source (``symlinks''), or a separate copy (``copies''); (latter useful
 +# in environments where it's not possible to keep /sys publicly readable)
 +#SYS_INCLUDE=         symlinks
 +
 +# don't try to generate PIC versions of libraries on machines
 +# which don't support PIC.
 +.if  (${MACHINE_ARCH} == "vax") || \
 +    ((${MACHINE_ARCH} == "mips") && defined(STATIC_TOOLCHAIN)) || \
 +    ((${MACHINE_ARCH} == "alpha") && defined(ECOFF_TOOLCHAIN))
 +MK_PIC=no
 +.endif
 +
 +# No lint, for now.
 +NOLINT=
 +
 +
 +.if ${MK_LINKLIB} == "no"
 +MK_PICINSTALL=        no
 +MK_PROFILE=   no
 +.endif
 +
 +.if ${MK_MAN} == "no"
 +MK_CATPAGES=  no
 +.endif
 +
 +.if ${MK_OBJ} == "no"
 +MK_OBJDIRS=   no
 +MK_AUTO_OBJ=  no
 +.endif
 +
 +.if ${MK_SHARE} == "no"
 +MK_CATPAGES=  no
 +MK_DOC=               no
 +MK_INFO=      no
 +MK_MAN=               no
 +MK_NLS=               no
 +.endif
 +
 +.endif
index 4d4c940b8bc7a1154182fd9100ed55442c675b6b,0000000000000000000000000000000000000000..296b73c68574facc98f03240af37f32b08844969
mode 100644,000000..100644
--- /dev/null
@@@ -1,48 -1,0 +1,53 @@@
- # $Id: rst2htm.mk,v 1.9 2014/02/22 01:52:41 sjg Exp $
++# $Id: rst2htm.mk,v 1.10 2015/09/08 22:17:46 sjg Exp $
 +#
 +#     @(#) Copyright (c) 2009, Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +# convert reStructuredText to HTML, using rst2html.py from
 +# docutils - http://docutils.sourceforge.net/
 +
 +.if empty(TXTSRCS)
 +TXTSRCS != 'ls' -1t ${.CURDIR}/*.txt ${.CURDIR}/*.rst 2>/dev/null; echo
 +.endif
 +RSTSRCS ?= ${TXTSRCS}
 +HTMFILES ?= ${RSTSRCS:R:T:O:u:%=%.htm}
 +RST2HTML ?= rst2html.py
 +RST2PDF ?= rst2pdf
 +RST2S5 ?= rst2s5.py
 +# the following will run RST2S5 if the target name contains the word 'slides'
 +# otherwise it uses RST2HTML
- RST2HTM = ${"${.TARGET:T:M*slides*}":?${RST2S5} ${RST2S5_FLAGS}:${RST2HTML} ${RST2HTML_FLAGS}}
++RST2HTM = ${"${.TARGET:T:M*slides*}":?${RST2S5}:${RST2HTML}}
++RST2HTM_SLIDES_FLAGS ?= ${RST2S5_FLAGS}
++RST2HTM_DOC_FLAGS ?= ${RST2HTML_FLAGS}
++RST2HTM_FLAGS ?= ${"${.TARGET:T:M*slides*}":?${RST2HTM_SLIDES_FLAGS}:${RST2HTM_DOC_FLAGS}}
++
++RST2PDF_FLAGS ?= ${"${.TARGET:T:M*slides*}":?${RST2PDF_SLIDES_FLAGS}:${RST2PDF_DOC_FLAGS}}
 +
 +RST_SUFFIXES ?= .rst .txt
 +
 +CLEANFILES += ${HTMFILES}
 +
 +html: ${HTMFILES}
 +
 +.SUFFIXES: ${RST_SUFFIXES} .htm .pdf
 +
 +${RST_SUFFIXES:@s@$s.htm@}:
-       ${RST2HTM} ${.IMPSRC} ${.TARGET}
++      ${RST2HTM} ${RST2HTM_FLAGS} ${FLAGS.${.TARGET}} ${.IMPSRC} ${.TARGET}
 +
 +${RST_SUFFIXES:@s@$s.pdf@}:
-       ${RST2PDF} ${.IMPSRC} ${.TARGET}
++      ${RST2PDF} ${RST2PDF_FLAGS} ${FLAGS.${.TARGET}} ${.IMPSRC} ${.TARGET}
 +
 +.for s in ${RSTSRCS:O:u}
 +${s:R:T}.htm: $s
 +${s:R:T}.pdf: $s
 +.endfor
index 9eaebc2e9b4b7679c2fb8fd755dea322b27500b9,0000000000000000000000000000000000000000..2332858c25ccdb2c553e09822e8ab325474275d9
mode 100644,000000..100644
--- /dev/null
@@@ -1,199 -1,0 +1,199 @@@
- /*    $NetBSD: nonints.h,v 1.68 2015/05/05 21:51:09 sjg Exp $ */
++/*    $NetBSD: nonints.h,v 1.69 2015/10/11 04:51:24 sjg Exp $ */
 +
 +/*-
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + *
 + *    from: @(#)nonints.h     8.3 (Berkeley) 3/19/94
 + */
 +
 +/*-
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + *
 + *    from: @(#)nonints.h     8.3 (Berkeley) 3/19/94
 + */
 +
 +/* arch.c */
 +ReturnStatus Arch_ParseArchive(char **, Lst, GNode *);
 +void Arch_Touch(GNode *);
 +void Arch_TouchLib(GNode *);
 +time_t Arch_MTime(GNode *);
 +time_t Arch_MemMTime(GNode *);
 +void Arch_FindLib(GNode *, Lst);
 +Boolean Arch_LibOODate(GNode *);
 +void Arch_Init(void);
 +void Arch_End(void);
 +int Arch_IsLib(GNode *);
 +
 +/* compat.c */
 +int CompatRunCommand(void *, void *);
 +void Compat_Run(Lst);
 +int Compat_Make(void *, void *);
 +
 +/* cond.c */
 +struct If;
 +int Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean);
 +int Cond_Eval(char *);
 +void Cond_restore_depth(unsigned int);
 +unsigned int Cond_save_depth(void);
 +
 +/* for.c */
 +int For_Eval(char *);
 +int For_Accum(char *);
 +void For_Run(int);
 +
 +/* job.c */
 +#ifdef WAIT_T
 +void JobReapChild(pid_t, WAIT_T, Boolean);
 +#endif
 +
 +/* main.c */
 +void Main_ParseArgLine(const char *);
 +void MakeMode(const char *);
 +int main(int, char **);
 +char *Cmd_Exec(const char *, const char **);
 +void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
 +void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
 +void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
 +void DieHorribly(void) MAKE_ATTR_DEAD;
 +int PrintAddr(void *, void *);
 +void Finish(int) MAKE_ATTR_DEAD;
 +int eunlink(const char *);
 +void execError(const char *, const char *);
 +char *getTmpdir(void);
 +Boolean getBoolean(const char *, Boolean);
 +
 +/* parse.c */
 +void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
 +Boolean Parse_AnyExport(void);
 +Boolean Parse_IsVar(char *);
 +void Parse_DoVar(char *, GNode *);
 +void Parse_AddIncludeDir(char *);
 +void Parse_File(const char *, int);
 +void Parse_Init(void);
 +void Parse_End(void);
 +void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *);
 +Lst Parse_MainName(void);
 +
 +/* str.c */
 +char *str_concat(const char *, const char *, int);
 +char **brk_string(const char *, int *, Boolean, char **);
 +char *Str_FindSubstring(const char *, const char *);
 +int Str_Match(const char *, const char *);
 +char *Str_SYSVMatch(const char *, const char *, int *len);
 +void Str_SYSVSubst(Buffer *, char *, char *, int);
 +
 +/* suff.c */
 +void Suff_ClearSuffixes(void);
 +Boolean Suff_IsTransform(char *);
 +GNode *Suff_AddTransform(char *);
 +int Suff_EndTransform(void *, void *);
 +void Suff_AddSuffix(char *, GNode **);
 +Lst Suff_GetPath(char *);
 +void Suff_DoPaths(void);
 +void Suff_AddInclude(char *);
 +void Suff_AddLib(char *);
 +void Suff_FindDeps(GNode *);
 +Lst Suff_FindPath(GNode *);
 +void Suff_SetNull(char *);
 +void Suff_Init(void);
 +void Suff_End(void);
 +void Suff_PrintAll(void);
 +
 +/* targ.c */
 +void Targ_Init(void);
 +void Targ_End(void);
 +Lst Targ_List(void);
 +GNode *Targ_NewGN(const char *);
 +GNode *Targ_FindNode(const char *, int);
 +Lst Targ_FindList(Lst, int);
 +Boolean Targ_Ignore(GNode *);
 +Boolean Targ_Silent(GNode *);
 +Boolean Targ_Precious(GNode *);
 +void Targ_SetMain(GNode *);
 +int Targ_PrintCmd(void *, void *);
 +int Targ_PrintNode(void *, void *);
 +char *Targ_FmtTime(time_t);
 +void Targ_PrintType(int);
 +void Targ_PrintGraph(int);
 +void Targ_Propagate(void);
 +void Targ_Propagate_Wait(void);
 +
 +/* var.c */
 +void Var_Delete(const char *, GNode *);
 +void Var_Set(const char *, const char *, GNode *, int);
 +void Var_Append(const char *, const char *, GNode *);
 +Boolean Var_Exists(const char *, GNode *);
 +char *Var_Value(const char *, GNode *, char **);
- char *Var_Parse(const char *, GNode *, Boolean, int *, void **);
- char *Var_Subst(const char *, const char *, GNode *, Boolean);
++char *Var_Parse(const char *, GNode *, Boolean, Boolean, int *, void **);
++char *Var_Subst(const char *, const char *, GNode *, Boolean, Boolean);
 +char *Var_GetTail(const char *);
 +char *Var_GetHead(const char *);
 +void Var_Init(void);
 +void Var_End(void);
 +void Var_Dump(GNode *);
 +void Var_ExportVars(void);
 +void Var_Export(char *, int);
 +void Var_UnExport(char *);
 +
 +/* util.c */
 +void (*bmake_signal(int, void (*)(int)))(int);
index b714eb782cec5453f023e444ad4159844409a96c,0000000000000000000000000000000000000000..1df67b8cd7c362e3e649a54607e21d35486e1c9c
mode 100755,000000..100755
--- /dev/null
@@@ -1,230 -1,0 +1,234 @@@
- #     $Id: os.sh,v 1.46 2014/05/19 16:38:09 sjg Exp $
 +:
 +# NAME:
 +#     os.sh - operating system specifics
 +#
 +# DESCRIPTION:
 +#     This file is included at the start of processing. Its role is
 +#     to set the variables OS, OSREL, OSMAJOR, MACHINE and MACHINE_ARCH to
 +#     reflect the current system.
 +#     
 +#     It also sets variables such as MAILER, LOCAL_FS, PS_AXC to hide
 +#     certain aspects of different UNIX flavours. 
 +#
 +# SEE ALSO:
 +#     site.sh,funcs.sh
 +#
 +# AUTHOR:
 +#     Simon J. Gerraty <sjg@crufty.net>
 +
 +# RCSid:
-       NetBSD) SHARE_ARCH=$OS/${MACHINE_ARCH:-$MACHINE};;
++#     $Id: os.sh,v 1.47 2015/09/10 05:53:10 sjg Exp $
 +#
 +#     @(#) Copyright (c) 1994 Simon J. Gerraty
 +#
 +#     This file is provided in the hope that it will
 +#     be of use.  There is absolutely NO WARRANTY.
 +#     Permission to copy, redistribute or otherwise
 +#     use this file is hereby granted provided that 
 +#     the above copyright notice and this notice are
 +#     left intact. 
 +#      
 +#     Please send copies of changes and bug-fixes to:
 +#     sjg@crufty.net
 +#
 +
 +# this lets us skip sourcing it again
 +_OS_SH=:
 +
 +OS=`uname`
 +OSREL=`uname -r`
 +OSMAJOR=`IFS=.; set $OSREL; echo $1`
 +MACHINE=`uname -m`
 +MACHINE_ARCH=`uname -p 2>/dev/null || echo $MACHINE`
 +
 +# there is at least one case of `uname -p` outputting
 +# a bunch of usless drivel
 +case "$MACHINE_ARCH" in
 +*[!A-Za-z0-9_-]*) MACHINE_ARCH="$MACHINE";;
 +esac
 +        
 +# we need this here, and it is not always available...
 +Which() {
 +      case "$1" in
 +      -*) t=$1; shift;;
 +      *) t=-x;;
 +      esac
 +      case "$1" in
 +      /*)     test $t $1 && echo $1;;
 +      *)
 +              # some shells cannot correctly handle `IFS`
 +              # in conjunction with the for loop.
 +              _dirs=`IFS=:; echo ${2:-$PATH}`
 +              for d in $_dirs
 +              do
 +                      test $t $d/$1 && { echo $d/$1; break; }
 +              done
 +              ;;
 +      esac
 +}
 +
 +# tr is insanely non-portable wrt char classes, so we need to
 +# spell out the alphabet. sed y/// would work too.
 +toUpper() {
 +        ${TR:-tr} abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
 +}
 +
 +toLower() {
 +        ${TR:-tr} ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz
 +}
 +
 +K=
 +case $OS in
 +AIX)  # everyone loves to be different...
 +      OSMAJOR=`uname -v`
 +      OSREL="$OSMAJOR.`uname -r`"
 +      LOCAL_FS=jfs
 +      PS_AXC=-e
 +      SHARE_ARCH=$OS/$OSMAJOR.X
 +      ;;
 +SunOS)
 +      CHOWN=`Which chown /usr/etc:/usr/bin`
 +      export CHOWN
 +      
 +      # Great! Solaris keeps moving arch(1)
 +        # should just bite the bullet and use uname -p
 +      arch=`Which arch /usr/bin:/usr/ucb`
 +      
 +      MAILER=/usr/ucb/Mail
 +      LOCAL_FS=4.2
 +      
 +      case "$OSREL" in
 +      4.0*)
 +              # uname -m just says sun which could be anything
 +              # so use arch(1).
 +              MACHINE_ARCH=`arch`
 +              MACHINE=$MACHINE_ARCH
 +              ;;
 +      4*)
 +                MACHINE_ARCH=`arch`
 +                ;;
 +      5*)
 +              K=-k
 +              LOCAL_FS=ufs
 +              MAILER=mailx
 +              PS_AXC=-e
 +              # can you believe that ln on Solaris defaults to
 +              # overwriting an existing file!!!!! We want one that works!
 +              test -x /usr/xpg4/bin/ln && LN=${LN:-/usr/xpg4/bin/ln}
 +              # wonderful, 5.8's tr again require's []'s
 +                # but /usr/xpg4/bin/tr causes problems if LC_COLLATE is set!
 +                # use toUpper/toLower instead.
 +              ;;
 +      esac
 +      case "$OS/$MACHINE_ARCH" in
 +      *sun386)        SHARE_ARCH=$MACHINE_ARCH;;
 +      esac
 +      ;;
 +*BSD)
 +      K=-k
 +      MAILER=/usr/bin/Mail
 +      LOCAL_FS=local
 +      : $-,$ENV
 +      case "$-,$ENV" in
 +      *i*,*) ;;
 +      *,|*ENVFILE*) ;;
 +      *) ENV=;;
 +      esac
 +      # NetBSD at least has good backward compatibility
 +      # so NetBSD/i386 is good enough
 +      case $OS in
- SHARE_ARCH=${SHARE_ARCH:-$OS/$OSMAJOR.X/$MACHINE_ARCH}
++      NetBSD)
++              HOST_ARCH=$MACHINE
++              SHARE_ARCH=$OS/$HOST
++              ;;
 +      OpenBSD)
 +              arch=`Which arch /usr/bin:/usr/ucb:$PATH`
 +                MACHINE_ARCH=`$arch -s`
 +                ;;
 +      esac
 +      NAWK=awk
 +      export NAWK
 +      ;;
 +HP-UX)
 +      TMP_DIRS="/tmp /usr/tmp"
 +      LOCAL_FS=hfs
 +      MAILER=mailx
 +      # don't rely on /bin/sh, its broken
 +      _shell=/bin/ksh; ENV=
 +      # also, no one would be interested in OSMAJOR=A
 +      case "$OSREL" in
 +      ?.09*)  OSMAJOR=9; PS_AXC=-e;;
 +      ?.10*)  OSMAJOR=10; PS_AXC=-e;;
 +      esac
 +      ;;
 +IRIX)
 +      LOCAL_FS=efs
 +      ;;
 +Interix)
 +      MACHINE=i386
 +      MACHINE_ARCH=i386
 +      ;;
 +UnixWare)
 +      OSREL=`uname -v`
 +      OSMAJOR=`IFS=.; set $OSREL; echo $1`
 +      MACHINE_ARCH=`uname -m`
 +      ;;
 +Linux)
 +      # Not really any such thing as Linux, but
 +      # this covers red-hat and hopefully others.
 +      case $MACHINE in
 +      i?86)   MACHINE_ARCH=i386;; # we don't care about i686 vs i586
 +      esac
 +      LOCAL_FS=ext2
 +      PS_AXC=axc
 +      [ -x /usr/bin/md5sum ] && { MD5=/usr/bin/md5sum; export MD5; }
 +      ;;
 +QNX)
 +      case $MACHINE in
 +      x86pc)  MACHINE_ARCH=i386;;
 +      esac
 +      ;;
 +Haiku)
 +      case $MACHINE in
 +      BeBox)  MACHINE_ARCH=powerpc;;
 +      BeMac)  MACHINE_ARCH=powerpc;;
 +      BePC)   MACHINE_ARCH=i386;;
 +      esac
 +      ;;
 +esac
 +
 +HOSTNAME=${HOSTNAME:-`( hostname ) 2>/dev/null`}
 +HOSTNAME=${HOSTNAME:-`( uname -n ) 2>/dev/null`}
 +case "$HOSTNAME" in
 +*.*)  HOST=`IFS=.; set -- $HOSTNAME; echo $1`;;
 +*)    HOST=$HOSTNAME;;
 +esac
 +
 +TMP_DIRS=${TMP_DIRS:-"/tmp /var/tmp"}
 +MACHINE_ARCH=${MACHINE_ARCH:-$MACHINE}
++HOST_ARCH=${HOST_ARCH:-$MACHINE_ARCH}
 +# we mount server:/share/arch/$SHARE_ARCH as /usr/local
- HOST_TARGET=`echo ${OS}${OSMAJOR}-${MACHINE_ARCH} | toLower`
++SHARE_ARCH=${SHARE_ARCH:-$OS/$OSMAJOR.X/$HOST_ARCH}
 +LN=${LN:-ln}
 +TR=${TR:-tr}
 +
 +# Some people like have /share/$HOST_TARGET/bin etc.
++HOST_TARGET=`echo ${OS}${OSMAJOR}-$HOST_ARCH | toLower`
 +export HOST_TARGET
 +
 +case `echo -n .` in -n*) N=; C="\c";; *) N=-n; C=;; esac
 +
 +export HOSTNAME HOST        
 +export OS MACHINE MACHINE_ARCH OSREL OSMAJOR LOCAL_FS TMP_DIRS MAILER N C K PS_AXC
 +export LN SHARE_ARCH TR
 +
 +case /$0 in
 +*/os.sh)
 +        for v in $*
 +      do
 +                eval vv=\$$v
 +                echo "$v='$vv'"
 +      done
 +        ;;
 +esac
 +
index a55582ec1cd2b564d36eee4a4b71090b51b13a5d,0000000000000000000000000000000000000000..171847019acd036b272e051a3af442e3ec0c02b5
mode 100644,000000..100644
--- /dev/null
@@@ -1,3268 -1,0 +1,3268 @@@
- /*    $NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $     */
++/*    $NetBSD: parse.c,v 1.205 2015/10/11 04:51:24 sjg Exp $  */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $";
++static char rcsid[] = "$NetBSD: parse.c,v 1.205 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)parse.c   8.3 (Berkeley) 3/19/94";
 +#else
- __RCSID("$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $");
++__RCSID("$NetBSD: parse.c,v 1.205 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * parse.c --
 + *    Functions to parse a makefile.
 + *
 + *    One function, Parse_Init, must be called before any functions
 + *    in this module are used. After that, the function Parse_File is the
 + *    main entry point and controls most of the other functions in this
 + *    module.
 + *
 + *    Most important structures are kept in Lsts. Directories for
 + *    the .include "..." function are kept in the 'parseIncPath' Lst, while
 + *    those for the .include <...> are kept in the 'sysIncPath' Lst. The
 + *    targets currently being defined are kept in the 'targets' Lst.
 + *
 + *    The variables 'fname' and 'lineno' are used to track the name
 + *    of the current file and the line number in that file so that error
 + *    messages can be more meaningful.
 + *
 + * Interface:
 + *    Parse_Init                  Initialization function which must be
 + *                                called before anything else in this module
 + *                                is used.
 + *
 + *    Parse_End                   Cleanup the module
 + *
 + *    Parse_File                  Function used to parse a makefile. It must
 + *                                be given the name of the file, which should
 + *                                already have been opened, and a function
 + *                                to call to read a character from the file.
 + *
 + *    Parse_IsVar                 Returns TRUE if the given line is a
 + *                                variable assignment. Used by MainParseArgs
 + *                                to determine if an argument is a target
 + *                                or a variable assignment. Used internally
 + *                                for pretty much the same thing...
 + *
 + *    Parse_Error                 Function called when an error occurs in
 + *                                parsing. Used by the variable and
 + *                                conditional modules.
 + *    Parse_MainName              Returns a Lst of the main target to create.
 + */
 +
 +#include <sys/types.h>
 +#include <sys/stat.h>
 +#include <assert.h>
 +#include <ctype.h>
 +#include <errno.h>
 +#include <fcntl.h>
 +#include <stdarg.h>
 +#include <stdio.h>
 +
 +#include "make.h"
 +#include "hash.h"
 +#include "dir.h"
 +#include "job.h"
 +#include "buf.h"
 +#include "pathnames.h"
 +
 +#ifdef HAVE_MMAP
 +#include <sys/mman.h>
 +
 +#ifndef MAP_COPY
 +#define MAP_COPY MAP_PRIVATE
 +#endif
 +#ifndef MAP_FILE
 +#define MAP_FILE 0
 +#endif
 +#endif
 +
 +////////////////////////////////////////////////////////////
 +// types and constants
 +
 +/*
 + * Structure for a file being read ("included file")
 + */
 +typedef struct IFile {
 +    char                  *fname;         /* name of file */
 +    int             lineno;         /* current line number in file */
 +    int             first_lineno;   /* line number of start of text */
 +    int             cond_depth;     /* 'if' nesting when file opened */
 +    char            *P_str;         /* point to base of string buffer */
 +    char            *P_ptr;         /* point to next char of string buffer */
 +    char            *P_end;         /* point to the end of string buffer */
 +    char            *(*nextbuf)(void *, size_t *); /* Function to get more data */
 +    void            *nextbuf_arg;   /* Opaque arg for nextbuf() */
 +    struct loadedfile *lf;          /* loadedfile object, if any */
 +} IFile;
 +
 +
 +/*
 + * These values are returned by ParseEOF to tell Parse_File whether to
 + * CONTINUE parsing, i.e. it had only reached the end of an include file,
 + * or if it's DONE.
 + */
 +#define CONTINUE      1
 +#define DONE          0
 +
 +/*
 + * Tokens for target attributes
 + */
 +typedef enum {
 +    Begin,        /* .BEGIN */
 +    Default,      /* .DEFAULT */
 +    End,          /* .END */
 +    dotError,     /* .ERROR */
 +    Ignore,       /* .IGNORE */
 +    Includes,     /* .INCLUDES */
 +    Interrupt,            /* .INTERRUPT */
 +    Libs,         /* .LIBS */
 +    Meta,         /* .META */
 +    MFlags,       /* .MFLAGS or .MAKEFLAGS */
 +    Main,         /* .MAIN and we don't have anything user-specified to
 +                   * make */
 +    NoExport,     /* .NOEXPORT */
 +    NoMeta,       /* .NOMETA */
 +    NoMetaCmp,            /* .NOMETA_CMP */
 +    NoPath,       /* .NOPATH */
 +    Not,          /* Not special */
 +    NotParallel,    /* .NOTPARALLEL */
 +    Null,         /* .NULL */
 +    ExObjdir,     /* .OBJDIR */
 +    Order,        /* .ORDER */
 +    Parallel,     /* .PARALLEL */
 +    ExPath,       /* .PATH */
 +    Phony,        /* .PHONY */
 +#ifdef POSIX
 +    Posix,        /* .POSIX */
 +#endif
 +    Precious,     /* .PRECIOUS */
 +    ExShell,      /* .SHELL */
 +    Silent,       /* .SILENT */
 +    SingleShell,    /* .SINGLESHELL */
 +    Stale,        /* .STALE */
 +    Suffixes,     /* .SUFFIXES */
 +    Wait,         /* .WAIT */
 +    Attribute     /* Generic attribute */
 +} ParseSpecial;
 +
 +/*
 + * Other tokens
 + */
 +#define LPAREN        '('
 +#define RPAREN        ')'
 +
 +
 +////////////////////////////////////////////////////////////
 +// result data
 +
 +/*
 + * The main target to create. This is the first target on the first
 + * dependency line in the first makefile.
 + */
 +static GNode *mainNode;
 +
 +////////////////////////////////////////////////////////////
 +// eval state
 +
 +/* targets we're working on */
 +static Lst targets;
 +
 +#ifdef CLEANUP
 +/* command lines for targets */
 +static Lst targCmds;
 +#endif
 +
 +/*
 + * specType contains the SPECial TYPE of the current target. It is
 + * Not if the target is unspecial. If it *is* special, however, the children
 + * are linked as children of the parent but not vice versa. This variable is
 + * set in ParseDoDependency
 + */
 +static ParseSpecial specType;
 +
 +/*
 + * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
 + * seen, then set to each successive source on the line.
 + */
 +static GNode  *predecessor;
 +
 +////////////////////////////////////////////////////////////
 +// parser state
 +
 +/* true if currently in a dependency line or its commands */
 +static Boolean inLine;
 +
 +/* number of fatal errors */
 +static int fatals = 0;
 +
 +/*
 + * Variables for doing includes
 + */
 +
 +/* current file being read */
 +static IFile *curFile;
 +
 +/* stack of IFiles generated by .includes */
 +static Lst includes;
 +
 +/* include paths (lists of directories) */
 +Lst parseIncPath;     /* dirs for "..." includes */
 +Lst sysIncPath;               /* dirs for <...> includes */
 +Lst defIncPath;               /* default for sysIncPath */
 +
 +////////////////////////////////////////////////////////////
 +// parser tables
 +
 +/*
 + * The parseKeywords table is searched using binary search when deciding
 + * if a target or source is special. The 'spec' field is the ParseSpecial
 + * type of the keyword ("Not" if the keyword isn't special as a target) while
 + * the 'op' field is the operator to apply to the list of targets if the
 + * keyword is used as a source ("0" if the keyword isn't special as a source)
 + */
 +static const struct {
 +    const char   *name;       /* Name of keyword */
 +    ParseSpecial  spec;               /* Type when used as a target */
 +    int                 op;           /* Operator when used as a source */
 +} parseKeywords[] = {
 +{ ".BEGIN",     Begin,        0 },
 +{ ".DEFAULT",   Default,      0 },
 +{ ".END",       End,          0 },
 +{ ".ERROR",             dotError,     0 },
 +{ ".EXEC",      Attribute,    OP_EXEC },
 +{ ".IGNORE",    Ignore,       OP_IGNORE },
 +{ ".INCLUDES",          Includes,     0 },
 +{ ".INTERRUPT",         Interrupt,    0 },
 +{ ".INVISIBLE",         Attribute,    OP_INVISIBLE },
 +{ ".JOIN",      Attribute,    OP_JOIN },
 +{ ".LIBS",      Libs,         0 },
 +{ ".MADE",      Attribute,    OP_MADE },
 +{ ".MAIN",      Main,         0 },
 +{ ".MAKE",      Attribute,    OP_MAKE },
 +{ ".MAKEFLAGS",         MFlags,       0 },
 +{ ".META",      Meta,         OP_META },
 +{ ".MFLAGS",    MFlags,       0 },
 +{ ".NOMETA",    NoMeta,       OP_NOMETA },
 +{ ".NOMETA_CMP",  NoMetaCmp,  OP_NOMETA_CMP },
 +{ ".NOPATH",    NoPath,       OP_NOPATH },
 +{ ".NOTMAIN",   Attribute,    OP_NOTMAIN },
 +{ ".NOTPARALLEL", NotParallel,        0 },
 +{ ".NO_PARALLEL", NotParallel,        0 },
 +{ ".NULL",      Null,         0 },
 +{ ".OBJDIR",    ExObjdir,     0 },
 +{ ".OPTIONAL",          Attribute,    OP_OPTIONAL },
 +{ ".ORDER",     Order,        0 },
 +{ ".PARALLEL",          Parallel,     0 },
 +{ ".PATH",      ExPath,       0 },
 +{ ".PHONY",     Phony,        OP_PHONY },
 +#ifdef POSIX
 +{ ".POSIX",     Posix,        0 },
 +#endif
 +{ ".PRECIOUS",          Precious,     OP_PRECIOUS },
 +{ ".RECURSIVE",         Attribute,    OP_MAKE },
 +{ ".SHELL",     ExShell,      0 },
 +{ ".SILENT",    Silent,       OP_SILENT },
 +{ ".SINGLESHELL", SingleShell,        0 },
 +{ ".STALE",     Stale,        0 },
 +{ ".SUFFIXES",          Suffixes,     0 },
 +{ ".USE",       Attribute,    OP_USE },
 +{ ".USEBEFORE",   Attribute,          OP_USEBEFORE },
 +{ ".WAIT",      Wait,         0 },
 +};
 +
 +////////////////////////////////////////////////////////////
 +// local functions
 +
 +static int ParseIsEscaped(const char *, const char *);
 +static void ParseErrorInternal(const char *, size_t, int, const char *, ...)
 +    MAKE_ATTR_PRINTFLIKE(4,5);
 +static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list)
 +    MAKE_ATTR_PRINTFLIKE(5, 0);
 +static int ParseFindKeyword(const char *);
 +static int ParseLinkSrc(void *, void *);
 +static int ParseDoOp(void *, void *);
 +static void ParseDoSrc(int, const char *);
 +static int ParseFindMain(void *, void *);
 +static int ParseAddDir(void *, void *);
 +static int ParseClearPath(void *, void *);
 +static void ParseDoDependency(char *);
 +static int ParseAddCmd(void *, void *);
 +static void ParseHasCommands(void *);
 +static void ParseDoInclude(char *);
 +static void ParseSetParseFile(const char *);
 +static void ParseSetIncludedFile(void);
 +#ifdef SYSVINCLUDE
 +static void ParseTraditionalInclude(char *);
 +#endif
 +#ifdef GMAKEEXPORT
 +static void ParseGmakeExport(char *);
 +#endif
 +static int ParseEOF(void);
 +static char *ParseReadLine(void);
 +static void ParseFinishLine(void);
 +static void ParseMark(GNode *);
 +
 +////////////////////////////////////////////////////////////
 +// file loader
 +
 +struct loadedfile {
 +      const char *path;               /* name, for error reports */
 +      char *buf;                      /* contents buffer */
 +      size_t len;                     /* length of contents */
 +      size_t maplen;                  /* length of mmap area, or 0 */
 +      Boolean used;                   /* XXX: have we used the data yet */
 +};
 +
 +/*
 + * Constructor/destructor for loadedfile
 + */
 +static struct loadedfile *
 +loadedfile_create(const char *path)
 +{
 +      struct loadedfile *lf;
 +
 +      lf = bmake_malloc(sizeof(*lf));
 +      lf->path = (path == NULL ? "(stdin)" : path);
 +      lf->buf = NULL;
 +      lf->len = 0;
 +      lf->maplen = 0;
 +      lf->used = FALSE;
 +      return lf;
 +}
 +
 +static void
 +loadedfile_destroy(struct loadedfile *lf)
 +{
 +      if (lf->buf != NULL) {
 +              if (lf->maplen > 0) {
 +#ifdef HAVE_MMAP
 +                      munmap(lf->buf, lf->maplen);
 +#endif
 +              } else {
 +                      free(lf->buf);
 +              }
 +      }
 +      free(lf);
 +}
 +
 +/*
 + * nextbuf() operation for loadedfile, as needed by the weird and twisted
 + * logic below. Once that's cleaned up, we can get rid of lf->used...
 + */
 +static char *
 +loadedfile_nextbuf(void *x, size_t *len)
 +{
 +      struct loadedfile *lf = x;
 +
 +      if (lf->used) {
 +              return NULL;
 +      }
 +      lf->used = TRUE;
 +      *len = lf->len;
 +      return lf->buf;
 +}
 +
 +/*
 + * Try to get the size of a file.
 + */
 +static ReturnStatus
 +load_getsize(int fd, size_t *ret)
 +{
 +      struct stat st;
 +
 +      if (fstat(fd, &st) < 0) {
 +              return FAILURE;
 +      }
 +
 +      if (!S_ISREG(st.st_mode)) {
 +              return FAILURE;
 +      }
 +
 +      /*
 +       * st_size is an off_t, which is 64 bits signed; *ret is
 +       * size_t, which might be 32 bits unsigned or 64 bits
 +       * unsigned. Rather than being elaborate, just punt on
 +       * files that are more than 2^31 bytes. We should never
 +       * see a makefile that size in practice...
 +       *
 +       * While we're at it reject negative sizes too, just in case.
 +       */
 +      if (st.st_size < 0 || st.st_size > 0x7fffffff) {
 +              return FAILURE;
 +      }
 +
 +      *ret = (size_t) st.st_size;
 +      return SUCCESS;
 +}
 +
 +/*
 + * Read in a file.
 + *
 + * Until the path search logic can be moved under here instead of
 + * being in the caller in another source file, we need to have the fd
 + * passed in already open. Bleh.
 + *
 + * If the path is NULL use stdin and (to insure against fd leaks)
 + * assert that the caller passed in -1.
 + */
 +static struct loadedfile *
 +loadfile(const char *path, int fd)
 +{
 +      struct loadedfile *lf;
 +#ifdef HAVE_MMAP
 +      long pagesize;
 +#endif
 +      ssize_t result;
 +      size_t bufpos;
 +
 +      lf = loadedfile_create(path);
 +
 +      if (path == NULL) {
 +              assert(fd == -1);
 +              fd = STDIN_FILENO;
 +      } else {
 +#if 0 /* notyet */
 +              fd = open(path, O_RDONLY);
 +              if (fd < 0) {
 +                      ...
 +                      Error("%s: %s", path, strerror(errno));
 +                      exit(1);
 +              }
 +#endif
 +      }
 +
 +#ifdef HAVE_MMAP
 +      if (load_getsize(fd, &lf->len) == SUCCESS) {
 +              /* found a size, try mmap */
 +              pagesize = sysconf(_SC_PAGESIZE);
 +              if (pagesize <= 0) {
 +                      pagesize = 0x1000;
 +              }
 +              /* round size up to a page */
 +              lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize);
 +
 +              /*
 +               * XXX hack for dealing with empty files; remove when
 +               * we're no longer limited by interfacing to the old
 +               * logic elsewhere in this file.
 +               */
 +              if (lf->maplen == 0) {
 +                      lf->maplen = pagesize;
 +              }
 +
 +              /*
 +               * FUTURE: remove PROT_WRITE when the parser no longer
 +               * needs to scribble on the input.
 +               */
 +              lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
 +                             MAP_FILE|MAP_COPY, fd, 0);
 +              if (lf->buf != MAP_FAILED) {
 +                      /* succeeded */
 +                      if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
 +                              char *b = malloc(lf->len + 1);
 +                              b[lf->len] = '\n';
 +                              memcpy(b, lf->buf, lf->len++);
 +                              munmap(lf->buf, lf->maplen);
 +                              lf->maplen = 0;
 +                              lf->buf = b;
 +                      }
 +                      goto done;
 +              }
 +      }
 +#endif
 +      /* cannot mmap; load the traditional way */
 +
 +      lf->maplen = 0;
 +      lf->len = 1024;
 +      lf->buf = bmake_malloc(lf->len);
 +
 +      bufpos = 0;
 +      while (1) {
 +              assert(bufpos <= lf->len);
 +              if (bufpos == lf->len) {
 +                      lf->len *= 2;
 +                      lf->buf = bmake_realloc(lf->buf, lf->len);
 +              }
 +              result = read(fd, lf->buf + bufpos, lf->len - bufpos);
 +              if (result < 0) {
 +                      Error("%s: read error: %s", path, strerror(errno));
 +                      exit(1);
 +              }
 +              if (result == 0) {
 +                      break;
 +              }
 +              bufpos += result;
 +      }
 +      assert(bufpos <= lf->len);
 +      lf->len = bufpos;
 +
 +      /* truncate malloc region to actual length (maybe not useful) */
 +      if (lf->len > 0) {
 +              lf->buf = bmake_realloc(lf->buf, lf->len);
 +      }
 +
 +#ifdef HAVE_MMAP
 +done:
 +#endif
 +      if (path != NULL) {
 +              close(fd);
 +      }
 +      return lf;
 +}
 +
 +////////////////////////////////////////////////////////////
 +// old code
 +
 +/*-
 + *----------------------------------------------------------------------
 + * ParseIsEscaped --
 + *    Check if the current character is escaped on the current line
 + *
 + * Results:
 + *    0 if the character is not backslash escaped, 1 otherwise
 + *
 + * Side Effects:
 + *    None
 + *----------------------------------------------------------------------
 + */
 +static int
 +ParseIsEscaped(const char *line, const char *c)
 +{
 +    int active = 0;
 +    for (;;) {
 +      if (line == c)
 +          return active;
 +      if (*--c != '\\')
 +          return active;
 +      active = !active;
 +    }
 +}
 +
 +/*-
 + *----------------------------------------------------------------------
 + * ParseFindKeyword --
 + *    Look in the table of keywords for one matching the given string.
 + *
 + * Input:
 + *    str             String to find
 + *
 + * Results:
 + *    The index of the keyword, or -1 if it isn't there.
 + *
 + * Side Effects:
 + *    None
 + *----------------------------------------------------------------------
 + */
 +static int
 +ParseFindKeyword(const char *str)
 +{
 +    int    start, end, cur;
 +    int    diff;
 +
 +    start = 0;
 +    end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1;
 +
 +    do {
 +      cur = start + ((end - start) / 2);
 +      diff = strcmp(str, parseKeywords[cur].name);
 +
 +      if (diff == 0) {
 +          return (cur);
 +      } else if (diff < 0) {
 +          end = cur - 1;
 +      } else {
 +          start = cur + 1;
 +      }
 +    } while (start <= end);
 +    return (-1);
 +}
 +
 +/*-
 + * ParseVErrorInternal  --
 + *    Error message abort function for parsing. Prints out the context
 + *    of the error (line number and file) as well as the message with
 + *    two optional arguments.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    "fatals" is incremented if the level is PARSE_FATAL.
 + */
 +/* VARARGS */
 +static void
 +ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type,
 +    const char *fmt, va_list ap)
 +{
 +      static Boolean fatal_warning_error_printed = FALSE;
 +
 +      (void)fprintf(f, "%s: ", progname);
 +
 +      if (cfname != NULL) {
 +              (void)fprintf(f, "\"");
 +              if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) {
 +                      char *cp;
 +                      const char *dir;
 +
 +                      /*
 +                       * Nothing is more annoying than not knowing
 +                       * which Makefile is the culprit.
 +                       */
 +                      dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp);
 +                      if (dir == NULL || *dir == '\0' ||
 +                          (*dir == '.' && dir[1] == '\0'))
 +                              dir = Var_Value(".CURDIR", VAR_GLOBAL, &cp);
 +                      if (dir == NULL)
 +                              dir = ".";
 +
 +                      (void)fprintf(f, "%s/%s", dir, cfname);
 +              } else
 +                      (void)fprintf(f, "%s", cfname);
 +
 +              (void)fprintf(f, "\" line %d: ", (int)clineno);
 +      }
 +      if (type == PARSE_WARNING)
 +              (void)fprintf(f, "warning: ");
 +      (void)vfprintf(f, fmt, ap);
 +      (void)fprintf(f, "\n");
 +      (void)fflush(f);
 +      if (type == PARSE_FATAL || parseWarnFatal)
 +              fatals += 1;
 +      if (parseWarnFatal && !fatal_warning_error_printed) {
 +              Error("parsing warnings being treated as errors");
 +              fatal_warning_error_printed = TRUE;
 +      }
 +}
 +
 +/*-
 + * ParseErrorInternal  --
 + *    Error function
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    None
 + */
 +/* VARARGS */
 +static void
 +ParseErrorInternal(const char *cfname, size_t clineno, int type,
 +    const char *fmt, ...)
 +{
 +      va_list ap;
 +
 +      va_start(ap, fmt);
 +      (void)fflush(stdout);
 +      ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap);
 +      va_end(ap);
 +
 +      if (debug_file != stderr && debug_file != stdout) {
 +              va_start(ap, fmt);
 +              ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap);
 +              va_end(ap);
 +      }
 +}
 +
 +/*-
 + * Parse_Error  --
 + *    External interface to ParseErrorInternal; uses the default filename
 + *    Line number.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    None
 + */
 +/* VARARGS */
 +void
 +Parse_Error(int type, const char *fmt, ...)
 +{
 +      va_list ap;
 +      const char *fname;
 +      size_t lineno;
 +
 +      if (curFile == NULL) {
 +              fname = NULL;
 +              lineno = 0;
 +      } else {
 +              fname = curFile->fname;
 +              lineno = curFile->lineno;
 +      }
 +
 +      va_start(ap, fmt);
 +      (void)fflush(stdout);
 +      ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
 +      va_end(ap);
 +
 +      if (debug_file != stderr && debug_file != stdout) {
 +              va_start(ap, fmt);
 +              ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap);
 +              va_end(ap);
 +      }
 +}
 +
 +
 +/*
 + * ParseMessage
 + *    Parse a .info .warning or .error directive
 + *
 + *    The input is the line minus the ".".  We substitute
 + *    variables, print the message and exit(1) (for .error) or just print
 + *    a warning if the directive is malformed.
 + */
 +static Boolean
 +ParseMessage(char *line)
 +{
 +    int mtype;
 +
 +    switch(*line) {
 +    case 'i':
 +      mtype = 0;
 +      break;
 +    case 'w':
 +      mtype = PARSE_WARNING;
 +      break;
 +    case 'e':
 +      mtype = PARSE_FATAL;
 +      break;
 +    default:
 +      Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line);
 +      return FALSE;
 +    }
 +
 +    while (isalpha((u_char)*line))
 +      line++;
 +    if (!isspace((u_char)*line))
 +      return FALSE;                   /* not for us */
 +    while (isspace((u_char)*line))
 +      line++;
 +
-     line = Var_Subst(NULL, line, VAR_CMD, 0);
++    line = Var_Subst(NULL, line, VAR_CMD, FALSE, TRUE);
 +    Parse_Error(mtype, "%s", line);
 +    free(line);
 +
 +    if (mtype == PARSE_FATAL) {
 +      /* Terminate immediately. */
 +      exit(1);
 +    }
 +    return TRUE;
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseLinkSrc  --
 + *    Link the parent node to its new child. Used in a Lst_ForEach by
 + *    ParseDoDependency. If the specType isn't 'Not', the parent
 + *    isn't linked as a parent of the child.
 + *
 + * Input:
 + *    pgnp            The parent node
 + *    cgpn            The child node
 + *
 + * Results:
 + *    Always = 0
 + *
 + * Side Effects:
 + *    New elements are added to the parents list of cgn and the
 + *    children list of cgn. the unmade field of pgn is updated
 + *    to reflect the additional child.
 + *---------------------------------------------------------------------
 + */
 +static int
 +ParseLinkSrc(void *pgnp, void *cgnp)
 +{
 +    GNode          *pgn = (GNode *)pgnp;
 +    GNode          *cgn = (GNode *)cgnp;
 +
 +    if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts))
 +      pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts));
 +    (void)Lst_AtEnd(pgn->children, cgn);
 +    if (specType == Not)
 +          (void)Lst_AtEnd(cgn->parents, pgn);
 +    pgn->unmade += 1;
 +    if (DEBUG(PARSE)) {
 +      fprintf(debug_file, "# %s: added child %s - %s\n", __func__,
 +          pgn->name, cgn->name);
 +      Targ_PrintNode(pgn, 0);
 +      Targ_PrintNode(cgn, 0);
 +    }
 +    return (0);
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseDoOp  --
 + *    Apply the parsed operator to the given target node. Used in a
 + *    Lst_ForEach call by ParseDoDependency once all targets have
 + *    been found and their operator parsed. If the previous and new
 + *    operators are incompatible, a major error is taken.
 + *
 + * Input:
 + *    gnp             The node to which the operator is to be applied
 + *    opp             The operator to apply
 + *
 + * Results:
 + *    Always 0
 + *
 + * Side Effects:
 + *    The type field of the node is altered to reflect any new bits in
 + *    the op.
 + *---------------------------------------------------------------------
 + */
 +static int
 +ParseDoOp(void *gnp, void *opp)
 +{
 +    GNode          *gn = (GNode *)gnp;
 +    int             op = *(int *)opp;
 +    /*
 +     * If the dependency mask of the operator and the node don't match and
 +     * the node has actually had an operator applied to it before, and
 +     * the operator actually has some dependency information in it, complain.
 +     */
 +    if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) &&
 +      !OP_NOP(gn->type) && !OP_NOP(op))
 +    {
 +      Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name);
 +      return (1);
 +    }
 +
 +    if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) {
 +      /*
 +       * If the node was the object of a :: operator, we need to create a
 +       * new instance of it for the children and commands on this dependency
 +       * line. The new instance is placed on the 'cohorts' list of the
 +       * initial one (note the initial one is not on its own cohorts list)
 +       * and the new instance is linked to all parents of the initial
 +       * instance.
 +       */
 +      GNode   *cohort;
 +
 +      /*
 +       * Propagate copied bits to the initial node.  They'll be propagated
 +       * back to the rest of the cohorts later.
 +       */
 +      gn->type |= op & ~OP_OPMASK;
 +
 +      cohort = Targ_FindNode(gn->name, TARG_NOHASH);
 +      if (doing_depend)
 +          ParseMark(cohort);
 +      /*
 +       * Make the cohort invisible as well to avoid duplicating it into
 +       * other variables. True, parents of this target won't tend to do
 +       * anything with their local variables, but better safe than
 +       * sorry. (I think this is pointless now, since the relevant list
 +       * traversals will no longer see this node anyway. -mycroft)
 +       */
 +      cohort->type = op | OP_INVISIBLE;
 +      (void)Lst_AtEnd(gn->cohorts, cohort);
 +      cohort->centurion = gn;
 +      gn->unmade_cohorts += 1;
 +      snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
 +              gn->unmade_cohorts);
 +    } else {
 +      /*
 +       * We don't want to nuke any previous flags (whatever they were) so we
 +       * just OR the new operator into the old
 +       */
 +      gn->type |= op;
 +    }
 +
 +    return (0);
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseDoSrc  --
 + *    Given the name of a source, figure out if it is an attribute
 + *    and apply it to the targets if it is. Else decide if there is
 + *    some attribute which should be applied *to* the source because
 + *    of some special target and apply it if so. Otherwise, make the
 + *    source be a child of the targets in the list 'targets'
 + *
 + * Input:
 + *    tOp             operator (if any) from special targets
 + *    src             name of the source to handle
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Operator bits may be added to the list of targets or to the source.
 + *    The targets may have a new source added to their lists of children.
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseDoSrc(int tOp, const char *src)
 +{
 +    GNode     *gn = NULL;
 +    static int wait_number = 0;
 +    char wait_src[16];
 +
 +    if (*src == '.' && isupper ((unsigned char)src[1])) {
 +      int keywd = ParseFindKeyword(src);
 +      if (keywd != -1) {
 +          int op = parseKeywords[keywd].op;
 +          if (op != 0) {
 +              Lst_ForEach(targets, ParseDoOp, &op);
 +              return;
 +          }
 +          if (parseKeywords[keywd].spec == Wait) {
 +              /*
 +               * We add a .WAIT node in the dependency list.
 +               * After any dynamic dependencies (and filename globbing)
 +               * have happened, it is given a dependency on the each
 +               * previous child back to and previous .WAIT node.
 +               * The next child won't be scheduled until the .WAIT node
 +               * is built.
 +               * We give each .WAIT node a unique name (mainly for diag).
 +               */
 +              snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
 +              gn = Targ_FindNode(wait_src, TARG_NOHASH);
 +              if (doing_depend)
 +                  ParseMark(gn);
 +              gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
 +              Lst_ForEach(targets, ParseLinkSrc, gn);
 +              return;
 +          }
 +      }
 +    }
 +
 +    switch (specType) {
 +    case Main:
 +      /*
 +       * If we have noted the existence of a .MAIN, it means we need
 +       * to add the sources of said target to the list of things
 +       * to create. The string 'src' is likely to be free, so we
 +       * must make a new copy of it. Note that this will only be
 +       * invoked if the user didn't specify a target on the command
 +       * line. This is to allow #ifmake's to succeed, or something...
 +       */
 +      (void)Lst_AtEnd(create, bmake_strdup(src));
 +      /*
 +       * Add the name to the .TARGETS variable as well, so the user can
 +       * employ that, if desired.
 +       */
 +      Var_Append(".TARGETS", src, VAR_GLOBAL);
 +      return;
 +
 +    case Order:
 +      /*
 +       * Create proper predecessor/successor links between the previous
 +       * source and the current one.
 +       */
 +      gn = Targ_FindNode(src, TARG_CREATE);
 +      if (doing_depend)
 +          ParseMark(gn);
 +      if (predecessor != NULL) {
 +          (void)Lst_AtEnd(predecessor->order_succ, gn);
 +          (void)Lst_AtEnd(gn->order_pred, predecessor);
 +          if (DEBUG(PARSE)) {
 +              fprintf(debug_file, "# %s: added Order dependency %s - %s\n",
 +                  __func__, predecessor->name, gn->name);
 +              Targ_PrintNode(predecessor, 0);
 +              Targ_PrintNode(gn, 0);
 +          }
 +      }
 +      /*
 +       * The current source now becomes the predecessor for the next one.
 +       */
 +      predecessor = gn;
 +      break;
 +
 +    default:
 +      /*
 +       * If the source is not an attribute, we need to find/create
 +       * a node for it. After that we can apply any operator to it
 +       * from a special target or link it to its parents, as
 +       * appropriate.
 +       *
 +       * In the case of a source that was the object of a :: operator,
 +       * the attribute is applied to all of its instances (as kept in
 +       * the 'cohorts' list of the node) or all the cohorts are linked
 +       * to all the targets.
 +       */
 +
 +      /* Find/create the 'src' node and attach to all targets */
 +      gn = Targ_FindNode(src, TARG_CREATE);
 +      if (doing_depend)
 +          ParseMark(gn);
 +      if (tOp) {
 +          gn->type |= tOp;
 +      } else {
 +          Lst_ForEach(targets, ParseLinkSrc, gn);
 +      }
 +      break;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseFindMain --
 + *    Find a real target in the list and set it to be the main one.
 + *    Called by ParseDoDependency when a main target hasn't been found
 + *    yet.
 + *
 + * Input:
 + *    gnp             Node to examine
 + *
 + * Results:
 + *    0 if main not found yet, 1 if it is.
 + *
 + * Side Effects:
 + *    mainNode is changed and Targ_SetMain is called.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +ParseFindMain(void *gnp, void *dummy)
 +{
 +    GNode       *gn = (GNode *)gnp;
 +    if ((gn->type & OP_NOTARGET) == 0) {
 +      mainNode = gn;
 +      Targ_SetMain(gn);
 +      return (dummy ? 1 : 1);
 +    } else {
 +      return (dummy ? 0 : 0);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseAddDir --
 + *    Front-end for Dir_AddDir to make sure Lst_ForEach keeps going
 + *
 + * Results:
 + *    === 0
 + *
 + * Side Effects:
 + *    See Dir_AddDir.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +ParseAddDir(void *path, void *name)
 +{
 +    (void)Dir_AddDir((Lst) path, (char *)name);
 +    return(0);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseClearPath --
 + *    Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going
 + *
 + * Results:
 + *    === 0
 + *
 + * Side Effects:
 + *    See Dir_ClearPath
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +ParseClearPath(void *path, void *dummy)
 +{
 +    Dir_ClearPath((Lst) path);
 +    return(dummy ? 0 : 0);
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseDoDependency  --
 + *    Parse the dependency line in line.
 + *
 + * Input:
 + *    line            the line to parse
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The nodes of the sources are linked as children to the nodes of the
 + *    targets. Some nodes may be created.
 + *
 + *    We parse a dependency line by first extracting words from the line and
 + * finding nodes in the list of all targets with that name. This is done
 + * until a character is encountered which is an operator character. Currently
 + * these are only ! and :. At this point the operator is parsed and the
 + * pointer into the line advanced until the first source is encountered.
 + *    The parsed operator is applied to each node in the 'targets' list,
 + * which is where the nodes found for the targets are kept, by means of
 + * the ParseDoOp function.
 + *    The sources are read in much the same way as the targets were except
 + * that now they are expanded using the wildcarding scheme of the C-Shell
 + * and all instances of the resulting words in the list of all targets
 + * are found. Each of the resulting nodes is then linked to each of the
 + * targets as one of its children.
 + *    Certain targets are handled specially. These are the ones detailed
 + * by the specType variable.
 + *    The storing of transformation rules is also taken care of here.
 + * A target is recognized as a transformation rule by calling
 + * Suff_IsTransform. If it is a transformation rule, its node is gotten
 + * from the suffix module via Suff_AddTransform rather than the standard
 + * Targ_FindNode in the target module.
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseDoDependency(char *line)
 +{
 +    char         *cp;         /* our current position */
 +    GNode        *gn = NULL;  /* a general purpose temporary node */
 +    int             op;               /* the operator on the line */
 +    char            savec;    /* a place to save a character */
 +    Lst           paths;      /* List of search paths to alter when parsing
 +                               * a list of .PATH targets */
 +    int                   tOp;        /* operator from special target */
 +    Lst                   sources;    /* list of archive source names after
 +                               * expansion */
 +    Lst           curTargs;   /* list of target names to be found and added
 +                               * to the targets list */
 +    char         *lstart = line;
 +
 +    if (DEBUG(PARSE))
 +      fprintf(debug_file, "ParseDoDependency(%s)\n", line);
 +    tOp = 0;
 +
 +    specType = Not;
 +    paths = NULL;
 +
 +    curTargs = Lst_Init(FALSE);
 +
 +    /*
 +     * First, grind through the targets.
 +     */
 +
 +    do {
 +      /*
 +       * Here LINE points to the beginning of the next word, and
 +       * LSTART points to the actual beginning of the line.
 +       */
 +
 +      /* Find the end of the next word. */
 +      for (cp = line; *cp && (ParseIsEscaped(lstart, cp) ||
 +                   !(isspace((unsigned char)*cp) ||
 +                       *cp == '!' || *cp == ':' || *cp == LPAREN));
 +               cp++) {
 +          if (*cp == '$') {
 +              /*
 +               * Must be a dynamic source (would have been expanded
 +               * otherwise), so call the Var module to parse the puppy
 +               * so we can safely advance beyond it...There should be
 +               * no errors in this, as they would have been discovered
 +               * in the initial Var_Subst and we wouldn't be here.
 +               */
 +              int     length;
 +              void    *freeIt;
 +
-               (void)Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt);
++              (void)Var_Parse(cp, VAR_CMD, TRUE, TRUE, &length, &freeIt);
 +              if (freeIt)
 +                  free(freeIt);
 +              cp += length-1;
 +          }
 +      }
 +
 +      /*
 +       * If the word is followed by a left parenthesis, it's the
 +       * name of an object file inside an archive (ar file).
 +       */
 +      if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) {
 +          /*
 +           * Archives must be handled specially to make sure the OP_ARCHV
 +           * flag is set in their 'type' field, for one thing, and because
 +           * things like "archive(file1.o file2.o file3.o)" are permissible.
 +           * Arch_ParseArchive will set 'line' to be the first non-blank
 +           * after the archive-spec. It creates/finds nodes for the members
 +           * and places them on the given list, returning SUCCESS if all
 +           * went well and FAILURE if there was an error in the
 +           * specification. On error, line should remain untouched.
 +           */
 +          if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) {
 +              Parse_Error(PARSE_FATAL,
 +                           "Error in archive specification: \"%s\"", line);
 +              goto out;
 +          } else {
 +              /* Done with this word; on to the next. */
 +              continue;
 +          }
 +      }
 +
 +      if (!*cp) {
 +          /*
 +           * We got to the end of the line while we were still
 +           * looking at targets.
 +           *
 +           * Ending a dependency line without an operator is a Bozo
 +           * no-no.  As a heuristic, this is also often triggered by
 +           * undetected conflicts from cvs/rcs merges.
 +           */
 +          if ((strncmp(line, "<<<<<<", 6) == 0) ||
 +              (strncmp(line, "======", 6) == 0) ||
 +              (strncmp(line, ">>>>>>", 6) == 0))
 +              Parse_Error(PARSE_FATAL,
 +                  "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts");
 +          else
 +              Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
 +                                   : "Need an operator");
 +          goto out;
 +      }
 +
 +      /* Insert a null terminator. */
 +      savec = *cp;
 +      *cp = '\0';
 +
 +      /*
 +       * Got the word. See if it's a special target and if so set
 +       * specType to match it.
 +       */
 +      if (*line == '.' && isupper ((unsigned char)line[1])) {
 +          /*
 +           * See if the target is a special target that must have it
 +           * or its sources handled specially.
 +           */
 +          int keywd = ParseFindKeyword(line);
 +          if (keywd != -1) {
 +              if (specType == ExPath && parseKeywords[keywd].spec != ExPath) {
 +                  Parse_Error(PARSE_FATAL, "Mismatched special targets");
 +                  goto out;
 +              }
 +
 +              specType = parseKeywords[keywd].spec;
 +              tOp = parseKeywords[keywd].op;
 +
 +              /*
 +               * Certain special targets have special semantics:
 +               *      .PATH           Have to set the dirSearchPath
 +               *                      variable too
 +               *      .MAIN           Its sources are only used if
 +               *                      nothing has been specified to
 +               *                      create.
 +               *      .DEFAULT        Need to create a node to hang
 +               *                      commands on, but we don't want
 +               *                      it in the graph, nor do we want
 +               *                      it to be the Main Target, so we
 +               *                      create it, set OP_NOTMAIN and
 +               *                      add it to the list, setting
 +               *                      DEFAULT to the new node for
 +               *                      later use. We claim the node is
 +               *                      A transformation rule to make
 +               *                      life easier later, when we'll
 +               *                      use Make_HandleUse to actually
 +               *                      apply the .DEFAULT commands.
 +               *      .PHONY          The list of targets
 +               *      .NOPATH         Don't search for file in the path
 +               *      .STALE
 +               *      .BEGIN
 +               *      .END
 +               *      .ERROR
 +               *      .INTERRUPT      Are not to be considered the
 +               *                      main target.
 +               *      .NOTPARALLEL    Make only one target at a time.
 +               *      .SINGLESHELL    Create a shell for each command.
 +               *      .ORDER          Must set initial predecessor to NULL
 +               */
 +              switch (specType) {
 +              case ExPath:
 +                  if (paths == NULL) {
 +                      paths = Lst_Init(FALSE);
 +                  }
 +                  (void)Lst_AtEnd(paths, dirSearchPath);
 +                  break;
 +              case Main:
 +                  if (!Lst_IsEmpty(create)) {
 +                      specType = Not;
 +                  }
 +                  break;
 +              case Begin:
 +              case End:
 +              case Stale:
 +              case dotError:
 +              case Interrupt:
 +                  gn = Targ_FindNode(line, TARG_CREATE);
 +                  if (doing_depend)
 +                      ParseMark(gn);
 +                  gn->type |= OP_NOTMAIN|OP_SPECIAL;
 +                  (void)Lst_AtEnd(targets, gn);
 +                  break;
 +              case Default:
 +                  gn = Targ_NewGN(".DEFAULT");
 +                  gn->type |= (OP_NOTMAIN|OP_TRANSFORM);
 +                  (void)Lst_AtEnd(targets, gn);
 +                  DEFAULT = gn;
 +                  break;
 +              case NotParallel:
 +                  maxJobs = 1;
 +                  break;
 +              case SingleShell:
 +                  compatMake = TRUE;
 +                  break;
 +              case Order:
 +                  predecessor = NULL;
 +                  break;
 +              default:
 +                  break;
 +              }
 +          } else if (strncmp(line, ".PATH", 5) == 0) {
 +              /*
 +               * .PATH<suffix> has to be handled specially.
 +               * Call on the suffix module to give us a path to
 +               * modify.
 +               */
 +              Lst     path;
 +
 +              specType = ExPath;
 +              path = Suff_GetPath(&line[5]);
 +              if (path == NULL) {
 +                  Parse_Error(PARSE_FATAL,
 +                               "Suffix '%s' not defined (yet)",
 +                               &line[5]);
 +                  goto out;
 +              } else {
 +                  if (paths == NULL) {
 +                      paths = Lst_Init(FALSE);
 +                  }
 +                  (void)Lst_AtEnd(paths, path);
 +              }
 +          }
 +      }
 +
 +      /*
 +       * Have word in line. Get or create its node and stick it at
 +       * the end of the targets list
 +       */
 +      if ((specType == Not) && (*line != '\0')) {
 +          if (Dir_HasWildcards(line)) {
 +              /*
 +               * Targets are to be sought only in the current directory,
 +               * so create an empty path for the thing. Note we need to
 +               * use Dir_Destroy in the destruction of the path as the
 +               * Dir module could have added a directory to the path...
 +               */
 +              Lst         emptyPath = Lst_Init(FALSE);
 +
 +              Dir_Expand(line, emptyPath, curTargs);
 +
 +              Lst_Destroy(emptyPath, Dir_Destroy);
 +          } else {
 +              /*
 +               * No wildcards, but we want to avoid code duplication,
 +               * so create a list with the word on it.
 +               */
 +              (void)Lst_AtEnd(curTargs, line);
 +          }
 +
 +          /* Apply the targets. */
 +
 +          while(!Lst_IsEmpty(curTargs)) {
 +              char    *targName = (char *)Lst_DeQueue(curTargs);
 +
 +              if (!Suff_IsTransform (targName)) {
 +                  gn = Targ_FindNode(targName, TARG_CREATE);
 +              } else {
 +                  gn = Suff_AddTransform(targName);
 +              }
 +              if (doing_depend)
 +                  ParseMark(gn);
 +
 +              (void)Lst_AtEnd(targets, gn);
 +          }
 +      } else if (specType == ExPath && *line != '.' && *line != '\0') {
 +          Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
 +      }
 +
 +      /* Don't need the inserted null terminator any more. */
 +      *cp = savec;
 +
 +      /*
 +       * If it is a special type and not .PATH, it's the only target we
 +       * allow on this line...
 +       */
 +      if (specType != Not && specType != ExPath) {
 +          Boolean warning = FALSE;
 +
 +          while (*cp && (ParseIsEscaped(lstart, cp) ||
 +              ((*cp != '!') && (*cp != ':')))) {
 +              if (ParseIsEscaped(lstart, cp) ||
 +                  (*cp != ' ' && *cp != '\t')) {
 +                  warning = TRUE;
 +              }
 +              cp++;
 +          }
 +          if (warning) {
 +              Parse_Error(PARSE_WARNING, "Extra target ignored");
 +          }
 +      } else {
 +          while (*cp && isspace ((unsigned char)*cp)) {
 +              cp++;
 +          }
 +      }
 +      line = cp;
 +    } while (*line && (ParseIsEscaped(lstart, line) ||
 +      ((*line != '!') && (*line != ':'))));
 +
 +    /*
 +     * Don't need the list of target names anymore...
 +     */
 +    Lst_Destroy(curTargs, NULL);
 +    curTargs = NULL;
 +
 +    if (!Lst_IsEmpty(targets)) {
 +      switch(specType) {
 +          default:
 +              Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored");
 +              break;
 +          case Default:
 +          case Stale:
 +          case Begin:
 +          case End:
 +          case dotError:
 +          case Interrupt:
 +              /*
 +               * These four create nodes on which to hang commands, so
 +               * targets shouldn't be empty...
 +               */
 +          case Not:
 +              /*
 +               * Nothing special here -- targets can be empty if it wants.
 +               */
 +              break;
 +      }
 +    }
 +
 +    /*
 +     * Have now parsed all the target names. Must parse the operator next. The
 +     * result is left in  op .
 +     */
 +    if (*cp == '!') {
 +      op = OP_FORCE;
 +    } else if (*cp == ':') {
 +      if (cp[1] == ':') {
 +          op = OP_DOUBLEDEP;
 +          cp++;
 +      } else {
 +          op = OP_DEPENDS;
 +      }
 +    } else {
 +      Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
 +                  : "Missing dependency operator");
 +      goto out;
 +    }
 +
 +    /* Advance beyond the operator */
 +    cp++;
 +
 +    /*
 +     * Apply the operator to the target. This is how we remember which
 +     * operator a target was defined with. It fails if the operator
 +     * used isn't consistent across all references.
 +     */
 +    Lst_ForEach(targets, ParseDoOp, &op);
 +
 +    /*
 +     * Onward to the sources.
 +     *
 +     * LINE will now point to the first source word, if any, or the
 +     * end of the string if not.
 +     */
 +    while (*cp && isspace ((unsigned char)*cp)) {
 +      cp++;
 +    }
 +    line = cp;
 +
 +    /*
 +     * Several special targets take different actions if present with no
 +     * sources:
 +     *        a .SUFFIXES line with no sources clears out all old suffixes
 +     *        a .PRECIOUS line makes all targets precious
 +     *        a .IGNORE line ignores errors for all targets
 +     *        a .SILENT line creates silence when making all targets
 +     *        a .PATH removes all directories from the search path(s).
 +     */
 +    if (!*line) {
 +      switch (specType) {
 +          case Suffixes:
 +              Suff_ClearSuffixes();
 +              break;
 +          case Precious:
 +              allPrecious = TRUE;
 +              break;
 +          case Ignore:
 +              ignoreErrors = TRUE;
 +              break;
 +          case Silent:
 +              beSilent = TRUE;
 +              break;
 +          case ExPath:
 +              Lst_ForEach(paths, ParseClearPath, NULL);
 +              Dir_SetPATH();
 +              break;
 +#ifdef POSIX
 +            case Posix:
 +                Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0);
 +                break;
 +#endif
 +          default:
 +              break;
 +      }
 +    } else if (specType == MFlags) {
 +      /*
 +       * Call on functions in main.c to deal with these arguments and
 +       * set the initial character to a null-character so the loop to
 +       * get sources won't get anything
 +       */
 +      Main_ParseArgLine(line);
 +      *line = '\0';
 +    } else if (specType == ExShell) {
 +      if (Job_ParseShell(line) != SUCCESS) {
 +          Parse_Error(PARSE_FATAL, "improper shell specification");
 +          goto out;
 +      }
 +      *line = '\0';
 +    } else if ((specType == NotParallel) || (specType == SingleShell)) {
 +      *line = '\0';
 +    }
 +
 +    /*
 +     * NOW GO FOR THE SOURCES
 +     */
 +    if ((specType == Suffixes) || (specType == ExPath) ||
 +      (specType == Includes) || (specType == Libs) ||
 +      (specType == Null) || (specType == ExObjdir))
 +    {
 +      while (*line) {
 +          /*
 +           * If the target was one that doesn't take files as its sources
 +           * but takes something like suffixes, we take each
 +           * space-separated word on the line as a something and deal
 +           * with it accordingly.
 +           *
 +           * If the target was .SUFFIXES, we take each source as a
 +           * suffix and add it to the list of suffixes maintained by the
 +           * Suff module.
 +           *
 +           * If the target was a .PATH, we add the source as a directory
 +           * to search on the search path.
 +           *
 +           * If it was .INCLUDES, the source is taken to be the suffix of
 +           * files which will be #included and whose search path should
 +           * be present in the .INCLUDES variable.
 +           *
 +           * If it was .LIBS, the source is taken to be the suffix of
 +           * files which are considered libraries and whose search path
 +           * should be present in the .LIBS variable.
 +           *
 +           * If it was .NULL, the source is the suffix to use when a file
 +           * has no valid suffix.
 +           *
 +           * If it was .OBJDIR, the source is a new definition for .OBJDIR,
 +           * and will cause make to do a new chdir to that path.
 +           */
 +          while (*cp && !isspace ((unsigned char)*cp)) {
 +              cp++;
 +          }
 +          savec = *cp;
 +          *cp = '\0';
 +          switch (specType) {
 +              case Suffixes:
 +                  Suff_AddSuffix(line, &mainNode);
 +                  break;
 +              case ExPath:
 +                  Lst_ForEach(paths, ParseAddDir, line);
 +                  break;
 +              case Includes:
 +                  Suff_AddInclude(line);
 +                  break;
 +              case Libs:
 +                  Suff_AddLib(line);
 +                  break;
 +              case Null:
 +                  Suff_SetNull(line);
 +                  break;
 +              case ExObjdir:
 +                  Main_SetObjdir(line);
 +                  break;
 +              default:
 +                  break;
 +          }
 +          *cp = savec;
 +          if (savec != '\0') {
 +              cp++;
 +          }
 +          while (*cp && isspace ((unsigned char)*cp)) {
 +              cp++;
 +          }
 +          line = cp;
 +      }
 +      if (paths) {
 +          Lst_Destroy(paths, NULL);
 +      }
 +      if (specType == ExPath)
 +          Dir_SetPATH();
 +    } else {
 +      while (*line) {
 +          /*
 +           * The targets take real sources, so we must beware of archive
 +           * specifications (i.e. things with left parentheses in them)
 +           * and handle them accordingly.
 +           */
 +          for (; *cp && !isspace ((unsigned char)*cp); cp++) {
 +              if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) {
 +                  /*
 +                   * Only stop for a left parenthesis if it isn't at the
 +                   * start of a word (that'll be for variable changes
 +                   * later) and isn't preceded by a dollar sign (a dynamic
 +                   * source).
 +                   */
 +                  break;
 +              }
 +          }
 +
 +          if (*cp == LPAREN) {
 +              sources = Lst_Init(FALSE);
 +              if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) {
 +                  Parse_Error(PARSE_FATAL,
 +                               "Error in source archive spec \"%s\"", line);
 +                  goto out;
 +              }
 +
 +              while (!Lst_IsEmpty (sources)) {
 +                  gn = (GNode *)Lst_DeQueue(sources);
 +                  ParseDoSrc(tOp, gn->name);
 +              }
 +              Lst_Destroy(sources, NULL);
 +              cp = line;
 +          } else {
 +              if (*cp) {
 +                  *cp = '\0';
 +                  cp += 1;
 +              }
 +
 +              ParseDoSrc(tOp, line);
 +          }
 +          while (*cp && isspace ((unsigned char)*cp)) {
 +              cp++;
 +          }
 +          line = cp;
 +      }
 +    }
 +
 +    if (mainNode == NULL) {
 +      /*
 +       * If we have yet to decide on a main target to make, in the
 +       * absence of any user input, we want the first target on
 +       * the first dependency line that is actually a real target
 +       * (i.e. isn't a .USE or .EXEC rule) to be made.
 +       */
 +      Lst_ForEach(targets, ParseFindMain, NULL);
 +    }
 +
 +out:
 +    if (curTargs)
 +          Lst_Destroy(curTargs, NULL);
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * Parse_IsVar  --
 + *    Return TRUE if the passed line is a variable assignment. A variable
 + *    assignment consists of a single word followed by optional whitespace
 + *    followed by either a += or an = operator.
 + *    This function is used both by the Parse_File function and main when
 + *    parsing the command-line arguments.
 + *
 + * Input:
 + *    line            the line to check
 + *
 + * Results:
 + *    TRUE if it is. FALSE if it ain't
 + *
 + * Side Effects:
 + *    none
 + *---------------------------------------------------------------------
 + */
 +Boolean
 +Parse_IsVar(char *line)
 +{
 +    Boolean wasSpace = FALSE; /* set TRUE if found a space */
 +    char ch;
 +    int level = 0;
 +#define ISEQOPERATOR(c) \
 +      (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
 +
 +    /*
 +     * Skip to variable name
 +     */
 +    for (;(*line == ' ') || (*line == '\t'); line++)
 +      continue;
 +
 +    /* Scan for one of the assignment operators outside a variable expansion */
 +    while ((ch = *line++) != 0) {
 +      if (ch == '(' || ch == '{') {
 +          level++;
 +          continue;
 +      }
 +      if (ch == ')' || ch == '}') {
 +          level--;
 +          continue;
 +      }
 +      if (level != 0)
 +          continue;
 +      while (ch == ' ' || ch == '\t') {
 +          ch = *line++;
 +          wasSpace = TRUE;
 +      }
 +#ifdef SUNSHCMD
 +      if (ch == ':' && strncmp(line, "sh", 2) == 0) {
 +          line += 2;
 +          continue;
 +      }
 +#endif
 +      if (ch == '=')
 +          return TRUE;
 +      if (*line == '=' && ISEQOPERATOR(ch))
 +          return TRUE;
 +      if (wasSpace)
 +          return FALSE;
 +    }
 +
 +    return FALSE;
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * Parse_DoVar  --
 + *    Take the variable assignment in the passed line and do it in the
 + *    global context.
 + *
 + *    Note: There is a lexical ambiguity with assignment modifier characters
 + *    in variable names. This routine interprets the character before the =
 + *    as a modifier. Therefore, an assignment like
 + *        C++=/usr/bin/CC
 + *    is interpreted as "C+ +=" instead of "C++ =".
 + *
 + * Input:
 + *    line            a line guaranteed to be a variable assignment.
 + *                    This reduces error checks
 + *    ctxt            Context in which to do the assignment
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    the variable structure of the given variable name is altered in the
 + *    global context.
 + *---------------------------------------------------------------------
 + */
 +void
 +Parse_DoVar(char *line, GNode *ctxt)
 +{
 +    char         *cp; /* pointer into line */
 +    enum {
 +      VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL
 +    }             type;       /* Type of assignment */
 +    char            *opc;     /* ptr to operator character to
 +                               * null-terminate the variable name */
 +    Boolean      freeCp = FALSE; /* TRUE if cp needs to be freed,
 +                                  * i.e. if any variable expansion was
 +                                  * performed */
 +    int depth;
 +
 +    /*
 +     * Skip to variable name
 +     */
 +    while ((*line == ' ') || (*line == '\t')) {
 +      line++;
 +    }
 +
 +    /*
 +     * Skip to operator character, nulling out whitespace as we go
 +     * XXX Rather than counting () and {} we should look for $ and
 +     * then expand the variable.
 +     */
 +    for (depth = 0, cp = line + 1; depth != 0 || *cp != '='; cp++) {
 +      if (*cp == '(' || *cp == '{') {
 +          depth++;
 +          continue;
 +      }
 +      if (*cp == ')' || *cp == '}') {
 +          depth--;
 +          continue;
 +      }
 +      if (depth == 0 && isspace ((unsigned char)*cp)) {
 +          *cp = '\0';
 +      }
 +    }
 +    opc = cp-1;               /* operator is the previous character */
 +    *cp++ = '\0';     /* nuke the = */
 +
 +    /*
 +     * Check operator type
 +     */
 +    switch (*opc) {
 +      case '+':
 +          type = VAR_APPEND;
 +          *opc = '\0';
 +          break;
 +
 +      case '?':
 +          /*
 +           * If the variable already has a value, we don't do anything.
 +           */
 +          *opc = '\0';
 +          if (Var_Exists(line, ctxt)) {
 +              return;
 +          } else {
 +              type = VAR_NORMAL;
 +          }
 +          break;
 +
 +      case ':':
 +          type = VAR_SUBST;
 +          *opc = '\0';
 +          break;
 +
 +      case '!':
 +          type = VAR_SHELL;
 +          *opc = '\0';
 +          break;
 +
 +      default:
 +#ifdef SUNSHCMD
 +          while (opc > line && *opc != ':')
 +              opc--;
 +
 +          if (strncmp(opc, ":sh", 3) == 0) {
 +              type = VAR_SHELL;
 +              *opc = '\0';
 +              break;
 +          }
 +#endif
 +          type = VAR_NORMAL;
 +          break;
 +    }
 +
 +    while (isspace ((unsigned char)*cp)) {
 +      cp++;
 +    }
 +
 +    if (type == VAR_APPEND) {
 +      Var_Append(line, cp, ctxt);
 +    } else if (type == VAR_SUBST) {
 +      /*
 +       * Allow variables in the old value to be undefined, but leave their
 +       * invocation alone -- this is done by forcing oldVars to be false.
 +       * XXX: This can cause recursive variables, but that's not hard to do,
 +       * and this allows someone to do something like
 +       *
 +       *  CFLAGS = $(.INCLUDES)
 +       *  CFLAGS := -I.. $(CFLAGS)
 +       *
 +       * And not get an error.
 +       */
 +      Boolean   oldOldVars = oldVars;
 +
 +      oldVars = FALSE;
 +
 +      /*
 +       * make sure that we set the variable the first time to nothing
 +       * so that it gets substituted!
 +       */
 +      if (!Var_Exists(line, ctxt))
 +          Var_Set(line, "", ctxt, 0);
 +
-       cp = Var_Subst(NULL, cp, ctxt, FALSE);
++      cp = Var_Subst(NULL, cp, ctxt, FALSE, TRUE);
 +      oldVars = oldOldVars;
 +      freeCp = TRUE;
 +
 +      Var_Set(line, cp, ctxt, 0);
 +    } else if (type == VAR_SHELL) {
 +      char *res;
 +      const char *error;
 +
 +      if (strchr(cp, '$') != NULL) {
 +          /*
 +           * There's a dollar sign in the command, so perform variable
 +           * expansion on the whole thing. The resulting string will need
 +           * freeing when we're done, so set freeCmd to TRUE.
 +           */
-           cp = Var_Subst(NULL, cp, VAR_CMD, TRUE);
++          cp = Var_Subst(NULL, cp, VAR_CMD, TRUE, TRUE);
 +          freeCp = TRUE;
 +      }
 +
 +      res = Cmd_Exec(cp, &error);
 +      Var_Set(line, res, ctxt, 0);
 +      free(res);
 +
 +      if (error)
 +          Parse_Error(PARSE_WARNING, error, cp);
 +    } else {
 +      /*
 +       * Normal assignment -- just do it.
 +       */
 +      Var_Set(line, cp, ctxt, 0);
 +    }
 +    if (strcmp(line, MAKEOVERRIDES) == 0)
 +      Main_ExportMAKEFLAGS(FALSE);    /* re-export MAKEFLAGS */
 +    else if (strcmp(line, ".CURDIR") == 0) {
 +      /*
 +       * Somone is being (too?) clever...
 +       * Let's pretend they know what they are doing and
 +       * re-initialize the 'cur' Path.
 +       */
 +      Dir_InitCur(cp);
 +      Dir_SetPATH();
 +    } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
 +      Job_SetPrefix();
 +    } else if (strcmp(line, MAKE_EXPORTED) == 0) {
 +      Var_Export(cp, 0);
 +    }
 +    if (freeCp)
 +      free(cp);
 +}
 +
 +
 +/*
 + * ParseMaybeSubMake --
 + *    Scan the command string to see if it a possible submake node
 + * Input:
 + *    cmd             the command to scan
 + * Results:
 + *    TRUE if the command is possibly a submake, FALSE if not.
 + */
 +static Boolean
 +ParseMaybeSubMake(const char *cmd)
 +{
 +    size_t i;
 +    static struct {
 +      const char *name;
 +      size_t len;
 +    } vals[] = {
 +#define MKV(A)        {       A, sizeof(A) - 1        }
 +      MKV("${MAKE}"),
 +      MKV("${.MAKE}"),
 +      MKV("$(MAKE)"),
 +      MKV("$(.MAKE)"),
 +      MKV("make"),
 +    };
 +    for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
 +      char *ptr;
 +      if ((ptr = strstr(cmd, vals[i].name)) == NULL)
 +          continue;
 +      if ((ptr == cmd || !isalnum((unsigned char)ptr[-1]))
 +          && !isalnum((unsigned char)ptr[vals[i].len]))
 +          return TRUE;
 +    }
 +    return FALSE;
 +}
 +
 +/*-
 + * ParseAddCmd  --
 + *    Lst_ForEach function to add a command line to all targets
 + *
 + * Input:
 + *    gnp             the node to which the command is to be added
 + *    cmd             the command to add
 + *
 + * Results:
 + *    Always 0
 + *
 + * Side Effects:
 + *    A new element is added to the commands list of the node,
 + *    and the node can be marked as a submake node if the command is
 + *    determined to be that.
 + */
 +static int
 +ParseAddCmd(void *gnp, void *cmd)
 +{
 +    GNode *gn = (GNode *)gnp;
 +
 +    /* Add to last (ie current) cohort for :: targets */
 +    if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
 +      gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
 +
 +    /* if target already supplied, ignore commands */
 +    if (!(gn->type & OP_HAS_COMMANDS)) {
 +      (void)Lst_AtEnd(gn->commands, cmd);
 +      if (ParseMaybeSubMake(cmd))
 +          gn->type |= OP_SUBMAKE;
 +      ParseMark(gn);
 +    } else {
 +#ifdef notyet
 +      /* XXX: We cannot do this until we fix the tree */
 +      (void)Lst_AtEnd(gn->commands, cmd);
 +      Parse_Error(PARSE_WARNING,
 +                   "overriding commands for target \"%s\"; "
 +                   "previous commands defined at %s: %d ignored",
 +                   gn->name, gn->fname, gn->lineno);
 +#else
 +      Parse_Error(PARSE_WARNING,
 +                   "duplicate script for target \"%s\" ignored",
 +                   gn->name);
 +      ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING,
 +                          "using previous script for \"%s\" defined here",
 +                          gn->name);
 +#endif
 +    }
 +    return(0);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseHasCommands --
 + *    Callback procedure for Parse_File when destroying the list of
 + *    targets on the last dependency line. Marks a target as already
 + *    having commands if it does, to keep from having shell commands
 + *    on multiple dependency lines.
 + *
 + * Input:
 + *    gnp             Node to examine
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    OP_HAS_COMMANDS may be set for the target.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +ParseHasCommands(void *gnp)
 +{
 +    GNode *gn = (GNode *)gnp;
 +    if (!Lst_IsEmpty(gn->commands)) {
 +      gn->type |= OP_HAS_COMMANDS;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Parse_AddIncludeDir --
 + *    Add a directory to the path searched for included makefiles
 + *    bracketed by double-quotes. Used by functions in main.c
 + *
 + * Input:
 + *    dir             The name of the directory to add
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The directory is appended to the list.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Parse_AddIncludeDir(char *dir)
 +{
 +    (void)Dir_AddDir(parseIncPath, dir);
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseDoInclude  --
 + *    Push to another file.
 + *
 + *    The input is the line minus the `.'. A file spec is a string
 + *    enclosed in <> or "". The former is looked for only in sysIncPath.
 + *    The latter in . and the directories specified by -I command line
 + *    options
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    A structure is added to the includes Lst and readProc, lineno,
 + *    fname and curFILE are altered for the new file
 + *---------------------------------------------------------------------
 + */
 +
 +static void
 +Parse_include_file(char *file, Boolean isSystem, int silent)
 +{
 +    struct loadedfile *lf;
 +    char          *fullname;  /* full pathname of file */
 +    char          *newName;
 +    char          *prefEnd, *incdir;
 +    int           fd;
 +    int           i;
 +
 +    /*
 +     * Now we know the file's name and its search path, we attempt to
 +     * find the durn thing. A return of NULL indicates the file don't
 +     * exist.
 +     */
 +    fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
 +
 +    if (fullname == NULL && !isSystem) {
 +      /*
 +       * Include files contained in double-quotes are first searched for
 +       * relative to the including file's location. We don't want to
 +       * cd there, of course, so we just tack on the old file's
 +       * leading path components and call Dir_FindFile to see if
 +       * we can locate the beast.
 +       */
 +
 +      incdir = bmake_strdup(curFile->fname);
 +      prefEnd = strrchr(incdir, '/');
 +      if (prefEnd != NULL) {
 +          *prefEnd = '\0';
 +          /* Now do lexical processing of leading "../" on the filename */
 +          for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
 +              prefEnd = strrchr(incdir + 1, '/');
 +              if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0)
 +                  break;
 +              *prefEnd = '\0';
 +          }
 +          newName = str_concat(incdir, file + i, STR_ADDSLASH);
 +          fullname = Dir_FindFile(newName, parseIncPath);
 +          if (fullname == NULL)
 +              fullname = Dir_FindFile(newName, dirSearchPath);
 +          free(newName);
 +      }
 +      free(incdir);
 +
 +      if (fullname == NULL) {
 +          /*
 +           * Makefile wasn't found in same directory as included makefile.
 +           * Search for it first on the -I search path,
 +           * then on the .PATH search path, if not found in a -I directory.
 +           * If we have a suffix specific path we should use that.
 +           */
 +          char *suff;
 +          Lst suffPath = NULL;
 +
 +          if ((suff = strrchr(file, '.'))) {
 +              suffPath = Suff_GetPath(suff);
 +              if (suffPath != NULL) {
 +                  fullname = Dir_FindFile(file, suffPath);
 +              }
 +          }
 +          if (fullname == NULL) {
 +              fullname = Dir_FindFile(file, parseIncPath);
 +              if (fullname == NULL) {
 +                  fullname = Dir_FindFile(file, dirSearchPath);
 +              }
 +          }
 +      }
 +    }
 +
 +    /* Looking for a system file or file still not found */
 +    if (fullname == NULL) {
 +      /*
 +       * Look for it on the system path
 +       */
 +      fullname = Dir_FindFile(file,
 +                  Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
 +    }
 +
 +    if (fullname == NULL) {
 +      if (!silent)
 +          Parse_Error(PARSE_FATAL, "Could not find %s", file);
 +      return;
 +    }
 +
 +    /* Actually open the file... */
 +    fd = open(fullname, O_RDONLY);
 +    if (fd == -1) {
 +      if (!silent)
 +          Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
 +      free(fullname);
 +      return;
 +    }
 +
 +    /* load it */
 +    lf = loadfile(fullname, fd);
 +
 +    ParseSetIncludedFile();
 +    /* Start reading from this file next */
 +    Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf);
 +    curFile->lf = lf;
 +}
 +
 +static void
 +ParseDoInclude(char *line)
 +{
 +    char          endc;               /* the character which ends the file spec */
 +    char          *cp;                /* current position in file spec */
 +    int                 silent = (*line != 'i') ? 1 : 0;
 +    char        *file = &line[7 + silent];
 +
 +    /* Skip to delimiter character so we know where to look */
 +    while (*file == ' ' || *file == '\t')
 +      file++;
 +
 +    if (*file != '"' && *file != '<') {
 +      Parse_Error(PARSE_FATAL,
 +          ".include filename must be delimited by '\"' or '<'");
 +      return;
 +    }
 +
 +    /*
 +     * Set the search path on which to find the include file based on the
 +     * characters which bracket its name. Angle-brackets imply it's
 +     * a system Makefile while double-quotes imply it's a user makefile
 +     */
 +    if (*file == '<') {
 +      endc = '>';
 +    } else {
 +      endc = '"';
 +    }
 +
 +    /* Skip to matching delimiter */
 +    for (cp = ++file; *cp && *cp != endc; cp++)
 +      continue;
 +
 +    if (*cp != endc) {
 +      Parse_Error(PARSE_FATAL,
 +                   "Unclosed %cinclude filename. '%c' expected",
 +                   '.', endc);
 +      return;
 +    }
 +    *cp = '\0';
 +
 +    /*
 +     * Substitute for any variables in the file name before trying to
 +     * find the thing.
 +     */
-     file = Var_Subst(NULL, file, VAR_CMD, FALSE);
++    file = Var_Subst(NULL, file, VAR_CMD, FALSE, TRUE);
 +
 +    Parse_include_file(file, endc == '>', silent);
 +    free(file);
 +}
 +
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseSetIncludedFile  --
 + *    Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE
 + *    and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The .INCLUDEDFROMFILE variable is overwritten by the contents
 + *    of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten
 + *    by the contents of .PARSEDIR
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseSetIncludedFile(void)
 +{
 +    char *pf, *fp = NULL;
 +    char *pd, *dp = NULL;
 +
 +    pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp);
 +    Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0);
 +    pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp);
 +    Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0);
 +
 +    if (DEBUG(PARSE))
 +      fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' "
 +          "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf);
 +
 +    if (fp)
 +      free(fp);
 +    if (dp)
 +      free(dp);
 +}
 +/*-
 + *---------------------------------------------------------------------
 + * ParseSetParseFile  --
 + *    Set the .PARSEDIR and .PARSEFILE variables to the dirname and
 + *    basename of the given filename
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The .PARSEDIR and .PARSEFILE variables are overwritten by the
 + *    dirname and basename of the given filename.
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseSetParseFile(const char *filename)
 +{
 +    char *slash, *dirname;
 +    const char *pd, *pf;
 +    int len;
 +
 +    slash = strrchr(filename, '/');
 +    if (slash == NULL) {
 +      Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0);
 +      Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0);
 +      dirname= NULL;
 +    } else {
 +      len = slash - filename;
 +      dirname = bmake_malloc(len + 1);
 +      memcpy(dirname, filename, len);
 +      dirname[len] = '\0';
 +      Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0);
 +      Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0);
 +    }
 +    if (DEBUG(PARSE))
 +      fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n",
 +          __func__, pd, pf);
 +    free(dirname);
 +}
 +
 +/*
 + * Track the makefiles we read - so makefiles can
 + * set dependencies on them.
 + * Avoid adding anything more than once.
 + */
 +
 +static void
 +ParseTrackInput(const char *name)
 +{
 +    char *old;
 +    char *fp = NULL;
 +    size_t name_len = strlen(name);
 +    
 +    old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp);
 +    if (old) {
 +      /* does it contain name? */
 +      for (; old != NULL; old = strchr(old, ' ')) {
 +          if (*old == ' ')
 +              old++;
 +          if (memcmp(old, name, name_len) == 0
 +                  && (old[name_len] == 0 || old[name_len] == ' '))
 +              goto cleanup;
 +      }
 +    }
 +    Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL);
 + cleanup:
 +    if (fp) {
 +      free(fp);
 +    }
 +}
 +
 +
 +/*-
 + *---------------------------------------------------------------------
 + * Parse_setInput  --
 + *    Start Parsing from the given source
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    A structure is added to the includes Lst and readProc, lineno,
 + *    fname and curFile are altered for the new file
 + *---------------------------------------------------------------------
 + */
 +void
 +Parse_SetInput(const char *name, int line, int fd,
 +      char *(*nextbuf)(void *, size_t *), void *arg)
 +{
 +    char *buf;
 +    size_t len;
 +
 +    if (name == NULL)
 +      name = curFile->fname;
 +    else
 +      ParseTrackInput(name);
 +
 +    if (DEBUG(PARSE))
 +      fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n",
 +          __func__, name, line, fd, nextbuf, arg);
 +
 +    if (fd == -1 && nextbuf == NULL)
 +      /* sanity */
 +      return;
 +
 +    if (curFile != NULL)
 +      /* Save exiting file info */
 +      Lst_AtFront(includes, curFile);
 +
 +    /* Allocate and fill in new structure */
 +    curFile = bmake_malloc(sizeof *curFile);
 +
 +    /*
 +     * Once the previous state has been saved, we can get down to reading
 +     * the new file. We set up the name of the file to be the absolute
 +     * name of the include file so error messages refer to the right
 +     * place.
 +     */
 +    curFile->fname = bmake_strdup(name);
 +    curFile->lineno = line;
 +    curFile->first_lineno = line;
 +    curFile->nextbuf = nextbuf;
 +    curFile->nextbuf_arg = arg;
 +    curFile->lf = NULL;
 +
 +    assert(nextbuf != NULL);
 +
 +    /* Get first block of input data */
 +    buf = curFile->nextbuf(curFile->nextbuf_arg, &len);
 +    if (buf == NULL) {
 +        /* Was all a waste of time ... */
 +      if (curFile->fname)
 +          free(curFile->fname);
 +      free(curFile);
 +      return;
 +    }
 +    curFile->P_str = buf;
 +    curFile->P_ptr = buf;
 +    curFile->P_end = buf+len;
 +
 +    curFile->cond_depth = Cond_save_depth();
 +    ParseSetParseFile(name);
 +}
 +
 +#ifdef SYSVINCLUDE
 +/*-
 + *---------------------------------------------------------------------
 + * ParseTraditionalInclude  --
 + *    Push to another file.
 + *
 + *    The input is the current line. The file name(s) are
 + *    following the "include".
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    A structure is added to the includes Lst and readProc, lineno,
 + *    fname and curFILE are altered for the new file
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseTraditionalInclude(char *line)
 +{
 +    char          *cp;                /* current position in file spec */
 +    int                  done = 0;
 +    int                  silent = (line[0] != 'i') ? 1 : 0;
 +    char        *file = &line[silent + 7];
 +    char        *all_files;
 +
 +    if (DEBUG(PARSE)) {
 +          fprintf(debug_file, "%s: %s\n", __func__, file);
 +    }
 +
 +    /*
 +     * Skip over whitespace
 +     */
 +    while (isspace((unsigned char)*file))
 +      file++;
 +
 +    /*
 +     * Substitute for any variables in the file name before trying to
 +     * find the thing.
 +     */
-     all_files = Var_Subst(NULL, file, VAR_CMD, FALSE);
++    all_files = Var_Subst(NULL, file, VAR_CMD, FALSE, TRUE);
 +
 +    if (*file == '\0') {
 +      Parse_Error(PARSE_FATAL,
 +                   "Filename missing from \"include\"");
 +      return;
 +    }
 +
 +    for (file = all_files; !done; file = cp + 1) {
 +      /* Skip to end of line or next whitespace */
 +      for (cp = file; *cp && !isspace((unsigned char) *cp); cp++)
 +          continue;
 +
 +      if (*cp)
 +          *cp = '\0';
 +      else
 +          done = 1;
 +
 +      Parse_include_file(file, FALSE, silent);
 +    }
 +    free(all_files);
 +}
 +#endif
 +
 +#ifdef GMAKEEXPORT
 +/*-
 + *---------------------------------------------------------------------
 + * ParseGmakeExport  --
 + *    Parse export <variable>=<value>
 + *
 + *    And set the environment with it.
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    None
 + *---------------------------------------------------------------------
 + */
 +static void
 +ParseGmakeExport(char *line)
 +{
 +    char        *variable = &line[6];
 +    char        *value;
 +
 +    if (DEBUG(PARSE)) {
 +          fprintf(debug_file, "%s: %s\n", __func__, variable);
 +    }
 +
 +    /*
 +     * Skip over whitespace
 +     */
 +    while (isspace((unsigned char)*variable))
 +      variable++;
 +
 +    for (value = variable; *value && *value != '='; value++)
 +      continue;
 +
 +    if (*value != '=') {
 +      Parse_Error(PARSE_FATAL,
 +                   "Variable/Value missing from \"export\"");
 +      return;
 +    }
 +    *value++ = '\0';                  /* terminate variable */
 +
 +    /*
 +     * Expand the value before putting it in the environment.
 +     */
-     value = Var_Subst(NULL, value, VAR_CMD, FALSE);
++    value = Var_Subst(NULL, value, VAR_CMD, FALSE, TRUE);
 +    setenv(variable, value, 1);
 +}
 +#endif
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseEOF  --
 + *    Called when EOF is reached in the current file. If we were reading
 + *    an include file, the includes stack is popped and things set up
 + *    to go back to reading the previous file at the previous location.
 + *
 + * Results:
 + *    CONTINUE if there's more to do. DONE if not.
 + *
 + * Side Effects:
 + *    The old curFILE, is closed. The includes list is shortened.
 + *    lineno, curFILE, and fname are changed if CONTINUE is returned.
 + *---------------------------------------------------------------------
 + */
 +static int
 +ParseEOF(void)
 +{
 +    char *ptr;
 +    size_t len;
 +
 +    assert(curFile->nextbuf != NULL);
 +
 +    /* get next input buffer, if any */
 +    ptr = curFile->nextbuf(curFile->nextbuf_arg, &len);
 +    curFile->P_ptr = ptr;
 +    curFile->P_str = ptr;
 +    curFile->P_end = ptr + len;
 +    curFile->lineno = curFile->first_lineno;
 +    if (ptr != NULL) {
 +      /* Iterate again */
 +      return CONTINUE;
 +    }
 +
 +    /* Ensure the makefile (or loop) didn't have mismatched conditionals */
 +    Cond_restore_depth(curFile->cond_depth);
 +
 +    if (curFile->lf != NULL) {
 +          loadedfile_destroy(curFile->lf);
 +          curFile->lf = NULL;
 +    }
 +
 +    /* Dispose of curFile info */
 +    /* Leak curFile->fname because all the gnodes have pointers to it */
 +    free(curFile->P_str);
 +    free(curFile);
 +
 +    curFile = Lst_DeQueue(includes);
 +
 +    if (curFile == NULL) {
 +      /* We've run out of input */
 +      Var_Delete(".PARSEDIR", VAR_GLOBAL);
 +      Var_Delete(".PARSEFILE", VAR_GLOBAL);
 +      Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
 +      Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
 +      return DONE;
 +    }
 +
 +    if (DEBUG(PARSE))
 +      fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n",
 +          curFile->fname, curFile->lineno);
 +
 +    /* Restore the PARSEDIR/PARSEFILE variables */
 +    ParseSetParseFile(curFile->fname);
 +    return (CONTINUE);
 +}
 +
 +#define PARSE_RAW 1
 +#define PARSE_SKIP 2
 +
 +static char *
 +ParseGetLine(int flags, int *length)
 +{
 +    IFile *cf = curFile;
 +    char *ptr;
 +    char ch;
 +    char *line;
 +    char *line_end;
 +    char *escaped;
 +    char *comment;
 +    char *tp;
 +
 +    /* Loop through blank lines and comment lines */
 +    for (;;) {
 +      cf->lineno++;
 +      line = cf->P_ptr;
 +      ptr = line;
 +      line_end = line;
 +      escaped = NULL;
 +      comment = NULL;
 +      for (;;) {
 +          if (cf->P_end != NULL && ptr == cf->P_end) {
 +              /* end of buffer */
 +              ch = 0;
 +              break;
 +          }
 +          ch = *ptr;
 +          if (ch == 0 || (ch == '\\' && ptr[1] == 0)) {
 +              if (cf->P_end == NULL)
 +                  /* End of string (aka for loop) data */
 +                  break;
 +              /* see if there is more we can parse */
 +              while (ptr++ < cf->P_end) {
 +                  if ((ch = *ptr) == '\n') {
 +                      if (ptr > line && ptr[-1] == '\\')
 +                          continue;
 +                      Parse_Error(PARSE_WARNING,
 +                          "Zero byte read from file, skipping rest of line.");
 +                      break;
 +                  }
 +              }
 +              if (cf->nextbuf != NULL) {
 +                  /*
 +                   * End of this buffer; return EOF and outer logic
 +                   * will get the next one. (eww)
 +                   */
 +                  break;
 +              }
 +              Parse_Error(PARSE_FATAL, "Zero byte read from file");
 +              return NULL;
 +          }
 +
 +          if (ch == '\\') {
 +              /* Don't treat next character as special, remember first one */
 +              if (escaped == NULL)
 +                  escaped = ptr;
 +              if (ptr[1] == '\n')
 +                  cf->lineno++;
 +              ptr += 2;
 +              line_end = ptr;
 +              continue;
 +          }
 +          if (ch == '#' && comment == NULL) {
 +              /* Remember first '#' for comment stripping */
 +              /* Unless previous char was '[', as in modifier :[#] */
 +              if (!(ptr > line && ptr[-1] == '['))
 +                  comment = line_end;
 +          }
 +          ptr++;
 +          if (ch == '\n')
 +              break;
 +          if (!isspace((unsigned char)ch))
 +              /* We are not interested in trailing whitespace */
 +              line_end = ptr;
 +      }
 +
 +      /* Save next 'to be processed' location */
 +      cf->P_ptr = ptr;
 +
 +      /* Check we have a non-comment, non-blank line */
 +      if (line_end == line || comment == line) {
 +          if (ch == 0)
 +              /* At end of file */
 +              return NULL;
 +          /* Parse another line */
 +          continue;
 +      }
 +
 +      /* We now have a line of data */
 +      *line_end = 0;
 +
 +      if (flags & PARSE_RAW) {
 +          /* Leave '\' (etc) in line buffer (eg 'for' lines) */
 +          *length = line_end - line;
 +          return line;
 +      }
 +
 +      if (flags & PARSE_SKIP) {
 +          /* Completely ignore non-directives */
 +          if (line[0] != '.')
 +              continue;
 +          /* We could do more of the .else/.elif/.endif checks here */
 +      }
 +      break;
 +    }
 +
 +    /* Brutally ignore anything after a non-escaped '#' in non-commands */
 +    if (comment != NULL && line[0] != '\t') {
 +      line_end = comment;
 +      *line_end = 0;
 +    }
 +
 +    /* If we didn't see a '\\' then the in-situ data is fine */
 +    if (escaped == NULL) {
 +      *length = line_end - line;
 +      return line;
 +    }
 +
 +    /* Remove escapes from '\n' and '#' */
 +    tp = ptr = escaped;
 +    escaped = line;
 +    for (; ; *tp++ = ch) {
 +      ch = *ptr++;
 +      if (ch != '\\') {
 +          if (ch == 0)
 +              break;
 +          continue;
 +      }
 +
 +      ch = *ptr++;
 +      if (ch == 0) {
 +          /* Delete '\\' at end of buffer */
 +          tp--;
 +          break;
 +      }
 +
 +      if (ch == '#' && line[0] != '\t')
 +          /* Delete '\\' from before '#' on non-command lines */
 +          continue;
 +
 +      if (ch != '\n') {
 +          /* Leave '\\' in buffer for later */
 +          *tp++ = '\\';
 +          /* Make sure we don't delete an escaped ' ' from the line end */
 +          escaped = tp + 1;
 +          continue;
 +      }
 +
 +      /* Escaped '\n' replace following whitespace with a single ' ' */
 +      while (ptr[0] == ' ' || ptr[0] == '\t')
 +          ptr++;
 +      ch = ' ';
 +    }
 +
 +    /* Delete any trailing spaces - eg from empty continuations */
 +    while (tp > escaped && isspace((unsigned char)tp[-1]))
 +      tp--;
 +
 +    *tp = 0;
 +    *length = tp - line;
 +    return line;
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * ParseReadLine --
 + *    Read an entire line from the input file. Called only by Parse_File.
 + *
 + * Results:
 + *    A line w/o its newline
 + *
 + * Side Effects:
 + *    Only those associated with reading a character
 + *---------------------------------------------------------------------
 + */
 +static char *
 +ParseReadLine(void)
 +{
 +    char        *line;        /* Result */
 +    int                 lineLength;   /* Length of result */
 +    int                 lineno;       /* Saved line # */
 +    int                 rval;
 +
 +    for (;;) {
 +      line = ParseGetLine(0, &lineLength);
 +      if (line == NULL)
 +          return NULL;
 +
 +      if (line[0] != '.')
 +          return line;
 +
 +      /*
 +       * The line might be a conditional. Ask the conditional module
 +       * about it and act accordingly
 +       */
 +      switch (Cond_Eval(line)) {
 +      case COND_SKIP:
 +          /* Skip to next conditional that evaluates to COND_PARSE.  */
 +          do {
 +              line = ParseGetLine(PARSE_SKIP, &lineLength);
 +          } while (line && Cond_Eval(line) != COND_PARSE);
 +          if (line == NULL)
 +              break;
 +          continue;
 +      case COND_PARSE:
 +          continue;
 +      case COND_INVALID:    /* Not a conditional line */
 +          /* Check for .for loops */
 +          rval = For_Eval(line);
 +          if (rval == 0)
 +              /* Not a .for line */
 +              break;
 +          if (rval < 0)
 +              /* Syntax error - error printed, ignore line */
 +              continue;
 +          /* Start of a .for loop */
 +          lineno = curFile->lineno;
 +          /* Accumulate loop lines until matching .endfor */
 +          do {
 +              line = ParseGetLine(PARSE_RAW, &lineLength);
 +              if (line == NULL) {
 +                  Parse_Error(PARSE_FATAL,
 +                           "Unexpected end of file in for loop.");
 +                  break;
 +              }
 +          } while (For_Accum(line));
 +          /* Stash each iteration as a new 'input file' */
 +          For_Run(lineno);
 +          /* Read next line from for-loop buffer */
 +          continue;
 +      }
 +      return (line);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseFinishLine --
 + *    Handle the end of a dependency group.
 + *
 + * Results:
 + *    Nothing.
 + *
 + * Side Effects:
 + *    inLine set FALSE. 'targets' list destroyed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +ParseFinishLine(void)
 +{
 +    if (inLine) {
 +      Lst_ForEach(targets, Suff_EndTransform, NULL);
 +      Lst_Destroy(targets, ParseHasCommands);
 +      targets = NULL;
 +      inLine = FALSE;
 +    }
 +}
 +
 +
 +/*-
 + *---------------------------------------------------------------------
 + * Parse_File --
 + *    Parse a file into its component parts, incorporating it into the
 + *    current dependency graph. This is the main function and controls
 + *    almost every other function in this module
 + *
 + * Input:
 + *    name            the name of the file being read
 + *    fd              Open file to makefile to parse
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    closes fd.
 + *    Loads. Nodes are added to the list of all targets, nodes and links
 + *    are added to the dependency graph. etc. etc. etc.
 + *---------------------------------------------------------------------
 + */
 +void
 +Parse_File(const char *name, int fd)
 +{
 +    char        *cp;          /* pointer into the line */
 +    char          *line;      /* the line we're working on */
 +    struct loadedfile *lf;
 +
 +    lf = loadfile(name, fd);
 +
 +    inLine = FALSE;
 +    fatals = 0;
 +
 +    if (name == NULL) {
 +          name = "(stdin)";
 +    }
 +
 +    Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf);
 +    curFile->lf = lf;
 +
 +    do {
 +      for (; (line = ParseReadLine()) != NULL; ) {
 +          if (DEBUG(PARSE))
 +              fprintf(debug_file, "ParseReadLine (%d): '%s'\n",
 +                      curFile->lineno, line);
 +          if (*line == '.') {
 +              /*
 +               * Lines that begin with the special character may be
 +               * include or undef directives.
 +               * On the other hand they can be suffix rules (.c.o: ...)
 +               * or just dependencies for filenames that start '.'.
 +               */
 +              for (cp = line + 1; isspace((unsigned char)*cp); cp++) {
 +                  continue;
 +              }
 +              if (strncmp(cp, "include", 7) == 0 ||
 +                      ((cp[0] == 's' || cp[0] == '-') &&
 +                          strncmp(&cp[1], "include", 7) == 0)) {
 +                  ParseDoInclude(cp);
 +                  continue;
 +              }
 +              if (strncmp(cp, "undef", 5) == 0) {
 +                  char *cp2;
 +                  for (cp += 5; isspace((unsigned char) *cp); cp++)
 +                      continue;
 +                  for (cp2 = cp; !isspace((unsigned char) *cp2) &&
 +                                 (*cp2 != '\0'); cp2++)
 +                      continue;
 +                  *cp2 = '\0';
 +                  Var_Delete(cp, VAR_GLOBAL);
 +                  continue;
 +              } else if (strncmp(cp, "export", 6) == 0) {
 +                  for (cp += 6; isspace((unsigned char) *cp); cp++)
 +                      continue;
 +                  Var_Export(cp, 1);
 +                  continue;
 +              } else if (strncmp(cp, "unexport", 8) == 0) {
 +                  Var_UnExport(cp);
 +                  continue;
 +              } else if (strncmp(cp, "info", 4) == 0 ||
 +                         strncmp(cp, "error", 5) == 0 ||
 +                         strncmp(cp, "warning", 7) == 0) {
 +                  if (ParseMessage(cp))
 +                      continue;
 +              }                   
 +          }
 +
 +          if (*line == '\t') {
 +              /*
 +               * If a line starts with a tab, it can only hope to be
 +               * a creation command.
 +               */
 +              cp = line + 1;
 +            shellCommand:
 +              for (; isspace ((unsigned char)*cp); cp++) {
 +                  continue;
 +              }
 +              if (*cp) {
 +                  if (!inLine)
 +                      Parse_Error(PARSE_FATAL,
 +                                   "Unassociated shell command \"%s\"",
 +                                   cp);
 +                  /*
 +                   * So long as it's not a blank line and we're actually
 +                   * in a dependency spec, add the command to the list of
 +                   * commands of all targets in the dependency spec
 +                   */
 +                  if (targets) {
 +                      cp = bmake_strdup(cp);
 +                      Lst_ForEach(targets, ParseAddCmd, cp);
 +#ifdef CLEANUP
 +                      Lst_AtEnd(targCmds, cp);
 +#endif
 +                  }
 +              }
 +              continue;
 +          }
 +
 +#ifdef SYSVINCLUDE
 +          if (((strncmp(line, "include", 7) == 0 &&
 +                  isspace((unsigned char) line[7])) ||
 +                      ((line[0] == 's' || line[0] == '-') &&
 +                          strncmp(&line[1], "include", 7) == 0 &&
 +                          isspace((unsigned char) line[8]))) &&
 +                  strchr(line, ':') == NULL) {
 +              /*
 +               * It's an S3/S5-style "include".
 +               */
 +              ParseTraditionalInclude(line);
 +              continue;
 +          }
 +#endif
 +#ifdef GMAKEEXPORT
 +          if (strncmp(line, "export", 6) == 0 &&
 +              isspace((unsigned char) line[6]) &&
 +              strchr(line, ':') == NULL) {
 +              /*
 +               * It's a Gmake "export".
 +               */
 +              ParseGmakeExport(line);
 +              continue;
 +          }
 +#endif
 +          if (Parse_IsVar(line)) {
 +              ParseFinishLine();
 +              Parse_DoVar(line, VAR_GLOBAL);
 +              continue;
 +          }
 +
 +#ifndef POSIX
 +          /*
 +           * To make life easier on novices, if the line is indented we
 +           * first make sure the line has a dependency operator in it.
 +           * If it doesn't have an operator and we're in a dependency
 +           * line's script, we assume it's actually a shell command
 +           * and add it to the current list of targets.
 +           */
 +          cp = line;
 +          if (isspace((unsigned char) line[0])) {
 +              while ((*cp != '\0') && isspace((unsigned char) *cp))
 +                  cp++;
 +              while (*cp && (ParseIsEscaped(line, cp) ||
 +                      (*cp != ':') && (*cp != '!'))) {
 +                  cp++;
 +              }
 +              if (*cp == '\0') {
 +                  if (inLine) {
 +                      Parse_Error(PARSE_WARNING,
 +                                   "Shell command needs a leading tab");
 +                      goto shellCommand;
 +                  }
 +              }
 +          }
 +#endif
 +          ParseFinishLine();
 +
 +          /*
 +           * For some reason - probably to make the parser impossible -
 +           * a ';' can be used to separate commands from dependencies.
 +           * Attempt to avoid ';' inside substitution patterns.
 +           */
 +          {
 +              int level = 0;
 +
 +              for (cp = line; *cp != 0; cp++) {
 +                  if (*cp == '\\' && cp[1] != 0) {
 +                      cp++;
 +                      continue;
 +                  }
 +                  if (*cp == '$' &&
 +                      (cp[1] == '(' || cp[1] == '{')) {
 +                      level++;
 +                      continue;
 +                  }
 +                  if (level > 0) {
 +                      if (*cp == ')' || *cp == '}') {
 +                          level--;
 +                          continue;
 +                      }
 +                  } else if (*cp == ';') {
 +                      break;
 +                  }
 +              }
 +          }
 +          if (*cp != 0)
 +              /* Terminate the dependency list at the ';' */
 +              *cp++ = 0;
 +          else
 +              cp = NULL;
 +
 +          /*
 +           * We now know it's a dependency line so it needs to have all
 +           * variables expanded before being parsed. Tell the variable
 +           * module to complain if some variable is undefined...
 +           */
-           line = Var_Subst(NULL, line, VAR_CMD, TRUE);
++          line = Var_Subst(NULL, line, VAR_CMD, TRUE, TRUE);
 +
 +          /*
 +           * Need a non-circular list for the target nodes
 +           */
 +          if (targets)
 +              Lst_Destroy(targets, NULL);
 +
 +          targets = Lst_Init(FALSE);
 +          inLine = TRUE;
 +
 +          ParseDoDependency(line);
 +          free(line);
 +
 +          /* If there were commands after a ';', add them now */
 +          if (cp != NULL) {
 +              goto shellCommand;
 +          }
 +      }
 +      /*
 +       * Reached EOF, but it may be just EOF of an include file...
 +       */
 +    } while (ParseEOF() == CONTINUE);
 +
 +    if (fatals) {
 +      (void)fflush(stdout);
 +      (void)fprintf(stderr,
 +          "%s: Fatal errors encountered -- cannot continue",
 +          progname);
 +      PrintOnError(NULL, NULL);
 +      exit(1);
 +    }
 +}
 +
 +/*-
 + *---------------------------------------------------------------------
 + * Parse_Init --
 + *    initialize the parsing module
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    the parseIncPath list is initialized...
 + *---------------------------------------------------------------------
 + */
 +void
 +Parse_Init(void)
 +{
 +    mainNode = NULL;
 +    parseIncPath = Lst_Init(FALSE);
 +    sysIncPath = Lst_Init(FALSE);
 +    defIncPath = Lst_Init(FALSE);
 +    includes = Lst_Init(FALSE);
 +#ifdef CLEANUP
 +    targCmds = Lst_Init(FALSE);
 +#endif
 +}
 +
 +void
 +Parse_End(void)
 +{
 +#ifdef CLEANUP
 +    Lst_Destroy(targCmds, (FreeProc *)free);
 +    if (targets)
 +      Lst_Destroy(targets, NULL);
 +    Lst_Destroy(defIncPath, Dir_Destroy);
 +    Lst_Destroy(sysIncPath, Dir_Destroy);
 +    Lst_Destroy(parseIncPath, Dir_Destroy);
 +    Lst_Destroy(includes, NULL);      /* Should be empty now */
 +#endif
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Parse_MainName --
 + *    Return a Lst of the main target to create for main()'s sake. If
 + *    no such target exists, we Punt with an obnoxious error message.
 + *
 + * Results:
 + *    A Lst of the single node to create.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +Lst
 +Parse_MainName(void)
 +{
 +    Lst           mainList;   /* result list */
 +
 +    mainList = Lst_Init(FALSE);
 +
 +    if (mainNode == NULL) {
 +      Punt("no target to make.");
 +      /*NOTREACHED*/
 +    } else if (mainNode->type & OP_DOUBLEDEP) {
 +      (void)Lst_AtEnd(mainList, mainNode);
 +      Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW);
 +    }
 +    else
 +      (void)Lst_AtEnd(mainList, mainNode);
 +    Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
 +    return (mainList);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * ParseMark --
 + *    Add the filename and lineno to the GNode so that we remember
 + *    where it was first defined.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +ParseMark(GNode *gn)
 +{
 +    gn->fname = curFile->fname;
 +    gn->lineno = curFile->lineno;
 +}
index c42d2ec57e8fa4c151813a16b4301871eda516c0,0000000000000000000000000000000000000000..15a3cc87cbc00621102df0de7a004ee8d558ceeb
mode 100644,000000..100644
--- /dev/null
@@@ -1,2654 -1,0 +1,2654 @@@
- /*    $NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $  */
++/*    $NetBSD: suff.c,v 1.74 2015/10/11 04:51:24 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $";
++static char rcsid[] = "$NetBSD: suff.c,v 1.74 2015/10/11 04:51:24 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)suff.c    8.4 (Berkeley) 3/21/94";
 +#else
- __RCSID("$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $");
++__RCSID("$NetBSD: suff.c,v 1.74 2015/10/11 04:51:24 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * suff.c --
 + *    Functions to maintain suffix lists and find implicit dependents
 + *    using suffix transformation rules
 + *
 + * Interface:
 + *    Suff_Init               Initialize all things to do with suffixes.
 + *
 + *    Suff_End                Cleanup the module
 + *
 + *    Suff_DoPaths            This function is used to make life easier
 + *                            when searching for a file according to its
 + *                            suffix. It takes the global search path,
 + *                            as defined using the .PATH: target, and appends
 + *                            its directories to the path of each of the
 + *                            defined suffixes, as specified using
 + *                            .PATH<suffix>: targets. In addition, all
 + *                            directories given for suffixes labeled as
 + *                            include files or libraries, using the .INCLUDES
 + *                            or .LIBS targets, are played with using
 + *                            Dir_MakeFlags to create the .INCLUDES and
 + *                            .LIBS global variables.
 + *
 + *    Suff_ClearSuffixes      Clear out all the suffixes and defined
 + *                            transformations.
 + *
 + *    Suff_IsTransform        Return TRUE if the passed string is the lhs
 + *                            of a transformation rule.
 + *
 + *    Suff_AddSuffix          Add the passed string as another known suffix.
 + *
 + *    Suff_GetPath            Return the search path for the given suffix.
 + *
 + *    Suff_AddInclude         Mark the given suffix as denoting an include
 + *                            file.
 + *
 + *    Suff_AddLib             Mark the given suffix as denoting a library.
 + *
 + *    Suff_AddTransform       Add another transformation to the suffix
 + *                            graph. Returns  GNode suitable for framing, I
 + *                            mean, tacking commands, attributes, etc. on.
 + *
 + *    Suff_SetNull            Define the suffix to consider the suffix of
 + *                            any file that doesn't have a known one.
 + *
 + *    Suff_FindDeps           Find implicit sources for and the location of
 + *                            a target based on its suffix. Returns the
 + *                            bottom-most node added to the graph or NULL
 + *                            if the target had no implicit sources.
 + *
 + *    Suff_FindPath           Return the appropriate path to search in
 + *                            order to find the node.
 + */
 +
 +#include        <stdio.h>
 +#include        "make.h"
 +#include        "hash.h"
 +#include        "dir.h"
 +
 +static Lst       sufflist;    /* Lst of suffixes */
 +#ifdef CLEANUP
 +static Lst     suffClean;     /* Lst of suffixes to be cleaned */
 +#endif
 +static Lst     srclist;       /* Lst of sources */
 +static Lst       transforms;  /* Lst of transformation rules */
 +
 +static int        sNum = 0;   /* Counter for assigning suffix numbers */
 +
 +/*
 + * Structure describing an individual suffix.
 + */
 +typedef struct _Suff {
 +    char         *name;               /* The suffix itself */
 +    int                nameLen;       /* Length of the suffix */
 +    short      flags;         /* Type of suffix */
 +#define SUFF_INCLUDE    0x01      /* One which is #include'd */
 +#define SUFF_LIBRARY    0x02      /* One which contains a library */
 +#define SUFF_NULL       0x04      /* The empty suffix */
 +    Lst        searchPath;    /* The path along which files of this suffix
 +                               * may be found */
 +    int          sNum;                /* The suffix number */
 +    int                refCount;      /* Reference count of list membership */
 +    Lst          parents;     /* Suffixes we have a transformation to */
 +    Lst          children;    /* Suffixes we have a transformation from */
 +    Lst                ref;           /* List of lists this suffix is referenced */
 +} Suff;
 +
 +/*
 + * for SuffSuffIsSuffix
 + */
 +typedef struct {
 +    char      *ename;         /* The end of the name */
 +    int                len;           /* Length of the name */
 +} SuffixCmpData;
 +
 +/*
 + * Structure used in the search for implied sources.
 + */
 +typedef struct _Src {
 +    char            *file;    /* The file to look for */
 +    char          *pref;      /* Prefix from which file was formed */
 +    Suff            *suff;    /* The suffix on the file */
 +    struct _Src     *parent;  /* The Src for which this is a source */
 +    GNode           *node;    /* The node describing the file */
 +    int                   children;   /* Count of existing children (so we don't free
 +                               * this thing too early or never nuke it) */
 +#ifdef DEBUG_SRC
 +    Lst                   cp;         /* Debug; children list */
 +#endif
 +} Src;
 +
 +/*
 + * A structure for passing more than one argument to the Lst-library-invoked
 + * function...
 + */
 +typedef struct {
 +    Lst            l;
 +    Src            *s;
 +} LstSrc;
 +
 +typedef struct {
 +    GNode       **gn;
 +    Suff         *s;
 +    Boolean       r;
 +} GNodeSuff;
 +
 +static Suff       *suffNull;  /* The NULL suffix for this run */
 +static Suff       *emptySuff; /* The empty suffix required for POSIX
 +                               * single-suffix transformation rules */
 +
 +
 +static const char *SuffStrIsPrefix(const char *, const char *);
 +static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *);
 +static int SuffSuffIsSuffixP(const void *, const void *);
 +static int SuffSuffHasNameP(const void *, const void *);
 +static int SuffSuffIsPrefix(const void *, const void *);
 +static int SuffGNHasNameP(const void *, const void *);
 +static void SuffUnRef(void *, void *);
 +static void SuffFree(void *);
 +static void SuffInsert(Lst, Suff *);
 +static void SuffRemove(Lst, Suff *);
 +static Boolean SuffParseTransform(char *, Suff **, Suff **);
 +static int SuffRebuildGraph(void *, void *);
 +static int SuffScanTargets(void *, void *);
 +static int SuffAddSrc(void *, void *);
 +static int SuffRemoveSrc(Lst);
 +static void SuffAddLevel(Lst, Src *);
 +static Src *SuffFindThem(Lst, Lst);
 +static Src *SuffFindCmds(Src *, Lst);
 +static void SuffExpandChildren(LstNode, GNode *);
 +static void SuffExpandWildcards(LstNode, GNode *);
 +static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *);
 +static void SuffFindDeps(GNode *, Lst);
 +static void SuffFindArchiveDeps(GNode *, Lst);
 +static void SuffFindNormalDeps(GNode *, Lst);
 +static int SuffPrintName(void *, void *);
 +static int SuffPrintSuff(void *, void *);
 +static int SuffPrintTrans(void *, void *);
 +
 +      /*************** Lst Predicates ****************/
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffStrIsPrefix  --
 + *    See if pref is a prefix of str.
 + *
 + * Input:
 + *    pref            possible prefix
 + *    str             string to check
 + *
 + * Results:
 + *    NULL if it ain't, pointer to character in str after prefix if so
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static const char *
 +SuffStrIsPrefix(const char *pref, const char *str)
 +{
 +    while (*str && *pref == *str) {
 +      pref++;
 +      str++;
 +    }
 +
 +    return (*pref ? NULL : str);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffSuffIsSuffix  --
 + *    See if suff is a suffix of str. sd->ename should point to THE END
 + *    of the string to check. (THE END == the null byte)
 + *
 + * Input:
 + *    s               possible suffix
 + *    sd              string to examine
 + *
 + * Results:
 + *    NULL if it ain't, pointer to character in str before suffix if
 + *    it is.
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd)
 +{
 +    char  *p1;                /* Pointer into suffix name */
 +    char  *p2;                /* Pointer into string being examined */
 +
 +    if (sd->len < s->nameLen)
 +      return NULL;            /* this string is shorter than the suffix */
 +
 +    p1 = s->name + s->nameLen;
 +    p2 = sd->ename;
 +
 +    while (p1 >= s->name && *p1 == *p2) {
 +      p1--;
 +      p2--;
 +    }
 +
 +    return (p1 == s->name - 1 ? p2 : NULL);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffSuffIsSuffixP --
 + *    Predicate form of SuffSuffIsSuffix. Passed as the callback function
 + *    to Lst_Find.
 + *
 + * Results:
 + *    0 if the suffix is the one desired, non-zero if not.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffSuffIsSuffixP(const void *s, const void *sd)
 +{
 +    return(!SuffSuffIsSuffix(s, sd));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffSuffHasNameP --
 + *    Callback procedure for finding a suffix based on its name. Used by
 + *    Suff_GetPath.
 + *
 + * Input:
 + *    s               Suffix to check
 + *    sd              Desired name
 + *
 + * Results:
 + *    0 if the suffix is of the given name. non-zero otherwise.
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffSuffHasNameP(const void *s, const void *sname)
 +{
 +    return (strcmp(sname, ((const Suff *)s)->name));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffSuffIsPrefix  --
 + *    See if the suffix described by s is a prefix of the string. Care
 + *    must be taken when using this to search for transformations and
 + *    what-not, since there could well be two suffixes, one of which
 + *    is a prefix of the other...
 + *
 + * Input:
 + *    s               suffix to compare
 + *    str             string to examine
 + *
 + * Results:
 + *    0 if s is a prefix of str. non-zero otherwise
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffSuffIsPrefix(const void *s, const void *str)
 +{
 +    return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffGNHasNameP  --
 + *    See if the graph node has the desired name
 + *
 + * Input:
 + *    gn              current node we're looking at
 + *    name            name we're looking for
 + *
 + * Results:
 + *    0 if it does. non-zero if it doesn't
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffGNHasNameP(const void *gn, const void *name)
 +{
 +    return (strcmp(name, ((const GNode *)gn)->name));
 +}
 +
 +          /*********** Maintenance Functions ************/
 +
 +static void
 +SuffUnRef(void *lp, void *sp)
 +{
 +    Lst l = (Lst) lp;
 +
 +    LstNode ln = Lst_Member(l, sp);
 +    if (ln != NULL) {
 +      Lst_Remove(l, ln);
 +      ((Suff *)sp)->refCount--;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffFree  --
 + *    Free up all memory associated with the given suffix structure.
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    the suffix entry is detroyed
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffFree(void *sp)
 +{
 +    Suff           *s = (Suff *)sp;
 +
 +    if (s == suffNull)
 +      suffNull = NULL;
 +
 +    if (s == emptySuff)
 +      emptySuff = NULL;
 +
 +#ifdef notdef
 +    /* We don't delete suffixes in order, so we cannot use this */
 +    if (s->refCount)
 +      Punt("Internal error deleting suffix `%s' with refcount = %d", s->name,
 +          s->refCount);
 +#endif
 +
 +    Lst_Destroy(s->ref, NULL);
 +    Lst_Destroy(s->children, NULL);
 +    Lst_Destroy(s->parents, NULL);
 +    Lst_Destroy(s->searchPath, Dir_Destroy);
 +
 +    free(s->name);
 +    free(s);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffRemove  --
 + *    Remove the suffix into the list
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The reference count for the suffix is decremented and the
 + *    suffix is possibly freed
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffRemove(Lst l, Suff *s)
 +{
 +    SuffUnRef(l, s);
 +    if (s->refCount == 0) {
 +      SuffUnRef(sufflist, s);
 +      SuffFree(s);
 +    }
 +}
 +\f
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffInsert  --
 + *    Insert the suffix into the list keeping the list ordered by suffix
 + *    numbers.
 + *
 + * Input:
 + *    l               the list where in s should be inserted
 + *    s               the suffix to insert
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The reference count of the suffix is incremented
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffInsert(Lst l, Suff *s)
 +{
 +    LstNode     ln;           /* current element in l we're examining */
 +    Suff          *s2 = NULL; /* the suffix descriptor in this element */
 +
 +    if (Lst_Open(l) == FAILURE) {
 +      return;
 +    }
 +    while ((ln = Lst_Next(l)) != NULL) {
 +      s2 = (Suff *)Lst_Datum(ln);
 +      if (s2->sNum >= s->sNum) {
 +          break;
 +      }
 +    }
 +
 +    Lst_Close(l);
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum);
 +    }
 +    if (ln == NULL) {
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "at end of list\n");
 +      }
 +      (void)Lst_AtEnd(l, s);
 +      s->refCount++;
 +      (void)Lst_AtEnd(s->ref, l);
 +    } else if (s2->sNum != s->sNum) {
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum);
 +      }
 +      (void)Lst_InsertBefore(l, ln, s);
 +      s->refCount++;
 +      (void)Lst_AtEnd(s->ref, l);
 +    } else if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "already there\n");
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_ClearSuffixes --
 + *    This is gross. Nuke the list of suffixes but keep all transformation
 + *    rules around. The transformation graph is destroyed in this process,
 + *    but we leave the list of rules so when a new graph is formed the rules
 + *    will remain.
 + *    This function is called from the parse module when a
 + *    .SUFFIXES:\n line is encountered.
 + *
 + * Results:
 + *    none
 + *
 + * Side Effects:
 + *    the sufflist and its graph nodes are destroyed
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_ClearSuffixes(void)
 +{
 +#ifdef CLEANUP
 +    Lst_Concat(suffClean, sufflist, LST_CONCLINK);
 +#endif
 +    sufflist = Lst_Init(FALSE);
 +    sNum = 0;
 +    suffNull = emptySuff;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffParseTransform --
 + *    Parse a transformation string to find its two component suffixes.
 + *
 + * Input:
 + *    str             String being parsed
 + *    srcPtr          Place to store source of trans.
 + *    targPtr         Place to store target of trans.
 + *
 + * Results:
 + *    TRUE if the string is a valid transformation and FALSE otherwise.
 + *
 + * Side Effects:
 + *    The passed pointers are overwritten.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr)
 +{
 +    LstNode           srcLn;      /* element in suffix list of trans source*/
 +    Suff              *src;       /* Source of transformation */
 +    LstNode           targLn;     /* element in suffix list of trans target*/
 +    char              *str2;      /* Extra pointer (maybe target suffix) */
 +    LstNode           singleLn;   /* element in suffix list of any suffix
 +                                   * that exactly matches str */
 +    Suff              *single = NULL;/* Source of possible transformation to
 +                                   * null suffix */
 +
 +    srcLn = NULL;
 +    singleLn = NULL;
 +
 +    /*
 +     * Loop looking first for a suffix that matches the start of the
 +     * string and then for one that exactly matches the rest of it. If
 +     * we can find two that meet these criteria, we've successfully
 +     * parsed the string.
 +     */
 +    for (;;) {
 +      if (srcLn == NULL) {
 +          srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix);
 +      } else {
 +          srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str,
 +                                SuffSuffIsPrefix);
 +      }
 +      if (srcLn == NULL) {
 +          /*
 +           * Ran out of source suffixes -- no such rule
 +           */
 +          if (singleLn != NULL) {
 +              /*
 +               * Not so fast Mr. Smith! There was a suffix that encompassed
 +               * the entire string, so we assume it was a transformation
 +               * to the null suffix (thank you POSIX). We still prefer to
 +               * find a double rule over a singleton, hence we leave this
 +               * check until the end.
 +               *
 +               * XXX: Use emptySuff over suffNull?
 +               */
 +              *srcPtr = single;
 +              *targPtr = suffNull;
 +              return(TRUE);
 +          }
 +          return (FALSE);
 +      }
 +      src = (Suff *)Lst_Datum(srcLn);
 +      str2 = str + src->nameLen;
 +      if (*str2 == '\0') {
 +          single = src;
 +          singleLn = srcLn;
 +      } else {
 +          targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP);
 +          if (targLn != NULL) {
 +              *srcPtr = src;
 +              *targPtr = (Suff *)Lst_Datum(targLn);
 +              return (TRUE);
 +          }
 +      }
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_IsTransform  --
 + *    Return TRUE if the given string is a transformation rule
 + *
 + *
 + * Input:
 + *    str             string to check
 + *
 + * Results:
 + *    TRUE if the string is a concatenation of two known suffixes.
 + *    FALSE otherwise
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Suff_IsTransform(char *str)
 +{
 +    Suff        *src, *targ;
 +
 +    return (SuffParseTransform(str, &src, &targ));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_AddTransform --
 + *    Add the transformation rule described by the line to the
 + *    list of rules and place the transformation itself in the graph
 + *
 + * Input:
 + *    line            name of transformation to add
 + *
 + * Results:
 + *    The node created for the transformation in the transforms list
 + *
 + * Side Effects:
 + *    The node is placed on the end of the transforms Lst and links are
 + *    made between the two suffixes mentioned in the target name
 + *-----------------------------------------------------------------------
 + */
 +GNode *
 +Suff_AddTransform(char *line)
 +{
 +    GNode         *gn;                /* GNode of transformation rule */
 +    Suff          *s,         /* source suffix */
 +                  *t;         /* target suffix */
 +    LstNode     ln;           /* Node for existing transformation */
 +
 +    ln = Lst_Find(transforms, line, SuffGNHasNameP);
 +    if (ln == NULL) {
 +      /*
 +       * Make a new graph node for the transformation. It will be filled in
 +       * by the Parse module.
 +       */
 +      gn = Targ_NewGN(line);
 +      (void)Lst_AtEnd(transforms, gn);
 +    } else {
 +      /*
 +       * New specification for transformation rule. Just nuke the old list
 +       * of commands so they can be filled in again... We don't actually
 +       * free the commands themselves, because a given command can be
 +       * attached to several different transformations.
 +       */
 +      gn = (GNode *)Lst_Datum(ln);
 +      Lst_Destroy(gn->commands, NULL);
 +      Lst_Destroy(gn->children, NULL);
 +      gn->commands = Lst_Init(FALSE);
 +      gn->children = Lst_Init(FALSE);
 +    }
 +
 +    gn->type = OP_TRANSFORM;
 +
 +    (void)SuffParseTransform(line, &s, &t);
 +
 +    /*
 +     * link the two together in the proper relationship and order
 +     */
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
 +              s->name, t->name);
 +    }
 +    SuffInsert(t->children, s);
 +    SuffInsert(s->parents, t);
 +
 +    return (gn);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_EndTransform --
 + *    Handle the finish of a transformation definition, removing the
 + *    transformation from the graph if it has neither commands nor
 + *    sources. This is a callback procedure for the Parse module via
 + *    Lst_ForEach
 + *
 + * Input:
 + *    gnp             Node for transformation
 + *    dummy           Node for transformation
 + *
 + * Results:
 + *    === 0
 + *
 + * Side Effects:
 + *    If the node has no commands or children, the children and parents
 + *    lists of the affected suffixes are altered.
 + *
 + *-----------------------------------------------------------------------
 + */
 +int
 +Suff_EndTransform(void *gnp, void *dummy)
 +{
 +    GNode *gn = (GNode *)gnp;
 +
 +    if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
 +      gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
 +    if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
 +      Lst_IsEmpty(gn->children))
 +    {
 +      Suff    *s, *t;
 +
 +      /*
 +       * SuffParseTransform() may fail for special rules which are not
 +       * actual transformation rules. (e.g. .DEFAULT)
 +       */
 +      if (SuffParseTransform(gn->name, &s, &t)) {
 +          Lst  p;
 +
 +          if (DEBUG(SUFF)) {
 +              fprintf(debug_file, "deleting transformation from `%s' to `%s'\n",
 +              s->name, t->name);
 +          }
 +
 +          /*
 +           * Store s->parents because s could be deleted in SuffRemove
 +           */
 +          p = s->parents;
 +
 +          /*
 +           * Remove the source from the target's children list. We check for a
 +           * nil return to handle a beanhead saying something like
 +           *  .c.o .c.o:
 +           *
 +           * We'll be called twice when the next target is seen, but .c and .o
 +           * are only linked once...
 +           */
 +          SuffRemove(t->children, s);
 +
 +          /*
 +           * Remove the target from the source's parents list
 +           */
 +          SuffRemove(p, t);
 +      }
 +    } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) {
 +      fprintf(debug_file, "transformation %s complete\n", gn->name);
 +    }
 +
 +    return(dummy ? 0 : 0);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffRebuildGraph --
 + *    Called from Suff_AddSuffix via Lst_ForEach to search through the
 + *    list of existing transformation rules and rebuild the transformation
 + *    graph when it has been destroyed by Suff_ClearSuffixes. If the
 + *    given rule is a transformation involving this suffix and another,
 + *    existing suffix, the proper relationship is established between
 + *    the two.
 + *
 + * Input:
 + *    transformp      Transformation to test
 + *    sp              Suffix to rebuild
 + *
 + * Results:
 + *    Always 0.
 + *
 + * Side Effects:
 + *    The appropriate links will be made between this suffix and
 + *    others if transformation rules exist for it.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffRebuildGraph(void *transformp, void *sp)
 +{
 +    GNode     *transform = (GNode *)transformp;
 +    Suff      *s = (Suff *)sp;
 +    char      *cp;
 +    LstNode   ln;
 +    Suff      *s2;
 +    SuffixCmpData sd;
 +
 +    /*
 +     * First see if it is a transformation from this suffix.
 +     */
 +    cp = UNCONST(SuffStrIsPrefix(s->name, transform->name));
 +    if (cp != NULL) {
 +      ln = Lst_Find(sufflist, cp, SuffSuffHasNameP);
 +      if (ln != NULL) {
 +          /*
 +           * Found target. Link in and return, since it can't be anything
 +           * else.
 +           */
 +          s2 = (Suff *)Lst_Datum(ln);
 +          SuffInsert(s2->children, s);
 +          SuffInsert(s->parents, s2);
 +          return(0);
 +      }
 +    }
 +
 +    /*
 +     * Not from, maybe to?
 +     */
 +    sd.len = strlen(transform->name);
 +    sd.ename = transform->name + sd.len;
 +    cp = SuffSuffIsSuffix(s, &sd);
 +    if (cp != NULL) {
 +      /*
 +       * Null-terminate the source suffix in order to find it.
 +       */
 +      cp[1] = '\0';
 +      ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP);
 +      /*
 +       * Replace the start of the target suffix
 +       */
 +      cp[1] = s->name[0];
 +      if (ln != NULL) {
 +          /*
 +           * Found it -- establish the proper relationship
 +           */
 +          s2 = (Suff *)Lst_Datum(ln);
 +          SuffInsert(s->children, s2);
 +          SuffInsert(s2->parents, s);
 +      }
 +    }
 +    return(0);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffScanTargets --
 + *    Called from Suff_AddSuffix via Lst_ForEach to search through the
 + *    list of existing targets and find if any of the existing targets
 + *    can be turned into a transformation rule.
 + *
 + * Results:
 + *    1 if a new main target has been selected, 0 otherwise.
 + *
 + * Side Effects:
 + *    If such a target is found and the target is the current main
 + *    target, the main target is set to NULL and the next target
 + *    examined (if that exists) becomes the main target.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffScanTargets(void *targetp, void *gsp)
 +{
 +    GNode     *target = (GNode *)targetp;
 +    GNodeSuff *gs = (GNodeSuff *)gsp;
 +    Suff      *s, *t;
 +    char      *ptr;
 +
 +    if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) {
 +      *gs->gn = target;
 +      Targ_SetMain(target);
 +      return 1;
 +    }
 +
 +    if ((unsigned int)target->type == OP_TRANSFORM)
 +      return 0;
 +
 +    if ((ptr = strstr(target->name, gs->s->name)) == NULL ||
 +      ptr == target->name)
 +      return 0;
 +
 +    if (SuffParseTransform(target->name, &s, &t)) {
 +      if (*gs->gn == target) {
 +          gs->r = TRUE;
 +          *gs->gn = NULL;
 +          Targ_SetMain(NULL);
 +      }
 +      Lst_Destroy(target->children, NULL);
 +      target->children = Lst_Init(FALSE);
 +      target->type = OP_TRANSFORM;
 +      /*
 +       * link the two together in the proper relationship and order
 +       */
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
 +              s->name, t->name);
 +      }
 +      SuffInsert(t->children, s);
 +      SuffInsert(s->parents, t);
 +    }
 +    return 0;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_AddSuffix --
 + *    Add the suffix in string to the end of the list of known suffixes.
 + *    Should we restructure the suffix graph? Make doesn't...
 + *
 + * Input:
 + *    str             the name of the suffix to add
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    A GNode is created for the suffix and a Suff structure is created and
 + *    added to the suffixes list unless the suffix was already known.
 + *    The mainNode passed can be modified if a target mutated into a
 + *    transform and that target happened to be the main target.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_AddSuffix(char *str, GNode **gn)
 +{
 +    Suff          *s;     /* new suffix descriptor */
 +    LstNode     ln;
 +    GNodeSuff   gs;
 +
 +    ln = Lst_Find(sufflist, str, SuffSuffHasNameP);
 +    if (ln == NULL) {
 +      s = bmake_malloc(sizeof(Suff));
 +
 +      s->name =       bmake_strdup(str);
 +      s->nameLen =    strlen(s->name);
 +      s->searchPath = Lst_Init(FALSE);
 +      s->children =   Lst_Init(FALSE);
 +      s->parents =    Lst_Init(FALSE);
 +      s->ref =        Lst_Init(FALSE);
 +      s->sNum =       sNum++;
 +      s->flags =      0;
 +      s->refCount =   1;
 +
 +      (void)Lst_AtEnd(sufflist, s);
 +      /*
 +       * We also look at our existing targets list to see if adding
 +       * this suffix will make one of our current targets mutate into
 +       * a suffix rule. This is ugly, but other makes treat all targets
 +       * that start with a . as suffix rules.
 +       */
 +      gs.gn = gn;
 +      gs.s  = s;
 +      gs.r  = FALSE;
 +      Lst_ForEach(Targ_List(), SuffScanTargets, &gs);
 +      /*
 +       * Look for any existing transformations from or to this suffix.
 +       * XXX: Only do this after a Suff_ClearSuffixes?
 +       */
 +      Lst_ForEach(transforms, SuffRebuildGraph, s);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_GetPath --
 + *    Return the search path for the given suffix, if it's defined.
 + *
 + * Results:
 + *    The searchPath for the desired suffix or NULL if the suffix isn't
 + *    defined.
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +Lst
 +Suff_GetPath(char *sname)
 +{
 +    LstNode             ln;
 +    Suff        *s;
 +
 +    ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
 +    if (ln == NULL) {
 +      return NULL;
 +    } else {
 +      s = (Suff *)Lst_Datum(ln);
 +      return (s->searchPath);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_DoPaths --
 + *    Extend the search paths for all suffixes to include the default
 + *    search path.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The searchPath field of all the suffixes is extended by the
 + *    directories in dirSearchPath. If paths were specified for the
 + *    ".h" suffix, the directories are stuffed into a global variable
 + *    called ".INCLUDES" with each directory preceded by a -I. The same
 + *    is done for the ".a" suffix, except the variable is called
 + *    ".LIBS" and the flag is -L.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_DoPaths(void)
 +{
 +    Suff              *s;
 +    LstNode           ln;
 +    char              *ptr;
 +    Lst                       inIncludes; /* Cumulative .INCLUDES path */
 +    Lst                       inLibs;     /* Cumulative .LIBS path */
 +
 +    if (Lst_Open(sufflist) == FAILURE) {
 +      return;
 +    }
 +
 +    inIncludes = Lst_Init(FALSE);
 +    inLibs = Lst_Init(FALSE);
 +
 +    while ((ln = Lst_Next(sufflist)) != NULL) {
 +      s = (Suff *)Lst_Datum(ln);
 +      if (!Lst_IsEmpty (s->searchPath)) {
 +#ifdef INCLUDES
 +          if (s->flags & SUFF_INCLUDE) {
 +              Dir_Concat(inIncludes, s->searchPath);
 +          }
 +#endif /* INCLUDES */
 +#ifdef LIBRARIES
 +          if (s->flags & SUFF_LIBRARY) {
 +              Dir_Concat(inLibs, s->searchPath);
 +          }
 +#endif /* LIBRARIES */
 +          Dir_Concat(s->searchPath, dirSearchPath);
 +      } else {
 +          Lst_Destroy(s->searchPath, Dir_Destroy);
 +          s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir);
 +      }
 +    }
 +
 +    Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0);
 +    free(ptr);
 +    Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0);
 +    free(ptr);
 +
 +    Lst_Destroy(inIncludes, Dir_Destroy);
 +    Lst_Destroy(inLibs, Dir_Destroy);
 +
 +    Lst_Close(sufflist);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_AddInclude --
 + *    Add the given suffix as a type of file which gets included.
 + *    Called from the parse module when a .INCLUDES line is parsed.
 + *    The suffix must have already been defined.
 + *
 + * Input:
 + *    sname           Name of the suffix to mark
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The SUFF_INCLUDE bit is set in the suffix's flags field
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_AddInclude(char *sname)
 +{
 +    LstNode     ln;
 +    Suff        *s;
 +
 +    ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
 +    if (ln != NULL) {
 +      s = (Suff *)Lst_Datum(ln);
 +      s->flags |= SUFF_INCLUDE;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_AddLib --
 + *    Add the given suffix as a type of file which is a library.
 + *    Called from the parse module when parsing a .LIBS line. The
 + *    suffix must have been defined via .SUFFIXES before this is
 + *    called.
 + *
 + * Input:
 + *    sname           Name of the suffix to mark
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The SUFF_LIBRARY bit is set in the suffix's flags field
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_AddLib(char *sname)
 +{
 +    LstNode     ln;
 +    Suff        *s;
 +
 +    ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
 +    if (ln != NULL) {
 +      s = (Suff *)Lst_Datum(ln);
 +      s->flags |= SUFF_LIBRARY;
 +    }
 +}
 +
 +        /********** Implicit Source Search Functions *********/
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffAddSrc  --
 + *    Add a suffix as a Src structure to the given list with its parent
 + *    being the given Src structure. If the suffix is the null suffix,
 + *    the prefix is used unaltered as the file name in the Src structure.
 + *
 + * Input:
 + *    sp              suffix for which to create a Src structure
 + *    lsp             list and parent for the new Src
 + *
 + * Results:
 + *    always returns 0
 + *
 + * Side Effects:
 + *    A Src structure is created and tacked onto the end of the list
 + *-----------------------------------------------------------------------
 + */
 +static int
 +SuffAddSrc(void *sp, void *lsp)
 +{
 +    Suff      *s = (Suff *)sp;
 +    LstSrc      *ls = (LstSrc *)lsp;
 +    Src         *s2;      /* new Src structure */
 +    Src       *targ;      /* Target structure */
 +
 +    targ = ls->s;
 +
 +    if ((s->flags & SUFF_NULL) && (*s->name != '\0')) {
 +      /*
 +       * If the suffix has been marked as the NULL suffix, also create a Src
 +       * structure for a file with no suffix attached. Two birds, and all
 +       * that...
 +       */
 +      s2 = bmake_malloc(sizeof(Src));
 +      s2->file =      bmake_strdup(targ->pref);
 +      s2->pref =      targ->pref;
 +      s2->parent =    targ;
 +      s2->node =      NULL;
 +      s2->suff =      s;
 +      s->refCount++;
 +      s2->children =  0;
 +      targ->children += 1;
 +      (void)Lst_AtEnd(ls->l, s2);
 +#ifdef DEBUG_SRC
 +      s2->cp = Lst_Init(FALSE);
 +      Lst_AtEnd(targ->cp, s2);
 +      fprintf(debug_file, "1 add %x %x to %x:", targ, s2, ls->l);
 +      Lst_ForEach(ls->l, PrintAddr, NULL);
 +      fprintf(debug_file, "\n");
 +#endif
 +    }
 +    s2 = bmake_malloc(sizeof(Src));
 +    s2->file =            str_concat(targ->pref, s->name, 0);
 +    s2->pref =            targ->pref;
 +    s2->parent =    targ;
 +    s2->node =            NULL;
 +    s2->suff =            s;
 +    s->refCount++;
 +    s2->children =  0;
 +    targ->children += 1;
 +    (void)Lst_AtEnd(ls->l, s2);
 +#ifdef DEBUG_SRC
 +    s2->cp = Lst_Init(FALSE);
 +    Lst_AtEnd(targ->cp, s2);
 +    fprintf(debug_file, "2 add %x %x to %x:", targ, s2, ls->l);
 +    Lst_ForEach(ls->l, PrintAddr, NULL);
 +    fprintf(debug_file, "\n");
 +#endif
 +
 +    return(0);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffAddLevel  --
 + *    Add all the children of targ as Src structures to the given list
 + *
 + * Input:
 + *    l               list to which to add the new level
 + *    targ            Src structure to use as the parent
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Lots of structures are created and added to the list
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffAddLevel(Lst l, Src *targ)
 +{
 +    LstSrc         ls;
 +
 +    ls.s = targ;
 +    ls.l = l;
 +
 +    Lst_ForEach(targ->suff->children, SuffAddSrc, &ls);
 +}
 +
 +/*-
 + *----------------------------------------------------------------------
 + * SuffRemoveSrc --
 + *    Free all src structures in list that don't have a reference count
 + *
 + * Results:
 + *    Ture if an src was removed
 + *
 + * Side Effects:
 + *    The memory is free'd.
 + *----------------------------------------------------------------------
 + */
 +static int
 +SuffRemoveSrc(Lst l)
 +{
 +    LstNode ln;
 +    Src *s;
 +    int t = 0;
 +
 +    if (Lst_Open(l) == FAILURE) {
 +      return 0;
 +    }
 +#ifdef DEBUG_SRC
 +    fprintf(debug_file, "cleaning %lx: ", (unsigned long) l);
 +    Lst_ForEach(l, PrintAddr, NULL);
 +    fprintf(debug_file, "\n");
 +#endif
 +
 +
 +    while ((ln = Lst_Next(l)) != NULL) {
 +      s = (Src *)Lst_Datum(ln);
 +      if (s->children == 0) {
 +          free(s->file);
 +          if (!s->parent)
 +              free(s->pref);
 +          else {
 +#ifdef DEBUG_SRC
 +              LstNode ln = Lst_Member(s->parent->cp, s);
 +              if (ln != NULL)
 +                  Lst_Remove(s->parent->cp, ln);
 +#endif
 +              --s->parent->children;
 +          }
 +#ifdef DEBUG_SRC
 +          fprintf(debug_file, "free: [l=%x] p=%x %d\n", l, s, s->children);
 +          Lst_Destroy(s->cp, NULL);
 +#endif
 +          Lst_Remove(l, ln);
 +          free(s);
 +          t |= 1;
 +          Lst_Close(l);
 +          return TRUE;
 +      }
 +#ifdef DEBUG_SRC
 +      else {
 +          fprintf(debug_file, "keep: [l=%x] p=%x %d: ", l, s, s->children);
 +          Lst_ForEach(s->cp, PrintAddr, NULL);
 +          fprintf(debug_file, "\n");
 +      }
 +#endif
 +    }
 +
 +    Lst_Close(l);
 +
 +    return t;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffFindThem --
 + *    Find the first existing file/target in the list srcs
 + *
 + * Input:
 + *    srcs            list of Src structures to search through
 + *
 + * Results:
 + *    The lowest structure in the chain of transformations
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static Src *
 +SuffFindThem(Lst srcs, Lst slst)
 +{
 +    Src            *s;                /* current Src */
 +    Src                  *rs;         /* returned Src */
 +    char         *ptr;
 +
 +    rs = NULL;
 +
 +    while (!Lst_IsEmpty (srcs)) {
 +      s = (Src *)Lst_DeQueue(srcs);
 +
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "\ttrying %s...", s->file);
 +      }
 +
 +      /*
 +       * A file is considered to exist if either a node exists in the
 +       * graph for it or the file actually exists.
 +       */
 +      if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) {
 +#ifdef DEBUG_SRC
 +          fprintf(debug_file, "remove %x from %x\n", s, srcs);
 +#endif
 +          rs = s;
 +          break;
 +      }
 +
 +      if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) {
 +          rs = s;
 +#ifdef DEBUG_SRC
 +          fprintf(debug_file, "remove %x from %x\n", s, srcs);
 +#endif
 +          free(ptr);
 +          break;
 +      }
 +
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "not there\n");
 +      }
 +
 +      SuffAddLevel(srcs, s);
 +      Lst_AtEnd(slst, s);
 +    }
 +
 +    if (DEBUG(SUFF) && rs) {
 +      fprintf(debug_file, "got it\n");
 +    }
 +    return (rs);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffFindCmds --
 + *    See if any of the children of the target in the Src structure is
 + *    one from which the target can be transformed. If there is one,
 + *    a Src structure is put together for it and returned.
 + *
 + * Input:
 + *    targ            Src structure to play with
 + *
 + * Results:
 + *    The Src structure of the "winning" child, or NULL if no such beast.
 + *
 + * Side Effects:
 + *    A Src structure may be allocated.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Src *
 +SuffFindCmds(Src *targ, Lst slst)
 +{
 +    LstNode           ln;     /* General-purpose list node */
 +    GNode             *t,     /* Target GNode */
 +                      *s;     /* Source GNode */
 +    int                       prefLen;/* The length of the defined prefix */
 +    Suff              *suff;  /* Suffix on matching beastie */
 +    Src                       *ret;   /* Return value */
 +    char              *cp;
 +
 +    t = targ->node;
 +    (void)Lst_Open(t->children);
 +    prefLen = strlen(targ->pref);
 +
 +    for (;;) {
 +      ln = Lst_Next(t->children);
 +      if (ln == NULL) {
 +          Lst_Close(t->children);
 +          return NULL;
 +      }
 +      s = (GNode *)Lst_Datum(ln);
 +
 +      if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) {
 +          /*
 +           * We haven't looked to see if .OPTIONAL files exist yet, so
 +           * don't use one as the implicit source.
 +           * This allows us to use .OPTIONAL in .depend files so make won't
 +           * complain "don't know how to make xxx.h' when a dependent file
 +           * has been moved/deleted.
 +           */
 +          continue;
 +      }
 +
 +      cp = strrchr(s->name, '/');
 +      if (cp == NULL) {
 +          cp = s->name;
 +      } else {
 +          cp++;
 +      }
 +      if (strncmp(cp, targ->pref, prefLen) != 0)
 +          continue;
 +      /*
 +       * The node matches the prefix ok, see if it has a known
 +       * suffix.
 +       */
 +      ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP);
 +      if (ln == NULL)
 +          continue;
 +      /*
 +       * It even has a known suffix, see if there's a transformation
 +       * defined between the node's suffix and the target's suffix.
 +       *
 +       * XXX: Handle multi-stage transformations here, too.
 +       */
 +      suff = (Suff *)Lst_Datum(ln);
 +
 +      if (Lst_Member(suff->parents, targ->suff) != NULL)
 +          break;
 +    }
 +
 +    /*
 +     * Hot Damn! Create a new Src structure to describe
 +     * this transformation (making sure to duplicate the
 +     * source node's name so Suff_FindDeps can free it
 +     * again (ick)), and return the new structure.
 +     */
 +    ret = bmake_malloc(sizeof(Src));
 +    ret->file = bmake_strdup(s->name);
 +    ret->pref = targ->pref;
 +    ret->suff = suff;
 +    suff->refCount++;
 +    ret->parent = targ;
 +    ret->node = s;
 +    ret->children = 0;
 +    targ->children += 1;
 +#ifdef DEBUG_SRC
 +    ret->cp = Lst_Init(FALSE);
 +    fprintf(debug_file, "3 add %x %x\n", targ, ret);
 +    Lst_AtEnd(targ->cp, ret);
 +#endif
 +    Lst_AtEnd(slst, ret);
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "\tusing existing source %s\n", s->name);
 +    }
 +    return (ret);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffExpandChildren --
 + *    Expand the names of any children of a given node that contain
 + *    variable invocations or file wildcards into actual targets.
 + *
 + * Input:
 + *    cln             Child to examine
 + *    pgn             Parent node being processed
 + *
 + * Results:
 + *    === 0 (continue)
 + *
 + * Side Effects:
 + *    The expanded node is removed from the parent's list of children,
 + *    and the parent's unmade counter is decremented, but other nodes
 + *    may be added.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffExpandChildren(LstNode cln, GNode *pgn)
 +{
 +    GNode     *cgn = (GNode *)Lst_Datum(cln);
 +    GNode     *gn;        /* New source 8) */
 +    char      *cp;        /* Expanded value */
 +
 +    if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ))
 +      /* It is all too hard to process the result of .ORDER */
 +      return;
 +
 +    if (cgn->type & OP_WAIT)
 +      /* Ignore these (& OP_PHONY ?) */
 +      return;
 +
 +    /*
 +     * First do variable expansion -- this takes precedence over
 +     * wildcard expansion. If the result contains wildcards, they'll be gotten
 +     * to later since the resulting words are tacked on to the end of
 +     * the children list.
 +     */
 +    if (strchr(cgn->name, '$') == NULL) {
 +      SuffExpandWildcards(cln, pgn);
 +      return;
 +    }
 +
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "Expanding \"%s\"...", cgn->name);
 +    }
-     cp = Var_Subst(NULL, cgn->name, pgn, TRUE);
++    cp = Var_Subst(NULL, cgn->name, pgn, TRUE, TRUE);
 +
 +    if (cp != NULL) {
 +      Lst         members = Lst_Init(FALSE);
 +
 +      if (cgn->type & OP_ARCHV) {
 +          /*
 +           * Node was an archive(member) target, so we want to call
 +           * on the Arch module to find the nodes for us, expanding
 +           * variables in the parent's context.
 +           */
 +          char        *sacrifice = cp;
 +
 +          (void)Arch_ParseArchive(&sacrifice, members, pgn);
 +      } else {
 +          /*
 +           * Break the result into a vector of strings whose nodes
 +           * we can find, then add those nodes to the members list.
 +           * Unfortunately, we can't use brk_string b/c it
 +           * doesn't understand about variable specifications with
 +           * spaces in them...
 +           */
 +          char            *start;
 +          char            *initcp = cp;   /* For freeing... */
 +
 +          for (start = cp; *start == ' ' || *start == '\t'; start++)
 +              continue;
 +          for (cp = start; *cp != '\0'; cp++) {
 +              if (*cp == ' ' || *cp == '\t') {
 +                  /*
 +                   * White-space -- terminate element, find the node,
 +                   * add it, skip any further spaces.
 +                   */
 +                  *cp++ = '\0';
 +                  gn = Targ_FindNode(start, TARG_CREATE);
 +                  (void)Lst_AtEnd(members, gn);
 +                  while (*cp == ' ' || *cp == '\t') {
 +                      cp++;
 +                  }
 +                  /*
 +                   * Adjust cp for increment at start of loop, but
 +                   * set start to first non-space.
 +                   */
 +                  start = cp--;
 +              } else if (*cp == '$') {
 +                  /*
 +                   * Start of a variable spec -- contact variable module
 +                   * to find the end so we can skip over it.
 +                   */
 +                  char        *junk;
 +                  int         len;
 +                  void        *freeIt;
 +
-                   junk = Var_Parse(cp, pgn, TRUE, &len, &freeIt);
++                  junk = Var_Parse(cp, pgn, TRUE, TRUE, &len, &freeIt);
 +                  if (junk != var_Error) {
 +                      cp += len - 1;
 +                  }
 +
 +                  if (freeIt)
 +                      free(freeIt);
 +              } else if (*cp == '\\' && *cp != '\0') {
 +                  /*
 +                   * Escaped something -- skip over it
 +                   */
 +                  cp++;
 +              }
 +          }
 +
 +          if (cp != start) {
 +              /*
 +               * Stuff left over -- add it to the list too
 +               */
 +              gn = Targ_FindNode(start, TARG_CREATE);
 +              (void)Lst_AtEnd(members, gn);
 +          }
 +          /*
 +           * Point cp back at the beginning again so the variable value
 +           * can be freed.
 +           */
 +          cp = initcp;
 +      }
 +
 +      /*
 +       * Add all elements of the members list to the parent node.
 +       */
 +      while(!Lst_IsEmpty(members)) {
 +          gn = (GNode *)Lst_DeQueue(members);
 +
 +          if (DEBUG(SUFF)) {
 +              fprintf(debug_file, "%s...", gn->name);
 +          }
 +          /* Add gn to the parents child list before the original child */
 +          (void)Lst_InsertBefore(pgn->children, cln, gn);
 +          (void)Lst_AtEnd(gn->parents, pgn);
 +          pgn->unmade++;
 +          /* Expand wildcards on new node */
 +          SuffExpandWildcards(Lst_Prev(cln), pgn);
 +      }
 +      Lst_Destroy(members, NULL);
 +
 +      /*
 +       * Free the result
 +       */
 +      free(cp);
 +    }
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "\n");
 +    }
 +
 +    /*
 +     * Now the source is expanded, remove it from the list of children to
 +     * keep it from being processed.
 +     */
 +    pgn->unmade--;
 +    Lst_Remove(pgn->children, cln);
 +    Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
 +}
 +
 +static void
 +SuffExpandWildcards(LstNode cln, GNode *pgn)
 +{
 +    GNode     *cgn = (GNode *)Lst_Datum(cln);
 +    GNode     *gn;        /* New source 8) */
 +    char      *cp;        /* Expanded value */
 +    Lst       explist;    /* List of expansions */
 +
 +    if (!Dir_HasWildcards(cgn->name))
 +      return;
 +
 +    /*
 +     * Expand the word along the chosen path
 +     */
 +    explist = Lst_Init(FALSE);
 +    Dir_Expand(cgn->name, Suff_FindPath(cgn), explist);
 +
 +    while (!Lst_IsEmpty(explist)) {
 +      /*
 +       * Fetch next expansion off the list and find its GNode
 +       */
 +      cp = (char *)Lst_DeQueue(explist);
 +
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "%s...", cp);
 +      }
 +      gn = Targ_FindNode(cp, TARG_CREATE);
 +
 +      /* Add gn to the parents child list before the original child */
 +      (void)Lst_InsertBefore(pgn->children, cln, gn);
 +      (void)Lst_AtEnd(gn->parents, pgn);
 +      pgn->unmade++;
 +    }
 +
 +    /*
 +     * Nuke what's left of the list
 +     */
 +    Lst_Destroy(explist, NULL);
 +
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "\n");
 +    }
 +
 +    /*
 +     * Now the source is expanded, remove it from the list of children to
 +     * keep it from being processed.
 +     */
 +    pgn->unmade--;
 +    Lst_Remove(pgn->children, cln);
 +    Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_FindPath --
 + *    Find a path along which to expand the node.
 + *
 + *    If the word has a known suffix, use that path.
 + *    If it has no known suffix, use the default system search path.
 + *
 + * Input:
 + *    gn              Node being examined
 + *
 + * Results:
 + *    The appropriate path to search for the GNode.
 + *
 + * Side Effects:
 + *    XXX: We could set the suffix here so that we don't have to scan
 + *    again.
 + *
 + *-----------------------------------------------------------------------
 + */
 +Lst
 +Suff_FindPath(GNode* gn)
 +{
 +    Suff *suff = gn->suffix;
 +
 +    if (suff == NULL) {
 +      SuffixCmpData sd;   /* Search string data */
 +      LstNode ln;
 +      sd.len = strlen(gn->name);
 +      sd.ename = gn->name + sd.len;
 +      ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP);
 +
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name);
 +      }
 +      if (ln != NULL)
 +          suff = (Suff *)Lst_Datum(ln);
 +      /* XXX: Here we can save the suffix so we don't have to do this again */
 +    }
 +
 +    if (suff != NULL) {
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "suffix is \"%s\"...", suff->name);
 +      }
 +      return suff->searchPath;
 +    } else {
 +      /*
 +       * Use default search path
 +       */
 +      return dirSearchPath;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffApplyTransform --
 + *    Apply a transformation rule, given the source and target nodes
 + *    and suffixes.
 + *
 + * Input:
 + *    tGn             Target node
 + *    sGn             Source node
 + *    t               Target suffix
 + *    s               Source suffix
 + *
 + * Results:
 + *    TRUE if successful, FALSE if not.
 + *
 + * Side Effects:
 + *    The source and target are linked and the commands from the
 + *    transformation are added to the target node's commands list.
 + *    All attributes but OP_DEPMASK and OP_TRANSFORM are applied
 + *    to the target. The target also inherits all the sources for
 + *    the transformation rule.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s)
 +{
 +    LstNode   ln, nln;    /* General node */
 +    char      *tname;     /* Name of transformation rule */
 +    GNode     *gn;        /* Node for same */
 +
 +    /*
 +     * Form the proper links between the target and source.
 +     */
 +    (void)Lst_AtEnd(tGn->children, sGn);
 +    (void)Lst_AtEnd(sGn->parents, tGn);
 +    tGn->unmade += 1;
 +
 +    /*
 +     * Locate the transformation rule itself
 +     */
 +    tname = str_concat(s->name, t->name, 0);
 +    ln = Lst_Find(transforms, tname, SuffGNHasNameP);
 +    free(tname);
 +
 +    if (ln == NULL) {
 +      /*
 +       * Not really such a transformation rule (can happen when we're
 +       * called to link an OP_MEMBER and OP_ARCHV node), so return
 +       * FALSE.
 +       */
 +      return(FALSE);
 +    }
 +
 +    gn = (GNode *)Lst_Datum(ln);
 +
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name);
 +    }
 +
 +    /*
 +     * Record last child for expansion purposes
 +     */
 +    ln = Lst_Last(tGn->children);
 +
 +    /*
 +     * Pass the buck to Make_HandleUse to apply the rule
 +     */
 +    (void)Make_HandleUse(gn, tGn);
 +
 +    /*
 +     * Deal with wildcards and variables in any acquired sources
 +     */
 +    for (ln = Lst_Succ(ln); ln != NULL; ln = nln) {
 +      nln = Lst_Succ(ln);
 +      SuffExpandChildren(ln, tGn);
 +    }
 +
 +    /*
 +     * Keep track of another parent to which this beast is transformed so
 +     * the .IMPSRC variable can be set correctly for the parent.
 +     */
 +    (void)Lst_AtEnd(sGn->iParents, tGn);
 +
 +    return(TRUE);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffFindArchiveDeps --
 + *    Locate dependencies for an OP_ARCHV node.
 + *
 + * Input:
 + *    gn              Node for which to locate dependencies
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Same as Suff_FindDeps
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffFindArchiveDeps(GNode *gn, Lst slst)
 +{
 +    char      *eoarch;    /* End of archive portion */
 +    char      *eoname;    /* End of member portion */
 +    GNode     *mem;       /* Node for member */
 +    static const char *copy[] = {
 +      /* Variables to be copied from the member node */
 +      TARGET,             /* Must be first */
 +      PREFIX,             /* Must be second */
 +    };
 +    int               i;          /* Index into copy and vals */
 +    Suff      *ms;        /* Suffix descriptor for member */
 +    char      *name;      /* Start of member's name */
 +
 +    /*
 +     * The node is an archive(member) pair. so we must find a
 +     * suffix for both of them.
 +     */
 +    eoarch = strchr(gn->name, '(');
 +    eoname = strchr(eoarch, ')');
 +
 +    *eoname = '\0';     /* Nuke parentheses during suffix search */
 +    *eoarch = '\0';     /* So a suffix can be found */
 +
 +    name = eoarch + 1;
 +
 +    /*
 +     * To simplify things, call Suff_FindDeps recursively on the member now,
 +     * so we can simply compare the member's .PREFIX and .TARGET variables
 +     * to locate its suffix. This allows us to figure out the suffix to
 +     * use for the archive without having to do a quadratic search over the
 +     * suffix list, backtracking for each one...
 +     */
 +    mem = Targ_FindNode(name, TARG_CREATE);
 +    SuffFindDeps(mem, slst);
 +
 +    /*
 +     * Create the link between the two nodes right off
 +     */
 +    (void)Lst_AtEnd(gn->children, mem);
 +    (void)Lst_AtEnd(mem->parents, gn);
 +    gn->unmade += 1;
 +
 +    /*
 +     * Copy in the variables from the member node to this one.
 +     */
 +    for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) {
 +      char *p1;
 +      Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0);
 +      if (p1)
 +          free(p1);
 +
 +    }
 +
 +    ms = mem->suffix;
 +    if (ms == NULL) {
 +      /*
 +       * Didn't know what it was -- use .NULL suffix if not in make mode
 +       */
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "using null suffix\n");
 +      }
 +      ms = suffNull;
 +    }
 +
 +
 +    /*
 +     * Set the other two local variables required for this target.
 +     */
 +    Var_Set(MEMBER, name, gn, 0);
 +    Var_Set(ARCHIVE, gn->name, gn, 0);
 +
 +    if (ms != NULL) {
 +      /*
 +       * Member has a known suffix, so look for a transformation rule from
 +       * it to a possible suffix of the archive. Rather than searching
 +       * through the entire list, we just look at suffixes to which the
 +       * member's suffix may be transformed...
 +       */
 +      LstNode         ln;
 +      SuffixCmpData   sd;             /* Search string data */
 +
 +      /*
 +       * Use first matching suffix...
 +       */
 +      sd.len = eoarch - gn->name;
 +      sd.ename = eoarch;
 +      ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP);
 +
 +      if (ln != NULL) {
 +          /*
 +           * Got one -- apply it
 +           */
 +          if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) &&
 +              DEBUG(SUFF))
 +          {
 +              fprintf(debug_file, "\tNo transformation from %s -> %s\n",
 +                     ms->name, ((Suff *)Lst_Datum(ln))->name);
 +          }
 +      }
 +    }
 +
 +    /*
 +     * Replace the opening and closing parens now we've no need of the separate
 +     * pieces.
 +     */
 +    *eoarch = '('; *eoname = ')';
 +
 +    /*
 +     * Pretend gn appeared to the left of a dependency operator so
 +     * the user needn't provide a transformation from the member to the
 +     * archive.
 +     */
 +    if (OP_NOP(gn->type)) {
 +      gn->type |= OP_DEPENDS;
 +    }
 +
 +    /*
 +     * Flag the member as such so we remember to look in the archive for
 +     * its modification time.
 +     */
 +    mem->type |= OP_MEMBER;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * SuffFindNormalDeps --
 + *    Locate implicit dependencies for regular targets.
 + *
 + * Input:
 + *    gn              Node for which to find sources
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    Same as Suff_FindDeps...
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +SuffFindNormalDeps(GNode *gn, Lst slst)
 +{
 +    char      *eoname;    /* End of name */
 +    char      *sopref;    /* Start of prefix */
 +    LstNode   ln, nln;    /* Next suffix node to check */
 +    Lst               srcs;       /* List of sources at which to look */
 +    Lst               targs;      /* List of targets to which things can be
 +                           * transformed. They all have the same file,
 +                           * but different suff and pref fields */
 +    Src               *bottom;    /* Start of found transformation path */
 +    Src       *src;       /* General Src pointer */
 +    char      *pref;      /* Prefix to use */
 +    Src               *targ;      /* General Src target pointer */
 +    SuffixCmpData sd;     /* Search string data */
 +
 +
 +    sd.len = strlen(gn->name);
 +    sd.ename = eoname = gn->name + sd.len;
 +
 +    sopref = gn->name;
 +
 +    /*
 +     * Begin at the beginning...
 +     */
 +    ln = Lst_First(sufflist);
 +    srcs = Lst_Init(FALSE);
 +    targs = Lst_Init(FALSE);
 +
 +    /*
 +     * We're caught in a catch-22 here. On the one hand, we want to use any
 +     * transformation implied by the target's sources, but we can't examine
 +     * the sources until we've expanded any variables/wildcards they may hold,
 +     * and we can't do that until we've set up the target's local variables
 +     * and we can't do that until we know what the proper suffix for the
 +     * target is (in case there are two suffixes one of which is a suffix of
 +     * the other) and we can't know that until we've found its implied
 +     * source, which we may not want to use if there's an existing source
 +     * that implies a different transformation.
 +     *
 +     * In an attempt to get around this, which may not work all the time,
 +     * but should work most of the time, we look for implied sources first,
 +     * checking transformations to all possible suffixes of the target,
 +     * use what we find to set the target's local variables, expand the
 +     * children, then look for any overriding transformations they imply.
 +     * Should we find one, we discard the one we found before.
 +     */
 +    bottom = NULL;
 +    targ = NULL;
 +
 +    if (!(gn->type & OP_PHONY)) {
 +
 +      while (ln != NULL) {
 +          /*
 +           * Look for next possible suffix...
 +           */
 +          ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP);
 +
 +          if (ln != NULL) {
 +              int         prefLen;        /* Length of the prefix */
 +
 +              /*
 +               * Allocate a Src structure to which things can be transformed
 +               */
 +              targ = bmake_malloc(sizeof(Src));
 +              targ->file = bmake_strdup(gn->name);
 +              targ->suff = (Suff *)Lst_Datum(ln);
 +              targ->suff->refCount++;
 +              targ->node = gn;
 +              targ->parent = NULL;
 +              targ->children = 0;
 +#ifdef DEBUG_SRC
 +              targ->cp = Lst_Init(FALSE);
 +#endif
 +
 +              /*
 +               * Allocate room for the prefix, whose end is found by
 +               * subtracting the length of the suffix from
 +               * the end of the name.
 +               */
 +              prefLen = (eoname - targ->suff->nameLen) - sopref;
 +              targ->pref = bmake_malloc(prefLen + 1);
 +              memcpy(targ->pref, sopref, prefLen);
 +              targ->pref[prefLen] = '\0';
 +
 +              /*
 +               * Add nodes from which the target can be made
 +               */
 +              SuffAddLevel(srcs, targ);
 +
 +              /*
 +               * Record the target so we can nuke it
 +               */
 +              (void)Lst_AtEnd(targs, targ);
 +
 +              /*
 +               * Search from this suffix's successor...
 +               */
 +              ln = Lst_Succ(ln);
 +          }
 +      }
 +
 +      /*
 +       * Handle target of unknown suffix...
 +       */
 +      if (Lst_IsEmpty(targs) && suffNull != NULL) {
 +          if (DEBUG(SUFF)) {
 +              fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
 +          }
 +
 +          targ = bmake_malloc(sizeof(Src));
 +          targ->file = bmake_strdup(gn->name);
 +          targ->suff = suffNull;
 +          targ->suff->refCount++;
 +          targ->node = gn;
 +          targ->parent = NULL;
 +          targ->children = 0;
 +          targ->pref = bmake_strdup(sopref);
 +#ifdef DEBUG_SRC
 +          targ->cp = Lst_Init(FALSE);
 +#endif
 +
 +          /*
 +           * Only use the default suffix rules if we don't have commands
 +           * defined for this gnode; traditional make programs used to
 +           * not define suffix rules if the gnode had children but we
 +           * don't do this anymore.
 +           */
 +          if (Lst_IsEmpty(gn->commands))
 +              SuffAddLevel(srcs, targ);
 +          else {
 +              if (DEBUG(SUFF))
 +                  fprintf(debug_file, "not ");
 +          }
 +
 +          if (DEBUG(SUFF))
 +              fprintf(debug_file, "adding suffix rules\n");
 +
 +          (void)Lst_AtEnd(targs, targ);
 +      }
 +
 +      /*
 +       * Using the list of possible sources built up from the target
 +       * suffix(es), try and find an existing file/target that matches.
 +       */
 +      bottom = SuffFindThem(srcs, slst);
 +
 +      if (bottom == NULL) {
 +          /*
 +           * No known transformations -- use the first suffix found
 +           * for setting the local variables.
 +           */
 +          if (!Lst_IsEmpty(targs)) {
 +              targ = (Src *)Lst_Datum(Lst_First(targs));
 +          } else {
 +              targ = NULL;
 +          }
 +      } else {
 +          /*
 +           * Work up the transformation path to find the suffix of the
 +           * target to which the transformation was made.
 +           */
 +          for (targ = bottom; targ->parent != NULL; targ = targ->parent)
 +              continue;
 +      }
 +    }
 +
 +    Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
 +
 +    pref = (targ != NULL) ? targ->pref : gn->name;
 +    Var_Set(PREFIX, pref, gn, 0);
 +
 +    /*
 +     * Now we've got the important local variables set, expand any sources
 +     * that still contain variables or wildcards in their names.
 +     */
 +    for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
 +      nln = Lst_Succ(ln);
 +      SuffExpandChildren(ln, gn);
 +    }
 +
 +    if (targ == NULL) {
 +      if (DEBUG(SUFF)) {
 +          fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name);
 +      }
 +
 +sfnd_abort:
 +      /*
 +       * Deal with finding the thing on the default search path. We
 +       * always do that, not only if the node is only a source (not
 +       * on the lhs of a dependency operator or [XXX] it has neither
 +       * children or commands) as the old pmake did.
 +       */
 +      if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) {
 +          free(gn->path);
 +          gn->path = Dir_FindFile(gn->name,
 +                                  (targ == NULL ? dirSearchPath :
 +                                   targ->suff->searchPath));
 +          if (gn->path != NULL) {
 +              char *ptr;
 +              Var_Set(TARGET, gn->path, gn, 0);
 +
 +              if (targ != NULL) {
 +                  /*
 +                   * Suffix known for the thing -- trim the suffix off
 +                   * the path to form the proper .PREFIX variable.
 +                   */
 +                  int     savep = strlen(gn->path) - targ->suff->nameLen;
 +                  char    savec;
 +
 +                  if (gn->suffix)
 +                      gn->suffix->refCount--;
 +                  gn->suffix = targ->suff;
 +                  gn->suffix->refCount++;
 +
 +                  savec = gn->path[savep];
 +                  gn->path[savep] = '\0';
 +
 +                  if ((ptr = strrchr(gn->path, '/')) != NULL)
 +                      ptr++;
 +                  else
 +                      ptr = gn->path;
 +
 +                  Var_Set(PREFIX, ptr, gn, 0);
 +
 +                  gn->path[savep] = savec;
 +              } else {
 +                  /*
 +                   * The .PREFIX gets the full path if the target has
 +                   * no known suffix.
 +                   */
 +                  if (gn->suffix)
 +                      gn->suffix->refCount--;
 +                  gn->suffix = NULL;
 +
 +                  if ((ptr = strrchr(gn->path, '/')) != NULL)
 +                      ptr++;
 +                  else
 +                      ptr = gn->path;
 +
 +                  Var_Set(PREFIX, ptr, gn, 0);
 +              }
 +          }
 +      }
 +
 +      goto sfnd_return;
 +    }
 +
 +    /*
 +     * If the suffix indicates that the target is a library, mark that in
 +     * the node's type field.
 +     */
 +    if (targ->suff->flags & SUFF_LIBRARY) {
 +      gn->type |= OP_LIB;
 +    }
 +
 +    /*
 +     * Check for overriding transformation rule implied by sources
 +     */
 +    if (!Lst_IsEmpty(gn->children)) {
 +      src = SuffFindCmds(targ, slst);
 +
 +      if (src != NULL) {
 +          /*
 +           * Free up all the Src structures in the transformation path
 +           * up to, but not including, the parent node.
 +           */
 +          while (bottom && bottom->parent != NULL) {
 +              if (Lst_Member(slst, bottom) == NULL) {
 +                  Lst_AtEnd(slst, bottom);
 +              }
 +              bottom = bottom->parent;
 +          }
 +          bottom = src;
 +      }
 +    }
 +
 +    if (bottom == NULL) {
 +      /*
 +       * No idea from where it can come -- return now.
 +       */
 +      goto sfnd_abort;
 +    }
 +
 +    /*
 +     * We now have a list of Src structures headed by 'bottom' and linked via
 +     * their 'parent' pointers. What we do next is create links between
 +     * source and target nodes (which may or may not have been created)
 +     * and set the necessary local variables in each target. The
 +     * commands for each target are set from the commands of the
 +     * transformation rule used to get from the src suffix to the targ
 +     * suffix. Note that this causes the commands list of the original
 +     * node, gn, to be replaced by the commands of the final
 +     * transformation rule. Also, the unmade field of gn is incremented.
 +     * Etc.
 +     */
 +    if (bottom->node == NULL) {
 +      bottom->node = Targ_FindNode(bottom->file, TARG_CREATE);
 +    }
 +
 +    for (src = bottom; src->parent != NULL; src = src->parent) {
 +      targ = src->parent;
 +
 +      if (src->node->suffix)
 +          src->node->suffix->refCount--;
 +      src->node->suffix = src->suff;
 +      src->node->suffix->refCount++;
 +
 +      if (targ->node == NULL) {
 +          targ->node = Targ_FindNode(targ->file, TARG_CREATE);
 +      }
 +
 +      SuffApplyTransform(targ->node, src->node,
 +                         targ->suff, src->suff);
 +
 +      if (targ->node != gn) {
 +          /*
 +           * Finish off the dependency-search process for any nodes
 +           * between bottom and gn (no point in questing around the
 +           * filesystem for their implicit source when it's already
 +           * known). Note that the node can't have any sources that
 +           * need expanding, since SuffFindThem will stop on an existing
 +           * node, so all we need to do is set the standard and System V
 +           * variables.
 +           */
 +          targ->node->type |= OP_DEPS_FOUND;
 +
 +          Var_Set(PREFIX, targ->pref, targ->node, 0);
 +
 +          Var_Set(TARGET, targ->node->name, targ->node, 0);
 +      }
 +    }
 +
 +    if (gn->suffix)
 +      gn->suffix->refCount--;
 +    gn->suffix = src->suff;
 +    gn->suffix->refCount++;
 +
 +    /*
 +     * Nuke the transformation path and the Src structures left over in the
 +     * two lists.
 +     */
 +sfnd_return:
 +    if (bottom)
 +      if (Lst_Member(slst, bottom) == NULL)
 +          Lst_AtEnd(slst, bottom);
 +
 +    while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
 +      continue;
 +
 +    Lst_Concat(slst, srcs, LST_CONCLINK);
 +    Lst_Concat(slst, targs, LST_CONCLINK);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_FindDeps  --
 + *    Find implicit sources for the target described by the graph node
 + *    gn
 + *
 + * Results:
 + *    Nothing.
 + *
 + * Side Effects:
 + *    Nodes are added to the graph below the passed-in node. The nodes
 + *    are marked to have their IMPSRC variable filled in. The
 + *    PREFIX variable is set for the given node and all its
 + *    implied children.
 + *
 + * Notes:
 + *    The path found by this target is the shortest path in the
 + *    transformation graph, which may pass through non-existent targets,
 + *    to an existing target. The search continues on all paths from the
 + *    root suffix until a file is found. I.e. if there's a path
 + *    .o -> .c -> .l -> .l,v from the root and the .l,v file exists but
 + *    the .c and .l files don't, the search will branch out in
 + *    all directions from .o and again from all the nodes on the
 + *    next level until the .l,v node is encountered.
 + *
 + *-----------------------------------------------------------------------
 + */
 +
 +void
 +Suff_FindDeps(GNode *gn)
 +{
 +
 +    SuffFindDeps(gn, srclist);
 +    while (SuffRemoveSrc(srclist))
 +      continue;
 +}
 +
 +
 +/*
 + * Input:
 + *    gn              node we're dealing with
 + *
 + */
 +static void
 +SuffFindDeps(GNode *gn, Lst slst)
 +{
 +    if (gn->type & OP_DEPS_FOUND) {
 +      /*
 +       * If dependencies already found, no need to do it again...
 +       */
 +      return;
 +    } else {
 +      gn->type |= OP_DEPS_FOUND;
 +    }
 +    /*
 +     * Make sure we have these set, may get revised below.
 +     */
 +    Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
 +    Var_Set(PREFIX, gn->name, gn, 0);
 +
 +    if (DEBUG(SUFF)) {
 +      fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name);
 +    }
 +
 +    if (gn->type & OP_ARCHV) {
 +      SuffFindArchiveDeps(gn, slst);
 +    } else if (gn->type & OP_LIB) {
 +      /*
 +       * If the node is a library, it is the arch module's job to find it
 +       * and set the TARGET variable accordingly. We merely provide the
 +       * search path, assuming all libraries end in ".a" (if the suffix
 +       * hasn't been defined, there's nothing we can do for it, so we just
 +       * set the TARGET variable to the node's name in order to give it a
 +       * value).
 +       */
 +      LstNode ln;
 +      Suff    *s;
 +
 +      ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP);
 +      if (gn->suffix)
 +          gn->suffix->refCount--;
 +      if (ln != NULL) {
 +          gn->suffix = s = (Suff *)Lst_Datum(ln);
 +          gn->suffix->refCount++;
 +          Arch_FindLib(gn, s->searchPath);
 +      } else {
 +          gn->suffix = NULL;
 +          Var_Set(TARGET, gn->name, gn, 0);
 +      }
 +      /*
 +       * Because a library (-lfoo) target doesn't follow the standard
 +       * filesystem conventions, we don't set the regular variables for
 +       * the thing. .PREFIX is simply made empty...
 +       */
 +      Var_Set(PREFIX, "", gn, 0);
 +    } else {
 +      SuffFindNormalDeps(gn, slst);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_SetNull --
 + *    Define which suffix is the null suffix.
 + *
 + * Input:
 + *    name            Name of null suffix
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    'suffNull' is altered.
 + *
 + * Notes:
 + *    Need to handle the changing of the null suffix gracefully so the
 + *    old transformation rules don't just go away.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_SetNull(char *name)
 +{
 +    Suff    *s;
 +    LstNode ln;
 +
 +    ln = Lst_Find(sufflist, name, SuffSuffHasNameP);
 +    if (ln != NULL) {
 +      s = (Suff *)Lst_Datum(ln);
 +      if (suffNull != NULL) {
 +          suffNull->flags &= ~SUFF_NULL;
 +      }
 +      s->flags |= SUFF_NULL;
 +      /*
 +       * XXX: Here's where the transformation mangling would take place
 +       */
 +      suffNull = s;
 +    } else {
 +      Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.",
 +                   name);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Suff_Init --
 + *    Initialize suffixes module
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    Many
 + *-----------------------------------------------------------------------
 + */
 +void
 +Suff_Init(void)
 +{
 +    sufflist = Lst_Init(FALSE);
 +#ifdef CLEANUP
 +    suffClean = Lst_Init(FALSE);
 +#endif
 +    srclist = Lst_Init(FALSE);
 +    transforms = Lst_Init(FALSE);
 +
 +    sNum = 0;
 +    /*
 +     * Create null suffix for single-suffix rules (POSIX). The thing doesn't
 +     * actually go on the suffix list or everyone will think that's its
 +     * suffix.
 +     */
 +    emptySuff = suffNull = bmake_malloc(sizeof(Suff));
 +
 +    suffNull->name =              bmake_strdup("");
 +    suffNull->nameLen =     0;
 +    suffNull->searchPath =  Lst_Init(FALSE);
 +    Dir_Concat(suffNull->searchPath, dirSearchPath);
 +    suffNull->children =    Lst_Init(FALSE);
 +    suffNull->parents =           Lst_Init(FALSE);
 +    suffNull->ref =       Lst_Init(FALSE);
 +    suffNull->sNum =              sNum++;
 +    suffNull->flags =             SUFF_NULL;
 +    suffNull->refCount =    1;
 +
 +}
 +
 +
 +/*-
 + *----------------------------------------------------------------------
 + * Suff_End --
 + *    Cleanup the this module
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The memory is free'd.
 + *----------------------------------------------------------------------
 + */
 +
 +void
 +Suff_End(void)
 +{
 +#ifdef CLEANUP
 +    Lst_Destroy(sufflist, SuffFree);
 +    Lst_Destroy(suffClean, SuffFree);
 +    if (suffNull)
 +      SuffFree(suffNull);
 +    Lst_Destroy(srclist, NULL);
 +    Lst_Destroy(transforms, NULL);
 +#endif
 +}
 +
 +
 +/********************* DEBUGGING FUNCTIONS **********************/
 +
 +static int SuffPrintName(void *s, void *dummy)
 +{
 +    fprintf(debug_file, "%s ", ((Suff *)s)->name);
 +    return (dummy ? 0 : 0);
 +}
 +
 +static int
 +SuffPrintSuff(void *sp, void *dummy)
 +{
 +    Suff    *s = (Suff *)sp;
 +    int           flags;
 +    int           flag;
 +
 +    fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount);
 +
 +    flags = s->flags;
 +    if (flags) {
 +      fputs(" (", debug_file);
 +      while (flags) {
 +          flag = 1 << (ffs(flags) - 1);
 +          flags &= ~flag;
 +          switch (flag) {
 +              case SUFF_NULL:
 +                  fprintf(debug_file, "NULL");
 +                  break;
 +              case SUFF_INCLUDE:
 +                  fprintf(debug_file, "INCLUDE");
 +                  break;
 +              case SUFF_LIBRARY:
 +                  fprintf(debug_file, "LIBRARY");
 +                  break;
 +          }
 +          fputc(flags ? '|' : ')', debug_file);
 +      }
 +    }
 +    fputc('\n', debug_file);
 +    fprintf(debug_file, "#\tTo: ");
 +    Lst_ForEach(s->parents, SuffPrintName, NULL);
 +    fputc('\n', debug_file);
 +    fprintf(debug_file, "#\tFrom: ");
 +    Lst_ForEach(s->children, SuffPrintName, NULL);
 +    fputc('\n', debug_file);
 +    fprintf(debug_file, "#\tSearch Path: ");
 +    Dir_PrintPath(s->searchPath);
 +    fputc('\n', debug_file);
 +    return (dummy ? 0 : 0);
 +}
 +
 +static int
 +SuffPrintTrans(void *tp, void *dummy)
 +{
 +    GNode   *t = (GNode *)tp;
 +
 +    fprintf(debug_file, "%-16s: ", t->name);
 +    Targ_PrintType(t->type);
 +    fputc('\n', debug_file);
 +    Lst_ForEach(t->commands, Targ_PrintCmd, NULL);
 +    fputc('\n', debug_file);
 +    return(dummy ? 0 : 0);
 +}
 +
 +void
 +Suff_PrintAll(void)
 +{
 +    fprintf(debug_file, "#*** Suffixes:\n");
 +    Lst_ForEach(sufflist, SuffPrintSuff, NULL);
 +
 +    fprintf(debug_file, "#*** Transformations:\n");
 +    Lst_ForEach(transforms, SuffPrintTrans, NULL);
 +}
index 1636aafc11e55d275b33da59e4e6101f7d1b2d48,0000000000000000000000000000000000000000..e2af496afabec13dfc225e91577c170c330fdd56
mode 100644,000000..100644
--- /dev/null
@@@ -1,2 -1,0 +1,20 @@@
 +
++:D expanded when var set
++true
++TRUE
++:U expanded when var undef
++true
++TRUE
++:D skipped if var undef
++
++:U skipped when var set
++is set
++:? only lhs when value true
++true
++TRUE
++:? only rhs when value false
++false
++FALSE
++do not evaluate or expand :? if discarding
++is set
 +exit status 0
index 4b4a931a37ac355a1687142b6b865891a1c0d1c9,0000000000000000000000000000000000000000..14b52d22c3e6be4f1033ec6c1d3ae5e9a77b8703
mode 100644,000000..100644
--- /dev/null
@@@ -1,8 -1,0 +1,42 @@@
- # $Id: varmisc.mk,v 1.2 2014/08/30 22:25:14 sjg Exp $
++# $Id: varmisc.mk,v 1.5 2015/10/12 17:10:48 sjg Exp $
 +#
 +# Miscellaneous variable tests.
 +
- all: unmatched_var_paren
++all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none
 +
 +unmatched_var_paren:
 +      @echo ${foo::=foo-text}
++
++True = ${echo true >&2:L:sh}TRUE
++False= ${echo false >&2:L:sh}FALSE
++
++VSET= is set
++.undef UNDEF
++
++U_false:
++      @echo :U skipped when var set
++      @echo ${VSET:U${False}}
++
++D_false:
++      @echo :D skipped if var undef
++      @echo ${UNDEF:D${False}}
++
++U_true:
++      @echo :U expanded when var undef
++      @echo ${UNDEF:U${True}}
++
++D_true:
++      @echo :D expanded when var set
++      @echo ${VSET:D${True}}
++
++Q_lhs:
++      @echo :? only lhs when value true
++      @echo ${1:L:?${True}:${False}}
++
++Q_rhs:
++      @echo :? only rhs when value false
++      @echo ${0:L:?${True}:${False}}
++
++NQ_none:
++      @echo do not evaluate or expand :? if discarding
++      @echo ${VSET:U${1:L:?${True}:${False}}}
index e15c301cf9cfc4fb4d2869d8a1519c0e262667e3,0000000000000000000000000000000000000000..6fd3825066e380d98c685dcaff8e4bcf47194498
mode 100644,000000..100644
--- /dev/null
@@@ -1,4194 -1,0 +1,4207 @@@
- /*    $NetBSD: var.c,v 1.192 2015/05/05 21:51:09 sjg Exp $    */
++/*    $NetBSD: var.c,v 1.199 2015/10/20 21:30:57 sjg Exp $    */
 +
 +/*
 + * Copyright (c) 1988, 1989, 1990, 1993
 + *    The Regents of the University of California.  All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +/*
 + * Copyright (c) 1989 by Berkeley Softworks
 + * All rights reserved.
 + *
 + * This code is derived from software contributed to Berkeley by
 + * Adam de Boor.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + * 1. Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + * 2. Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in the
 + *    documentation and/or other materials provided with the distribution.
 + * 3. All advertising materials mentioning features or use of this software
 + *    must display the following acknowledgement:
 + *    This product includes software developed by the University of
 + *    California, Berkeley and its contributors.
 + * 4. Neither the name of the University nor the names of its contributors
 + *    may be used to endorse or promote products derived from this software
 + *    without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 + * SUCH DAMAGE.
 + */
 +
 +#ifndef MAKE_NATIVE
- static char rcsid[] = "$NetBSD: var.c,v 1.192 2015/05/05 21:51:09 sjg Exp $";
++static char rcsid[] = "$NetBSD: var.c,v 1.199 2015/10/20 21:30:57 sjg Exp $";
 +#else
 +#include <sys/cdefs.h>
 +#ifndef lint
 +#if 0
 +static char sccsid[] = "@(#)var.c     8.3 (Berkeley) 3/19/94";
 +#else
- __RCSID("$NetBSD: var.c,v 1.192 2015/05/05 21:51:09 sjg Exp $");
++__RCSID("$NetBSD: var.c,v 1.199 2015/10/20 21:30:57 sjg Exp $");
 +#endif
 +#endif /* not lint */
 +#endif
 +
 +/*-
 + * var.c --
 + *    Variable-handling functions
 + *
 + * Interface:
 + *    Var_Set             Set the value of a variable in the given
 + *                        context. The variable is created if it doesn't
 + *                        yet exist. The value and variable name need not
 + *                        be preserved.
 + *
 + *    Var_Append          Append more characters to an existing variable
 + *                        in the given context. The variable needn't
 + *                        exist already -- it will be created if it doesn't.
 + *                        A space is placed between the old value and the
 + *                        new one.
 + *
 + *    Var_Exists          See if a variable exists.
 + *
 + *    Var_Value           Return the value of a variable in a context or
 + *                        NULL if the variable is undefined.
 + *
 + *    Var_Subst           Substitute named variable, or all variables if
 + *                        NULL in a string using
 + *                        the given context as the top-most one. If the
 + *                        third argument is non-zero, Parse_Error is
 + *                        called if any variables are undefined.
 + *
 + *    Var_Parse           Parse a variable expansion from a string and
 + *                        return the result and the number of characters
 + *                        consumed.
 + *
 + *    Var_Delete          Delete a variable in a context.
 + *
 + *    Var_Init            Initialize this module.
 + *
 + * Debugging:
 + *    Var_Dump            Print out all variables defined in the given
 + *                        context.
 + *
 + * XXX: There's a lot of duplication in these functions.
 + */
 +
 +#include    <sys/stat.h>
 +#ifndef NO_REGEX
 +#include    <sys/types.h>
 +#include    <regex.h>
 +#endif
 +#include    <ctype.h>
 +#include    <inttypes.h>
 +#include    <stdlib.h>
 +#include    <limits.h>
 +#include    <time.h>
 +
 +#include    "make.h"
 +#include    "buf.h"
 +#include    "dir.h"
 +#include    "job.h"
++#include    "metachar.h"
 +
 +extern int makelevel;
 +/*
 + * This lets us tell if we have replaced the original environ
 + * (which we cannot free).
 + */
 +char **savedEnv = NULL;
 +
 +/*
 + * This is a harmless return value for Var_Parse that can be used by Var_Subst
 + * to determine if there was an error in parsing -- easier than returning
 + * a flag, as things outside this module don't give a hoot.
 + */
 +char  var_Error[] = "";
 +
 +/*
 + * Similar to var_Error, but returned when the 'errnum' flag for Var_Parse is
 + * set false. Why not just use a constant? Well, gcc likes to condense
 + * identical string instances...
 + */
 +static char   varNoError[] = "";
 +
 +/*
 + * Internally, variables are contained in four different contexts.
 + *    1) the environment. They may not be changed. If an environment
 + *        variable is appended-to, the result is placed in the global
 + *        context.
 + *    2) the global context. Variables set in the Makefile are located in
 + *        the global context. It is the penultimate context searched when
 + *        substituting.
 + *    3) the command-line context. All variables set on the command line
 + *       are placed in this context. They are UNALTERABLE once placed here.
 + *    4) the local context. Each target has associated with it a context
 + *       list. On this list are located the structures describing such
 + *       local variables as $(@) and $(*)
 + * The four contexts are searched in the reverse order from which they are
 + * listed.
 + */
 +GNode          *VAR_INTERNAL; /* variables from make itself */
 +GNode          *VAR_GLOBAL;   /* variables from the makefile */
 +GNode          *VAR_CMD;      /* variables defined on the command-line */
 +
 +#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 */
 +
 +typedef struct Var {
 +    char          *name;      /* the variable's name */
 +    Buffer      val;          /* its value */
 +    int                 flags;        /* miscellaneous status flags */
 +#define VAR_IN_USE    1           /* Variable's value currently being used.
 +                                   * Used to avoid recursion */
 +#define VAR_FROM_ENV  2           /* Variable comes from the environment */
 +#define VAR_JUNK      4           /* Variable is a junk variable that
 +                                   * should be destroyed when done with
 +                                   * it. Used by Var_Parse for undefined,
 +                                   * modified variables */
 +#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_EXPORTED  16          /* Variable is exported */
 +#define VAR_REEXPORT  32          /* Indicate if var needs re-export.
 +                                   * This would be true if it contains $'s
 +                                   */
 +#define VAR_FROM_CMD  64          /* Variable came from command line */
 +}  Var;
 +
 +/*
 + * Exporting vars is expensive so skip it if we can
 + */
 +#define VAR_EXPORTED_NONE     0
 +#define VAR_EXPORTED_YES      1
 +#define VAR_EXPORTED_ALL      2
 +static int var_exportedVars = VAR_EXPORTED_NONE;
 +/*
 + * We pass this to Var_Export when doing the initial export
 + * or after updating an exported var.
 + */
 +#define VAR_EXPORT_PARENT 1
 +
 +/* Var*Pattern flags */
 +#define VAR_SUB_GLOBAL        0x01    /* Apply substitution globally */
 +#define VAR_SUB_ONE   0x02    /* Apply substitution to one word */
 +#define VAR_SUB_MATCHED       0x04    /* There was a match */
 +#define VAR_MATCH_START       0x08    /* Match at start of word */
 +#define VAR_MATCH_END 0x10    /* Match at end of word */
 +#define VAR_NOSUBST   0x20    /* don't expand vars in VarGetPattern */
 +
 +/* Var_Set flags */
 +#define VAR_NO_EXPORT 0x01    /* do not export */
 +
 +typedef struct {
 +    /*
 +     * The following fields are set by Var_Parse() when it
 +     * encounters modifiers that need to keep state for use by
 +     * subsequent modifiers within the same variable expansion.
 +     */
 +    Byte      varSpace;       /* Word separator in expansions */
 +    Boolean   oneBigWord;     /* TRUE if we will treat the variable as a
 +                               * single big word, even if it contains
 +                               * embedded spaces (as opposed to the
 +                               * usual behaviour of treating it as
 +                               * several space-separated words). */
 +} Var_Parse_State;
 +
 +/* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/",
 + * to VarSYSVMatch() for ":lhs=rhs". */
 +typedef struct {
 +    const char   *lhs;            /* String to match */
 +    int                 leftLen; /* Length of string */
 +    const char   *rhs;            /* Replacement string (w/ &'s removed) */
 +    int                 rightLen; /* Length of replacement */
 +    int                 flags;
 +} VarPattern;
 +
 +/* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */
 +typedef struct {
 +    GNode     *ctxt;          /* variable context */
 +    char      *tvar;          /* name of temp var */
 +    int               tvarLen;
 +    char      *str;           /* string to expand */
 +    int               strLen;
 +    int               errnum;         /* errnum for not defined */
 +} VarLoop_t;
 +
 +#ifndef NO_REGEX
 +/* struct passed as 'void *' to VarRESubstitute() for ":C///" */
 +typedef struct {
 +    regex_t      re;
 +    int                  nsub;
 +    regmatch_t          *matches;
 +    char        *replace;
 +    int                  flags;
 +} VarREPattern;
 +#endif
 +
 +/* struct passed to VarSelectWords() for ":[start..end]" */
 +typedef struct {
 +    int               start;          /* first word to select */
 +    int               end;            /* last word to select */
 +} VarSelectWords_t;
 +
 +static Var *VarFind(const char *, GNode *, int);
 +static void VarAdd(const char *, const char *, GNode *);
 +static Boolean VarHead(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static Boolean VarTail(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static Boolean VarSuffix(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static Boolean VarRoot(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static Boolean VarMatch(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +#ifdef SYSVVARSUB
 +static Boolean VarSYSVMatch(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +#endif
 +static Boolean VarNoMatch(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +#ifndef NO_REGEX
 +static void VarREError(int, regex_t *, const char *);
 +static Boolean VarRESubstitute(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +#endif
 +static Boolean VarSubstitute(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static Boolean VarLoopExpand(GNode *, Var_Parse_State *,
 +                      char *, Boolean, Buffer *, void *);
 +static char *VarGetPattern(GNode *, Var_Parse_State *,
 +                         int, const char **, int, int *, int *,
 +                         VarPattern *);
 +static char *VarQuote(char *);
 +static char *VarHash(char *);
 +static char *VarModify(GNode *, Var_Parse_State *,
 +    const char *,
 +    Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *),
 +    void *);
 +static char *VarOrder(const char *, const char);
 +static char *VarUniq(const char *);
 +static int VarWordCompare(const void *, const void *);
 +static void VarPrintVar(void *);
 +
 +#define BROPEN        '{'
 +#define BRCLOSE       '}'
 +#define PROPEN        '('
 +#define PRCLOSE       ')'
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarFind --
 + *    Find the given variable in the given context and any other contexts
 + *    indicated.
 + *
 + * Input:
 + *    name            name to find
 + *    ctxt            context in which to find it
 + *    flags           FIND_GLOBAL set means to look in the
 + *                    VAR_GLOBAL context as well. FIND_CMD set means
 + *                    to look in the VAR_CMD context also. FIND_ENV
 + *                    set means to look in the environment
 + *
 + * Results:
 + *    A pointer to the structure describing the desired variable or
 + *    NULL if the variable does not exist.
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +static Var *
 +VarFind(const char *name, GNode *ctxt, int flags)
 +{
 +    Hash_Entry                *var;
 +    Var                       *v;
 +
 +      /*
 +       * If the variable name begins with a '.', it could very well be one of
 +       * the local ones.  We check the name against all the local variables
 +       * and substitute the short version in for 'name' if it matches one of
 +       * them.
 +       */
 +      if (*name == '.' && isupper((unsigned char) name[1]))
 +              switch (name[1]) {
 +              case 'A':
 +                      if (!strcmp(name, ".ALLSRC"))
 +                              name = ALLSRC;
 +                      if (!strcmp(name, ".ARCHIVE"))
 +                              name = ARCHIVE;
 +                      break;
 +              case 'I':
 +                      if (!strcmp(name, ".IMPSRC"))
 +                              name = IMPSRC;
 +                      break;
 +              case 'M':
 +                      if (!strcmp(name, ".MEMBER"))
 +                              name = MEMBER;
 +                      break;
 +              case 'O':
 +                      if (!strcmp(name, ".OODATE"))
 +                              name = OODATE;
 +                      break;
 +              case 'P':
 +                      if (!strcmp(name, ".PREFIX"))
 +                              name = PREFIX;
 +                      break;
 +              case 'T':
 +                      if (!strcmp(name, ".TARGET"))
 +                              name = TARGET;
 +                      break;
 +              }
 +#ifdef notyet
 +    /* for compatibility with gmake */
 +    if (name[0] == '^' && name[1] == '\0')
 +          name = ALLSRC;
 +#endif
 +
 +    /*
 +     * First look for the variable in the given context. If it's not there,
 +     * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order,
 +     * depending on the FIND_* flags in 'flags'
 +     */
 +    var = Hash_FindEntry(&ctxt->context, name);
 +
 +    if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) {
 +      var = Hash_FindEntry(&VAR_CMD->context, name);
 +    }
 +    if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) &&
 +      (ctxt != VAR_GLOBAL))
 +    {
 +      var = Hash_FindEntry(&VAR_GLOBAL->context, name);
 +      if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
 +          /* VAR_INTERNAL is subordinate to VAR_GLOBAL */
 +          var = Hash_FindEntry(&VAR_INTERNAL->context, name);
 +      }
 +    }
 +    if ((var == NULL) && (flags & FIND_ENV)) {
 +      char *env;
 +
 +      if ((env = getenv(name)) != NULL) {
 +          int         len;
 +
 +          v = bmake_malloc(sizeof(Var));
 +          v->name = bmake_strdup(name);
 +
 +          len = strlen(env);
 +
 +          Buf_Init(&v->val, len + 1);
 +          Buf_AddBytes(&v->val, len, env);
 +
 +          v->flags = VAR_FROM_ENV;
 +          return (v);
 +      } else if (checkEnvFirst && (flags & FIND_GLOBAL) &&
 +                 (ctxt != VAR_GLOBAL))
 +      {
 +          var = Hash_FindEntry(&VAR_GLOBAL->context, name);
 +          if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
 +              var = Hash_FindEntry(&VAR_INTERNAL->context, name);
 +          }
 +          if (var == NULL) {
 +              return NULL;
 +          } else {
 +              return ((Var *)Hash_GetValue(var));
 +          }
 +      } else {
 +          return NULL;
 +      }
 +    } else if (var == NULL) {
 +      return NULL;
 +    } else {
 +      return ((Var *)Hash_GetValue(var));
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarFreeEnv  --
 + *    If the variable is an environment variable, free it
 + *
 + * Input:
 + *    v               the variable
 + *    destroy         true if the value buffer should be destroyed.
 + *
 + * Results:
 + *    1 if it is an environment variable 0 ow.
 + *
 + * Side Effects:
 + *    The variable is free'ed if it is an environent variable.
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarFreeEnv(Var *v, Boolean destroy)
 +{
 +    if ((v->flags & VAR_FROM_ENV) == 0)
 +      return FALSE;
 +    free(v->name);
 +    Buf_Destroy(&v->val, destroy);
 +    free(v);
 +    return TRUE;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarAdd  --
 + *    Add a new variable of name name and value val to the given context
 + *
 + * Input:
 + *    name            name of variable to add
 + *    val             value to set it to
 + *    ctxt            context in which to set it
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The new variable is placed at the front of the given context
 + *    The name and val arguments are duplicated so they may
 + *    safely be freed.
 + *-----------------------------------------------------------------------
 + */
 +static void
 +VarAdd(const char *name, const char *val, GNode *ctxt)
 +{
 +    Var         *v;
 +    int                 len;
 +    Hash_Entry    *h;
 +
 +    v = bmake_malloc(sizeof(Var));
 +
 +    len = val ? strlen(val) : 0;
 +    Buf_Init(&v->val, len+1);
 +    Buf_AddBytes(&v->val, len, val);
 +
 +    v->flags = 0;
 +
 +    h = Hash_CreateEntry(&ctxt->context, name, NULL);
 +    Hash_SetValue(h, v);
 +    v->name = h->name;
 +    if (DEBUG(VAR)) {
 +      fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Delete --
 + *    Remove a variable from a context.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    The Var structure is removed and freed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +void
 +Var_Delete(const char *name, GNode *ctxt)
 +{
 +    Hash_Entry          *ln;
 +    char *cp;
 +    
 +    if (strchr(name, '$')) {
-       cp = Var_Subst(NULL, name, VAR_GLOBAL, 0);
++      cp = Var_Subst(NULL, name, VAR_GLOBAL, FALSE, TRUE);
 +    } else {
 +      cp = (char *)name;
 +    }
 +    ln = Hash_FindEntry(&ctxt->context, cp);
 +    if (DEBUG(VAR)) {
 +      fprintf(debug_file, "%s:delete %s%s\n",
 +          ctxt->name, cp, ln ? "" : " (not found)");
 +    }
 +    if (cp != name) {
 +      free(cp);
 +    }
 +    if (ln != NULL) {
 +      Var       *v;
 +
 +      v = (Var *)Hash_GetValue(ln);
 +      if ((v->flags & VAR_EXPORTED)) {
 +          unsetenv(v->name);
 +      }
 +      if (strcmp(MAKE_EXPORTED, v->name) == 0) {
 +          var_exportedVars = VAR_EXPORTED_NONE;
 +      }
 +      if (v->name != ln->name)
 +              free(v->name);
 +      Hash_DeleteEntry(&ctxt->context, ln);
 +      Buf_Destroy(&v->val, TRUE);
 +      free(v);
 +    }
 +}
 +
 +
 +/*
 + * Export a var.
 + * We ignore make internal variables (those which start with '.')
 + * Also we jump through some hoops to avoid calling setenv
 + * more than necessary since it can leak.
 + * We only manipulate flags of vars if 'parent' is set.
 + */
 +static int
 +Var_Export1(const char *name, int parent)
 +{
 +    char tmp[BUFSIZ];
 +    Var *v;
 +    char *val = NULL;
 +    int n;
 +
 +    if (*name == '.')
 +      return 0;                       /* skip internals */
 +    if (!name[1]) {
 +      /*
 +       * A single char.
 +       * If it is one of the vars that should only appear in
 +       * local context, skip it, else we can get Var_Subst
 +       * into a loop.
 +       */
 +      switch (name[0]) {
 +      case '@':
 +      case '%':
 +      case '*':
 +      case '!':
 +          return 0;
 +      }
 +    }
 +    v = VarFind(name, VAR_GLOBAL, 0);
 +    if (v == NULL) {
 +      return 0;
 +    }
 +    if (!parent &&
 +      (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
 +      return 0;                       /* nothing to do */
 +    }
 +    val = Buf_GetAll(&v->val, NULL);
 +    if (strchr(val, '$')) {
 +      if (parent) {
 +          /*
 +           * Flag this as something we need to re-export.
 +           * No point actually exporting it now though,
 +           * the child can do it at the last minute.
 +           */
 +          v->flags |= (VAR_EXPORTED|VAR_REEXPORT);
 +          return 1;
 +      }
 +      if (v->flags & VAR_IN_USE) {
 +          /*
 +           * We recursed while exporting in a child.
 +           * This isn't going to end well, just skip it.
 +           */
 +          return 0;
 +      }
 +      n = snprintf(tmp, sizeof(tmp), "${%s}", name);
 +      if (n < (int)sizeof(tmp)) {
-           val = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++          val = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +          setenv(name, val, 1);
 +          free(val);
 +      }
 +    } else {
 +      if (parent) {
 +          v->flags &= ~VAR_REEXPORT;  /* once will do */
 +      }
 +      if (parent || !(v->flags & VAR_EXPORTED)) {
 +          setenv(name, val, 1);
 +      }
 +    }
 +    /*
 +     * This is so Var_Set knows to call Var_Export again...
 +     */
 +    if (parent) {
 +      v->flags |= VAR_EXPORTED;
 +    }
 +    return 1;
 +}
 +
 +/*
 + * This gets called from our children.
 + */
 +void
 +Var_ExportVars(void)
 +{
 +    char tmp[BUFSIZ];
 +    Hash_Entry                *var;
 +    Hash_Search       state;
 +    Var *v;
 +    char *val;
 +    int n;
 +
 +    /*
 +     * Several make's support this sort of mechanism for tracking
 +     * recursion - but each uses a different name.
 +     * We allow the makefiles to update MAKELEVEL and ensure
 +     * children see a correctly incremented value.
 +     */
 +    snprintf(tmp, sizeof(tmp), "%d", makelevel + 1);
 +    setenv(MAKE_LEVEL_ENV, tmp, 1);
 +
 +    if (VAR_EXPORTED_NONE == var_exportedVars)
 +      return;
 +
 +    if (VAR_EXPORTED_ALL == var_exportedVars) {
 +      /*
 +       * Ouch! This is crazy...
 +       */
 +      for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state);
 +           var != NULL;
 +           var = Hash_EnumNext(&state)) {
 +          v = (Var *)Hash_GetValue(var);
 +          Var_Export1(v->name, 0);
 +      }
 +      return;
 +    }
 +    /*
 +     * We have a number of exported vars,
 +     */
 +    n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
 +    if (n < (int)sizeof(tmp)) {
 +      char **av;
 +      char *as;
 +      int ac;
 +      int i;
 +
-       val = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++      val = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +      av = brk_string(val, &ac, FALSE, &as);
 +      for (i = 0; i < ac; i++) {
 +          Var_Export1(av[i], 0);
 +      }
 +      free(val);
 +      free(as);
 +      free(av);
 +    }
 +}
 +
 +/*
 + * This is called when .export is seen or
 + * .MAKE.EXPORTED is modified.
 + * It is also called when any exported var is modified.
 + */
 +void
 +Var_Export(char *str, int isExport)
 +{
 +    char *name;
 +    char *val;
 +    char **av;
 +    char *as;
 +    int track;
 +    int ac;
 +    int i;
 +
 +    if (isExport && (!str || !str[0])) {
 +      var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
 +      return;
 +    }
 +
 +    if (strncmp(str, "-env", 4) == 0) {
 +      track = 0;
 +      str += 4;
 +    } else {
 +      track = VAR_EXPORT_PARENT;
 +    }
-     val = Var_Subst(NULL, str, VAR_GLOBAL, 0);
++    val = Var_Subst(NULL, str, VAR_GLOBAL, FALSE, TRUE);
 +    av = brk_string(val, &ac, FALSE, &as);
 +    for (i = 0; i < ac; i++) {
 +      name = av[i];
 +      if (!name[1]) {
 +          /*
 +           * A single char.
 +           * If it is one of the vars that should only appear in
 +           * local context, skip it, else we can get Var_Subst
 +           * into a loop.
 +           */
 +          switch (name[0]) {
 +          case '@':
 +          case '%':
 +          case '*':
 +          case '!':
 +              continue;
 +          }
 +      }
 +      if (Var_Export1(name, track)) {
 +          if (VAR_EXPORTED_ALL != var_exportedVars)
 +              var_exportedVars = VAR_EXPORTED_YES;
 +          if (isExport && track) {
 +              Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL);
 +          }
 +      }
 +    }
 +    free(val);
 +    free(as);
 +    free(av);
 +}
 +
 +
 +/*
 + * This is called when .unexport[-env] is seen.
 + */
 +extern char **environ;
 +
 +void
 +Var_UnExport(char *str)
 +{
 +    char tmp[BUFSIZ];
 +    char *vlist;
 +    char *cp;
 +    Boolean unexport_env;
 +    int n;
 +
 +    if (!str || !str[0]) {
 +      return;                         /* assert? */
 +    }
 +
 +    vlist = NULL;
 +
 +    str += 8;
 +    unexport_env = (strncmp(str, "-env", 4) == 0);
 +    if (unexport_env) {
 +      char **newenv;
 +
 +      cp = getenv(MAKE_LEVEL_ENV);    /* we should preserve this */
 +      if (environ == savedEnv) {
 +          /* we have been here before! */
 +          newenv = bmake_realloc(environ, 2 * sizeof(char *));
 +      } else {
 +          if (savedEnv) {
 +              free(savedEnv);
 +              savedEnv = NULL;
 +          }
 +          newenv = bmake_malloc(2 * sizeof(char *));
 +      }
 +      if (!newenv)
 +          return;
 +      /* Note: we cannot safely free() the original environ. */
 +      environ = savedEnv = newenv;
 +      newenv[0] = NULL;
 +      newenv[1] = NULL;
 +      setenv(MAKE_LEVEL_ENV, cp, 1);
 +    } else {
 +      for (; *str != '\n' && isspace((unsigned char) *str); str++)
 +          continue;
 +      if (str[0] && str[0] != '\n') {
 +          vlist = str;
 +      }
 +    }
 +
 +    if (!vlist) {
 +      /* Using .MAKE.EXPORTED */
 +      n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
 +      if (n < (int)sizeof(tmp)) {
-           vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++          vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +      }
 +    }
 +    if (vlist) {
 +      Var *v;
 +      char **av;
 +      char *as;
 +      int ac;
 +      int i;
 +
 +      av = brk_string(vlist, &ac, FALSE, &as);
 +      for (i = 0; i < ac; i++) {
 +          v = VarFind(av[i], VAR_GLOBAL, 0);
 +          if (!v)
 +              continue;
 +          if (!unexport_env &&
 +              (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
 +              unsetenv(v->name);
 +          }
 +          v->flags &= ~(VAR_EXPORTED|VAR_REEXPORT);
 +          /*
 +           * If we are unexporting a list,
 +           * remove each one from .MAKE.EXPORTED.
 +           * If we are removing them all,
 +           * just delete .MAKE.EXPORTED below.
 +           */
 +          if (vlist == str) {
 +              n = snprintf(tmp, sizeof(tmp),
 +                           "${" MAKE_EXPORTED ":N%s}", v->name);
 +              if (n < (int)sizeof(tmp)) {
-                   cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
++                  cp = Var_Subst(NULL, tmp, VAR_GLOBAL, FALSE, TRUE);
 +                  Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL, 0);
 +                  free(cp);
 +              }
 +          }
 +      }
 +      free(as);
 +      free(av);
 +      if (vlist != str) {
 +          Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
 +          free(vlist);
 +      }
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Set --
 + *    Set the variable name to the value val in the given context.
 + *
 + * Input:
 + *    name            name of variable to set
 + *    val             value to give to the variable
 + *    ctxt            context in which to set it
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    If the variable doesn't yet exist, a new record is created for it.
 + *    Else the old value is freed and the new one stuck in its place
 + *
 + * Notes:
 + *    The variable is searched for only in its context before being
 + *    created in that context. I.e. if the context is VAR_GLOBAL,
 + *    only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only
 + *    VAR_CMD->context is searched. This is done to avoid the literally
 + *    thousands of unnecessary strcmp's that used to be done to
 + *    set, say, $(@) or $(<).
 + *    If the context is VAR_GLOBAL though, we check if the variable
 + *    was set in VAR_CMD from the command line and skip it if so.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Var_Set(const char *name, const char *val, GNode *ctxt, int flags)
 +{
 +    Var   *v;
 +    char *expanded_name = NULL;
 +
 +    /*
 +     * We only look for a variable in the given context since anything set
 +     * here will override anything in a lower context, so there's not much
 +     * point in searching them all just to save a bit of memory...
 +     */
 +    if (strchr(name, '$') != NULL) {
-       expanded_name = Var_Subst(NULL, name, ctxt, 0);
++      expanded_name = Var_Subst(NULL, name, ctxt, FALSE, TRUE);
 +      if (expanded_name[0] == 0) {
 +          if (DEBUG(VAR)) {
 +              fprintf(debug_file, "Var_Set(\"%s\", \"%s\", ...) "
 +                      "name expands to empty string - ignored\n",
 +                      name, val);
 +          }
 +          free(expanded_name);
 +          return;
 +      }
 +      name = expanded_name;
 +    }
 +    if (ctxt == VAR_GLOBAL) {
 +      v = VarFind(name, VAR_CMD, 0);
 +      if (v != NULL) {
 +          if ((v->flags & VAR_FROM_CMD)) {
 +              if (DEBUG(VAR)) {
 +                  fprintf(debug_file, "%s:%s = %s ignored!\n", ctxt->name, name, val);
 +              }
 +              goto out;
 +          }
 +          VarFreeEnv(v, TRUE);
 +      }
 +    }
 +    v = VarFind(name, ctxt, 0);
 +    if (v == NULL) {
 +      if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
 +          /*
 +           * This var would normally prevent the same name being added
 +           * to VAR_GLOBAL, so delete it from there if needed.
 +           * Otherwise -V name may show the wrong value.
 +           */
 +          Var_Delete(name, VAR_GLOBAL);
 +      }
 +      VarAdd(name, val, ctxt);
 +    } else {
 +      Buf_Empty(&v->val);
 +      Buf_AddBytes(&v->val, strlen(val), val);
 +
 +      if (DEBUG(VAR)) {
 +          fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
 +      }
 +      if ((v->flags & VAR_EXPORTED)) {
 +          Var_Export1(name, VAR_EXPORT_PARENT);
 +      }
 +    }
 +    /*
 +     * Any variables given on the command line are automatically exported
 +     * to the environment (as per POSIX standard)
 +     */
 +    if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
 +      if (v == NULL) {
 +          /* we just added it */
 +          v = VarFind(name, ctxt, 0);
 +      }
 +      if (v != NULL)
 +          v->flags |= VAR_FROM_CMD;
 +      /*
 +       * If requested, don't export these in the environment
 +       * individually.  We still put them in MAKEOVERRIDES so
 +       * that the command-line settings continue to override
 +       * Makefile settings.
 +       */
 +      if (varNoExportEnv != TRUE)
 +          setenv(name, val, 1);
 +
 +      Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
 +    }
 +
 +
 + out:
-     if (expanded_name != NULL)
-       free(expanded_name);
++    free(expanded_name);
 +    if (v != NULL)
 +      VarFreeEnv(v, TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Append --
 + *    The variable of the given name has the given value appended to it in
 + *    the given context.
 + *
 + * Input:
 + *    name            name of variable to modify
 + *    val             String to append to it
 + *    ctxt            Context in which this should occur
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    If the variable doesn't exist, it is created. Else the strings
 + *    are concatenated (with a space in between).
 + *
 + * Notes:
 + *    Only if the variable is being sought in the global context is the
 + *    environment searched.
 + *    XXX: Knows its calling circumstances in that if called with ctxt
 + *    an actual target, it will only search that context since only
 + *    a local variable could be being appended to. This is actually
 + *    a big win and must be tolerated.
 + *-----------------------------------------------------------------------
 + */
 +void
 +Var_Append(const char *name, const char *val, GNode *ctxt)
 +{
 +    Var                  *v;
 +    Hash_Entry           *h;
 +    char *expanded_name = NULL;
 +
 +    if (strchr(name, '$') != NULL) {
-       expanded_name = Var_Subst(NULL, name, ctxt, 0);
++      expanded_name = Var_Subst(NULL, name, ctxt, FALSE, TRUE);
 +      if (expanded_name[0] == 0) {
 +          if (DEBUG(VAR)) {
 +              fprintf(debug_file, "Var_Append(\"%s\", \"%s\", ...) "
 +                      "name expands to empty string - ignored\n",
 +                      name, val);
 +          }
 +          free(expanded_name);
 +          return;
 +      }
 +      name = expanded_name;
 +    }
 +
 +    v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0);
 +
 +    if (v == NULL) {
 +      VarAdd(name, val, ctxt);
 +    } else {
 +      Buf_AddByte(&v->val, ' ');
 +      Buf_AddBytes(&v->val, strlen(val), val);
 +
 +      if (DEBUG(VAR)) {
 +          fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name,
 +                 Buf_GetAll(&v->val, NULL));
 +      }
 +
 +      if (v->flags & VAR_FROM_ENV) {
 +          /*
 +           * If the original variable came from the environment, we
 +           * have to install it in the global context (we could place
 +           * it in the environment, but then we should provide a way to
 +           * export other variables...)
 +           */
 +          v->flags &= ~VAR_FROM_ENV;
 +          h = Hash_CreateEntry(&ctxt->context, name, NULL);
 +          Hash_SetValue(h, v);
 +      }
 +    }
-     if (expanded_name != NULL)
-       free(expanded_name);
++    free(expanded_name);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Exists --
 + *    See if the given variable exists.
 + *
 + * Input:
 + *    name            Variable to find
 + *    ctxt            Context in which to start search
 + *
 + * Results:
 + *    TRUE if it does, FALSE if it doesn't
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +Boolean
 +Var_Exists(const char *name, GNode *ctxt)
 +{
 +    Var                 *v;
 +    char          *cp;
 +
 +    if ((cp = strchr(name, '$')) != NULL) {
-       cp = Var_Subst(NULL, name, ctxt, FALSE);
++      cp = Var_Subst(NULL, name, ctxt, FALSE, TRUE);
 +    }
 +    v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV);
-     if (cp != NULL) {
-       free(cp);
-     }
++    free(cp);
 +    if (v == NULL) {
 +      return(FALSE);
 +    } else {
 +      (void)VarFreeEnv(v, TRUE);
 +    }
 +    return(TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Value --
 + *    Return the value of the named variable in the given context
 + *
 + * Input:
 + *    name            name to find
 + *    ctxt            context in which to search for it
 + *
 + * Results:
 + *    The value if the variable exists, NULL if it doesn't
 + *
 + * Side Effects:
 + *    None
 + *-----------------------------------------------------------------------
 + */
 +char *
 +Var_Value(const char *name, GNode *ctxt, char **frp)
 +{
 +    Var            *v;
 +
 +    v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
 +    *frp = NULL;
 +    if (v != NULL) {
 +      char *p = (Buf_GetAll(&v->val, NULL));
 +      if (VarFreeEnv(v, FALSE))
 +          *frp = p;
 +      return p;
 +    } else {
 +      return NULL;
 +    }
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarHead --
 + *    Remove the tail of the given word and place the result in the given
 + *    buffer.
 + *
 + * Input:
 + *    word            Word to trim
 + *    addSpace        True if need to add a space to the buffer
 + *                    before sticking in the head
 + *    buf             Buffer in which to store it
 + *
 + * Results:
 + *    TRUE if characters were added to the buffer (a space needs to be
 + *    added to the buffer before the next word).
 + *
 + * Side Effects:
 + *    The trimmed word is added to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarHead(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +      char *word, Boolean addSpace, Buffer *buf,
 +      void *dummy)
 +{
 +    char *slash;
 +
 +    slash = strrchr(word, '/');
 +    if (slash != NULL) {
 +      if (addSpace && vpstate->varSpace) {
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      }
 +      *slash = '\0';
 +      Buf_AddBytes(buf, strlen(word), word);
 +      *slash = '/';
 +      return (TRUE);
 +    } else {
 +      /*
 +       * If no directory part, give . (q.v. the POSIX standard)
 +       */
 +      if (addSpace && vpstate->varSpace)
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      Buf_AddByte(buf, '.');
 +    }
 +    return(dummy ? TRUE : TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarTail --
 + *    Remove the head of the given word and place the result in the given
 + *    buffer.
 + *
 + * Input:
 + *    word            Word to trim
 + *    addSpace        True if need to add a space to the buffer
 + *                    before adding the tail
 + *    buf             Buffer in which to store it
 + *
 + * Results:
 + *    TRUE if characters were added to the buffer (a space needs to be
 + *    added to the buffer before the next word).
 + *
 + * Side Effects:
 + *    The trimmed word is added to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarTail(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +      char *word, Boolean addSpace, Buffer *buf,
 +      void *dummy)
 +{
 +    char *slash;
 +
 +    if (addSpace && vpstate->varSpace) {
 +      Buf_AddByte(buf, vpstate->varSpace);
 +    }
 +
 +    slash = strrchr(word, '/');
 +    if (slash != NULL) {
 +      *slash++ = '\0';
 +      Buf_AddBytes(buf, strlen(slash), slash);
 +      slash[-1] = '/';
 +    } else {
 +      Buf_AddBytes(buf, strlen(word), word);
 +    }
 +    return (dummy ? TRUE : TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarSuffix --
 + *    Place the suffix of the given word in the given buffer.
 + *
 + * Input:
 + *    word            Word to trim
 + *    addSpace        TRUE if need to add a space before placing the
 + *                    suffix in the buffer
 + *    buf             Buffer in which to store it
 + *
 + * Results:
 + *    TRUE if characters were added to the buffer (a space needs to be
 + *    added to the buffer before the next word).
 + *
 + * Side Effects:
 + *    The suffix from the word is placed in the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarSuffix(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +        char *word, Boolean addSpace, Buffer *buf,
 +        void *dummy)
 +{
 +    char *dot;
 +
 +    dot = strrchr(word, '.');
 +    if (dot != NULL) {
 +      if (addSpace && vpstate->varSpace) {
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      }
 +      *dot++ = '\0';
 +      Buf_AddBytes(buf, strlen(dot), dot);
 +      dot[-1] = '.';
 +      addSpace = TRUE;
 +    }
 +    return (dummy ? addSpace : addSpace);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarRoot --
 + *    Remove the suffix of the given word and place the result in the
 + *    buffer.
 + *
 + * Input:
 + *    word            Word to trim
 + *    addSpace        TRUE if need to add a space to the buffer
 + *                    before placing the root in it
 + *    buf             Buffer in which to store it
 + *
 + * Results:
 + *    TRUE if characters were added to the buffer (a space needs to be
 + *    added to the buffer before the next word).
 + *
 + * Side Effects:
 + *    The trimmed word is added to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarRoot(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +      char *word, Boolean addSpace, Buffer *buf,
 +      void *dummy)
 +{
 +    char *dot;
 +
 +    if (addSpace && vpstate->varSpace) {
 +      Buf_AddByte(buf, vpstate->varSpace);
 +    }
 +
 +    dot = strrchr(word, '.');
 +    if (dot != NULL) {
 +      *dot = '\0';
 +      Buf_AddBytes(buf, strlen(word), word);
 +      *dot = '.';
 +    } else {
 +      Buf_AddBytes(buf, strlen(word), word);
 +    }
 +    return (dummy ? TRUE : TRUE);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarMatch --
 + *    Place the word in the buffer if it matches the given pattern.
 + *    Callback function for VarModify to implement the :M modifier.
 + *
 + * Input:
 + *    word            Word to examine
 + *    addSpace        TRUE if need to add a space to the buffer
 + *                    before adding the word, if it matches
 + *    buf             Buffer in which to store it
 + *    pattern         Pattern the word must match
 + *
 + * Results:
 + *    TRUE if a space should be placed in the buffer before the next
 + *    word.
 + *
 + * Side Effects:
 + *    The word may be copied to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +       char *word, Boolean addSpace, Buffer *buf,
 +       void *pattern)
 +{
 +    if (DEBUG(VAR))
 +      fprintf(debug_file, "VarMatch [%s] [%s]\n", word, (char *)pattern);
 +    if (Str_Match(word, (char *)pattern)) {
 +      if (addSpace && vpstate->varSpace) {
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      }
 +      addSpace = TRUE;
 +      Buf_AddBytes(buf, strlen(word), word);
 +    }
 +    return(addSpace);
 +}
 +
 +#ifdef SYSVVARSUB
 +/*-
 + *-----------------------------------------------------------------------
 + * VarSYSVMatch --
 + *    Place the word in the buffer if it matches the given pattern.
 + *    Callback function for VarModify to implement the System V %
 + *    modifiers.
 + *
 + * Input:
 + *    word            Word to examine
 + *    addSpace        TRUE if need to add a space to the buffer
 + *                    before adding the word, if it matches
 + *    buf             Buffer in which to store it
 + *    patp            Pattern the word must match
 + *
 + * Results:
 + *    TRUE if a space should be placed in the buffer before the next
 + *    word.
 + *
 + * Side Effects:
 + *    The word may be copied to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarSYSVMatch(GNode *ctx, Var_Parse_State *vpstate,
 +           char *word, Boolean addSpace, Buffer *buf,
 +           void *patp)
 +{
 +    int len;
 +    char *ptr;
 +    VarPattern          *pat = (VarPattern *)patp;
 +    char *varexp;
 +
 +    if (addSpace && vpstate->varSpace)
 +      Buf_AddByte(buf, vpstate->varSpace);
 +
 +    addSpace = TRUE;
 +
 +    if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) {
-         varexp = Var_Subst(NULL, pat->rhs, ctx, 0);
++        varexp = Var_Subst(NULL, pat->rhs, ctx, FALSE, TRUE);
 +      Str_SYSVSubst(buf, varexp, ptr, len);
 +      free(varexp);
 +    } else {
 +      Buf_AddBytes(buf, strlen(word), word);
 +    }
 +
 +    return(addSpace);
 +}
 +#endif
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarNoMatch --
 + *    Place the word in the buffer if it doesn't match the given pattern.
 + *    Callback function for VarModify to implement the :N modifier.
 + *
 + * Input:
 + *    word            Word to examine
 + *    addSpace        TRUE if need to add a space to the buffer
 + *                    before adding the word, if it matches
 + *    buf             Buffer in which to store it
 + *    pattern         Pattern the word must match
 + *
 + * Results:
 + *    TRUE if a space should be placed in the buffer before the next
 + *    word.
 + *
 + * Side Effects:
 + *    The word may be copied to the buffer.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarNoMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +         char *word, Boolean addSpace, Buffer *buf,
 +         void *pattern)
 +{
 +    if (!Str_Match(word, (char *)pattern)) {
 +      if (addSpace && vpstate->varSpace) {
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      }
 +      addSpace = TRUE;
 +      Buf_AddBytes(buf, strlen(word), word);
 +    }
 +    return(addSpace);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarSubstitute --
 + *    Perform a string-substitution on the given word, placing the
 + *    result in the passed buffer.
 + *
 + * Input:
 + *    word            Word to modify
 + *    addSpace        True if space should be added before
 + *                    other characters
 + *    buf             Buffer for result
 + *    patternp        Pattern for substitution
 + *
 + * Results:
 + *    TRUE if a space is needed before more characters are added.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarSubstitute(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +            char *word, Boolean addSpace, Buffer *buf,
 +            void *patternp)
 +{
 +    int       wordLen;    /* Length of word */
 +    char      *cp;        /* General pointer */
 +    VarPattern        *pattern = (VarPattern *)patternp;
 +
 +    wordLen = strlen(word);
 +    if ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) !=
 +      (VAR_SUB_ONE|VAR_SUB_MATCHED)) {
 +      /*
 +       * Still substituting -- break it down into simple anchored cases
 +       * and if none of them fits, perform the general substitution case.
 +       */
 +      if ((pattern->flags & VAR_MATCH_START) &&
 +          (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) {
 +              /*
 +               * Anchored at start and beginning of word matches pattern
 +               */
 +              if ((pattern->flags & VAR_MATCH_END) &&
 +                  (wordLen == pattern->leftLen)) {
 +                      /*
 +                       * Also anchored at end and matches to the end (word
 +                       * is same length as pattern) add space and rhs only
 +                       * if rhs is non-null.
 +                       */
 +                      if (pattern->rightLen != 0) {
 +                          if (addSpace && vpstate->varSpace) {
 +                              Buf_AddByte(buf, vpstate->varSpace);
 +                          }
 +                          addSpace = TRUE;
 +                          Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
 +                      }
 +                      pattern->flags |= VAR_SUB_MATCHED;
 +              } else if (pattern->flags & VAR_MATCH_END) {
 +                  /*
 +                   * Doesn't match to end -- copy word wholesale
 +                   */
 +                  goto nosub;
 +              } else {
 +                  /*
 +                   * Matches at start but need to copy in trailing characters
 +                   */
 +                  if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){
 +                      if (addSpace && vpstate->varSpace) {
 +                          Buf_AddByte(buf, vpstate->varSpace);
 +                      }
 +                      addSpace = TRUE;
 +                  }
 +                  Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
 +                  Buf_AddBytes(buf, wordLen - pattern->leftLen,
 +                               (word + pattern->leftLen));
 +                  pattern->flags |= VAR_SUB_MATCHED;
 +              }
 +      } else if (pattern->flags & VAR_MATCH_START) {
 +          /*
 +           * Had to match at start of word and didn't -- copy whole word.
 +           */
 +          goto nosub;
 +      } else if (pattern->flags & VAR_MATCH_END) {
 +          /*
 +           * Anchored at end, Find only place match could occur (leftLen
 +           * characters from the end of the word) and see if it does. Note
 +           * that because the $ will be left at the end of the lhs, we have
 +           * to use strncmp.
 +           */
 +          cp = word + (wordLen - pattern->leftLen);
 +          if ((cp >= word) &&
 +              (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) {
 +              /*
 +               * Match found. If we will place characters in the buffer,
 +               * add a space before hand as indicated by addSpace, then
 +               * stuff in the initial, unmatched part of the word followed
 +               * by the right-hand-side.
 +               */
 +              if (((cp - word) + pattern->rightLen) != 0) {
 +                  if (addSpace && vpstate->varSpace) {
 +                      Buf_AddByte(buf, vpstate->varSpace);
 +                  }
 +                  addSpace = TRUE;
 +              }
 +              Buf_AddBytes(buf, cp - word, word);
 +              Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
 +              pattern->flags |= VAR_SUB_MATCHED;
 +          } else {
 +              /*
 +               * Had to match at end and didn't. Copy entire word.
 +               */
 +              goto nosub;
 +          }
 +      } else {
 +          /*
 +           * Pattern is unanchored: search for the pattern in the word using
 +           * String_FindSubstring, copying unmatched portions and the
 +           * right-hand-side for each match found, handling non-global
 +           * substitutions correctly, etc. When the loop is done, any
 +           * remaining part of the word (word and wordLen are adjusted
 +           * accordingly through the loop) is copied straight into the
 +           * buffer.
 +           * addSpace is set FALSE as soon as a space is added to the
 +           * buffer.
 +           */
 +          Boolean done;
 +          int origSize;
 +
 +          done = FALSE;
 +          origSize = Buf_Size(buf);
 +          while (!done) {
 +              cp = Str_FindSubstring(word, pattern->lhs);
 +              if (cp != NULL) {
 +                  if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
 +                      Buf_AddByte(buf, vpstate->varSpace);
 +                      addSpace = FALSE;
 +                  }
 +                  Buf_AddBytes(buf, cp-word, word);
 +                  Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
 +                  wordLen -= (cp - word) + pattern->leftLen;
 +                  word = cp + pattern->leftLen;
 +                  if (wordLen == 0) {
 +                      done = TRUE;
 +                  }
 +                  if ((pattern->flags & VAR_SUB_GLOBAL) == 0) {
 +                      done = TRUE;
 +                  }
 +                  pattern->flags |= VAR_SUB_MATCHED;
 +              } else {
 +                  done = TRUE;
 +              }
 +          }
 +          if (wordLen != 0) {
 +              if (addSpace && vpstate->varSpace) {
 +                  Buf_AddByte(buf, vpstate->varSpace);
 +              }
 +              Buf_AddBytes(buf, wordLen, word);
 +          }
 +          /*
 +           * If added characters to the buffer, need to add a space
 +           * before we add any more. If we didn't add any, just return
 +           * the previous value of addSpace.
 +           */
 +          return ((Buf_Size(buf) != origSize) || addSpace);
 +      }
 +      return (addSpace);
 +    }
 + nosub:
 +    if (addSpace && vpstate->varSpace) {
 +      Buf_AddByte(buf, vpstate->varSpace);
 +    }
 +    Buf_AddBytes(buf, wordLen, word);
 +    return(TRUE);
 +}
 +
 +#ifndef NO_REGEX
 +/*-
 + *-----------------------------------------------------------------------
 + * VarREError --
 + *    Print the error caused by a regcomp or regexec call.
 + *
 + * Results:
 + *    None.
 + *
 + * Side Effects:
 + *    An error gets printed.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static void
 +VarREError(int errnum, regex_t *pat, const char *str)
 +{
 +    char *errbuf;
 +    int errlen;
 +
 +    errlen = regerror(errnum, pat, 0, 0);
 +    errbuf = bmake_malloc(errlen);
 +    regerror(errnum, pat, errbuf, errlen);
 +    Error("%s: %s", str, errbuf);
 +    free(errbuf);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarRESubstitute --
 + *    Perform a regex substitution on the given word, placing the
 + *    result in the passed buffer.
 + *
 + * Results:
 + *    TRUE if a space is needed before more characters are added.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarRESubstitute(GNode *ctx MAKE_ATTR_UNUSED,
 +              Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
 +              char *word, Boolean addSpace, Buffer *buf,
 +              void *patternp)
 +{
 +    VarREPattern *pat;
 +    int xrv;
 +    char *wp;
 +    char *rp;
 +    int added;
 +    int flags = 0;
 +
 +#define MAYBE_ADD_SPACE()             \
 +      if (addSpace && !added)         \
 +          Buf_AddByte(buf, ' ');      \
 +      added = 1
 +
 +    added = 0;
 +    wp = word;
 +    pat = patternp;
 +
 +    if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) ==
 +      (VAR_SUB_ONE|VAR_SUB_MATCHED))
 +      xrv = REG_NOMATCH;
 +    else {
 +    tryagain:
 +      xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags);
 +    }
 +
 +    switch (xrv) {
 +    case 0:
 +      pat->flags |= VAR_SUB_MATCHED;
 +      if (pat->matches[0].rm_so > 0) {
 +          MAYBE_ADD_SPACE();
 +          Buf_AddBytes(buf, pat->matches[0].rm_so, wp);
 +      }
 +
 +      for (rp = pat->replace; *rp; rp++) {
 +          if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) {
 +              MAYBE_ADD_SPACE();
 +              Buf_AddByte(buf,rp[1]);
 +              rp++;
 +          }
 +          else if ((*rp == '&') ||
 +              ((*rp == '\\') && isdigit((unsigned char)rp[1]))) {
 +              int n;
 +              const char *subbuf;
 +              int sublen;
 +              char errstr[3];
 +
 +              if (*rp == '&') {
 +                  n = 0;
 +                  errstr[0] = '&';
 +                  errstr[1] = '\0';
 +              } else {
 +                  n = rp[1] - '0';
 +                  errstr[0] = '\\';
 +                  errstr[1] = rp[1];
 +                  errstr[2] = '\0';
 +                  rp++;
 +              }
 +
 +              if (n > pat->nsub) {
 +                  Error("No subexpression %s", &errstr[0]);
 +                  subbuf = "";
 +                  sublen = 0;
 +              } else if ((pat->matches[n].rm_so == -1) &&
 +                         (pat->matches[n].rm_eo == -1)) {
 +                  Error("No match for subexpression %s", &errstr[0]);
 +                  subbuf = "";
 +                  sublen = 0;
 +              } else {
 +                  subbuf = wp + pat->matches[n].rm_so;
 +                  sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so;
 +              }
 +
 +              if (sublen > 0) {
 +                  MAYBE_ADD_SPACE();
 +                  Buf_AddBytes(buf, sublen, subbuf);
 +              }
 +          } else {
 +              MAYBE_ADD_SPACE();
 +              Buf_AddByte(buf, *rp);
 +          }
 +      }
 +      wp += pat->matches[0].rm_eo;
 +      if (pat->flags & VAR_SUB_GLOBAL) {
 +          flags |= REG_NOTBOL;
 +          if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) {
 +              MAYBE_ADD_SPACE();
 +              Buf_AddByte(buf, *wp);
 +              wp++;
 +
 +          }
 +          if (*wp)
 +              goto tryagain;
 +      }
 +      if (*wp) {
 +          MAYBE_ADD_SPACE();
 +          Buf_AddBytes(buf, strlen(wp), wp);
 +      }
 +      break;
 +    default:
 +      VarREError(xrv, &pat->re, "Unexpected regex error");
 +       /* fall through */
 +    case REG_NOMATCH:
 +      if (*wp) {
 +          MAYBE_ADD_SPACE();
 +          Buf_AddBytes(buf,strlen(wp),wp);
 +      }
 +      break;
 +    }
 +    return(addSpace||added);
 +}
 +#endif
 +
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarLoopExpand --
 + *    Implements the :@<temp>@<string>@ modifier of ODE make.
 + *    We set the temp variable named in pattern.lhs to word and expand
 + *    pattern.rhs storing the result in the passed buffer.
 + *
 + * Input:
 + *    word            Word to modify
 + *    addSpace        True if space should be added before
 + *                    other characters
 + *    buf             Buffer for result
 + *    pattern         Datafor substitution
 + *
 + * Results:
 + *    TRUE if a space is needed before more characters are added.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static Boolean
 +VarLoopExpand(GNode *ctx MAKE_ATTR_UNUSED,
 +            Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
 +            char *word, Boolean addSpace, Buffer *buf,
 +            void *loopp)
 +{
 +    VarLoop_t *loop = (VarLoop_t *)loopp;
 +    char *s;
 +    int slen;
 +
 +    if (word && *word) {
 +        Var_Set(loop->tvar, word, loop->ctxt, VAR_NO_EXPORT);
-         s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum);
++        s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum, TRUE);
 +        if (s != NULL && *s != '\0') {
 +            if (addSpace && *s != '\n')
 +                Buf_AddByte(buf, ' ');
 +            Buf_AddBytes(buf, (slen = strlen(s)), s);
 +            addSpace = (slen > 0 && s[slen - 1] != '\n');
 +            free(s);
 +        }
 +    }
 +    return addSpace;
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarSelectWords --
 + *    Implements the :[start..end] modifier.
 + *    This is a special case of VarModify since we want to be able
 + *    to scan the list backwards if start > end.
 + *
 + * Input:
 + *    str             String whose words should be trimmed
 + *    seldata         words to select
 + *
 + * Results:
 + *    A string of all the words selected.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarSelectWords(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +             const char *str, VarSelectWords_t *seldata)
 +{
 +    Buffer      buf;              /* Buffer for the new string */
 +    Boolean     addSpace;         /* TRUE if need to add a space to the
 +                                   * buffer before adding the trimmed
 +                                   * word */
 +    char **av;                            /* word list */
 +    char *as;                     /* word list memory */
 +    int ac, i;
 +    int start, end, step;
 +
 +    Buf_Init(&buf, 0);
 +    addSpace = FALSE;
 +
 +    if (vpstate->oneBigWord) {
 +      /* fake what brk_string() would do if there were only one word */
 +      ac = 1;
 +      av = bmake_malloc((ac + 1) * sizeof(char *));
 +      as = bmake_strdup(str);
 +      av[0] = as;
 +      av[1] = NULL;
 +    } else {
 +      av = brk_string(str, &ac, FALSE, &as);
 +    }
 +
 +    /*
 +     * Now sanitize seldata.
 +     * If seldata->start or seldata->end are negative, convert them to
 +     * the positive equivalents (-1 gets converted to argc, -2 gets
 +     * converted to (argc-1), etc.).
 +     */
 +    if (seldata->start < 0)
 +      seldata->start = ac + seldata->start + 1;
 +    if (seldata->end < 0)
 +      seldata->end = ac + seldata->end + 1;
 +
 +    /*
 +     * We avoid scanning more of the list than we need to.
 +     */
 +    if (seldata->start > seldata->end) {
 +      start = MIN(ac, seldata->start) - 1;
 +      end = MAX(0, seldata->end - 1);
 +      step = -1;
 +    } else {
 +      start = MAX(0, seldata->start - 1);
 +      end = MIN(ac, seldata->end);
 +      step = 1;
 +    }
 +
 +    for (i = start;
 +       (step < 0 && i >= end) || (step > 0 && i < end);
 +       i += step) {
 +      if (av[i] && *av[i]) {
 +          if (addSpace && vpstate->varSpace) {
 +              Buf_AddByte(&buf, vpstate->varSpace);
 +          }
 +          Buf_AddBytes(&buf, strlen(av[i]), av[i]);
 +          addSpace = TRUE;
 +      }
 +    }
 +
 +    free(as);
 +    free(av);
 +
 +    return Buf_Destroy(&buf, FALSE);
 +}
 +
 +
 +/*-
 + * VarRealpath --
 + *    Replace each word with the result of realpath()
 + *    if successful.
 + */
 +static Boolean
 +VarRealpath(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
 +          char *word, Boolean addSpace, Buffer *buf,
 +          void *patternp MAKE_ATTR_UNUSED)
 +{
 +      struct stat st;
 +      char rbuf[MAXPATHLEN];
 +      char *rp;
 +                          
 +      if (addSpace && vpstate->varSpace) {
 +          Buf_AddByte(buf, vpstate->varSpace);
 +      }
 +      addSpace = TRUE;
 +      rp = realpath(word, rbuf);
 +      if (rp && *rp == '/' && stat(rp, &st) == 0)
 +              word = rp;
 +      
 +      Buf_AddBytes(buf, strlen(word), word);
 +      return(addSpace);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarModify --
 + *    Modify each of the words of the passed string using the given
 + *    function. Used to implement all modifiers.
 + *
 + * Input:
 + *    str             String whose words should be trimmed
 + *    modProc         Function to use to modify them
 + *    datum           Datum to pass it
 + *
 + * Results:
 + *    A string of all the words modified appropriately.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarModify(GNode *ctx, Var_Parse_State *vpstate,
 +    const char *str,
 +    Boolean (*modProc)(GNode *, Var_Parse_State *, char *,
 +                     Boolean, Buffer *, void *),
 +    void *datum)
 +{
 +    Buffer      buf;              /* Buffer for the new string */
 +    Boolean     addSpace;         /* TRUE if need to add a space to the
 +                                   * buffer before adding the trimmed
 +                                   * word */
 +    char **av;                            /* word list */
 +    char *as;                     /* word list memory */
 +    int ac, i;
 +
 +    Buf_Init(&buf, 0);
 +    addSpace = FALSE;
 +
 +    if (vpstate->oneBigWord) {
 +      /* fake what brk_string() would do if there were only one word */
 +      ac = 1;
 +      av = bmake_malloc((ac + 1) * sizeof(char *));
 +      as = bmake_strdup(str);
 +      av[0] = as;
 +      av[1] = NULL;
 +    } else {
 +      av = brk_string(str, &ac, FALSE, &as);
 +    }
 +
 +    for (i = 0; i < ac; i++) {
 +      addSpace = (*modProc)(ctx, vpstate, av[i], addSpace, &buf, datum);
 +    }
 +
 +    free(as);
 +    free(av);
 +
 +    return Buf_Destroy(&buf, FALSE);
 +}
 +
 +
 +static int
 +VarWordCompare(const void *a, const void *b)
 +{
 +      int r = strcmp(*(const char * const *)a, *(const char * const *)b);
 +      return r;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarOrder --
 + *    Order the words in the string.
 + *
 + * Input:
 + *    str             String whose words should be sorted.
 + *    otype           How to order: s - sort, x - random.
 + *
 + * Results:
 + *    A string containing the words ordered.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarOrder(const char *str, const char otype)
 +{
 +    Buffer      buf;              /* Buffer for the new string */
 +    char **av;                            /* word list [first word does not count] */
 +    char *as;                     /* word list memory */
 +    int ac, i;
 +
 +    Buf_Init(&buf, 0);
 +
 +    av = brk_string(str, &ac, FALSE, &as);
 +
 +    if (ac > 0)
 +      switch (otype) {
 +      case 's':       /* sort alphabetically */
 +          qsort(av, ac, sizeof(char *), VarWordCompare);
 +          break;
 +      case 'x':       /* randomize */
 +      {
 +          int rndidx;
 +          char *t;
 +
 +          /*
 +           * We will use [ac..2] range for mod factors. This will produce
 +           * random numbers in [(ac-1)..0] interval, and minimal
 +           * reasonable value for mod factor is 2 (the mod 1 will produce
 +           * 0 with probability 1).
 +           */
 +          for (i = ac-1; i > 0; i--) {
 +              rndidx = random() % (i + 1);
 +              if (i != rndidx) {
 +                  t = av[i];
 +                  av[i] = av[rndidx];
 +                  av[rndidx] = t;
 +              }
 +          }
 +      }
 +      } /* end of switch */
 +
 +    for (i = 0; i < ac; i++) {
 +      Buf_AddBytes(&buf, strlen(av[i]), av[i]);
 +      if (i != ac - 1)
 +          Buf_AddByte(&buf, ' ');
 +    }
 +
 +    free(as);
 +    free(av);
 +
 +    return Buf_Destroy(&buf, FALSE);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarUniq --
 + *    Remove adjacent duplicate words.
 + *
 + * Input:
 + *    str             String whose words should be sorted
 + *
 + * Results:
 + *    A string containing the resulting words.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarUniq(const char *str)
 +{
 +    Buffer      buf;              /* Buffer for new string */
 +    char      **av;               /* List of words to affect */
 +    char       *as;               /* Word list memory */
 +    int         ac, i, j;
 +
 +    Buf_Init(&buf, 0);
 +    av = brk_string(str, &ac, FALSE, &as);
 +
 +    if (ac > 1) {
 +      for (j = 0, i = 1; i < ac; i++)
 +          if (strcmp(av[i], av[j]) != 0 && (++j != i))
 +              av[j] = av[i];
 +      ac = j + 1;
 +    }
 +
 +    for (i = 0; i < ac; i++) {
 +      Buf_AddBytes(&buf, strlen(av[i]), av[i]);
 +      if (i != ac - 1)
 +          Buf_AddByte(&buf, ' ');
 +    }
 +
 +    free(as);
 +    free(av);
 +
 +    return Buf_Destroy(&buf, FALSE);
 +}
 +
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarGetPattern --
 + *    Pass through the tstr looking for 1) escaped delimiters,
 + *    '$'s and backslashes (place the escaped character in
 + *    uninterpreted) and 2) unescaped $'s that aren't before
 + *    the delimiter (expand the variable substitution unless flags
 + *    has VAR_NOSUBST set).
 + *    Return the expanded string or NULL if the delimiter was missing
 + *    If pattern is specified, handle escaped ampersands, and replace
 + *    unescaped ampersands with the lhs of the pattern.
 + *
 + * Results:
 + *    A string of all the words modified appropriately.
 + *    If length is specified, return the string length of the buffer
 + *    If flags is specified and the last character of the pattern is a
 + *    $ set the VAR_MATCH_END bit of flags.
 + *
 + * Side Effects:
 + *    None.
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarGetPattern(GNode *ctxt, Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
 +            int errnum, const char **tstr, int delim, int *flags,
 +            int *length, VarPattern *pattern)
 +{
 +    const char *cp;
 +    char *rstr;
 +    Buffer buf;
 +    int junk;
 +
 +    Buf_Init(&buf, 0);
 +    if (length == NULL)
 +      length = &junk;
 +
 +#define IS_A_MATCH(cp, delim) \
 +    ((cp[0] == '\\') && ((cp[1] == delim) ||  \
 +     (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&'))))
 +
 +    /*
 +     * Skim through until the matching delimiter is found;
 +     * pick up variable substitutions on the way. Also allow
 +     * backslashes to quote the delimiter, $, and \, but don't
 +     * touch other backslashes.
 +     */
 +    for (cp = *tstr; *cp && (*cp != delim); cp++) {
 +      if (IS_A_MATCH(cp, delim)) {
 +          Buf_AddByte(&buf, cp[1]);
 +          cp++;
 +      } else if (*cp == '$') {
 +          if (cp[1] == delim) {
 +              if (flags == NULL)
 +                  Buf_AddByte(&buf, *cp);
 +              else
 +                  /*
 +                   * Unescaped $ at end of pattern => anchor
 +                   * pattern at end.
 +                   */
 +                  *flags |= VAR_MATCH_END;
 +          } else {
 +              if (flags == NULL || (*flags & VAR_NOSUBST) == 0) {
 +                  char   *cp2;
 +                  int     len;
 +                  void   *freeIt;
 +
 +                  /*
 +                   * If unescaped dollar sign not before the
 +                   * delimiter, assume it's a variable
 +                   * substitution and recurse.
 +                   */
-                   cp2 = Var_Parse(cp, ctxt, errnum, &len, &freeIt);
++                  cp2 = Var_Parse(cp, ctxt, errnum, TRUE, &len, &freeIt);
 +                  Buf_AddBytes(&buf, strlen(cp2), cp2);
-                   if (freeIt)
-                       free(freeIt);
++                  free(freeIt);
 +                  cp += len - 1;
 +              } else {
 +                  const char *cp2 = &cp[1];
 +
 +                  if (*cp2 == PROPEN || *cp2 == BROPEN) {
 +                      /*
 +                       * Find the end of this variable reference
 +                       * and suck it in without further ado.
 +                       * It will be interperated later.
 +                       */
 +                      int have = *cp2;
 +                      int want = (*cp2 == PROPEN) ? PRCLOSE : BRCLOSE;
 +                      int depth = 1;
 +
 +                      for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) {
 +                          if (cp2[-1] != '\\') {
 +                              if (*cp2 == have)
 +                                  ++depth;
 +                              if (*cp2 == want)
 +                                  --depth;
 +                          }
 +                      }
 +                      Buf_AddBytes(&buf, cp2 - cp, cp);
 +                      cp = --cp2;
 +                  } else
 +                      Buf_AddByte(&buf, *cp);
 +              }
 +          }
 +      }
 +      else if (pattern && *cp == '&')
 +          Buf_AddBytes(&buf, pattern->leftLen, pattern->lhs);
 +      else
 +          Buf_AddByte(&buf, *cp);
 +    }
 +
 +    if (*cp != delim) {
 +      *tstr = cp;
 +      *length = 0;
 +      return NULL;
 +    }
 +
 +    *tstr = ++cp;
 +    *length = Buf_Size(&buf);
 +    rstr = Buf_Destroy(&buf, FALSE);
 +    if (DEBUG(VAR))
 +      fprintf(debug_file, "Modifier pattern: \"%s\"\n", rstr);
 +    return rstr;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarQuote --
-  *    Quote shell meta-characters in the string
++ *    Quote shell meta-characters and space characters in the string
 + *
 + * Results:
 + *    The quoted string
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarQuote(char *str)
 +{
 +
 +    Buffer      buf;
-     /* This should cover most shells :-( */
-     static const char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~";
 +    const char        *newline;
-     size_t len, nlen;
++    size_t nlen;
 +
 +    if ((newline = Shell_GetNewline()) == NULL)
 +          newline = "\\\n";
 +    nlen = strlen(newline);
 +
 +    Buf_Init(&buf, 0);
-     while (*str != '\0') {
-       if ((len = strcspn(str, meta)) != 0) {
-           Buf_AddBytes(&buf, len, str);
-           str += len;
-       } else if (*str == '\n') {
++
++    for (; *str != '\0'; str++) {
++      if (*str == '\n') {
 +          Buf_AddBytes(&buf, nlen, newline);
-           ++str;
-       } else {
-           Buf_AddByte(&buf, '\\');
-           Buf_AddByte(&buf, *str);
-           ++str;
++          continue;
 +      }
++      if (isspace((unsigned char)*str) || ismeta((unsigned char)*str))
++          Buf_AddByte(&buf, '\\');
++      Buf_AddByte(&buf, *str);
 +    }
++
 +    str = Buf_Destroy(&buf, FALSE);
 +    if (DEBUG(VAR))
 +      fprintf(debug_file, "QuoteMeta: [%s]\n", str);
 +    return str;
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * VarHash --
 + *      Hash the string using the MurmurHash3 algorithm.
 + *      Output is computed using 32bit Little Endian arithmetic.
 + *
 + * Input:
 + *    str             String to modify
 + *
 + * Results:
 + *      Hash value of str, encoded as 8 hex digits.
 + *
 + * Side Effects:
 + *      None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +static char *
 +VarHash(char *str)
 +{
 +    static const char    hexdigits[16] = "0123456789abcdef";
 +    Buffer         buf;
 +    size_t         len, len2;
 +    unsigned char  *ustr = (unsigned char *)str;
 +    uint32_t       h, k, c1, c2;
 +
 +    h  = 0x971e137bU;
 +    c1 = 0x95543787U;
 +    c2 = 0x2ad7eb25U;
 +    len2 = strlen(str);
 +
 +    for (len = len2; len; ) {
 +      k = 0;
 +      switch (len) {
 +      default:
 +          k = (ustr[3] << 24) | (ustr[2] << 16) | (ustr[1] << 8) | ustr[0];
 +          len -= 4;
 +          ustr += 4;
 +          break;
 +      case 3:
 +          k |= (ustr[2] << 16);
 +      case 2:
 +          k |= (ustr[1] << 8);
 +      case 1:
 +          k |= ustr[0];
 +          len = 0;
 +      }
 +      c1 = c1 * 5 + 0x7b7d159cU;
 +      c2 = c2 * 5 + 0x6bce6396U;
 +      k *= c1;
 +      k = (k << 11) ^ (k >> 21);
 +      k *= c2;
 +      h = (h << 13) ^ (h >> 19);
 +      h = h * 5 + 0x52dce729U;
 +      h ^= k;
 +   }
 +   h ^= len2;
 +   h *= 0x85ebca6b;
 +   h ^= h >> 13;
 +   h *= 0xc2b2ae35;
 +   h ^= h >> 16;
 +
 +   Buf_Init(&buf, 0);
 +   for (len = 0; len < 8; ++len) {
 +       Buf_AddByte(&buf, hexdigits[h & 15]);
 +       h >>= 4;
 +   }
 +
 +   return Buf_Destroy(&buf, FALSE);
 +}
 +
 +static char *
 +VarStrftime(const char *fmt, int zulu)
 +{
 +    char buf[BUFSIZ];
 +    time_t utc;
 +
 +    time(&utc);
 +    if (!*fmt)
 +      fmt = "%c";
 +    strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&utc) : localtime(&utc));
 +    
 +    buf[sizeof(buf) - 1] = '\0';
 +    return bmake_strdup(buf);
 +}
 +
 +/*
 + * Now we need to apply any modifiers the user wants applied.
 + * These are:
 + *      :M<pattern>   words which match the given <pattern>.
 + *                    <pattern> is of the standard file
 + *                    wildcarding form.
 + *      :N<pattern>   words which do not match the given <pattern>.
 + *      :S<d><pat1><d><pat2><d>[1gW]
 + *                    Substitute <pat2> for <pat1> in the value
 + *      :C<d><pat1><d><pat2><d>[1gW]
 + *                    Substitute <pat2> for regex <pat1> in the value
 + *      :H            Substitute the head of each word
 + *      :T            Substitute the tail of each word
 + *      :E            Substitute the extension (minus '.') of
 + *                    each word
 + *      :R            Substitute the root of each word
 + *                    (pathname minus the suffix).
 + *      :O            ("Order") Alphabeticaly sort words in variable.
 + *      :Ox           ("intermiX") Randomize words in variable.
 + *      :u            ("uniq") Remove adjacent duplicate words.
 + *      :tu           Converts the variable contents to uppercase.
 + *      :tl           Converts the variable contents to lowercase.
 + *      :ts[c]        Sets varSpace - the char used to
 + *                    separate words to 'c'. If 'c' is
 + *                    omitted then no separation is used.
 + *      :tW           Treat the variable contents as a single
 + *                    word, even if it contains spaces.
 + *                    (Mnemonic: one big 'W'ord.)
 + *      :tw           Treat the variable contents as multiple
 + *                    space-separated words.
 + *                    (Mnemonic: many small 'w'ords.)
 + *      :[index]      Select a single word from the value.
 + *      :[start..end] Select multiple words from the value.
 + *      :[*] or :[0]  Select the entire value, as a single
 + *                    word.  Equivalent to :tW.
 + *      :[@]          Select the entire value, as multiple
 + *                    words.  Undoes the effect of :[*].
 + *                    Equivalent to :tw.
 + *      :[#]          Returns the number of words in the value.
 + *
 + *      :?<true-value>:<false-value>
 + *                    If the variable evaluates to true, return
 + *                    true value, else return the second value.
 + *              :lhs=rhs      Like :S, but the rhs goes to the end of
 + *                            the invocation.
 + *      :sh           Treat the current value as a command
 + *                    to be run, new value is its output.
 + * The following added so we can handle ODE makefiles.
 + *      :@<tmpvar>@<newval>@
 + *                    Assign a temporary local variable <tmpvar>
 + *                    to the current value of each word in turn
 + *                    and replace each word with the result of
 + *                    evaluating <newval>
 + *      :D<newval>    Use <newval> as value if variable defined
 + *      :U<newval>    Use <newval> as value if variable undefined
 + *      :L            Use the name of the variable as the value.
 + *      :P            Use the path of the node that has the same
 + *                    name as the variable as the value.  This
 + *                    basically includes an implied :L so that
 + *                    the common method of refering to the path
 + *                    of your dependent 'x' in a rule is to use
 + *                    the form '${x:P}'.
 + *      :!<cmd>!      Run cmd much the same as :sh run's the
 + *                    current value of the variable.
 + * The ::= modifiers, actually assign a value to the variable.
 + * Their main purpose is in supporting modifiers of .for loop
 + * iterators and other obscure uses.  They always expand to
 + * nothing.  In a target rule that would otherwise expand to an
 + * empty line they can be preceded with @: to keep make happy.
 + * Eg.
 + *
 + * foo:       .USE
 + * .for i in ${.TARGET} ${.TARGET:R}.gz
 + *    @: ${t::=$i}
 + *    @echo blah ${t:T}
 + * .endfor
 + *
 + *      ::=<str>      Assigns <str> as the new value of variable.
 + *      ::?=<str>     Assigns <str> as value of variable if
 + *                    it was not already set.
 + *      ::+=<str>     Appends <str> to variable.
 + *      ::!=<cmd>     Assigns output of <cmd> as the new value of
 + *                    variable.
 + */
 +
 +/* we now have some modifiers with long names */
 +#define STRMOD_MATCH(s, want, n) \
 +    (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':'))
 +
 +static char *
 +ApplyModifiers(char *nstr, const char *tstr,
 +             int startc, int endc,
-              Var *v, GNode *ctxt, Boolean errnum,
++             Var *v, GNode *ctxt, Boolean errnum, Boolean wantit,
 +             int *lengthPtr, void **freePtr)
 +{
 +    const char           *start;
 +    const char     *cp;       /* Secondary pointer into str (place marker
 +                               * for tstr) */
 +    char         *newStr;     /* New value to return */
 +    char          termc;      /* Character which terminated scan */
 +    int             cnt;      /* Used to count brace pairs when variable in
 +                               * in parens or braces */
 +    char      delim;
 +    int               modifier;       /* that we are processing */
 +    Var_Parse_State parsestate; /* Flags passed to helper functions */
 +
 +    delim = '\0';
 +    parsestate.oneBigWord = FALSE;
 +    parsestate.varSpace = ' ';        /* word separator */
 +
 +    start = cp = tstr;
 +
 +    while (*tstr && *tstr != endc) {
 +
 +      if (*tstr == '$') {
 +          /*
 +           * We may have some complex modifiers in a variable.
 +           */
 +          void *freeIt;
 +          char *rval;
 +          int rlen;
 +          int c;
 +
-           rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt);
++          rval = Var_Parse(tstr, ctxt, errnum, wantit, &rlen, &freeIt);
 +
 +          /*
 +           * If we have not parsed up to endc or ':',
 +           * we are not interested.
 +           */
 +          if (rval != NULL && *rval &&
 +              (c = tstr[rlen]) != '\0' &&
 +              c != ':' &&
 +              c != endc) {
-               if (freeIt)
-                   free(freeIt);
++              free(freeIt);
 +              goto apply_mods;
 +          }
 +
 +          if (DEBUG(VAR)) {
 +              fprintf(debug_file, "Got '%s' from '%.*s'%.*s\n",
 +                     rval, rlen, tstr, rlen, tstr + rlen);
 +          }
 +
 +          tstr += rlen;
 +
 +          if (rval != NULL && *rval) {
 +              int used;
 +
 +              nstr = ApplyModifiers(nstr, rval,
 +                                    0, 0,
-                                     v, ctxt, errnum, &used, freePtr);
++                                    v, ctxt, errnum, wantit, &used, freePtr);
 +              if (nstr == var_Error
 +                  || (nstr == varNoError && errnum == 0)
 +                  || strlen(rval) != (size_t) used) {
-                   if (freeIt)
-                       free(freeIt);
++                  free(freeIt);
 +                  goto out;           /* error already reported */
 +              }
 +          }
-           if (freeIt)
-               free(freeIt);
++          free(freeIt);
 +          if (*tstr == ':')
 +              tstr++;
 +          else if (!*tstr && endc) {
 +              Error("Unclosed variable specification after complex modifier (expecting '%c') for %s", endc, v->name);
 +              goto out;
 +          }
 +          continue;
 +      }
 +    apply_mods:
 +      if (DEBUG(VAR)) {
 +          fprintf(debug_file, "Applying[%s] :%c to \"%s\"\n", v->name,
 +              *tstr, nstr);
 +      }
 +      newStr = var_Error;
 +      switch ((modifier = *tstr)) {
 +      case ':':
 +          {
 +              if (tstr[1] == '=' ||
 +                  (tstr[2] == '=' &&
 +                   (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) {
 +                  /*
 +                   * "::=", "::!=", "::+=", or "::?="
 +                   */
 +                  GNode *v_ctxt;              /* context where v belongs */
 +                  const char *emsg;
 +                  char *sv_name;
 +                  VarPattern  pattern;
 +                  int how;
++                  int flags;
 +
 +                  if (v->name[0] == 0)
 +                      goto bad_modifier;
 +
 +                  v_ctxt = ctxt;
 +                  sv_name = NULL;
 +                  ++tstr;
 +                  if (v->flags & VAR_JUNK) {
 +                      /*
 +                       * We need to bmake_strdup() it incase
 +                       * VarGetPattern() recurses.
 +                       */
 +                      sv_name = v->name;
 +                      v->name = bmake_strdup(v->name);
 +                  } else if (ctxt != VAR_GLOBAL) {
 +                      Var *gv = VarFind(v->name, ctxt, 0);
 +                      if (gv == NULL)
 +                          v_ctxt = VAR_GLOBAL;
 +                      else
 +                          VarFreeEnv(gv, TRUE);
 +                  }
 +
 +                  switch ((how = *tstr)) {
 +                  case '+':
 +                  case '?':
 +                  case '!':
 +                      cp = &tstr[2];
 +                      break;
 +                  default:
 +                      cp = ++tstr;
 +                      break;
 +                  }
 +                  delim = startc == PROPEN ? PRCLOSE : BRCLOSE;
 +                  pattern.flags = 0;
 +
++                  flags = (wantit) ? 0 : VAR_NOSUBST;
 +                  pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
-                                               &cp, delim, NULL,
++                                              &cp, delim, &flags,
 +                                              &pattern.rightLen,
 +                                              NULL);
 +                  if (v->flags & VAR_JUNK) {
 +                      /* restore original name */
 +                      free(v->name);
 +                      v->name = sv_name;
 +                  }
 +                  if (pattern.rhs == NULL)
 +                      goto cleanup;
 +
 +                  termc = *--cp;
 +                  delim = '\0';
 +
-                   switch (how) {
-                   case '+':
-                       Var_Append(v->name, pattern.rhs, v_ctxt);
-                       break;
-                   case '!':
-                       newStr = Cmd_Exec(pattern.rhs, &emsg);
-                       if (emsg)
-                           Error(emsg, nstr);
-                       else
-                           Var_Set(v->name, newStr,  v_ctxt, 0);
-                       if (newStr)
++                  if (wantit) {
++                      switch (how) {
++                      case '+':
++                          Var_Append(v->name, pattern.rhs, v_ctxt);
++                          break;
++                      case '!':
++                          newStr = Cmd_Exec(pattern.rhs, &emsg);
++                          if (emsg)
++                              Error(emsg, nstr);
++                          else
++                              Var_Set(v->name, newStr,  v_ctxt, 0);
 +                          free(newStr);
-                       break;
-                   case '?':
-                       if ((v->flags & VAR_JUNK) == 0)
 +                          break;
-                       /* FALLTHROUGH */
-                   default:
-                       Var_Set(v->name, pattern.rhs, v_ctxt, 0);
-                       break;
++                      case '?':
++                          if ((v->flags & VAR_JUNK) == 0)
++                              break;
++                          /* FALLTHROUGH */
++                      default:
++                          Var_Set(v->name, pattern.rhs, v_ctxt, 0);
++                          break;
++                      }
 +                  }
 +                  free(UNCONST(pattern.rhs));
 +                  newStr = varNoError;
 +                  break;
 +              }
 +              goto default_case; /* "::<unrecognised>" */
 +          }
 +      case '@':
 +          {
 +              VarLoop_t       loop;
 +              int flags = VAR_NOSUBST;
 +
 +              cp = ++tstr;
 +              delim = '@';
 +              if ((loop.tvar = VarGetPattern(ctxt, &parsestate, errnum,
 +                                             &cp, delim,
 +                                             &flags, &loop.tvarLen,
 +                                             NULL)) == NULL)
 +                  goto cleanup;
 +
 +              if ((loop.str = VarGetPattern(ctxt, &parsestate, errnum,
 +                                            &cp, delim,
 +                                            &flags, &loop.strLen,
 +                                            NULL)) == NULL)
 +                  goto cleanup;
 +
 +              termc = *cp;
 +              delim = '\0';
 +
 +              loop.errnum = errnum;
 +              loop.ctxt = ctxt;
 +              newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
 +                                 &loop);
 +              free(loop.tvar);
 +              free(loop.str);
 +              break;
 +          }
 +      case 'D':
 +      case 'U':
 +          {
 +              Buffer  buf;            /* Buffer for patterns */
-               int         wantit;     /* want data in buffer */
++              int         wantit_;    /* want data in buffer */
 +
++              if (wantit) {
++                  if (*tstr == 'U')
++                      wantit_ = ((v->flags & VAR_JUNK) != 0);
++                  else
++                      wantit_ = ((v->flags & VAR_JUNK) == 0);
++              } else
++                  wantit_ = wantit;
 +              /*
 +               * Pass through tstr looking for 1) escaped delimiters,
 +               * '$'s and backslashes (place the escaped character in
 +               * uninterpreted) and 2) unescaped $'s that aren't before
 +               * the delimiter (expand the variable substitution).
 +               * The result is left in the Buffer buf.
 +               */
 +              Buf_Init(&buf, 0);
 +              for (cp = tstr + 1;
 +                   *cp != endc && *cp != ':' && *cp != '\0';
 +                   cp++) {
 +                  if ((*cp == '\\') &&
 +                      ((cp[1] == ':') ||
 +                       (cp[1] == '$') ||
 +                       (cp[1] == endc) ||
 +                       (cp[1] == '\\')))
 +                      {
 +                          Buf_AddByte(&buf, cp[1]);
 +                          cp++;
 +                      } else if (*cp == '$') {
 +                          /*
 +                           * If unescaped dollar sign, assume it's a
 +                           * variable substitution and recurse.
 +                           */
 +                          char    *cp2;
 +                          int     len;
 +                          void    *freeIt;
 +
-                           cp2 = Var_Parse(cp, ctxt, errnum, &len, &freeIt);
++                          cp2 = Var_Parse(cp, ctxt, errnum, wantit_, &len, &freeIt);
 +                          Buf_AddBytes(&buf, strlen(cp2), cp2);
-                           if (freeIt)
-                               free(freeIt);
++                          free(freeIt);
 +                          cp += len - 1;
 +                      } else {
 +                          Buf_AddByte(&buf, *cp);
 +                      }
 +              }
 +
 +              termc = *cp;
 +
-               if (*tstr == 'U')
-                   wantit = ((v->flags & VAR_JUNK) != 0);
-               else
-                   wantit = ((v->flags & VAR_JUNK) == 0);
 +              if ((v->flags & VAR_JUNK) != 0)
 +                  v->flags |= VAR_KEEP;
-               if (wantit) {
++              if (wantit_) {
 +                  newStr = Buf_Destroy(&buf, FALSE);
 +              } else {
 +                  newStr = nstr;
 +                  Buf_Destroy(&buf, TRUE);
 +              }
 +              break;
 +          }
 +      case 'L':
 +          {
 +              if ((v->flags & VAR_JUNK) != 0)
 +                  v->flags |= VAR_KEEP;
 +              newStr = bmake_strdup(v->name);
 +              cp = ++tstr;
 +              termc = *tstr;
 +              break;
 +          }
 +      case 'P':
 +          {
 +              GNode *gn;
 +
 +              if ((v->flags & VAR_JUNK) != 0)
 +                  v->flags |= VAR_KEEP;
 +              gn = Targ_FindNode(v->name, TARG_NOCREATE);
 +              if (gn == NULL || gn->type & OP_NOPATH) {
 +                  newStr = NULL;
 +              } else if (gn->path) {
 +                  newStr = bmake_strdup(gn->path);
 +              } else {
 +                  newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
 +              }
 +              if (!newStr) {
 +                  newStr = bmake_strdup(v->name);
 +              }
 +              cp = ++tstr;
 +              termc = *tstr;
 +              break;
 +          }
 +      case '!':
 +          {
 +              const char *emsg;
 +              VarPattern          pattern;
 +              pattern.flags = 0;
 +
 +              delim = '!';
++              emsg = NULL;
 +              cp = ++tstr;
 +              if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
 +                                               &cp, delim,
 +                                               NULL, &pattern.rightLen,
 +                                               NULL)) == NULL)
 +                  goto cleanup;
-               newStr = Cmd_Exec(pattern.rhs, &emsg);
++              if (wantit)
++                  newStr = Cmd_Exec(pattern.rhs, &emsg);
++              else
++                  newStr = varNoError;
 +              free(UNCONST(pattern.rhs));
 +              if (emsg)
 +                  Error(emsg, nstr);
 +              termc = *cp;
 +              delim = '\0';
 +              if (v->flags & VAR_JUNK) {
 +                  v->flags |= VAR_KEEP;
 +              }
 +              break;
 +          }
 +      case '[':
 +          {
 +              /*
 +               * Look for the closing ']', recursively
 +               * expanding any embedded variables.
 +               *
 +               * estr is a pointer to the expanded result,
 +               * which we must free().
 +               */
 +              char *estr;
 +
 +              cp = tstr+1; /* point to char after '[' */
 +              delim = ']'; /* look for closing ']' */
 +              estr = VarGetPattern(ctxt, &parsestate,
 +                                   errnum, &cp, delim,
 +                                   NULL, NULL, NULL);
 +              if (estr == NULL)
 +                  goto cleanup; /* report missing ']' */
 +              /* now cp points just after the closing ']' */
 +              delim = '\0';
 +              if (cp[0] != ':' && cp[0] != endc) {
 +                  /* Found junk after ']' */
 +                  free(estr);
 +                  goto bad_modifier;
 +              }
 +              if (estr[0] == '\0') {
 +                  /* Found empty square brackets in ":[]". */
 +                  free(estr);
 +                  goto bad_modifier;
 +              } else if (estr[0] == '#' && estr[1] == '\0') {
 +                  /* Found ":[#]" */
 +
 +                  /*
 +                   * We will need enough space for the decimal
 +                   * representation of an int.  We calculate the
 +                   * space needed for the octal representation,
 +                   * and add enough slop to cope with a '-' sign
 +                   * (which should never be needed) and a '\0'
 +                   * string terminator.
 +                   */
 +                  int newStrSize =
 +                      (sizeof(int) * CHAR_BIT + 2) / 3 + 2;
 +
 +                  newStr = bmake_malloc(newStrSize);
 +                  if (parsestate.oneBigWord) {
 +                      strncpy(newStr, "1", newStrSize);
 +                  } else {
 +                      /* XXX: brk_string() is a rather expensive
 +                       * way of counting words. */
 +                      char **av;
 +                      char *as;
 +                      int ac;
 +
 +                      av = brk_string(nstr, &ac, FALSE, &as);
 +                      snprintf(newStr, newStrSize,  "%d", ac);
 +                      free(as);
 +                      free(av);
 +                  }
 +                  termc = *cp;
 +                  free(estr);
 +                  break;
 +              } else if (estr[0] == '*' && estr[1] == '\0') {
 +                  /* Found ":[*]" */
 +                  parsestate.oneBigWord = TRUE;
 +                  newStr = nstr;
 +                  termc = *cp;
 +                  free(estr);
 +                  break;
 +              } else if (estr[0] == '@' && estr[1] == '\0') {
 +                  /* Found ":[@]" */
 +                  parsestate.oneBigWord = FALSE;
 +                  newStr = nstr;
 +                  termc = *cp;
 +                  free(estr);
 +                  break;
 +              } else {
 +                  /*
 +                   * We expect estr to contain a single
 +                   * integer for :[N], or two integers
 +                   * separated by ".." for :[start..end].
 +                   */
 +                  char *ep;
 +
 +                  VarSelectWords_t seldata = { 0, 0 };
 +
 +                  seldata.start = strtol(estr, &ep, 0);
 +                  if (ep == estr) {
 +                      /* Found junk instead of a number */
 +                      free(estr);
 +                      goto bad_modifier;
 +                  } else if (ep[0] == '\0') {
 +                      /* Found only one integer in :[N] */
 +                      seldata.end = seldata.start;
 +                  } else if (ep[0] == '.' && ep[1] == '.' &&
 +                             ep[2] != '\0') {
 +                      /* Expecting another integer after ".." */
 +                      ep += 2;
 +                      seldata.end = strtol(ep, &ep, 0);
 +                      if (ep[0] != '\0') {
 +                          /* Found junk after ".." */
 +                          free(estr);
 +                          goto bad_modifier;
 +                      }
 +                  } else {
 +                      /* Found junk instead of ".." */
 +                      free(estr);
 +                      goto bad_modifier;
 +                  }
 +                  /*
 +                   * Now seldata is properly filled in,
 +                   * but we still have to check for 0 as
 +                   * a special case.
 +                   */
 +                  if (seldata.start == 0 && seldata.end == 0) {
 +                      /* ":[0]" or perhaps ":[0..0]" */
 +                      parsestate.oneBigWord = TRUE;
 +                      newStr = nstr;
 +                      termc = *cp;
 +                      free(estr);
 +                      break;
 +                  } else if (seldata.start == 0 ||
 +                             seldata.end == 0) {
 +                      /* ":[0..N]" or ":[N..0]" */
 +                      free(estr);
 +                      goto bad_modifier;
 +                  }
 +                  /*
 +                   * Normal case: select the words
 +                   * described by seldata.
 +                   */
 +                  newStr = VarSelectWords(ctxt, &parsestate,
 +                                          nstr, &seldata);
 +
 +                  termc = *cp;
 +                  free(estr);
 +                  break;
 +              }
 +
 +          }
 +      case 'g':
 +          cp = tstr + 1;      /* make sure it is set */
 +          if (STRMOD_MATCH(tstr, "gmtime", 6)) {
 +              newStr = VarStrftime(nstr, 1);
 +              cp = tstr + 6;
 +              termc = *cp;
 +          } else {
 +              goto default_case;
 +          }
 +          break;
 +      case 'h':
 +          cp = tstr + 1;      /* make sure it is set */
 +          if (STRMOD_MATCH(tstr, "hash", 4)) {
 +              newStr = VarHash(nstr);
 +              cp = tstr + 4;
 +              termc = *cp;
 +          } else {
 +              goto default_case;
 +          }
 +          break;
 +      case 'l':
 +          cp = tstr + 1;      /* make sure it is set */
 +          if (STRMOD_MATCH(tstr, "localtime", 9)) {
 +              newStr = VarStrftime(nstr, 0);
 +              cp = tstr + 9;
 +              termc = *cp;
 +          } else {
 +              goto default_case;
 +          }
 +          break;
 +      case 't':
 +          {
 +              cp = tstr + 1;  /* make sure it is set */
 +              if (tstr[1] != endc && tstr[1] != ':') {
 +                  if (tstr[1] == 's') {
 +                      /*
 +                       * Use the char (if any) at tstr[2]
 +                       * as the word separator.
 +                       */
 +                      VarPattern pattern;
 +
 +                      if (tstr[2] != endc &&
 +                          (tstr[3] == endc || tstr[3] == ':')) {
 +                          /* ":ts<unrecognised><endc>" or
 +                           * ":ts<unrecognised>:" */
 +                          parsestate.varSpace = tstr[2];
 +                          cp = tstr + 3;
 +                      } else if (tstr[2] == endc || tstr[2] == ':') {
 +                          /* ":ts<endc>" or ":ts:" */
 +                          parsestate.varSpace = 0; /* no separator */
 +                          cp = tstr + 2;
 +                      } else if (tstr[2] == '\\') {
 +                          switch (tstr[3]) {
 +                          case 'n':
 +                              parsestate.varSpace = '\n';
 +                              cp = tstr + 4;
 +                              break;
 +                          case 't':
 +                              parsestate.varSpace = '\t';
 +                              cp = tstr + 4;
 +                              break;
 +                          default:
 +                              if (isdigit((unsigned char)tstr[3])) {
 +                                  char *ep;
 +
 +                                  parsestate.varSpace =
 +                                      strtoul(&tstr[3], &ep, 0);
 +                                  if (*ep != ':' && *ep != endc)
 +                                      goto bad_modifier;
 +                                  cp = ep;
 +                              } else {
 +                                  /*
 +                                   * ":ts<backslash><unrecognised>".
 +                                   */
 +                                  goto bad_modifier;
 +                              }
 +                              break;
 +                          }
 +                      } else {
 +                          /*
 +                           * Found ":ts<unrecognised><unrecognised>".
 +                           */
 +                          goto bad_modifier;
 +                      }
 +
 +                      termc = *cp;
 +
 +                      /*
 +                       * We cannot be certain that VarModify
 +                       * will be used - even if there is a
 +                       * subsequent modifier, so do a no-op
 +                       * VarSubstitute now to for str to be
 +                       * re-expanded without the spaces.
 +                       */
 +                      pattern.flags = VAR_SUB_ONE;
 +                      pattern.lhs = pattern.rhs = "\032";
 +                      pattern.leftLen = pattern.rightLen = 1;
 +
 +                      newStr = VarModify(ctxt, &parsestate, nstr,
 +                                         VarSubstitute,
 +                                         &pattern);
 +                  } else if (tstr[2] == endc || tstr[2] == ':') {
 +                      /*
 +                       * Check for two-character options:
 +                       * ":tu", ":tl"
 +                       */
 +                      if (tstr[1] == 'A') { /* absolute path */
 +                          newStr = VarModify(ctxt, &parsestate, nstr,
 +                                             VarRealpath, NULL);
 +                          cp = tstr + 2;
 +                          termc = *cp;
 +                      } else if (tstr[1] == 'u') {
 +                          char *dp = bmake_strdup(nstr);
 +                          for (newStr = dp; *dp; dp++)
 +                              *dp = toupper((unsigned char)*dp);
 +                          cp = tstr + 2;
 +                          termc = *cp;
 +                      } else if (tstr[1] == 'l') {
 +                          char *dp = bmake_strdup(nstr);
 +                          for (newStr = dp; *dp; dp++)
 +                              *dp = tolower((unsigned char)*dp);
 +                          cp = tstr + 2;
 +                          termc = *cp;
 +                      } else if (tstr[1] == 'W' || tstr[1] == 'w') {
 +                          parsestate.oneBigWord = (tstr[1] == 'W');
 +                          newStr = nstr;
 +                          cp = tstr + 2;
 +                          termc = *cp;
 +                      } else {
 +                          /* Found ":t<unrecognised>:" or
 +                           * ":t<unrecognised><endc>". */
 +                          goto bad_modifier;
 +                      }
 +                  } else {
 +                      /*
 +                       * Found ":t<unrecognised><unrecognised>".
 +                       */
 +                      goto bad_modifier;
 +                  }
 +              } else {
 +                  /*
 +                   * Found ":t<endc>" or ":t:".
 +                   */
 +                  goto bad_modifier;
 +              }
 +              break;
 +          }
 +      case 'N':
 +      case 'M':
 +          {
 +              char    *pattern;
 +              const char *endpat; /* points just after end of pattern */
 +              char    *cp2;
 +              Boolean copy;   /* pattern should be, or has been, copied */
 +              Boolean needSubst;
 +              int nest;
 +
 +              copy = FALSE;
 +              needSubst = FALSE;
 +              nest = 1;
 +              /*
 +               * In the loop below, ignore ':' unless we are at
 +               * (or back to) the original brace level.
 +               * XXX This will likely not work right if $() and ${}
 +               * are intermixed.
 +               */
 +              for (cp = tstr + 1;
 +                   *cp != '\0' && !(*cp == ':' && nest == 1);
 +                   cp++)
 +                  {
 +                      if (*cp == '\\' &&
 +                          (cp[1] == ':' ||
 +                           cp[1] == endc || cp[1] == startc)) {
 +                          if (!needSubst) {
 +                              copy = TRUE;
 +                          }
 +                          cp++;
 +                          continue;
 +                      }
 +                      if (*cp == '$') {
 +                          needSubst = TRUE;
 +                      }
 +                      if (*cp == '(' || *cp == '{')
 +                          ++nest;
 +                      if (*cp == ')' || *cp == '}') {
 +                          --nest;
 +                          if (nest == 0)
 +                              break;
 +                      }
 +                  }
 +              termc = *cp;
 +              endpat = cp;
 +              if (copy) {
 +                  /*
 +                   * Need to compress the \:'s out of the pattern, so
 +                   * allocate enough room to hold the uncompressed
 +                   * pattern (note that cp started at tstr+1, so
 +                   * cp - tstr takes the null byte into account) and
 +                   * compress the pattern into the space.
 +                   */
 +                  pattern = bmake_malloc(cp - tstr);
 +                  for (cp2 = pattern, cp = tstr + 1;
 +                       cp < endpat;
 +                       cp++, cp2++)
 +                      {
 +                          if ((*cp == '\\') && (cp+1 < endpat) &&
 +                              (cp[1] == ':' || cp[1] == endc)) {
 +                              cp++;
 +                          }
 +                          *cp2 = *cp;
 +                      }
 +                  *cp2 = '\0';
 +                  endpat = cp2;
 +              } else {
 +                  /*
 +                   * Either Var_Subst or VarModify will need a
 +                   * nul-terminated string soon, so construct one now.
 +                   */
 +                  pattern = bmake_strndup(tstr+1, endpat - (tstr + 1));
 +              }
 +              if (needSubst) {
 +                  /*
 +                   * pattern contains embedded '$', so use Var_Subst to
 +                   * expand it.
 +                   */
 +                  cp2 = pattern;
-                   pattern = Var_Subst(NULL, cp2, ctxt, errnum);
++                  pattern = Var_Subst(NULL, cp2, ctxt, errnum, TRUE);
 +                  free(cp2);
 +              }
 +              if (DEBUG(VAR))
 +                  fprintf(debug_file, "Pattern[%s] for [%s] is [%s]\n",
 +                      v->name, nstr, pattern);
 +              if (*tstr == 'M') {
 +                  newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
 +                                     pattern);
 +              } else {
 +                  newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
 +                                     pattern);
 +              }
 +              free(pattern);
 +              break;
 +          }
 +      case 'S':
 +          {
 +              VarPattern          pattern;
 +              Var_Parse_State tmpparsestate;
 +
 +              pattern.flags = 0;
 +              tmpparsestate = parsestate;
 +              delim = tstr[1];
 +              tstr += 2;
 +
 +              /*
 +               * If pattern begins with '^', it is anchored to the
 +               * start of the word -- skip over it and flag pattern.
 +               */
 +              if (*tstr == '^') {
 +                  pattern.flags |= VAR_MATCH_START;
 +                  tstr += 1;
 +              }
 +
 +              cp = tstr;
 +              if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, errnum,
 +                                               &cp, delim,
 +                                               &pattern.flags,
 +                                               &pattern.leftLen,
 +                                               NULL)) == NULL)
 +                  goto cleanup;
 +
 +              if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
 +                                               &cp, delim, NULL,
 +                                               &pattern.rightLen,
 +                                               &pattern)) == NULL)
 +                  goto cleanup;
 +
 +              /*
 +               * Check for global substitution. If 'g' after the final
 +               * delimiter, substitution is global and is marked that
 +               * way.
 +               */
 +              for (;; cp++) {
 +                  switch (*cp) {
 +                  case 'g':
 +                      pattern.flags |= VAR_SUB_GLOBAL;
 +                      continue;
 +                  case '1':
 +                      pattern.flags |= VAR_SUB_ONE;
 +                      continue;
 +                  case 'W':
 +                      tmpparsestate.oneBigWord = TRUE;
 +                      continue;
 +                  }
 +                  break;
 +              }
 +
 +              termc = *cp;
 +              newStr = VarModify(ctxt, &tmpparsestate, nstr,
 +                                 VarSubstitute,
 +                                 &pattern);
 +
 +              /*
 +               * Free the two strings.
 +               */
 +              free(UNCONST(pattern.lhs));
 +              free(UNCONST(pattern.rhs));
 +              delim = '\0';
 +              break;
 +          }
 +      case '?':
 +          {
 +              VarPattern      pattern;
 +              Boolean value;
++              int cond_rc;
++              int lhs_flags, rhs_flags;
++              
 +              /* find ':', and then substitute accordingly */
++              if (wantit) {
++                  cond_rc = Cond_EvalExpression(NULL, v->name, &value, 0, FALSE);
++                  if (cond_rc == COND_INVALID) {
++                      lhs_flags = rhs_flags = VAR_NOSUBST;
++                  } else if (value) {
++                      lhs_flags = 0;
++                      rhs_flags = VAR_NOSUBST;
++                  } else {
++                      lhs_flags = VAR_NOSUBST;
++                      rhs_flags = 0;
++                  }
++              } else {
++                  /* we are just consuming and discarding */
++                  cond_rc = value = 0;
++                  lhs_flags = rhs_flags = VAR_NOSUBST;
++              }
 +              pattern.flags = 0;
 +
 +              cp = ++tstr;
 +              delim = ':';
 +              if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, errnum,
-                                                &cp, delim, NULL,
++                                               &cp, delim, &lhs_flags,
 +                                               &pattern.leftLen,
 +                                               NULL)) == NULL)
 +                  goto cleanup;
 +
 +              /* BROPEN or PROPEN */
 +              delim = endc;
 +              if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
-                                                &cp, delim, NULL,
++                                               &cp, delim, &rhs_flags,
 +                                               &pattern.rightLen,
 +                                               NULL)) == NULL)
 +                  goto cleanup;
 +
 +              termc = *--cp;
 +              delim = '\0';
-               if (Cond_EvalExpression(NULL, v->name, &value, 0, FALSE)
-                   == COND_INVALID) {
++              if (cond_rc == COND_INVALID) {
 +                  Error("Bad conditional expression `%s' in %s?%s:%s",
 +                        v->name, v->name, pattern.lhs, pattern.rhs);
 +                  goto cleanup;
 +              }
 +
 +              if (value) {
 +                  newStr = UNCONST(pattern.lhs);
 +                  free(UNCONST(pattern.rhs));
 +              } else {
 +                  newStr = UNCONST(pattern.rhs);
 +                  free(UNCONST(pattern.lhs));
 +              }
 +              if (v->flags & VAR_JUNK) {
 +                  v->flags |= VAR_KEEP;
 +              }
 +              break;
 +          }
 +#ifndef NO_REGEX
 +      case 'C':
 +          {
 +              VarREPattern    pattern;
 +              char           *re;
 +              int             error;
 +              Var_Parse_State tmpparsestate;
 +
 +              pattern.flags = 0;
 +              tmpparsestate = parsestate;
 +              delim = tstr[1];
 +              tstr += 2;
 +
 +              cp = tstr;
 +
 +              if ((re = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim,
 +                                      NULL, NULL, NULL)) == NULL)
 +                  goto cleanup;
 +
 +              if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
 +                                                   errnum, &cp, delim, NULL,
 +                                                   NULL, NULL)) == NULL){
 +                  free(re);
 +                  goto cleanup;
 +              }
 +
 +              for (;; cp++) {
 +                  switch (*cp) {
 +                  case 'g':
 +                      pattern.flags |= VAR_SUB_GLOBAL;
 +                      continue;
 +                  case '1':
 +                      pattern.flags |= VAR_SUB_ONE;
 +                      continue;
 +                  case 'W':
 +                      tmpparsestate.oneBigWord = TRUE;
 +                      continue;
 +                  }
 +                  break;
 +              }
 +
 +              termc = *cp;
 +
 +              error = regcomp(&pattern.re, re, REG_EXTENDED);
 +              free(re);
 +              if (error)  {
 +                  *lengthPtr = cp - start + 1;
 +                  VarREError(error, &pattern.re, "RE substitution error");
 +                  free(pattern.replace);
 +                  goto cleanup;
 +              }
 +
 +              pattern.nsub = pattern.re.re_nsub + 1;
 +              if (pattern.nsub < 1)
 +                  pattern.nsub = 1;
 +              if (pattern.nsub > 10)
 +                  pattern.nsub = 10;
 +              pattern.matches = bmake_malloc(pattern.nsub *
 +                                        sizeof(regmatch_t));
 +              newStr = VarModify(ctxt, &tmpparsestate, nstr,
 +                                 VarRESubstitute,
 +                                 &pattern);
 +              regfree(&pattern.re);
 +              free(pattern.replace);
 +              free(pattern.matches);
 +              delim = '\0';
 +              break;
 +          }
 +#endif
 +      case 'Q':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarQuote(nstr);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +      case 'T':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
 +                                 NULL);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +      case 'H':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
 +                                 NULL);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +      case 'E':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
 +                                 NULL);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +      case 'R':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
 +                                 NULL);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +      case 'O':
 +          {
 +              char otype;
 +
 +              cp = tstr + 1;  /* skip to the rest in any case */
 +              if (tstr[1] == endc || tstr[1] == ':') {
 +                  otype = 's';
 +                  termc = *cp;
 +              } else if ( (tstr[1] == 'x') &&
 +                          (tstr[2] == endc || tstr[2] == ':') ) {
 +                  otype = tstr[1];
 +                  cp = tstr + 2;
 +                  termc = *cp;
 +              } else {
 +                  goto bad_modifier;
 +              }
 +              newStr = VarOrder(nstr, otype);
 +              break;
 +          }
 +      case 'u':
 +          if (tstr[1] == endc || tstr[1] == ':') {
 +              newStr = VarUniq(nstr);
 +              cp = tstr + 1;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +#ifdef SUNSHCMD
 +      case 's':
 +          if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
 +              const char *emsg;
-               newStr = Cmd_Exec(nstr, &emsg);
-               if (emsg)
-                   Error(emsg, nstr);
++              if (wantit) {
++                  newStr = Cmd_Exec(nstr, &emsg);
++                  if (emsg)
++                      Error(emsg, nstr);
++              } else
++                  newStr = varNoError;
 +              cp = tstr + 2;
 +              termc = *cp;
 +              break;
 +          }
 +          goto default_case;
 +#endif
 +      default:
 +      default_case:
 +      {
 +#ifdef SYSVVARSUB
 +          /*
 +           * This can either be a bogus modifier or a System-V
 +           * substitution command.
 +           */
 +          VarPattern      pattern;
 +          Boolean         eqFound;
 +
 +          pattern.flags = 0;
 +          eqFound = FALSE;
 +          /*
 +           * First we make a pass through the string trying
 +           * to verify it is a SYSV-make-style translation:
 +           * it must be: <string1>=<string2>)
 +           */
 +          cp = tstr;
 +          cnt = 1;
 +          while (*cp != '\0' && cnt) {
 +              if (*cp == '=') {
 +                  eqFound = TRUE;
 +                  /* continue looking for endc */
 +              }
 +              else if (*cp == endc)
 +                  cnt--;
 +              else if (*cp == startc)
 +                  cnt++;
 +              if (cnt)
 +                  cp++;
 +          }
 +          if (*cp == endc && eqFound) {
 +
 +              /*
 +               * Now we break this sucker into the lhs and
 +               * rhs. We must null terminate them of course.
 +               */
 +              delim='=';
 +              cp = tstr;
 +              if ((pattern.lhs = VarGetPattern(ctxt, &parsestate,
 +                                               errnum, &cp, delim, &pattern.flags,
 +                                               &pattern.leftLen, NULL)) == NULL)
 +                  goto cleanup;
 +              delim = endc;
 +              if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
 +                                               errnum, &cp, delim, NULL, &pattern.rightLen,
 +                                               &pattern)) == NULL)
 +                  goto cleanup;
 +
 +              /*
 +               * SYSV modifications happen through the whole
 +               * string. Note the pattern is anchored at the end.
 +               */
 +              termc = *--cp;
 +              delim = '\0';
 +              if (pattern.leftLen == 0 && *nstr == '\0') {
 +                  newStr = nstr;      /* special case */
 +              } else {
 +                  newStr = VarModify(ctxt, &parsestate, nstr,
 +                                     VarSYSVMatch,
 +                                     &pattern);
 +              }
 +              free(UNCONST(pattern.lhs));
 +              free(UNCONST(pattern.rhs));
 +          } else
 +#endif
 +              {
 +                  Error("Unknown modifier '%c'", *tstr);
 +                  for (cp = tstr+1;
 +                       *cp != ':' && *cp != endc && *cp != '\0';
 +                       cp++)
 +                      continue;
 +                  termc = *cp;
 +                  newStr = var_Error;
 +              }
 +          }
 +      }
 +      if (DEBUG(VAR)) {
 +          fprintf(debug_file, "Result[%s] of :%c is \"%s\"\n",
 +              v->name, modifier, newStr);
 +      }
 +
 +      if (newStr != nstr) {
 +          if (*freePtr) {
 +              free(nstr);
 +              *freePtr = NULL;
 +          }
 +          nstr = newStr;
 +          if (nstr != var_Error && nstr != varNoError) {
 +              *freePtr = nstr;
 +          }
 +      }
 +      if (termc == '\0' && endc != '\0') {
 +          Error("Unclosed variable specification (expecting '%c') for \"%s\" (value \"%s\") modifier %c", endc, v->name, nstr, modifier);
 +      } else if (termc == ':') {
 +          cp++;
 +      }
 +      tstr = cp;
 +    }
 + out:
 +    *lengthPtr = tstr - start;
 +    return (nstr);
 +
 + bad_modifier:
 +    /* "{(" */
 +    Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
 +        v->name);
 +
 + cleanup:
 +    *lengthPtr = cp - start;
 +    if (delim != '\0')
 +      Error("Unclosed substitution for %s (%c missing)",
 +            v->name, delim);
-     if (*freePtr) {
-       free(*freePtr);
-       *freePtr = NULL;
-     }
++    free(*freePtr);
++    *freePtr = NULL;
 +    return (var_Error);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Parse --
 + *    Given the start of a variable invocation, extract the variable
 + *    name and find its value, then modify it according to the
 + *    specification.
 + *
 + * Input:
 + *    str             The string to parse
 + *    ctxt            The context for the variable
 + *    errnum          TRUE if undefined variables are an error
++ *    wantit          TRUE if we actually want the result
 + *    lengthPtr       OUT: The length of the specification
 + *    freePtr         OUT: Non-NULL if caller should free *freePtr
 + *
 + * Results:
 + *    The (possibly-modified) value of the variable or var_Error if the
 + *    specification is invalid. The length of the specification is
 + *    placed in *lengthPtr (for invalid specifications, this is just
 + *    2...?).
 + *    If *freePtr is non-NULL then it's a pointer that the caller
 + *    should pass to free() to free memory used by the result.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +/* coverity[+alloc : arg-*4] */
 +char *
- Var_Parse(const char *str, GNode *ctxt, Boolean errnum, int *lengthPtr,
-         void **freePtr)
++Var_Parse(const char *str, GNode *ctxt,
++         Boolean errnum, Boolean wantit,
++         int *lengthPtr, void **freePtr)
 +{
 +    const char           *tstr;       /* Pointer into str */
 +    Var                  *v;          /* Variable in invocation */
 +    Boolean       haveModifier;/* TRUE if have modifiers for the variable */
 +    char          endc;       /* Ending character when variable in parens
 +                               * or braces */
 +    char          startc;     /* Starting character when variable in parens
 +                               * or braces */
 +    int                   vlen;       /* Length of variable name */
 +    const char           *start;      /* Points to original start of str */
 +    char         *nstr;       /* New string, used during expansion */
 +    Boolean       dynamic;    /* TRUE if the variable is local and we're
 +                               * expanding it in a non-local context. This
 +                               * is done to support dynamic sources. The
 +                               * result is just the invocation, unaltered */
 +    const char     *extramodifiers; /* extra modifiers to apply first */
 +    char        name[2];
 +
 +    *freePtr = NULL;
 +    extramodifiers = NULL;
 +    dynamic = FALSE;
 +    start = str;
 +
 +    startc = str[1];
 +    if (startc != PROPEN && startc != BROPEN) {
 +      /*
 +       * If it's not bounded by braces of some sort, life is much simpler.
 +       * We just need to check for the first character and return the
 +       * value if it exists.
 +       */
 +
 +      /* Error out some really stupid names */
 +      if (startc == '\0' || strchr(")}:$", startc)) {
 +          *lengthPtr = 1;
 +          return var_Error;
 +      }
 +      name[0] = startc;
 +      name[1] = '\0';
 +
 +      v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
 +      if (v == NULL) {
 +          *lengthPtr = 2;
 +
 +          if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
 +              /*
 +               * If substituting a local variable in a non-local context,
 +               * assume it's for dynamic source stuff. We have to handle
 +               * this specially and return the longhand for the variable
 +               * with the dollar sign escaped so it makes it back to the
 +               * caller. Only four of the local variables are treated
 +               * specially as they are the only four that will be set
 +               * when dynamic sources are expanded.
 +               */
 +              switch (str[1]) {
 +                  case '@':
 +                      return UNCONST("$(.TARGET)");
 +                  case '%':
 +                      return UNCONST("$(.ARCHIVE)");
 +                  case '*':
 +                      return UNCONST("$(.PREFIX)");
 +                  case '!':
 +                      return UNCONST("$(.MEMBER)");
 +              }
 +          }
 +          /*
 +           * Error
 +           */
 +          return (errnum ? var_Error : varNoError);
 +      } else {
 +          haveModifier = FALSE;
 +          tstr = &str[1];
 +          endc = str[1];
 +      }
 +    } else {
 +      Buffer buf;     /* Holds the variable name */
 +      int depth = 1;
 +
 +      endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
 +      Buf_Init(&buf, 0);
 +
 +      /*
 +       * Skip to the end character or a colon, whichever comes first.
 +       */
 +      for (tstr = str + 2; *tstr != '\0'; tstr++)
 +      {
 +          /*
 +           * Track depth so we can spot parse errors.
 +           */
 +          if (*tstr == startc) {
 +              depth++;
 +          }
 +          if (*tstr == endc) {
 +              if (--depth == 0)
 +                  break;
 +          }
 +          if (depth == 1 && *tstr == ':') {
 +              break;
 +          }
 +          /*
 +           * A variable inside a variable, expand
 +           */
 +          if (*tstr == '$') {
 +              int rlen;
 +              void *freeIt;
-               char *rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt);
++              char *rval = Var_Parse(tstr, ctxt, errnum, wantit, &rlen, &freeIt);
 +              if (rval != NULL) {
 +                  Buf_AddBytes(&buf, strlen(rval), rval);
 +              }
-               if (freeIt)
-                   free(freeIt);
++              free(freeIt);
 +              tstr += rlen - 1;
 +          }
 +          else
 +              Buf_AddByte(&buf, *tstr);
 +      }
 +      if (*tstr == ':') {
 +          haveModifier = TRUE;
 +      } else if (*tstr == endc) {
 +          haveModifier = FALSE;
 +      } else {
 +          /*
 +           * If we never did find the end character, return NULL
 +           * right now, setting the length to be the distance to
 +           * the end of the string, since that's what make does.
 +           */
 +          *lengthPtr = tstr - str;
 +          Buf_Destroy(&buf, TRUE);
 +          return (var_Error);
 +      }
 +      str = Buf_GetAll(&buf, &vlen);
 +
 +      /*
 +       * At this point, str points into newly allocated memory from
 +       * buf, containing only the name of the variable.
 +       *
 +       * start and tstr point into the const string that was pointed
 +       * to by the original value of the str parameter.  start points
 +       * to the '$' at the beginning of the string, while tstr points
 +       * to the char just after the end of the variable name -- this
 +       * will be '\0', ':', PRCLOSE, or BRCLOSE.
 +       */
 +
 +      v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
 +      /*
 +       * Check also for bogus D and F forms of local variables since we're
 +       * in a local context and the name is the right length.
 +       */
 +      if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
 +              (vlen == 2) && (str[1] == 'F' || str[1] == 'D') &&
 +              strchr("@%?*!<>", str[0]) != NULL) {
 +          /*
 +           * Well, it's local -- go look for it.
 +           */
 +          name[0] = *str;
 +          name[1] = '\0';
 +          v = VarFind(name, ctxt, 0);
 +
 +          if (v != NULL) {
 +              if (str[1] == 'D') {
 +                      extramodifiers = "H:";
 +              }
 +              else { /* F */
 +                      extramodifiers = "T:";
 +              }
 +          }
 +      }
 +
 +      if (v == NULL) {
 +          if (((vlen == 1) ||
 +               (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) &&
 +              ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
 +          {
 +              /*
 +               * If substituting a local variable in a non-local context,
 +               * assume it's for dynamic source stuff. We have to handle
 +               * this specially and return the longhand for the variable
 +               * with the dollar sign escaped so it makes it back to the
 +               * caller. Only four of the local variables are treated
 +               * specially as they are the only four that will be set
 +               * when dynamic sources are expanded.
 +               */
 +              switch (*str) {
 +                  case '@':
 +                  case '%':
 +                  case '*':
 +                  case '!':
 +                      dynamic = TRUE;
 +                      break;
 +              }
 +          } else if ((vlen > 2) && (*str == '.') &&
 +                     isupper((unsigned char) str[1]) &&
 +                     ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
 +          {
 +              int     len;
 +
 +              len = vlen - 1;
 +              if ((strncmp(str, ".TARGET", len) == 0) ||
 +                  (strncmp(str, ".ARCHIVE", len) == 0) ||
 +                  (strncmp(str, ".PREFIX", len) == 0) ||
 +                  (strncmp(str, ".MEMBER", len) == 0))
 +              {
 +                  dynamic = TRUE;
 +              }
 +          }
 +
 +          if (!haveModifier) {
 +              /*
 +               * No modifiers -- have specification length so we can return
 +               * now.
 +               */
 +              *lengthPtr = tstr - start + 1;
 +              if (dynamic) {
 +                  char *pstr = bmake_strndup(start, *lengthPtr);
 +                  *freePtr = pstr;
 +                  Buf_Destroy(&buf, TRUE);
 +                  return(pstr);
 +              } else {
 +                  Buf_Destroy(&buf, TRUE);
 +                  return (errnum ? var_Error : varNoError);
 +              }
 +          } else {
 +              /*
 +               * Still need to get to the end of the variable specification,
 +               * so kludge up a Var structure for the modifications
 +               */
 +              v = bmake_malloc(sizeof(Var));
 +              v->name = UNCONST(str);
 +              Buf_Init(&v->val, 1);
 +              v->flags = VAR_JUNK;
 +              Buf_Destroy(&buf, FALSE);
 +          }
 +      } else
 +          Buf_Destroy(&buf, TRUE);
 +    }
 +
 +    if (v->flags & VAR_IN_USE) {
 +      Fatal("Variable %s is recursive.", v->name);
 +      /*NOTREACHED*/
 +    } else {
 +      v->flags |= VAR_IN_USE;
 +    }
 +    /*
 +     * Before doing any modification, we have to make sure the value
 +     * has been fully expanded. If it looks like recursion might be
 +     * necessary (there's a dollar sign somewhere in the variable's value)
 +     * we just call Var_Subst to do any other substitutions that are
 +     * necessary. Note that the value returned by Var_Subst will have
 +     * been dynamically-allocated, so it will need freeing when we
 +     * return.
 +     */
 +    nstr = Buf_GetAll(&v->val, NULL);
 +    if (strchr(nstr, '$') != NULL) {
-       nstr = Var_Subst(NULL, nstr, ctxt, errnum);
++      nstr = Var_Subst(NULL, nstr, ctxt, errnum, wantit);
 +      *freePtr = nstr;
 +    }
 +
 +    v->flags &= ~VAR_IN_USE;
 +
 +    if ((nstr != NULL) && (haveModifier || extramodifiers != NULL)) {
 +      void *extraFree;
 +      int used;
 +
 +      extraFree = NULL;
 +      if (extramodifiers != NULL) {
 +              nstr = ApplyModifiers(nstr, extramodifiers, '(', ')',
-                                     v, ctxt, errnum, &used, &extraFree);
++                                    v, ctxt, errnum, wantit, &used, &extraFree);
 +      }
 +
 +      if (haveModifier) {
 +              /* Skip initial colon. */
 +              tstr++;
 +
 +              nstr = ApplyModifiers(nstr, tstr, startc, endc,
-                                     v, ctxt, errnum, &used, freePtr);
++                                    v, ctxt, errnum, wantit, &used, freePtr);
 +              tstr += used;
-               if (extraFree) {
-                       free(extraFree);
-               }
++              free(extraFree);
 +      } else {
 +              *freePtr = extraFree;
 +      }
 +    }
 +    if (*tstr) {
 +      *lengthPtr = tstr - start + 1;
 +    } else {
 +      *lengthPtr = tstr - start;
 +    }
 +
 +    if (v->flags & VAR_FROM_ENV) {
 +      Boolean   destroy = FALSE;
 +
 +      if (nstr != Buf_GetAll(&v->val, NULL)) {
 +          destroy = TRUE;
 +      } else {
 +          /*
 +           * Returning the value unmodified, so tell the caller to free
 +           * the thing.
 +           */
 +          *freePtr = nstr;
 +      }
 +      VarFreeEnv(v, destroy);
 +    } else if (v->flags & VAR_JUNK) {
 +      /*
 +       * Perform any free'ing needed and set *freePtr to NULL so the caller
 +       * doesn't try to free a static pointer.
 +       * If VAR_KEEP is also set then we want to keep str as is.
 +       */
 +      if (!(v->flags & VAR_KEEP)) {
 +          if (*freePtr) {
 +              free(nstr);
 +              *freePtr = NULL;
 +          }
 +          if (dynamic) {
 +              nstr = bmake_strndup(start, *lengthPtr);
 +              *freePtr = nstr;
 +          } else {
 +              nstr = errnum ? var_Error : varNoError;
 +          }
 +      }
 +      if (nstr != Buf_GetAll(&v->val, NULL))
 +          Buf_Destroy(&v->val, TRUE);
 +      free(v->name);
 +      free(v);
 +    }
 +    return (nstr);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Subst  --
 + *    Substitute for all variables in the given string in the given context
 + *    If undefErr is TRUE, Parse_Error will be called when an undefined
 + *    variable is encountered.
 + *
 + * Input:
 + *    var             Named variable || NULL for all
 + *    str             the string which to substitute
 + *    ctxt            the context wherein to find variables
 + *    undefErr        TRUE if undefineds are an error
++ *    wantit          TRUE if we actually want the result
 + *
 + * Results:
 + *    The resulting string.
 + *
 + * Side Effects:
 + *    None. The old string must be freed by the caller
 + *-----------------------------------------------------------------------
 + */
 +char *
- Var_Subst(const char *var, const char *str, GNode *ctxt, Boolean undefErr)
++Var_Subst(const char *var, const char *str, GNode *ctxt,
++         Boolean undefErr, Boolean wantit)
 +{
 +    Buffer      buf;              /* Buffer for forming things */
 +    char        *val;             /* Value to substitute for a variable */
 +    int                 length;           /* Length of the variable invocation */
 +    Boolean     trailingBslash;   /* variable ends in \ */
 +    void        *freeIt = NULL;    /* Set if it should be freed */
 +    static Boolean errorReported;   /* Set true if an error has already
 +                                   * been reported to prevent a plethora
 +                                   * of messages when recursing */
 +
 +    Buf_Init(&buf, 0);
 +    errorReported = FALSE;
 +    trailingBslash = FALSE;
 +
 +    while (*str) {
 +      if (*str == '\n' && trailingBslash)
 +          Buf_AddByte(&buf, ' ');
 +      if (var == NULL && (*str == '$') && (str[1] == '$')) {
 +          /*
 +           * A dollar sign may be escaped either with another dollar sign.
 +           * In such a case, we skip over the escape character and store the
 +           * dollar sign into the buffer directly.
 +           */
 +          str++;
 +          Buf_AddByte(&buf, *str);
 +          str++;
 +      } else if (*str != '$') {
 +          /*
 +           * Skip as many characters as possible -- either to the end of
 +           * the string or to the next dollar sign (variable invocation).
 +           */
 +          const char  *cp;
 +
 +          for (cp = str++; *str != '$' && *str != '\0'; str++)
 +              continue;
 +          Buf_AddBytes(&buf, str - cp, cp);
 +      } else {
 +          if (var != NULL) {
 +              int expand;
 +              for (;;) {
 +                  if (str[1] == '\0') {
 +                      /* A trailing $ is kind of a special case */
 +                      Buf_AddByte(&buf, str[0]);
 +                      str++;
 +                      expand = FALSE;
 +                  } else if (str[1] != PROPEN && str[1] != BROPEN) {
 +                      if (str[1] != *var || strlen(var) > 1) {
 +                          Buf_AddBytes(&buf, 2, str);
 +                          str += 2;
 +                          expand = FALSE;
 +                      }
 +                      else
 +                          expand = TRUE;
 +                      break;
 +                  }
 +                  else {
 +                      const char *p;
 +
 +                      /*
 +                       * Scan up to the end of the variable name.
 +                       */
 +                      for (p = &str[2]; *p &&
 +                           *p != ':' && *p != PRCLOSE && *p != BRCLOSE; p++)
 +                          if (*p == '$')
 +                              break;
 +                      /*
 +                       * A variable inside the variable. We cannot expand
 +                       * the external variable yet, so we try again with
 +                       * the nested one
 +                       */
 +                      if (*p == '$') {
 +                          Buf_AddBytes(&buf, p - str, str);
 +                          str = p;
 +                          continue;
 +                      }
 +
 +                      if (strncmp(var, str + 2, p - str - 2) != 0 ||
 +                          var[p - str - 2] != '\0') {
 +                          /*
 +                           * Not the variable we want to expand, scan
 +                           * until the next variable
 +                           */
 +                          for (;*p != '$' && *p != '\0'; p++)
 +                              continue;
 +                          Buf_AddBytes(&buf, p - str, str);
 +                          str = p;
 +                          expand = FALSE;
 +                      }
 +                      else
 +                          expand = TRUE;
 +                      break;
 +                  }
 +              }
 +              if (!expand)
 +                  continue;
 +          }
 +
-           val = Var_Parse(str, ctxt, undefErr, &length, &freeIt);
++          val = Var_Parse(str, ctxt, undefErr, wantit, &length, &freeIt);
 +
 +          /*
 +           * When we come down here, val should either point to the
 +           * value of this variable, suitably modified, or be NULL.
 +           * Length should be the total length of the potential
 +           * variable invocation (from $ to end character...)
 +           */
 +          if (val == var_Error || val == varNoError) {
 +              /*
 +               * If performing old-time variable substitution, skip over
 +               * the variable and continue with the substitution. Otherwise,
 +               * store the dollar sign and advance str so we continue with
 +               * the string...
 +               */
 +              if (oldVars) {
 +                  str += length;
 +              } else if (undefErr || val == var_Error) {
 +                  /*
 +                   * If variable is undefined, complain and skip the
 +                   * variable. The complaint will stop us from doing anything
 +                   * when the file is parsed.
 +                   */
 +                  if (!errorReported) {
 +                      Parse_Error(PARSE_FATAL,
 +                                   "Undefined variable \"%.*s\"",length,str);
 +                  }
 +                  str += length;
 +                  errorReported = TRUE;
 +              } else {
 +                  Buf_AddByte(&buf, *str);
 +                  str += 1;
 +              }
 +          } else {
 +              /*
 +               * We've now got a variable structure to store in. But first,
 +               * advance the string pointer.
 +               */
 +              str += length;
 +
 +              /*
 +               * Copy all the characters from the variable value straight
 +               * into the new string.
 +               */
 +              length = strlen(val);
 +              Buf_AddBytes(&buf, length, val);
 +              trailingBslash = length > 0 && val[length - 1] == '\\';
 +          }
-           if (freeIt) {
-               free(freeIt);
-               freeIt = NULL;
-           }
++          free(freeIt);
++          freeIt = NULL;
 +      }
 +    }
 +
 +    return Buf_DestroyCompact(&buf);
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_GetTail --
 + *    Return the tail from each of a list of words. Used to set the
 + *    System V local variables.
 + *
 + * Input:
 + *    file            Filename to modify
 + *
 + * Results:
 + *    The resulting string.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +#if 0
 +char *
 +Var_GetTail(char *file)
 +{
 +    return(VarModify(file, VarTail, NULL));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_GetHead --
 + *    Find the leading components of a (list of) filename(s).
 + *    XXX: VarHead does not replace foo by ., as (sun) System V make
 + *    does.
 + *
 + * Input:
 + *    file            Filename to manipulate
 + *
 + * Results:
 + *    The leading components.
 + *
 + * Side Effects:
 + *    None.
 + *
 + *-----------------------------------------------------------------------
 + */
 +char *
 +Var_GetHead(char *file)
 +{
 +    return(VarModify(file, VarHead, NULL));
 +}
 +#endif
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Init --
 + *    Initialize the module
 + *
 + * Results:
 + *    None
 + *
 + * Side Effects:
 + *    The VAR_CMD and VAR_GLOBAL contexts are created
 + *-----------------------------------------------------------------------
 + */
 +void
 +Var_Init(void)
 +{
 +    VAR_INTERNAL = Targ_NewGN("Internal");
 +    VAR_GLOBAL = Targ_NewGN("Global");
 +    VAR_CMD = Targ_NewGN("Command");
 +
 +}
 +
 +
 +void
 +Var_End(void)
 +{
 +}
 +
 +
 +/****************** PRINT DEBUGGING INFO *****************/
 +static void
 +VarPrintVar(void *vp)
 +{
 +    Var    *v = (Var *)vp;
 +    fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL));
 +}
 +
 +/*-
 + *-----------------------------------------------------------------------
 + * Var_Dump --
 + *    print all variables in a context
 + *-----------------------------------------------------------------------
 + */
 +void
 +Var_Dump(GNode *ctxt)
 +{
 +    Hash_Search search;
 +    Hash_Entry *h;
 +
 +    for (h = Hash_EnumFirst(&ctxt->context, &search);
 +       h != NULL;
 +       h = Hash_EnumNext(&search)) {
 +          VarPrintVar(Hash_GetValue(h));
 +    }
 +}
index 4f83731294ca4f2b63faa1d6046b2b6252b2a1de,0000000000000000000000000000000000000000..e12072da85c90ff6a7b7ecb749ad42fa58fc7543
mode 100644,000000..100644
--- /dev/null
@@@ -1,176 -1,0 +1,177 @@@
- #     $Id: Makefile,v 1.39 2015/06/07 15:54:37 sjg Exp $
 +# This is a generated file, do NOT edit!
 +# See contrib/bmake/bsd.after-import.mk
 +#
 +# $FreeBSD$
 +
 +.sinclude "Makefile.inc"
 +
 +SRCTOP?= ${.CURDIR:H:H}
 +
 +# look here first for config.h
 +CFLAGS+= -I${.CURDIR}
 +
 +# for after-import
 +CLEANDIRS+= FreeBSD
 +CLEANFILES+= bootstrap
 +
- MAKE_VERSION= 20150606
++#     $Id: Makefile,v 1.44 2015/10/20 21:41:40 sjg Exp $
 +
 +# Base version on src date
++MAKE_VERSION= 20151020
 +
 +PROG?=        ${.CURDIR:T}
 +
 +SRCS= \
 +      arch.c \
 +      buf.c \
 +      compat.c \
 +      cond.c \
 +      dir.c \
 +      for.c \
 +      hash.c \
 +      job.c \
 +      main.c \
 +      make.c \
 +      make_malloc.c \
 +      meta.c \
++      metachar.c \
 +      parse.c \
 +      str.c \
 +      strlist.c \
 +      suff.c \
 +      targ.c \
 +      trace.c \
 +      util.c \
 +      var.c
 +
 +# from lst.lib/
 +SRCS+= \
 +      lstAppend.c \
 +      lstAtEnd.c \
 +      lstAtFront.c \
 +      lstClose.c \
 +      lstConcat.c \
 +      lstDatum.c \
 +      lstDeQueue.c \
 +      lstDestroy.c \
 +      lstDupl.c \
 +      lstEnQueue.c \
 +      lstFind.c \
 +      lstFindFrom.c \
 +      lstFirst.c \
 +      lstForEach.c \
 +      lstForEachFrom.c \
 +      lstInit.c \
 +      lstInsert.c \
 +      lstIsAtEnd.c \
 +      lstIsEmpty.c \
 +      lstLast.c \
 +      lstMember.c \
 +      lstNext.c \
 +      lstOpen.c \
 +      lstPrev.c \
 +      lstRemove.c \
 +      lstReplace.c \
 +      lstSucc.c
 +
 +# this file gets generated by configure
 +.sinclude "Makefile.config"
 +
 +.if !empty(LIBOBJS)
 +SRCS+= ${LIBOBJS:T:.o=.c}
 +.endif
 +
 +# just in case
 +prefix?= /usr
 +srcdir?= ${.CURDIR}
 +
 +DEFAULT_SYS_PATH?= ${prefix}/share/mk
 +
 +CPPFLAGS+= -DUSE_META
 +CFLAGS+= ${CPPFLAGS}
 +CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\"
 +CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE
 +CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}}
 +COPTS.main.c+= "-DMAKE_VERSION=\"${MAKE_VERSION}\""
 +
 +# meta mode can be useful even without filemon 
 +FILEMON_H ?= /usr/include/dev/filemon/filemon.h
 +.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h"
 +COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H}
 +.endif
 +
 +.PATH:        ${srcdir}
 +.PATH:        ${srcdir}/lst.lib
 +
 +.if make(obj) || make(clean)
 +SUBDIR+= unit-tests
 +.endif
 +
 +
 +MAN= ${PROG}.1
 +MAN1= ${MAN}
 +
 +.if (${PROG} != "make")
 +CLEANFILES+= my.history
 +.if make(${MAN}) || !exists(${srcdir}/${MAN})
 +my.history: ${MAKEFILE}
 +      @(echo ".Nm"; \
 +      echo "is derived from NetBSD"; \
 +      echo ".Xr make 1 ."; \
 +      echo "It uses autoconf to facilitate portability to other platforms."; \
 +      echo ".Pp") > $@
 +
 +.NOPATH: ${MAN}
 +${MAN}:       make.1 my.history
 +      @echo making $@
 +      @sed -e 's/^.Nx/NetBSD/' -e '/^.Nm/s/make/${PROG}/' \
 +      -e '/^.Sh HISTORY/rmy.history' \
 +      -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@
 +
 +all beforeinstall: ${MAN}
 +_mfromdir=.
 +.endif
 +.endif
 +
 +MANTARGET?= cat
 +MANDEST?= ${MANDIR}/${MANTARGET}1
 +
 +.if ${MANTARGET} == "cat"
 +_mfromdir=${srcdir}
 +.endif
 +
 +.include <bsd.prog.mk>
 +
 +CPPFLAGS+= -DMAKE_NATIVE -DHAVE_CONFIG_H
 +COPTS.var.c += -Wno-cast-qual
 +COPTS.job.c += -Wno-format-nonliteral
 +COPTS.parse.c += -Wno-format-nonliteral
 +COPTS.var.c += -Wno-format-nonliteral
 +
 +# Force these
 +SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share}
 +BINDIR= ${BINDIR.bmake:U${prefix}/bin}
 +MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man}
 +
 +.if !exists(.depend)
 +${OBJS}: config.h
 +.endif
 +
 +# make sure that MAKE_VERSION gets updated.
 +main.o: ${SRCS} ${MAKEFILE}
 +
 +
 +# A simple unit-test driver to help catch regressions
 +accept test:
 +      cd ${.CURDIR}/unit-tests && MAKEFLAGS= ${.MAKE} -r -m / TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET}
 +
 +# override some simple things
 +BINDIR= /usr/bin
 +MANDIR= /usr/share/man/man
 +
 +# make sure we get this
 +CFLAGS+= ${COPTS.${.IMPSRC:T}}
 +
 +after-import: ${SRCTOP}/contrib/bmake/bsd.after-import.mk
 +      cd ${.CURDIR} && ${.MAKE} -f ${SRCTOP}/contrib/bmake/bsd.after-import.mk
 +