Index: vendor/NetBSD/bmake/dist/ChangeLog =================================================================== --- vendor/NetBSD/bmake/dist/ChangeLog (revision 251419) +++ vendor/NetBSD/bmake/dist/ChangeLog (revision 251420) @@ -1,1541 +1,1554 @@ +2013-06-04 Simon J. Gerraty + + * 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 + + * 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 * 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 * 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 * 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 * 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 * 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 * 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 * boot-strap: remove MAKEOBJDIRPREFIX from environment since we want to use MAKEOBJDIR 2013-01-27 Simon J. Gerraty * 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 * 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 * 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 * Makefile.in (MAKE_VERSION): 20121111 fix generation of bmake.cat1 2012-11-09 Simon J. Gerraty * 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 * 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 * Makefile.in: we need to use ${srcdir} not ${.CURDIR} 2012-10-10 Simon J. Gerraty * 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 * Makefile.in: do not lose history from make.1 when generating bmake.1 2012-10-07 Simon J. Gerraty * 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 * 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 * 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 * 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 * bsd.after-import.mk (after-import): ensure unit-tests/Makefile gets SRCTOP set. 2012-07-04 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Makefile.in: replace use of .Nx in bmake.1 with NetBSD since some systems cannot cope with .Nx 2011-11-14 Simon J. Gerraty * 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 * Makefile.in (MAKE_VERSION): bump version to 20111010 2011-10-09 Simon J. Gerraty * 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 * 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 * Makefile.in (MAKE_VERSION): 20110909 is a better number ;-) 2011-09-05 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * Merge with NetBSD make, pick up o unit-tests/modts now works on MirBSD 2011-06-04 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * parse.c: SunOS 5.8 at least does not have MAP_FILE 2011-01-01 Simon J. Gerraty * 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 * 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 * 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 * 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 * Makefile.in (MAKE_VERSION): bump version to 20101101 2010-10-16 Simon J. Gerraty * machine.sh: like os.sh, allow for uname -p producing useless drivel 2010-09-13 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * Makefile.in (MAKE_VERSION): bump version to 20100606 Merge with NetBSD make, pick up o man page update 2010-06-05 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * boot-strap: add FindHereOrAbove so we can use -m .../mk 2010-04-20 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * Makefile.in (MAKE_VERSION): bump version to 20100102 * Merge with NetBSD make, pick up: o fix for -m .../ 2009-11-18 Simon J. Gerraty * 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 * configure.in: Ensure that srcdir and mksrc are absolute paths. 2009-10-09 Simon J. Gerraty * Makefile.in (MAKE_VERSION): fix version to 20091007 2009-10-07 Simon J. Gerraty * 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 * 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 * 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 * configure.in: Don't -D__EXTENSIONS__ if AC_USE_SYSTEM_EXTENSIONS says "no". 2009-08-26 Simon J. Gerraty * 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 * Appy some patches for MiNT from David Brownlee 2009-02-26 Simon J. Gerraty * 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 * 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 * 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 * 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 * Makefile.in (BMAKE_VERSION): bump version to 20081030 pick up man page tweaks. 2008-10-29 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Makefile.in (BMAKE_VERSION): bump version to 20071022 * Merge with NetBSD make, pick up: o Allow .PATH to be used for .include "" * boot-strap: source default settings from .bmake-boot-strap.rc 2007-10-16 Simon J. Gerraty * 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 * boot-strap: make bmake.1 too, so maninstall works. 2007-10-14 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Makefile.in (BMAKE_VERSION): bump to 20051031 * ranlib.h: skip on OSF too. (NetBSD PR 31864) 2005-10-10 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051002 fix a silly typo 2005-10-09 Simon J. Gerraty * 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 * 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 * 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 * 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 * Important to use .MAKE rather than MAKE. 2005-03-15 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * Makefile.in (accept test): use ${.MAKE:S,^./,${.CURDIR}/,} so that './bmake -f Makefile test' works. 2004-02-14 Simon J. Gerraty * 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 * arch.c: don't include ranlib.h on ELF systems (thanks to Chuck Cranor ). 2004-01-18 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * boot-strap: Allow setting configure args on command line. 2003-07-31 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 I may have missed some - since they are based on bmake-3.1.12 2002-12-03 Simon J. Gerraty * 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 . * bmake.cat1: update the pre-formatted man page! 2002-11-30 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * makefile.boot.in: Give bmake.boot a MAKESYSPATH that might work. Tue Jun 12 16:48:57 2001 Simon J. Gerraty * 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 * job.c (Job_CatchChildren): don't call Job_CatchOutput() here, its the wrong "fix". Sat Jun 9 00:11:24 2001 Simon J. Gerraty * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Merged with NetBSD make Sat Jun 10 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Thu Jun 1 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Tue May 30 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Thu Apr 27 00:07:47 2000 Simon J. Gerraty * 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 * 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 * 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 * Imported NetBSD's -current make(1) and resolve conflicts. * Applied autoconf patches from bmake v2 * Imported clean code base from NetBSD-1.0 Index: vendor/NetBSD/bmake/dist/Makefile =================================================================== --- vendor/NetBSD/bmake/dist/Makefile (revision 251419) +++ vendor/NetBSD/bmake/dist/Makefile (revision 251420) @@ -1,221 +1,221 @@ -# $Id: Makefile,v 1.12 2013/05/20 16:05:10 sjg Exp $ +# $Id: Makefile,v 1.14 2013/06/05 04:03:22 sjg Exp $ # Base version on src date -MAKE_VERSION= 20130520 +MAKE_VERSION= 20130604 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 \ 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?= .../share/mk:${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 # 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= # supress with -DWITHOUT_* OPTIONS_DEFAULT_YES+= \ AUTOCONF_MK \ INSTALL_MK \ PROG_LINK OPTIONS_DEFAULT_NO+= \ PROG_VERSION # process options now .include .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 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= ${prefix}/share BINDIR= ${prefix}/bin MANDIR= ${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 .endif SHARE_MK?=${SHAREDIR}/mk MKSRC=${srcdir}/mk INSTALL?=${srcdir}/install-sh .if ${MK_INSTALL_MK} == "yes" install: install-mk .endif beforeinstall: test -d ${DESTDIR}${BINDIR} || ${INSTALL} -m 775 -d ${DESTDIR}${BINDIR} test -d ${DESTDIR}${MANDEST} || ${INSTALL} -m 775 -d ${DESTDIR}${MANDEST} install-mk: .if exists(${MKSRC}/install-mk) test -d ${DESTDIR}${SHARE_MK} || ${INSTALL} -m 775 -d ${DESTDIR}${SHARE_MK} sh ${MKSRC}/install-mk -v -m 644 ${DESTDIR}${SHARE_MK} .else @echo need to unpack mk.tar.gz under ${srcdir} or set MKSRC; false .endif # end-delete2 # A simple unit-test driver to help catch regressions accept test: cd ${.CURDIR}/unit-tests && MAKEFLAGS= ${.MAKE} -r -m / TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET} Index: vendor/NetBSD/bmake/dist/job.c =================================================================== --- vendor/NetBSD/bmake/dist/job.c (revision 251419) +++ vendor/NetBSD/bmake/dist/job.c (revision 251420) @@ -1,3030 +1,3032 @@ -/* $NetBSD: job.c,v 1.172 2013/03/05 22:01:43 christos Exp $ */ +/* $NetBSD: job.c,v 1.173 2013/06/05 03:59:43 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.172 2013/03/05 22:01:43 christos Exp $"; +static char rcsid[] = "$NetBSD: job.c,v 1.173 2013/06/05 03:59:43 sjg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: job.c,v 1.172 2013/03/05 22:01:43 christos Exp $"); +__RCSID("$NetBSD: job.c,v 1.173 2013/06/05 03:59:43 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 #include #include #include #include "wait.h" #include #include #include #if !defined(USE_SELECT) && defined(HAVE_POLL_H) #include #else #ifndef USE_SELECT /* no poll.h */ # define USE_SELECT #endif #if defined(HAVE_SYS_SELECT_H) # include #endif #endif #include #include #include #include #if defined(HAVE_SYS_SOCKET_H) # include #endif #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" # define STATIC static /* * 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 */ 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) \ (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); - - 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; - } - } } /*- *----------------------------------------------------------------------- * 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); 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 '-': job->flags |= JOB_IGNERR; 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. */ 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); (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 (job->node->type & OP_MAKE) { /* * 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); } } if (i < max - 1) { /* shift the remaining characters down */ (void)memcpy(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); } else { /* * We have written everything out, so we just start over * from the start of the buffer. No copying. No nothing. */ 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 = ""; } } /*- * 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); } /*- *----------------------------------------------------------------------- * Job_Init -- * Initialize the process module * * Input: * * Results: * none * * Side Effects: * lists and counters are initialized *----------------------------------------------------------------------- */ void Job_Init(void) { /* 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; /* * There is a non-zero chance that we already have children. * eg after 'make -f- < 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; } } 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; } /*- *----------------------------------------------------------------------- * 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 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, JOB_TOKENS[aborting]); while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; } /*- *----------------------------------------------------------------------- * Job_ServerStartTokenAdd -- * Prep the job token pipe in the root make process. * *----------------------------------------------------------------------- */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { int i; char jobarg[64]; if (jp_0 >= 0 && jp_1 >= 0) { /* Pipe passed in from parent */ tokenWaitJob.inPipe = jp_0; tokenWaitJob.outPipe = jp_1; + (void)fcntl(jp_0, F_SETFD, 1); + (void)fcntl(jp_1, F_SETFD, 1); return; } JobCreatePipe(&tokenWaitJob, 15); snprintf(jobarg, sizeof(jobarg), "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); /* * Preload the job pipe with one token per job, save the one * "extra" token for the primary job. * * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is * larger than the write buffer size of the pipe, we will * deadlock here. */ for (i = 1; i < max_tokens; i++) JobTokenAdd(); } /*- *----------------------------------------------------------------------- * Job_TokenReturn -- * Return a withdrawn token to the pool. * *----------------------------------------------------------------------- */ void Job_TokenReturn(void) { jobTokensRunning--; if (jobTokensRunning < 0) Punt("token botch"); if (jobTokensRunning || JOB_TOKENS[aborting] != '+') JobTokenAdd(); } /*- *----------------------------------------------------------------------- * Job_TokenWithdraw -- * Attempt to withdraw a token from the pool. * * Results: * Returns TRUE if a token was withdrawn, and FALSE if the pool * is currently empty. * * Side Effects: * If pool is empty, set wantToken so that we wake up * when a token is released. * *----------------------------------------------------------------------- */ Boolean Job_TokenWithdraw(void) { char tok, tok1; int count; wantToken = 0; if (DEBUG(JOB)) fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n", getpid(), aborting, jobTokensRunning); if (aborting || (jobTokensRunning >= maxJobs)) return FALSE; count = read(tokenWaitJob.inPipe, &tok, 1); if (count == 0) Fatal("eof on job pipe!"); if (count < 0 && jobTokensRunning != 0) { if (errno != EAGAIN) { Fatal("job pipe read: %s", strerror(errno)); } if (DEBUG(JOB)) fprintf(debug_file, "(%d) blocked for token\n", getpid()); wantToken = 1; return FALSE; } if (count == 1 && tok != '+') { /* make being abvorted - remove any other job tokens */ if (DEBUG(JOB)) fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok); while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; /* And put the stopper back */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; Fatal("A failure has been detected in another branch of the parallel make"); } if (count == 1 && jobTokensRunning == 0) /* We didn't want the token really */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; jobTokensRunning++; if (DEBUG(JOB)) fprintf(debug_file, "(%d) withdrew token\n", getpid()); return TRUE; } /*- *----------------------------------------------------------------------- * Job_RunTarget -- * Run the named target if found. If a filename is specified, then * set that to the sources. * * Results: * None * * Side Effects: * exits if the target fails. * *----------------------------------------------------------------------- */ Boolean Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target, TARG_NOCREATE); if (gn == NULL) return FALSE; if (fname) Var_Set(ALLSRC, fname, gn, 0); JobRun(gn); if (gn->made == ERROR) { PrintOnError(gn, "\n\nStop."); exit(1); } return TRUE; } #ifdef USE_SELECT int emul_poll(struct pollfd *fd, int nfd, int timeout) { fd_set rfds, wfds; int i, maxfd, nselect, npoll; struct timeval tv, *tvp; long usecs; FD_ZERO(&rfds); FD_ZERO(&wfds); maxfd = -1; for (i = 0; i < nfd; i++) { fd[i].revents = 0; if (fd[i].events & POLLIN) FD_SET(fd[i].fd, &rfds); if (fd[i].events & POLLOUT) FD_SET(fd[i].fd, &wfds); if (fd[i].fd > maxfd) maxfd = fd[i].fd; } if (maxfd >= FD_SETSIZE) { Punt("Ran out of fd_set slots; " "recompile with a larger FD_SETSIZE."); } if (timeout < 0) { tvp = NULL; } else { usecs = timeout * 1000; tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; tvp = &tv; } nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); if (nselect <= 0) return nselect; npoll = 0; for (i = 0; i < nfd; i++) { if (FD_ISSET(fd[i].fd, &rfds)) fd[i].revents |= POLLIN; if (FD_ISSET(fd[i].fd, &wfds)) fd[i].revents |= POLLOUT; if (fd[i].revents) npoll++; } return npoll; } #endif /* USE_SELECT */ Index: vendor/NetBSD/bmake/dist/make.1 =================================================================== --- vendor/NetBSD/bmake/dist/make.1 (revision 251419) +++ vendor/NetBSD/bmake/dist/make.1 (revision 251420) @@ -1,2123 +1,2130 @@ -.\" $NetBSD: make.1,v 1.213 2013/03/31 05:49:51 sjg Exp $ +.\" $NetBSD: make.1,v 1.215 2013/05/22 19:35:11 christos Exp $ .\" .\" Copyright (c) 1990, 1993 .\" 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. .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd March 30, 2013 +.Dd May 22, 2013 .Dt MAKE 1 .Os .Sh NAME .Nm make .Nd maintain program dependencies .Sh SYNOPSIS .Nm .Op Fl BeikNnqrstWX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags .Op Fl f Ar makefile .Op Fl I Ar directory .Op Fl J Ar private .Op Fl j Ar max_jobs .Op Fl m Ar directory .Op Fl T Ar file .Op Fl V Ar variable .Op Ar variable=value .Op Ar target ... .Sh DESCRIPTION .Nm is a program designed to simplify the maintenance of other programs. Its input is a list of specifications as to the files upon which programs and other files depend. If no .Fl f Ar makefile makefile option is given, .Nm will try to open .Ql Pa makefile then .Ql Pa Makefile in order to find the specifications. If the file .Ql Pa .depend exists, it is read (see .Xr mkdep 1 ) . .Pp This manual page is intended as a reference document only. For a more thorough description of .Nm and makefiles, please refer to .%T "PMake \- A Tutorial" . .Pp .Nm will prepend the contents of the .Va MAKEFLAGS environment variable to the command line arguments before parsing them. .Pp The options are as follows: .Bl -tag -width Ds .It Fl B Try to be backwards compatible by executing a single shell per command and by executing the commands to make the sources of a dependency line in sequence. .It Fl C Ar directory Change to .Ar directory before reading the makefiles or doing anything else. If multiple .Fl C options are specified, each is interpreted relative to the previous one: .Fl C Pa / Fl C Pa etc is equivalent to .Fl C Pa /etc . .It Fl D Ar variable Define .Ar variable to be 1, in the global context. .It Fl d Ar [-]flags Turn on debugging, and specify which portions of .Nm are to print debugging information. Unless the flags are preceded by .Ql \- they are added to the .Va MAKEFLAGS environment variable and will be processed by any child make processes. By default, debugging information is printed to standard error, but this can be changed using the .Ar F debugging flag. The debugging output is always unbuffered; in addition, if debugging is enabled but debugging output is not directed to standard output, then the standard output is line buffered. .Ar Flags is one or more of the following: .Bl -tag -width Ds .It Ar A Print all possible debugging information; equivalent to specifying all of the debugging flags. .It Ar a Print debugging information about archive searching and caching. .It Ar C Print debugging information about current working directory. .It Ar c Print debugging information about conditional evaluation. .It Ar d Print debugging information about directory searching and caching. .It Ar e Print debugging information about failed commands and targets. .It Ar F Ns Oo Sy \&+ Oc Ns Ar filename Specify where debugging output is written. This must be the last flag, because it consumes the remainder of the argument. If the character immediately after the .Ql F flag is .Ql \&+ , then the file will be opened in append mode; otherwise the file will be overwritten. If the file name is .Ql stdout or .Ql stderr then debugging output will be written to the standard output or standard error output file descriptors respectively (and the .Ql \&+ option has no effect). Otherwise, the output will be written to the named file. If the file name ends .Ql .%d then the .Ql %d is replaced by the pid. .It Ar f Print debugging information about loop evaluation. .It Ar "g1" Print the input graph before making anything. .It Ar "g2" Print the input graph after making everything, or before exiting on error. .It Ar "g3" Print the input graph before exiting on error. .It Ar j Print debugging information about running multiple shells. .It Ar l Print commands in Makefiles regardless of whether or not they are prefixed by .Ql @ or other "quiet" flags. Also known as "loud" behavior. .It Ar M Print debugging information about "meta" mode decisions about targets. .It Ar m Print debugging information about making targets, including modification dates. .It Ar n Don't delete the temporary command scripts created when running commands. These temporary scripts are created in the directory referred to by the .Ev TMPDIR environment variable, or in .Pa /tmp if .Ev TMPDIR is unset or set to the empty string. The temporary scripts are created by .Xr mkstemp 3 , and have names of the form .Pa makeXXXXXX . .Em NOTE : This can create many files in .Ev TMPDIR or .Pa /tmp , so use with care. .It Ar p Print debugging information about makefile parsing. .It Ar s Print debugging information about suffix-transformation rules. .It Ar t Print debugging information about target list maintenance. .It Ar V Force the .Fl V option to print raw values of variables. .It Ar v Print debugging information about variable assignment. .It Ar x Run shell commands with .Fl x so the actual commands are printed as they are executed. .El .It Fl e Specify that environment variables override macro assignments within makefiles. .It Fl f Ar makefile Specify a makefile to read instead of the default .Ql Pa makefile . If .Ar makefile is .Ql Fl , standard input is read. Multiple makefiles may be specified, and are read in the order specified. .It Fl I Ar directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the .Fl m option) is automatically included as part of this list. .It Fl i Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying .Ql Fl before each command line in the makefile. .It Fl J Ar private This option should .Em not be specified by the user. .Pp When the .Ar j option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. The value is saved in .Va .MAKE.JOBS . Turns compatibility mode off, unless the .Ar B flag is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. .It Fl k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for sys.mk and makefiles included via the .Ao Ar file Ac Ns -style include statement. The .Fl m option can be used multiple times to form a search path. This path will override the default system include path: /usr/share/mk. Furthermore the system include path will be appended to the search path used for .Qo Ar file Qc Ns -style include statements (see the .Fl I option). .Pp If a file or directory name in the .Fl m argument (or the .Ev MAKESYSPATH environment variable) starts with the string .Qq \&.../ then .Nm will search for the specified file or directory named in the remaining part of the argument string. The search starts with the current directory of the Makefile and then works upward towards the root of the filesystem. If the search is successful, then the resulting directory replaces the .Qq \&.../ specification in the .Fl m argument. If used, this feature allows .Nm to easily search in the current source tree for customized sys.mk files (e.g., by using .Qq \&.../mk/sys.mk as an argument). .It Fl n Display the commands that would have been executed, but do not actually execute them unless the target depends on the .MAKE special source (see below). .It Fl N Display the commands which would have been executed, but do not actually execute any of them; useful for debugging top-level makefiles without descending into subdirectories. .It Fl q Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. .It Fl s Do not echo any commands as they are executed. Equivalent to specifying .Ql Ic @ before each command line in the makefile. .It Fl T Ar tracefile When used with the .Fl j flag, append a trace record to .Ar tracefile for each job started and completed. .It Fl t Rather than re-building a target as specified in the makefile, create it or update its modification time to make it appear up-to-date. .It Fl V Ar variable Print .Nm Ns 's idea of the value of .Ar variable , in the global context. Do not build any targets. Multiple instances of this option may be specified; the variables will be printed one per line, with a blank line for each null or undefined variable. If .Ar variable contains a .Ql \&$ then the value will be expanded before printing. .It Fl W Treat any warnings during makefile parsing as errors. .It Fl X Don't export variables passed on the command line to the environment individually. Variables passed on the command line are still exported via the .Va MAKEFLAGS environment variable. This option may be useful on systems which have a small limit on the size of command arguments. .It Ar variable=value Set the value of the variable .Ar variable to .Ar value . Normally, all values passed on the command line are also exported to sub-makes in the environment. The .Fl X flag disables this behavior. Variable assignments should follow options for POSIX compatibility but no ordering is enforced. .El .Pp There are seven different types of lines in a makefile: file dependency specifications, shell commands, variable assignments, include statements, conditional directives, for loops, and comments. .Pp In general, lines may be continued from one line to the next by ending them with a backslash .Pq Ql \e . The trailing newline character and initial whitespace on the following line are compressed into a single space. .Sh FILE DEPENDENCY SPECIFICATIONS Dependency lines consist of one or more targets, an operator, and zero or more sources. This creates a relationship where the targets .Dq depend on the sources and are usually created from them. The exact relationship between the target and the source is determined by the operator that separates them. The three operators are as follows: .Bl -tag -width flag .It Ic \&: A target is considered out-of-date if its modification time is less than those of any of its sources. Sources for a target accumulate over dependency lines when this operator is used. The target is removed if .Nm is interrupted. .It Ic \&! Targets are always re-created, but not until all sources have been examined and re-created as necessary. Sources for a target accumulate over dependency lines when this operator is used. The target is removed if .Nm is interrupted. .It Ic \&:: If no sources are specified, the target is always re-created. Otherwise, a target is considered out-of-date if any of its sources has been modified more recently than the target. Sources for a target do not accumulate over dependency lines when this operator is used. The target will not be removed if .Nm is interrupted. .El .Pp Targets and sources may contain the shell wildcard values .Ql \&? , .Ql * , .Ql [] , and .Ql {} . The values .Ql \&? , .Ql * , and .Ql [] may only be used as part of the final component of the target or source, and must be used to describe existing files. The value .Ql {} need not necessarily be used to describe existing files. Expansion is in directory order, not alphabetically as done in the shell. .Sh SHELL COMMANDS Each target may have associated with it a series of shell commands, normally used to create the target. Each of the commands in this script .Em must be preceded by a tab. While any target may appear on a dependency line, only one of these dependencies may be followed by a creation script, unless the .Ql Ic \&:: operator is used. .Pp If the first characters of the command line are any combination of .Ql Ic @ , .Ql Ic + , or .Ql Ic \- , the command is treated specially. A .Ql Ic @ causes the command not to be echoed before it is executed. A .Ql Ic + causes the command to be executed even when .Fl n is given. This is similar to the effect of the .MAKE special source, except that the effect can be limited to a single line of a script. A .Ql Ic \- causes any non-zero exit status of the command line to be ignored. .Pp When .Nm is run in jobs mode with .Fl j Ar max_jobs , the entire script for the target is fed to a single instance of the shell. .Pp In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters .Pq Ql #=|^(){};&<>*?[]:$`\e\en it will be passed to the shell, otherwise .Nm will attempt direct execution. .Pp Since .Nm will .Xr chdir 2 to .Ql Va .OBJDIR before executing any targets, each child process starts with that as its current working directory. .Pp Makefiles should be written so that the mode of .Nm operation does not change their behavior. For example, any command which needs to use .Dq cd or .Dq chdir , without side-effect should be put in parenthesis: .Bd -literal -offset indent avoid-chdir-side-effects: @echo Building $@ in `pwd` @(cd ${.CURDIR} && ${.MAKE} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: @echo Building $@ in `pwd`; \\ (cd ${.CURDIR} && ${.MAKE} $@); \\ echo Back in `pwd` .Ed .Sh VARIABLE ASSIGNMENTS Variables in make are much like variables in the shell, and, by tradition, consist of all upper-case letters. .Ss Variable assignment modifiers The five operators that can be used to assign values to variables are as follows: .Bl -tag -width Ds .It Ic \&= Assign the value to the variable. Any previous value is overridden. .It Ic \&+= Append the value to the current value of the variable. .It Ic \&?= Assign the value to the variable if it is not already defined. .It Ic \&:= Assign with expansion, i.e. expand the value before assigning it to the variable. Normally, expansion is not done until the variable is referenced. .Em NOTE : References to undefined variables are .Em not expanded. This can cause problems when variable modifiers are used. .It Ic \&!= Expand the value and pass it to the shell for execution and assign the result to the variable. Any newlines in the result are replaced with spaces. .El .Pp Any white-space before the assigned .Ar value is removed; if the value is being appended, a single space is inserted between the previous contents of the variable and the appended value. .Pp Variables are expanded by surrounding the variable name with either curly braces .Pq Ql {} or parentheses .Pq Ql () and preceding it with a dollar sign .Pq Ql \&$ . If the variable name contains only a single letter, the surrounding braces or parentheses are not required. This shorter form is not recommended. .Pp If the variable name contains a dollar, then the name itself is expanded first. This allows almost arbitrary variable names, however names containing dollar, braces, parenthesis, or whitespace are really best avoided! .Pp If the result of expanding a variable contains a dollar sign .Pq Ql \&$ the string is expanded again. .Pp Variable substitution occurs at three distinct times, depending on where the variable is being used. .Bl -enum .It Variables in dependency lines are expanded as the line is read. .It Variables in shell commands are expanded when the shell command is executed. .It .Dq .for loop index variables are expanded on each loop iteration. Note that other variables are not expanded inside loops so the following example code: .Bd -literal -offset indent .Dv .for i in 1 2 3 a+= ${i} j= ${i} b+= ${j} .Dv .endfor all: @echo ${a} @echo ${b} .Ed will print: .Bd -literal -offset indent 1 2 3 3 3 3 .Ed Because while ${a} contains .Dq 1 2 3 after the loop is executed, ${b} contains .Dq ${j} ${j} ${j} which expands to .Dq 3 3 3 since after the loop completes ${j} contains .Dq 3 . .El .Ss Variable classes The four different classes of variables (in order of increasing precedence) are: .Bl -tag -width Ds .It Environment variables Variables defined as part of .Nm Ns 's environment. .It Global variables Variables defined in the makefile or in included makefiles. .It Command line variables Variables defined as part of the command line. .It Local variables Variables that are defined specific to a certain target. The seven local variables are as follows: .Bl -tag -width ".ARCHIVE" .It Va .ALLSRC The list of all sources for this target; also known as .Ql Va \&\*[Gt] . .It Va .ARCHIVE The name of the archive file. .It Va .IMPSRC In suffix-transformation rules, the name/path of the source from which the target is to be transformed (the .Dq implied source); also known as .Ql Va \&\*[Lt] . It is not defined in explicit rules. .It Va .MEMBER The name of the archive member. .It Va .OODATE The list of sources for this target that were deemed out-of-date; also known as .Ql Va \&? . .It Va .PREFIX The file prefix of the target, containing only the file portion, no suffix or preceding directory components; also known as .Ql Va * . .It Va .TARGET The name of the target; also known as .Ql Va @ . .El .Pp The shorter forms .Ql Va @ , .Ql Va \&? , .Ql Va \&\*[Lt] , .Ql Va \&\*[Gt] , and .Ql Va * are permitted for backward compatibility with historical makefiles and are not recommended. The six variables .Ql Va "@F" , .Ql Va "@D" , .Ql Va "\*[Lt]F" , .Ql Va "\*[Lt]D" , .Ql Va "*F" , and .Ql Va "*D" are permitted for compatibility with .At V makefiles and are not recommended. .Pp Four of the local variables may be used in sources on dependency lines because they expand to the proper value for each target on the line. These variables are .Ql Va .TARGET , .Ql Va .PREFIX , .Ql Va .ARCHIVE , and .Ql Va .MEMBER . .El .Ss Additional built-in variables In addition, .Nm sets or knows about the following variables: .Bl -tag -width .MAKEOVERRIDES .It Va \&$ A single dollar sign .Ql \&$ , i.e. .Ql \&$$ expands to a single dollar sign. .It Va .ALLTARGETS The list of all targets encountered in the Makefile. If evaluated during Makefile parsing, lists only those targets encountered thus far. .It Va .CURDIR A path to the directory where .Nm was executed. Refer to the description of .Ql Ev PWD for more details. .It Ev MAKE The name that .Nm was executed with .Pq Va argv[0] . For compatibility .Nm also sets .Va .MAKE with the same value. The preferred variable to use is the environment variable .Ev MAKE because it is more compatible with other versions of .Nm and cannot be confused with the special target with the same name. .It Va .MAKE.DEPENDFILE Names the makefile (default .Ql Pa .depend ) from which generated dependencies are read. .It Va .MAKE.EXPAND_VARIABLES A boolean that controls the default behavior of the .Fl V option. .It Va .MAKE.EXPORTED The list of variables exported by .Nm . .It Va .MAKE.JOBS The argument to the .Fl j option. .It Va .MAKE.JOB.PREFIX If .Nm is run with .Ar j then output for each target is prefixed with a token .Ql --- target --- the first part of which can be controlled via .Va .MAKE.JOB.PREFIX . .br For example: .Li .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}] would produce tokens like .Ql ---make[1234] target --- making it easier to track the degree of parallelism being achieved. .It Ev MAKEFLAGS The environment variable .Ql Ev MAKEFLAGS may contain anything that may be specified on .Nm Ns 's command line. Anything specified on .Nm Ns 's command line is appended to the .Ql Ev MAKEFLAGS variable which is then entered into the environment for all programs which .Nm executes. .It Va .MAKE.LEVEL The recursion depth of .Nm . The initial instance of .Nm will be 0, and an incremented value is put into the environment to be seen by the next generation. This allows tests like: .Li .if ${.MAKE.LEVEL} == 0 to protect things which should only be evaluated in the initial instance of .Nm . .It Va .MAKE.MAKEFILE_PREFERENCE The ordered list of makefile names (default .Ql Pa makefile , .Ql Pa Makefile ) that .Nm will look for. .It Va .MAKE.MAKEFILES The list of makefiles read by .Nm , which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. .It Va .MAKE.MODE Processed after reading all makefiles. Can affect the mode that .Nm runs in. It can contain a number of keywords: .Bl -hang -width ignore-cmd .It Pa compat Like .Fl B , puts .Nm into "compat" mode. .It Pa meta Puts .Nm into "meta" mode, where meta files are created for each target to capture the command run, the output generated and if .Xr filemon 4 is available, the system calls which are of interest to .Nm . The captured output can be very useful when diagnosing errors. .It Pa curdirOk= Ar bf Normally .Nm will not create .meta files in .Ql Va .CURDIR . This can be overridden by setting .Va bf to a value which represents True. .It Pa env For debugging, it can be useful to inlcude the environment in the .meta file. .It Pa verbose If in "meta" mode, print a clue about the target being built. This is useful if the build is otherwise running silently. The message printed the value of: .Va .MAKE.META.PREFIX . .It Pa ignore-cmd Some makefiles have commands which are simply not stable. This keyword causes them to be ignored for determining whether a target is out of date in "meta" mode. See also .Ic .NOMETA_CMP . .It Pa silent= Ar bf If .Va bf is True, when a .meta file is created, mark the target .Ic .SILENT . .El .It Va .MAKE.META.BAILIWICK In "meta" mode, provides a list of prefixes which match the directories controlled by .Nm . If a file that was generated outside of .Va .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. .It Va .MAKE.META.CREATED In "meta" mode, this variable contains a list of all the meta files updated. If not empty, it can be used to trigger processing of .Va .MAKE.META.FILES . .It Va .MAKE.META.FILES In "meta" mode, this variable contains a list of all the meta files used (updated or not). This list can be used to process the meta files to extract dependency information. .It Va .MAKE.META.PREFIX Defines the message printed for each meta file updated in "meta verbose" mode. The default value is: .Dl Building ${.TARGET:H:tA}/${.TARGET:T} .It Va .MAKEOVERRIDES This variable is used to record the names of variables assigned to on the command line, so that they may be exported as part of .Ql Ev MAKEFLAGS . This behaviour can be disabled by assigning an empty value to .Ql Va .MAKEOVERRIDES within a makefile. Extra variables can be exported from a makefile by appending their names to .Ql Va .MAKEOVERRIDES . .Ql Ev MAKEFLAGS is re-exported whenever .Ql Va .MAKEOVERRIDES is modified. .It Va .MAKE.PATH_FILEMON If .Nm was built with .Xr filemon 4 support, this is set to the path of the device node. This allows makefiles to test for this support. .It Va .MAKE.PID The process-id of .Nm . .It Va .MAKE.PPID The parent process-id of .Nm . .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm stops due to an error, it prints its name and the value of .Ql Va .CURDIR as well as the value of any variables named in .Ql Va MAKE_PRINT_VAR_ON_ERROR . .It Va .newline This variable is simply assigned a newline character as its value. This allows expansions using the .Cm \&:@ modifier to put a newline between iterations of the loop rather than a space. For example, the printing of .Ql Va MAKE_PRINT_VAR_ON_ERROR could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}. .It Va .OBJDIR A path to the directory where the targets are built. Its value is determined by trying to .Xr chdir 2 to the following directories in order and using the first match: .Bl -enum .It .Ev ${MAKEOBJDIRPREFIX}${.CURDIR} .Pp (Only if .Ql Ev MAKEOBJDIRPREFIX is set in the environment or on the command line.) .It .Ev ${MAKEOBJDIR} .Pp (Only if .Ql Ev MAKEOBJDIR is set in the environment or on the command line.) .It .Ev ${.CURDIR} Ns Pa /obj. Ns Ev ${MACHINE} .It .Ev ${.CURDIR} Ns Pa /obj .It .Pa /usr/obj/ Ns Ev ${.CURDIR} .It .Ev ${.CURDIR} .El .Pp Variable expansion is performed on the value before it's used, so expressions such as .Dl ${.CURDIR:S,^/usr/src,/var/obj,} may be used. This is especially useful with .Ql Ev MAKEOBJDIR . .Pp .Ql Va .OBJDIR may be modified in the makefile as a global variable. In all cases, .Nm will .Xr chdir 2 to .Ql Va .OBJDIR and set .Ql Ev PWD to that directory before executing any targets. . .It Va .PARSEDIR A path to the directory of the current .Ql Pa Makefile being parsed. .It Va .PARSEFILE The basename of the current .Ql Pa Makefile being parsed. This variable and .Ql Va .PARSEDIR are both set only while the .Ql Pa Makefiles are being parsed. If you want to retain their current values, assign them to a variable using assignment with expansion: .Pq Ql Cm \&:= . .It Va .PATH A variable that represents the list of directories that .Nm will search for files. The search list should be updated using the target .Ql Va .PATH rather than the variable. .It Ev PWD Alternate path to the current directory. .Nm normally sets .Ql Va .CURDIR to the canonical path given by .Xr getcwd 3 . However, if the environment variable .Ql Ev PWD is set and gives a path to the current directory, then .Nm sets .Ql Va .CURDIR to the value of .Ql Ev PWD instead. This behaviour is disabled if .Ql Ev MAKEOBJDIRPREFIX is set or .Ql Ev MAKEOBJDIR contains a variable transform. .Ql Ev PWD is set to the value of .Ql Va .OBJDIR for all programs which .Nm executes. .It Ev .TARGETS The list of targets explicitly specified on the command line, if any. .It Ev VPATH Colon-separated .Pq Dq \&: lists of directories that .Nm will search for files. The variable is supported for compatibility with old make programs only, use .Ql Va .PATH instead. .El .Ss Variable modifiers Variable expansion may be modified to select or modify each word of the variable (where a .Dq word is white-space delimited sequence of characters). The general format of a variable expansion is as follows: .Pp .Dl ${variable[:modifier[:...]]} .Pp Each modifier begins with a colon, which may be escaped with a backslash .Pq Ql \e . .Pp A set of modifiers can be specified via a variable, as follows: .Pp .Dl modifier_variable=modifier[:...] .Dl ${variable:${modifier_variable}[:...]} .Pp In this case the first modifier in the modifier_variable does not start with a colon, since that must appear in the referencing variable. If any of the modifiers in the modifier_variable contain a dollar sign .Pq Ql $ , these must be doubled to avoid early expansion. .Pp The supported modifiers are: .Bl -tag -width EEE .It Cm \&:E Replaces each word in the variable with its suffix. .It Cm \&:H Replaces each word in the variable with everything but the last component. .It Cm \&:M Ns Ar pattern Select only those words that match .Ar pattern . The standard shell wildcard characters .Pf ( Ql * , .Ql \&? , and .Ql Oo Oc ) may be used. The wildcard characters may be escaped with a backslash .Pq Ql \e . .It Cm \&:N Ns Ar pattern This is identical to .Ql Cm \&:M , but selects all words which do not match .Ar pattern . .It Cm \&:O Order every word in variable alphabetically. To sort words in reverse order use the .Ql Cm \&:O:[-1..1] combination of modifiers. .It Cm \&:Ox Randomize words in variable. The results will be different each time you are referring to the modified variable; use the assignment with expansion .Pq Ql Cm \&:= to prevent such behaviour. For example, .Bd -literal -offset indent LIST= uno due tre quattro RANDOM_LIST= ${LIST:Ox} STATIC_RANDOM_LIST:= ${LIST:Ox} all: @echo "${RANDOM_LIST}" @echo "${RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" .Ed may produce output similar to: .Bd -literal -offset indent quattro due tre uno tre due quattro uno due uno quattro tre due uno quattro tre .Ed .It Cm \&:Q Quotes every shell meta-character in the variable, so that it can be passed safely through recursive invocations of .Nm . .It Cm \&:R Replaces each word in the variable with everything but its suffix. .It Cm \&:gmtime The value is a format string for .Xr strftime 3 , using the current .Xr gmtime 3 . .It Cm \&:hash Compute a 32bit hash of the value and encode it as hex digits. .It Cm \&:localtime The value is a format string for .Xr strftime 3 , using the current .Xr localtime 3 . .It Cm \&:tA Attempt to convert variable to an absolute path using .Xr realpath 3 , if that fails, the value is unchanged. .It Cm \&:tl Converts variable to lower-case letters. .It Cm \&:ts Ns Ar c Words in the variable are normally separated by a space on expansion. This modifier sets the separator to the character .Ar c . If .Ar c is omitted, then no separator is used. The common escapes (including octal numeric codes), work as expected. .It Cm \&:tu Converts variable to upper-case letters. .It Cm \&:tW Causes the value to be treated as a single word (possibly containing embedded white space). See also .Ql Cm \&:[*] . .It Cm \&:tw Causes the value to be treated as a sequence of words delimited by white space. See also .Ql Cm \&:[@] . .Sm off .It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW .Sm on Modify the first occurrence of .Ar old_string in the variable's value, replacing it with .Ar new_string . If a .Ql g is appended to the last slash of the pattern, all occurrences in each word are replaced. If a .Ql 1 is appended to the last slash of the pattern, only the first word is affected. If a .Ql W is appended to the last slash of the pattern, then the value is treated as a single word (possibly containing embedded white space). If .Ar old_string begins with a caret .Pq Ql ^ , .Ar old_string is anchored at the beginning of each word. If .Ar old_string ends with a dollar sign .Pq Ql \&$ , it is anchored at the end of each word. Inside .Ar new_string , an ampersand .Pq Ql \*[Am] is replaced by .Ar old_string (without any .Ql ^ or .Ql \&$ ) . Any character may be used as a delimiter for the parts of the modifier string. The anchoring, ampersand and delimiter characters may be escaped with a backslash .Pq Ql \e . .Pp Variable expansion occurs in the normal fashion inside both .Ar old_string and .Ar new_string with the single exception that a backslash is used to prevent the expansion of a dollar sign .Pq Ql \&$ , not a preceding dollar sign as is usual. .Sm off .It Cm \&:C No \&/ Ar pattern No \&/ Ar replacement No \&/ Op Cm 1gW .Sm on The .Cm \&:C modifier is just like the .Cm \&:S modifier except that the old and new strings, instead of being simple strings, are a regular expression (see .Xr regex 3 ) string .Ar pattern and an .Xr ed 1 Ns \-style string .Ar replacement . Normally, the first occurrence of the pattern .Ar pattern in each word of the value is substituted with .Ar replacement . The .Ql 1 modifier causes the substitution to apply to at most one word; the .Ql g modifier causes the substitution to apply to as many instances of the search pattern .Ar pattern as occur in the word or words it is found in; the .Ql W modifier causes the value to be treated as a single word (possibly containing embedded white space). Note that .Ql 1 and .Ql g are orthogonal; the former specifies whether multiple words are potentially affected, the latter whether multiple substitutions can potentially occur within each affected word. .It Cm \&:T Replaces each word in the variable with its last component. .It Cm \&:u Remove adjacent duplicate words (like .Xr uniq 1 ) . .Sm off .It Cm \&:\&? Ar true_string Cm \&: Ar false_string .Sm on If the variable name (not its value), when parsed as a .if conditional expression, evaluates to true, return as its value the .Ar true_string , otherwise return the .Ar false_string . Since the variable name is used as the expression, \&:\&? must be the first modifier after the variable name itself - which will, of course, usually contain variable expansions. A common error is trying to use expressions like .Dl ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS), to determine is any words match "42" you need to use something like: .Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} . .It Ar :old_string=new_string This is the .At V style variable substitution. It must be the last modifier specified. If .Ar old_string or .Ar new_string do not contain the pattern matching character .Ar % then it is assumed that they are anchored at the end of each word, so only suffixes or entire words may be replaced. Otherwise .Ar % is the substring of .Ar old_string to be replaced in .Ar new_string . .Pp Variable expansion occurs in the normal fashion inside both .Ar old_string and .Ar new_string with the single exception that a backslash is used to prevent the expansion of a dollar sign .Pq Ql \&$ , not a preceding dollar sign as is usual. .Sm off .It Cm \&:@ Ar temp Cm @ Ar string Cm @ .Sm on This is the loop expansion mechanism from the OSF Development Environment (ODE) make. Unlike .Cm \&.for loops expansion occurs at the time of reference. Assign .Ar temp to each word in the variable and evaluate .Ar string . The ODE convention is that .Ar temp should start and end with a period. For example. .Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} .Pp However a single character varaiable is often more readable: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} .It Cm \&:U Ns Ar newval If the variable is undefined .Ar newval is the value. If the variable is defined, the existing value is returned. This is another ODE make feature. It is handy for setting per-target CFLAGS for instance: .Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: .Dl ${VAR:D:Unewval} .It Cm \&:D Ns Ar newval If the variable is defined .Ar newval is the value. .It Cm \&:L The name of the variable is the value. .It Cm \&:P The path of the node which has the same name as the variable is the value. If no such node exists or its path is null, then the name of the variable is used. In order for this modifier to work, the name (node) must at least have appeared on the rhs of a dependency. .Sm off .It Cm \&:\&! Ar cmd Cm \&! .Sm on The output of running .Ar cmd is the value. .It Cm \&:sh If the variable is non-empty it is run as a command and the output becomes the new value. .It Cm \&::= Ns Ar str The variable is assigned the value .Ar str after substitution. This modifier and its variations are useful in obscure situations such as wanting to set a variable when shell commands are being parsed. These assignment modifiers always expand to nothing, so if appearing in a rule line by themselves should be preceded with something to keep .Nm happy. .Pp The .Ql Cm \&:: helps avoid false matches with the .At V style .Cm \&:= modifier and since substitution always occurs the .Cm \&::= form is vaguely appropriate. .It Cm \&::?= Ns Ar str As for .Cm \&::= but only if the variable does not already have a value. .It Cm \&::+= Ns Ar str Append .Ar str to the variable. .It Cm \&::!= Ns Ar cmd Assign the output of .Ar cmd to the variable. .It Cm \&:\&[ Ns Ar range Ns Cm \&] Selects one or more words from the value, or performs other operations related to the way in which the value is divided into words. .Pp Ordinarily, a value is treated as a sequence of words delimited by white space. Some modifiers suppress this behaviour, causing a value to be treated as a single word (possibly containing embedded white space). An empty value, or a value that consists entirely of white-space, is treated as a single word. For the purposes of the .Ql Cm \&:[] modifier, the words are indexed both forwards using positive integers (where index 1 represents the first word), and backwards using negative integers (where index \-1 represents the last word). .Pp The .Ar range is subjected to variable expansion, and the expanded result is then interpreted as follows: .Bl -tag -width index .\" :[n] .It Ar index Selects a single word from the value. .\" :[start..end] .It Ar start Ns Cm \&.. Ns Ar end Selects all words from .Ar start to .Ar end , inclusive. For example, .Ql Cm \&:[2..-1] selects all words from the second word to the last word. If .Ar start is greater than .Ar end , then the words are output in reverse order. For example, .Ql Cm \&:[-1..1] selects all the words from last to first. .\" :[*] .It Cm \&* Causes subsequent modifiers to treat the value as a single word (possibly containing embedded white space). Analogous to the effect of \&"$*\&" in Bourne shell. .\" :[0] .It 0 Means the same as .Ql Cm \&:[*] . .\" :[*] .It Cm \&@ Causes subsequent modifiers to treat the value as a sequence of words delimited by white space. Analogous to the effect of \&"$@\&" in Bourne shell. .\" :[#] .It Cm \&# Returns the number of words in the value. .El \" :[range] .El .Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS Makefile inclusion, conditional structures and for loops reminiscent of the C programming language are provided in .Nm . All such structures are identified by a line beginning with a single dot .Pq Ql \&. character. Files are included with either .Cm \&.include Aq Ar file or .Cm \&.include Pf \*q Ar file Ns \*q . Variables between the angle brackets or double quotes are expanded to form the file name. If angle brackets are used, the included makefile is expected to be in the system makefile directory. If double quotes are used, the including makefile's directory and any directories specified using the .Fl I option are searched before the system makefile directory. For compatibility with other versions of .Nm .Ql include file ... is also accepted. If the include statement is written as .Cm .-include or as .Cm .sinclude then errors locating and/or opening include files are ignored. .Pp Conditional expressions are also preceded by a single dot as the first character of a line. The possible conditionals are as follows: .Bl -tag -width Ds .It Ic .error Ar message The message is printed along with the name of the makefile and line number, then .Nm will exit. .It Ic .export Ar variable ... Export the specified global variable. If no variable list is provided, all globals are exported except for internal variables (those that start with .Ql \&. ) . This is not affected by the .Fl X flag, so should be used with caution. For compatibility with other .Nm programs .Ql export variable=value is also accepted. .Pp Appending a variable name to .Va .MAKE.EXPORTED is equivalent to exporting a variable. .It Ic .export-env Ar variable ... The same as .Ql .export , except that the variable is not appended to .Va .MAKE.EXPORTED . This allows exporting a value to the environment which is different from that used by .Nm internally. .It Ic .info Ar message The message is printed along with the name of the makefile and line number. .It Ic .undef Ar variable Un-define the specified global variable. Only global variables may be un-defined. .It Ic .unexport Ar variable ... The opposite of .Ql .export . The specified global .Va variable will be removed from .Va .MAKE.EXPORTED . If no variable list is provided, all globals are unexported, and .Va .MAKE.EXPORTED deleted. .It Ic .unexport-env Unexport all globals previously exported and clear the environment inherited from the parent. This operation will cause a memory leak of the original environment, so should be used sparingly. Testing for .Va .MAKE.LEVEL being 0, would make sense. Also note that any variables which originated in the parent environment should be explicitly preserved if desired. For example: .Bd -literal -offset indent .Li .if ${.MAKE.LEVEL} == 0 PATH := ${PATH} .Li .unexport-env .Li .export PATH .Li .endif .Pp .Ed Would result in an environment containing only .Ql Ev PATH , which is the minimal useful environment. Actually .Ql Ev .MAKE.LEVEL will also be pushed into the new environment. .It Ic .warning Ar message The message prefixed by .Ql Pa warning: is printed along with the name of the makefile and line number. .It Ic \&.if Oo \&! Oc Ns Ar expression Op Ar operator expression ... Test the value of an expression. .It Ic .ifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ... Test the value of a variable. .It Ic .ifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ... Test the value of a variable. .It Ic .ifmake Oo \&! Oc Ns Ar target Op Ar operator target ... Test the target being built. .It Ic .ifnmake Oo \&! Ns Oc Ar target Op Ar operator target ... Test the target being built. .It Ic .else Reverse the sense of the last conditional. .It Ic .elif Oo \&! Ns Oc Ar expression Op Ar operator expression ... A combination of .Ql Ic .else followed by .Ql Ic .if . .It Ic .elifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ... A combination of .Ql Ic .else followed by .Ql Ic .ifdef . .It Ic .elifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ... A combination of .Ql Ic .else followed by .Ql Ic .ifndef . .It Ic .elifmake Oo \&! Oc Ns Ar target Op Ar operator target ... A combination of .Ql Ic .else followed by .Ql Ic .ifmake . .It Ic .elifnmake Oo \&! Oc Ns Ar target Op Ar operator target ... A combination of .Ql Ic .else followed by .Ql Ic .ifnmake . .It Ic .endif End the body of the conditional. .El .Pp The .Ar operator may be any one of the following: .Bl -tag -width "Cm XX" .It Cm \&|\&| Logical OR. .It Cm \&\*[Am]\*[Am] Logical .Tn AND ; of higher precedence than .Dq \&|\&| . .El .Pp As in C, .Nm will only evaluate a conditional as far as is necessary to determine its value. Parentheses may be used to change the order of evaluation. The boolean operator .Ql Ic \&! may be used to logically negate an entire conditional. It is of higher precedence than .Ql Ic \&\*[Am]\*[Am] . .Pp The value of .Ar expression may be any of the following: .Bl -tag -width defined .It Ic defined Takes a variable name as an argument and evaluates to true if the variable has been defined. .It Ic make Takes a target name as an argument and evaluates to true if the target was specified as part of .Nm Ns 's command line or was declared the default target (either implicitly or explicitly, see .Va .MAIN ) before the line containing the conditional. .It Ic empty Takes a variable, with possible modifiers, and evaluates to true if the expansion of the variable would result in an empty string. .It Ic exists Takes a file name as an argument and evaluates to true if the file exists. The file is searched for on the system search path (see .Va .PATH ) . .It Ic target Takes a target name as an argument and evaluates to true if the target has been defined. .It Ic commands Takes a target name as an argument and evaluates to true if the target has been defined and has commands associated with it. .El .Pp .Ar Expression may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison, after which the integral values are compared. A value is interpreted as hexadecimal if it is preceded by 0x, otherwise it is decimal; octal numbers are not supported. The standard C relational operators are all supported. If after variable expansion, either the left or right hand side of a .Ql Ic == or .Ql Ic "!=" operator is not an integral value, then string comparison is performed between the expanded variables. If no relational operator is given, it is assumed that the expanded variable is being compared against 0 or an empty string in the case of a string comparison. .Pp When .Nm is evaluating one of these conditional expressions, and it encounters a (white-space separated) word it doesn't recognize, either the .Dq make or .Dq defined expression is applied to it, depending on the form of the conditional. If the form is .Ql Ic .ifdef , .Ql Ic .ifndef , or .Ql Ic .if the .Dq defined expression is applied. Similarly, if the form is .Ql Ic .ifmake or .Ql Ic .ifnmake , the .Dq make expression is applied. .Pp If the conditional evaluates to true the parsing of the makefile continues as before. If it evaluates to false, the following lines are skipped. In both cases this continues until a .Ql Ic .else or .Ql Ic .endif is found. .Pp For loops are typically used to apply a set of rules to a list of files. The syntax of a for loop is: .Pp .Bl -tag -compact -width Ds .It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression .It Aq make-rules .It Ic \&.endfor .El .Pp After the for .Ic expression is evaluated, it is split into words. On each iteration of the loop, one word is taken and assigned to each .Ic variable , in order, and these .Ic variables are substituted into the .Ic make-rules inside the body of the for loop. The number of words must come out even; that is, if there are three iteration variables, the number of words provided must be a multiple of three. .Sh COMMENTS Comments begin with a hash .Pq Ql \&# character, anywhere but in a shell command line, and continue to the end of an unescaped new line. .Sh SPECIAL SOURCES (ATTRIBUTES) .Bl -tag -width .IGNOREx .It Ic .EXEC Target is never out of date, but always execute commands anyway. .It Ic .IGNORE Ignore any errors from the commands associated with this target, exactly as if they all were preceded by a dash .Pq Ql \- . .\" .It Ic .INVISIBLE .\" XXX .\" .It Ic .JOIN .\" XXX .It Ic .MADE Mark all sources of this target as being up-to-date. .It Ic .MAKE Execute the commands associated with this target even if the .Fl n or .Fl t options were specified. Normally used to mark recursive .Nm Ns 's . .It Ic .META Create a meta file for the target, even if it is flagged as .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL . Usage in conjunction with .Ic .MAKE is the most likely case. In "meta" mode, the target is out-of-date if the meta file is missing. .It Ic .NOMETA Do not create a meta file for the target. Meta files are also not created for .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL targets. .It Ic .NOMETA_CMP Ignore differences in commands when deciding if target is out of date. This is useful if the command contains a value which always changes. If the number of commands change, though, the target will still be out of date. The same effect applies to any command line that uses the variable .Va .OODATE , which can be used for that purpose even when not otherwise needed or desired: .Bd -literal -offset indent skip-compare-for-some: @echo this will be compared @echo this will not ${.OODATE:M.NOMETA_CMP} @echo this will also be compared .Ed The .Cm \&:M pattern suppresses any expansion of the unwanted variable. .It Ic .NOPATH Do not search for the target in the directories specified by .Ic .PATH . .It Ic .NOTMAIN Normally .Nm selects the first target it encounters as the default target to be built if no target was specified. This source prevents this target from being selected. .It Ic .OPTIONAL If a target is marked with this attribute and .Nm can't figure out how to create it, it will ignore this fact and assume the file isn't needed or already exists. .It Ic .PHONY The target does not correspond to an actual file; it is always considered to be out of date, and will not be created with the .Fl t option. Suffix-transformation rules are not applied to .Ic .PHONY targets. .It Ic .PRECIOUS When .Nm is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. .It Ic .RECURSIVE Synonym for .Ic .MAKE . .It Ic .SILENT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign .Pq Ql @ . .It Ic .USE Turn the target into .Nm Ns 's version of a macro. When the target is used as a source for another target, the other target acquires the commands, sources, and attributes (except for .Ic .USE ) of the source. If the target already has commands, the .Ic .USE target's commands are appended to them. .It Ic .USEBEFORE Exactly like .Ic .USE , but prepend the .Ic .USEBEFORE target commands to the target. .It Ic .WAIT If .Ic .WAIT appears in a dependency line, the sources that precede it are made before the sources that succeed it in the line. Since the dependents of files are not made until the file itself could be made, this also stops the dependents being built unless they are needed for another branch of the dependency tree. So given: .Bd -literal x: a .WAIT b echo x a: echo a b: b1 echo b b1: echo b1 .Ed the output is always .Ql a , .Ql b1 , .Ql b , .Ql x . .br The ordering imposed by .Ic .WAIT is only relevant for parallel makes. .El .Sh SPECIAL TARGETS Special targets may not be included with other targets, i.e. they must be the only target specified. .Bl -tag -width .BEGINx .It Ic .BEGIN Any command lines attached to this target are executed before anything else is done. .It Ic .DEFAULT This is sort of a .Ic .USE rule for any target (that was used only as a source) that .Nm can't figure out any other way to create. Only the shell script is used. The .Ic .IMPSRC variable of a target that inherits .Ic .DEFAULT Ns 's commands is set to the target's own name. .It Ic .END Any command lines attached to this target are executed after everything else is done. .It Ic .ERROR Any command lines attached to this target are executed when another target fails. The .Ic .ERROR_TARGET variable is set to the target that failed. See also .Ic MAKE_PRINT_VAR_ON_ERROR . .It Ic .IGNORE Mark each of the sources with the .Ic .IGNORE attribute. If no sources are specified, this is the equivalent of specifying the .Fl i option. .It Ic .INTERRUPT If .Nm is interrupted, the commands for this target will be executed. .It Ic .MAIN If no target is specified when .Nm is invoked, this target will be built. .It Ic .MAKEFLAGS This target provides a way to specify flags for .Nm when the makefile is used. The flags are as if typed to the shell, though the .Fl f option will have no effect. .\" XXX: NOT YET!!!! .\" .It Ic .NOTPARALLEL .\" The named targets are executed in non parallel mode. .\" If no targets are .\" specified, then all targets are executed in non parallel mode. .It Ic .NOPATH Apply the .Ic .NOPATH attribute to any specified sources. .It Ic .NOTPARALLEL Disable parallel mode. .It Ic .NO_PARALLEL Synonym for .Ic .NOTPARALLEL , for compatibility with other pmake variants. .It Ic .ORDER The named targets are made in sequence. This ordering does not add targets to the list of targets to be made. Since the dependents of a target do not get built until the target itself could be built, unless .Ql a is built by another part of the dependency graph, the following is a dependency loop: .Bd -literal \&.ORDER: b a b: a .Ed .Pp The ordering imposed by .Ic .ORDER is only relevant for parallel makes. .\" XXX: NOT YET!!!! .\" .It Ic .PARALLEL .\" The named targets are executed in parallel mode. .\" If no targets are .\" specified, then all targets are executed in parallel mode. .It Ic .PATH The sources are directories which are to be searched for files not found in the current directory. If no sources are specified, any previously specified directories are deleted. If the source is the special .Ic .DOTLAST target, then the current working directory is searched last. .It Ic .PHONY Apply the .Ic .PHONY attribute to any specified sources. .It Ic .PRECIOUS Apply the .Ic .PRECIOUS attribute to any specified sources. If no sources are specified, the .Ic .PRECIOUS attribute is applied to every target in the file. .It Ic .SHELL Sets the shell that .Nm will use to execute commands. The sources are a set of .Ar field=value pairs. .Bl -tag -width hasErrCtls .It Ar name This is the minimal specification, used to select one of the builtin shell specs; .Ar sh , .Ar ksh , and .Ar csh . .It Ar path Specifies the path to the shell. .It Ar hasErrCtl Indicates whether the shell supports exit on error. .It Ar check The command to turn on error checking. .It Ar ignore The command to disable error checking. .It Ar echo The command to turn on echoing of commands executed. .It Ar quiet The command to turn off echoing of commands executed. .It Ar filter The output to filter after issuing the .Ar quiet command. It is typically identical to .Ar quiet . .It Ar errFlag The flag to pass the shell to enable error checking. .It Ar echoFlag The flag to pass the shell to enable command echoing. .It Ar newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. .El Example: .Bd -literal \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e check="set \-e" ignore="set +e" \e echo="set \-v" quiet="set +v" filter="set +v" \e echoFlag=v errFlag=e newline="'\en'" .Ed .It Ic .SILENT Apply the .Ic .SILENT attribute to any specified sources. If no sources are specified, the .Ic .SILENT attribute is applied to every command in the file. .It Ic .STALE This target gets run when a dependency file contains stale entries, having .Va .ALLSRC set to the name of that dependency file. .It Ic .SUFFIXES Each source specifies a suffix to .Nm . If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. .Pp Example: .Bd -literal \&.SUFFIXES: .o \&.c.o: cc \-o ${.TARGET} \-c ${.IMPSRC} .Ed .El .Sh ENVIRONMENT .Nm uses the following environment variables, if they exist: .Ev MACHINE , .Ev MACHINE_ARCH , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , .Ev MAKESYSPATH , .Ev PWD , and .Ev TMPDIR . .Pp .Ev MAKEOBJDIRPREFIX and .Ev MAKEOBJDIR may only be set in the environment or on the command line to .Nm and not as makefile variables; see the description of .Ql Va .OBJDIR for more details. .Sh FILES .Bl -tag -width /usr/share/mk -compact .It .depend list of dependencies .It Makefile list of dependencies .It makefile list of dependencies .It sys.mk system makefile .It /usr/share/mk system makefile directory .El .Sh COMPATIBILITY The basic make syntax is compatible between different versions of make, however the special variables, variable modifiers and conditionals are not. .Pp The way that parallel makes are scheduled changed in .Nx 4.0 so that .ORDER and .WAIT apply recursively to the dependent nodes. The algorithms used may change again in the future. .Pp The way that .for loop variables are substituted changed after .Nx 5.0 so that they still appear to be variable expansions. In particular this stops them being treated as syntax, and removes some obscure problems using them in .if statements. .Sh SEE ALSO .Xr mkdep 1 .Sh HISTORY A .Nm command appeared in .At v7 . This .Nm implementation is based on Adam De Boor's pmake program which was written for Sprite at Berkeley. It was designed to be a parallel distributed make running jobs on different machines using a daemon called .Dq customs . +.Pp +Historically the target/dependency +.Dq FRC +has been used to FoRCe rebuilding (since the target/dependency +does not exist... unless someone creates an +.Dq FRC +file). .Sh BUGS The .Nm syntax is difficult to parse without actually acting of the data. For instance finding the end of a variable use should involve scanning each the modifiers using the correct terminator for each field. In many places .Nm just counts {} and () in order to find the end of a variable expansion. .Pp There is no way of escaping a space character in a filename. Index: vendor/NetBSD/bmake/dist/var.c =================================================================== --- vendor/NetBSD/bmake/dist/var.c (revision 251419) +++ vendor/NetBSD/bmake/dist/var.c (revision 251420) @@ -1,4183 +1,4181 @@ -/* $NetBSD: var.c,v 1.174 2013/05/18 13:12:45 sjg Exp $ */ +/* $NetBSD: var.c,v 1.175 2013/05/29 00:23:31 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.174 2013/05/18 13:12:45 sjg Exp $"; +static char rcsid[] = "$NetBSD: var.c,v 1.175 2013/05/29 00:23:31 sjg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: var.c,v 1.174 2013/05/18 13:12:45 sjg Exp $"); +__RCSID("$NetBSD: var.c,v 1.175 2013/05/29 00:23:31 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 #ifndef NO_REGEX #include #include #endif #include #include #include #include #include #include "make.h" #include "buf.h" #include "dir.h" #include "job.h" /* * 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_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) && (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) { 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); } 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); 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; 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); 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); 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); /* 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, cp, 1); #ifdef MAKE_LEVEL_SAFE setenv(MAKE_LEVEL_SAFE, cp, 1); #endif } 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); } } 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); 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); 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) { 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); } /* * Another special case. * Several make's support this sort of mechanism for tracking * recursion - but each uses a different name. * We allow the makefiles to update .MAKE.LEVEL and ensure * children see a correctly incremented value. */ if (ctxt == VAR_GLOBAL && strcmp(MAKE_LEVEL, name) == 0) { char tmp[64]; int level; level = atoi(val); snprintf(tmp, sizeof(tmp), "%u", level + 1); setenv(MAKE_LEVEL, tmp, 1); #ifdef MAKE_LEVEL_SAFE setenv(MAKE_LEVEL_SAFE, tmp, 1); #endif } out: if (expanded_name != NULL) 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); 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); } /*- *----------------------------------------------------------------------- * 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); } v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); if (cp != NULL) { 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); 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 :@@@ 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); 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); Buf_AddBytes(&buf, strlen(cp2), cp2); if (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 * * 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; 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') { Buf_AddBytes(&buf, nlen, newline); ++str; } else { Buf_AddByte(&buf, '\\'); Buf_AddByte(&buf, *str); ++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; - int done; - done = 1; 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; - } while (!done); + } 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 words which match the given . * is of the standard file * wildcarding form. * :N words which do not match the given . * :S[1gW] * Substitute for in the value * :C[1gW] * Substitute for regex 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. * * :?: * 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. * :@@@ * Assign a temporary local variable * to the current value of each word in turn * and replace each word with the result of * evaluating * :D Use as value if variable defined * :U Use 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}'. * :!! 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 * * ::= Assigns as the new value of variable. * ::?= Assigns as value of variable if * it was not already set. * ::+= Appends to variable. * ::!= Assigns output of 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, 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); /* * 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); 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); if (nstr == var_Error || (nstr == varNoError && errnum == 0) || strlen(rval) != (size_t) used) { if (freeIt) free(freeIt); goto out; /* error already reported */ } } if (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; 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; pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim, NULL, &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) free(newStr); 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 = var_Error; break; } goto default_case; /* "::" */ } 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 */ /* * 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); Buf_AddBytes(&buf, strlen(cp2), cp2); if (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) { 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 = '!'; cp = ++tstr; if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim, NULL, &pattern.rightLen, NULL)) == NULL) goto cleanup; newStr = Cmd_Exec(pattern.rhs, &emsg); 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" or * ":ts:" */ parsestate.varSpace = tstr[2]; cp = tstr + 3; } else if (tstr[2] == endc || tstr[2] == ':') { /* ":ts" 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". */ goto bad_modifier; } break; } } else { /* * Found ":ts". */ 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:" or * ":t". */ goto bad_modifier; } } else { /* * Found ":t". */ goto bad_modifier; } } else { /* * Found ":t" 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); 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; /* find ':', and then substitute accordingly */ pattern.flags = 0; cp = ++tstr; delim = ':'; if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim, NULL, &pattern.leftLen, NULL)) == NULL) goto cleanup; /* BROPEN or PROPEN */ delim = endc; if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim, NULL, &pattern.rightLen, NULL)) == NULL) goto cleanup; termc = *--cp; delim = '\0'; if (Cond_EvalExpression(NULL, v->name, &value, 0) == 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); 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: =) */ 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; } 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 * 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) { 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 */ Var_Parse_State parsestate; /* Flags passed to helper functions */ char name[2]; *freePtr = NULL; dynamic = FALSE; start = str; parsestate.oneBigWord = FALSE; parsestate.varSpace = ' '; /* word separator */ 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 */ 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 != endc && *tstr != ':'; tstr++) { /* * A variable inside a variable, expand */ if (*tstr == '$') { int rlen; void *freeIt; char *rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt); if (rval != NULL) { Buf_AddBytes(&buf, strlen(rval), rval); } if (freeIt) free(freeIt); tstr += rlen - 1; } else Buf_AddByte(&buf, *tstr); } if (*tstr == ':') { haveModifier = TRUE; } else if (*tstr != '\0') { 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) { /* * No need for nested expansion or anything, as we're * the only one who sets these things and we sure don't * but nested invocations in them... */ nstr = Buf_GetAll(&v->val, NULL); if (str[1] == 'D') { nstr = VarModify(ctxt, &parsestate, nstr, VarHead, NULL); } else { nstr = VarModify(ctxt, &parsestate, nstr, VarTail, NULL); } /* * Resulting string is dynamically allocated, so * tell caller to free it. */ *freePtr = nstr; *lengthPtr = tstr-start+1; Buf_Destroy(&buf, TRUE); VarFreeEnv(v, TRUE); return nstr; } } 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); *freePtr = nstr; } v->flags &= ~VAR_IN_USE; if ((nstr != NULL) && haveModifier) { int used; /* * Skip initial colon. */ tstr++; nstr = ApplyModifiers(nstr, tstr, startc, endc, v, ctxt, errnum, &used, freePtr); tstr += used; } 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 * * 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) { 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); /* * 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) { /* * 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; } } } 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_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)); } }