--- /dev/null
++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
--- /dev/null
+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
--- /dev/null
- # $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}
--- /dev/null
- /* $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, ×);
+ }
+#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;
+}
--- /dev/null
- /* $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);
+ }
+ }
+}
--- /dev/null
- /* $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;
+}
--- /dev/null
- 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
--- /dev/null
- /* $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);
+}
--- /dev/null
- /* $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, ×) < 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 */
--- /dev/null
- /* $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);
+}
--- /dev/null
- 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}
--- /dev/null
- /* $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;
+}
--- /dev/null
- /* $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_ */
--- /dev/null
- /* $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 */
--- /dev/null
--- /dev/null
++/* $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,
++};
++
--- /dev/null
--- /dev/null
++/* $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 */
--- /dev/null
++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
+
+
+
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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'
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- # $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
--- /dev/null
- /* $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);
--- /dev/null
- # $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
+
--- /dev/null
- /* $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;
+}
--- /dev/null
- /* $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);
+}
--- /dev/null
+
++: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
--- /dev/null
- # $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}}}
--- /dev/null
- /* $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));
+ }
+}
--- /dev/null
- # $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
+