Index: head/contrib/bmake/ChangeLog =================================================================== --- head/contrib/bmake/ChangeLog (revision 281811) +++ head/contrib/bmake/ChangeLog (revision 281812) @@ -1,1667 +1,1735 @@ +2015-04-18 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20150418 + Merge with NetBSD make, pick up + o job.c: use memmove() rather than memcpy() + + * unit-tests/varshell.mk: SunOS cannot handle the TERMINATED_BY_SIGNAL + case, so skip it. + +2015-04-11 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20150411 + bump version - only mk/ changes. + +2015-04-10 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20150410 + Merge with NetBSD make, pick up + o document different handling of '-' in jobs mode vs compat + o fix jobs mode so that '-' only applies to whole job + when shell lacks hasErrCtl + o meta.c: use separate vars to track lcwd and latestdir (read) + per process + +2015-04-01 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20150401 + Merge with NetBSD make, pick up + o meta.c: close meta file in child + + * Makefile: use BINDIR.bmake if set. + Same for MANDIR and SHAREDIR + Handy for testing release candidates + in various environments. + +2015-03-26 Simon J. Gerraty + + * move initialization of savederr to block where it is used + to avoid spurious warning from gcc5 + +2014-11-11 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20141111 + just a cooler number + +2014-11-05 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20141105 + Merge with NetBSD make, pick up + o revert major overhaul of suffix handling + and POSIX compliance - too much breakage + and impossible to make backwards compatible. + o we still have the new unit test structure which is ok. + o meta.c ensure "-- filemon" is at start of line. + +2014-09-17 Simon J. Gerraty + + * configure.in: test that result of getconf PATH_MAX is numeric + and discard if not. Apparently needed for Hurd. + +2014-08-30 Simon J. Gerraty + + * Makefile (MAKE_VERSION): 20140830 + Merge with NetBSD make, pick up + o major overhaul of suffix handling + o improved POSIX compliance + o overhauled unit-tests + 2014-06-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140620 Merge with NetBSD make, pick up - o var.c return varNoError rather than var_Error for ::= modidiers. + o var.c return varNoError rather than var_Error for ::= modifiers. 2014-05-22 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140522 Merge with NetBSD make, pick up o var.c detect some parse errors. 2014-04-05 Simon J. Gerraty * Fix spelling errors - patch from Pedro Giffuni 2014-02-14 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140214 Merge with NetBSD make, pick up o .INCLUDEFROM* o use Var_Value to get MAKEOBJDIR[PREFIX] o reduced realloc'ign in brk_string. * configure.in: add a check for compiler supporting __func__ 2014-01-03 Simon J. Gerraty * boot-strap: ignore mksrc=none 2014-01-02 Simon J. Gerraty * Makefile (DEFAULT_SYS_PATH?): use just ${prefix}/share/mk 2014-01-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140101 * configure.in: set bmake_path_max to min(_SC_PATH_MAX,1024) * Makefile.config: defined BMAKE_PATH_MAX to bmake_path_max * make.h: use BMAKE_PATH_MAX if MAXPATHLEN not defined (needed for Hurd) * configure.in: Add AC_PREREQ and check for sysctl; patch from Andrew Shadura andrewsh at debian.org 2013-10-16 Simon J. Gerraty * Makefile (MAKE_VERSION): 20131010 * lose the const from arg to systcl to avoid problems on older BSDs. 2013-10-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20131001 Merge with NetBSD make, pick up o main.c: for NATIVE build sysctl to get MACHINE_ARCH from hw.machine_arch if necessary. o meta.c: meta_oodate - need to look at src of Link and target of Move as well. * main.c: check that CTL_HW and HW_MACHINE_ARCH exist. provide __arraycount() if needed. 2013-09-04 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130904 Merge with NetBSD make, pick up o Add VAR_INTERNAL context, so that internal setting of MAKEFILE does not override value set by makefiles. 2013-09-02 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130902 Merge with NetBSD make, pick up o CompatRunCommand: only apply shellErrFlag when errCheck is true 2013-08-28 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130828 Merge with NetBSD make, pick up o Fix VAR :sh = syntax from Will Andrews at freebsd.org o Call Job_SetPrefix() from Job_Init() so makefiles have opportunity to set .MAKE.JOB.PREFIX 2013-07-30 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130730 Merge with NetBSD make, pick up o Allow suppression of --- job -- tokens by setting .MAKE.JOB.PREFIX empty. 2013-07-16 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130716 Merge with NetBSD make, pick up o number of gmake compatibility tweaks -w for gmake style entering/leaving messages if .MAKE.LEVEL > 0 indicate it in progname "make[1]" etc. handle MAKEFLAGS containing only letters. o when overriding a GLOBAL variable on the command line, delete it from GLOBAL context so -V doesn't show the wrong value. 2013-07-06 Simon J. Gerraty * configure.in: We don't need MAKE_LEVEL_SAFE anymore. * Makefile (MAKE_VERSION): 20130706 Merge with NetBSD make, pick up o Shell_Init(): export shellErrFlag if commandShell hasErrCtl is true so that CompatRunCommand() can use it, to ensure consistent behavior with jobs mode. o use MAKE_LEVEL_ENV to define the variable to propagate .MAKE.LEVEL - currently set to MAKELEVEL (same as gmake). o meta.c: use .MAKE.META.IGNORE_PATHS to allow customization of paths to ignore. 2013-06-04 Simon J. Gerraty * 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: head/contrib/bmake/FILES =================================================================== --- head/contrib/bmake/FILES (revision 281811) +++ head/contrib/bmake/FILES (revision 281812) @@ -1,126 +1,165 @@ ChangeLog FILES Makefile Makefile.config.in PSD.doc/Makefile PSD.doc/tutorial.ms README aclocal.m4 arch.c bmake.1 bmake.cat1 boot-strap bsd.after-import.mk buf.c buf.h compat.c cond.c config.h.in configure configure.in dir.c dir.h dirname.c find_lib.sh for.c getopt.c hash.c hash.h install-sh job.c job.h lst.h lst.lib/Makefile lst.lib/lstAppend.c lst.lib/lstAtEnd.c lst.lib/lstAtFront.c lst.lib/lstClose.c lst.lib/lstConcat.c lst.lib/lstDatum.c lst.lib/lstDeQueue.c lst.lib/lstDestroy.c lst.lib/lstDupl.c lst.lib/lstEnQueue.c lst.lib/lstFind.c lst.lib/lstFindFrom.c lst.lib/lstFirst.c lst.lib/lstForEach.c lst.lib/lstForEachFrom.c lst.lib/lstInit.c lst.lib/lstInsert.c lst.lib/lstInt.h lst.lib/lstIsAtEnd.c lst.lib/lstIsEmpty.c lst.lib/lstLast.c lst.lib/lstMember.c lst.lib/lstNext.c lst.lib/lstOpen.c lst.lib/lstPrev.c lst.lib/lstRemove.c lst.lib/lstReplace.c lst.lib/lstSucc.c machine.sh main.c make-bootstrap.sh.in make-conf.h make.1 make.c make.h make_malloc.c make_malloc.h makefile.in meta.c meta.h missing/sys/cdefs.h mkdeps.sh nonints.h os.sh parse.c pathnames.h ranlib.h realpath.c setenv.c sigcompat.c sprite.h str.c stresep.c strlcpy.c strlist.c strlist.h suff.c targ.c trace.c trace.h unit-tests/Makefile.in -unit-tests/comment -unit-tests/cond1 -unit-tests/doterror -unit-tests/dotwait -unit-tests/error -unit-tests/export -unit-tests/export-all -unit-tests/export-env -unit-tests/forloop -unit-tests/forsubst -unit-tests/hash -unit-tests/misc -unit-tests/moderrs -unit-tests/modmatch -unit-tests/modmisc -unit-tests/modorder -unit-tests/modts -unit-tests/modword -unit-tests/order -unit-tests/phony-end -unit-tests/posix -unit-tests/qequals -unit-tests/sunshcmd -unit-tests/sysv -unit-tests/ternary -unit-tests/test.exp -unit-tests/unexport -unit-tests/unexport-env -unit-tests/varcmd +unit-tests/comment.exp +unit-tests/comment.mk +unit-tests/cond1.exp +unit-tests/cond1.mk +unit-tests/doterror.exp +unit-tests/doterror.mk +unit-tests/dotwait.exp +unit-tests/dotwait.mk +unit-tests/error.exp +unit-tests/error.mk +unit-tests/escape.exp +unit-tests/escape.mk +unit-tests/export-all.exp +unit-tests/export-all.mk +unit-tests/export-env.exp +unit-tests/export-env.mk +unit-tests/export.exp +unit-tests/export.mk +unit-tests/forloop.exp +unit-tests/forloop.mk +unit-tests/forsubst.exp +unit-tests/forsubst.mk +unit-tests/hash.exp +unit-tests/hash.mk +unit-tests/impsrc.exp +unit-tests/impsrc.mk +unit-tests/misc.exp +unit-tests/misc.mk +unit-tests/moderrs.exp +unit-tests/moderrs.mk +unit-tests/modmatch.exp +unit-tests/modmatch.mk +unit-tests/modmisc.exp +unit-tests/modmisc.mk +unit-tests/modorder.exp +unit-tests/modorder.mk +unit-tests/modts.exp +unit-tests/modts.mk +unit-tests/modword.exp +unit-tests/modword.mk +unit-tests/order.exp +unit-tests/order.mk +unit-tests/phony-end.exp +unit-tests/phony-end.mk +unit-tests/posix.exp +unit-tests/posix.mk +unit-tests/posix1.exp +unit-tests/posix1.mk +unit-tests/qequals.exp +unit-tests/qequals.mk +unit-tests/suffixes.exp +unit-tests/suffixes.mk +unit-tests/sunshcmd.exp +unit-tests/sunshcmd.mk +unit-tests/sysv.exp +unit-tests/sysv.mk +unit-tests/ternary.exp +unit-tests/ternary.mk +unit-tests/unexport-env.exp +unit-tests/unexport-env.mk +unit-tests/unexport.exp +unit-tests/unexport.mk +unit-tests/varcmd.exp +unit-tests/varcmd.mk +unit-tests/varmisc.exp +unit-tests/varmisc.mk +unit-tests/varshell.exp +unit-tests/varshell.mk util.c var.c wait.h Index: head/contrib/bmake/Makefile =================================================================== --- head/contrib/bmake/Makefile (revision 281811) +++ head/contrib/bmake/Makefile (revision 281812) @@ -1,221 +1,221 @@ -# $Id: Makefile,v 1.27 2014/06/20 14:51:54 sjg Exp $ +# $Id: Makefile,v 1.36 2015/04/18 19:58:53 sjg Exp $ # Base version on src date -MAKE_VERSION= 20140620 +MAKE_VERSION= 20150418 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?= ${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= # suppress 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 +SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share} +BINDIR= ${BINDIR.bmake:U${prefix}/bin} +MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} .if !exists(.depend) ${OBJS}: config.h .endif # make sure that MAKE_VERSION gets updated. main.o: ${SRCS} ${MAKEFILE} # start-delete2 for bsd.after-import.mk .if ${MK_AUTOCONF_MK} == "yes" .include .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: head/contrib/bmake/PSD.doc/Makefile =================================================================== --- head/contrib/bmake/PSD.doc/Makefile (revision 281811) +++ head/contrib/bmake/PSD.doc/Makefile (revision 281812) @@ -1,8 +1,10 @@ -# $NetBSD: Makefile,v 1.2 1995/06/14 15:20:23 christos Exp $ +# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:43 dholland Exp $ # @(#)Makefile 8.1 (Berkeley) 8/14/93 -DIR= psd/12.make +SECTION=reference/ref1 +ARTICLE=make SRCS= tutorial.ms MACROS= -ms +EXTRAHTMLFILES=make1.png make2.png .include Index: head/contrib/bmake/PSD.doc/tutorial.ms =================================================================== --- head/contrib/bmake/PSD.doc/tutorial.ms (revision 281811) +++ head/contrib/bmake/PSD.doc/tutorial.ms (revision 281812) @@ -1,3773 +1,3783 @@ -.\" $NetBSD: tutorial.ms,v 1.11 2011/08/18 15:19:30 sjg Exp $ +.\" $NetBSD: tutorial.ms,v 1.12 2014/09/30 21:33:14 christos Exp $ .\" Copyright (c) 1988, 1989, 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) 1988, 1989 by Adam de Boor .\" Copyright (c) 1989 by Berkeley Softworks .\" .\" 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. .\" .\" @(#)tutorial.ms 8.1 (Berkeley) 8/18/93 .\" .EH 'PSD:12-%''PMake \*- A Tutorial' .OH 'PMake \*- A Tutorial''PSD:12-%' +.\" Ix is an indexing macro similar to .IX but I've disabled it for now +.\" Since that would require 2 passes and I am not in the mood for that. +.de Ix +.. +.\" Rd is section (region) define and Rm is region mention? Again disable for +.\" now. +.de Rd +.. +.de Rm +.. .\" xH is a macro to provide numbered headers that are automatically stuffed .\" into a table-of-contents, properly indented, etc. If the first argument .\" is numeric, it is taken as the depth for numbering (as for .NH), else .\" the default (1) is assumed. .\" .\" @P The initial paragraph distance. .\" @Q The piece of section number to increment (or 0 if none given) .\" @R Section header. .\" @S Indent for toc entry .\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter) .de xH .NH \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 .nr PD .1v .XS \\n% .ta 0.6i \\*(SN \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 .XE .nr PD .3v .. .\" CW is used to place a string in fixed-width or switch to a .\" fixed-width font. .\" C is a typewriter font for a laserwriter. Use something else if .\" you don't have one... .de CW .ie !\\n(.$ .ft C .el \&\\$3\fC\\$1\fP\\$2 .. .\" Anything I put in a display I want to be in fixed-width .am DS .CW .. .\" The stuff in .No produces a little stop sign in the left margin .\" that says NOTE in it. Unfortunately, it does cause a break, but .\" hey. Can't have everything. In case you're wondering how I came .\" up with such weird commands, they came from running grn on a .\" gremlin file... .de No .br .ne 0.5i .po -0.5i .br .mk .nr g3 \\n(.f .nr g4 \\n(.s .sp -1 .\" .st cf \D't 5u' .sp -1 \h'50u' .sp -1 \D't 3u' .sp -1 .sp 7u \h'53u' \d\D'p -0.19i 0.0i 0.0i -0.13i 0.30i 0.0i 0.0i 0.13i' .sp -1 .ft R .ps 6 .nr g8 \\n(.d .ds g9 "NOTE .sp 74u \h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9 .sp |\\n(g8u .sp 166u \D't 3u' .br .po .rt .ft \\n(g3 .ps \\n(g4 .. .de Bp .ie !\\n(.$ .IP \(bu 2 .el .IP "\&" 2 .. .po +.3i .TL PMake \*- A Tutorial .AU Adam de Boor .AI Berkeley Softworks 2150 Shattuck Ave, Penthouse Berkeley, CA 94704 adam@bsw.uu.net \&...!uunet!bsw!adam .FS Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies. The University of California, Berkeley Softworks, and Adam de Boor make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. .FE .PP .xH 1 Introduction .LP PMake is a program for creating other programs, or anything else you can think of for it to do. The basic idea behind PMake is that, for any given system, be it a program or a document or whatever, there will be some files that depend on the state of other files (on when they were last modified). PMake takes these dependencies, which you must specify, and uses them to build whatever it is you want it to build. .LP PMake is almost fully-compatible with Make, with which you may already be familiar. PMake's most important feature is its ability to run several different jobs at once, making the creation of systems considerably faster. It also has a great deal more functionality than Make. Throughout the text, whenever something is mentioned that is an important difference between PMake and Make (i.e. something that will cause a makefile to fail if you don't do something about it), or is simply important, it will be flagged with a little sign in the left margin, like this: .No .LP This tutorial is divided into three main sections corresponding to basic, intermediate and advanced PMake usage. If you already know Make well, you will only need to skim chapter 2 (there are some aspects of PMake that I consider basic to its use that didn't exist in Make). Things in chapter 3 make life much easier, while those in chapter 4 are strictly for those who know what they are doing. Chapter 5 has definitions for the jargon I use and chapter 6 contains possible solutions to the problems presented throughout the tutorial. .xH 1 The Basics of PMake .LP PMake takes as input a file that tells a) which files depend on which other files to be complete and b) what to do about files that are ``out-of-date.'' This file is known as a ``makefile'' and is usually .Ix 0 def makefile kept in the top-most directory of the system to be built. While you can call the makefile anything you want, PMake will look for .CW Makefile and .CW makefile (in that order) in the current directory if you don't tell it otherwise. .Ix 0 def makefile default To specify a different makefile, use the .B \-f flag (e.g. .CW "pmake -f program.mk" ''). `` .Ix 0 ref flags -f .Ix 0 ref makefile other .LP A makefile has four different types of lines in it: .RS .IP \(bu 2 File dependency specifications .IP \(bu 2 Creation commands .IP \(bu 2 Variable assignments .IP \(bu 2 Comments, include statements and conditional directives .RE .LP Any line may be continued over multiple lines by ending it with a backslash. .Ix 0 def "continuation line" The backslash, following newline and any initial whitespace on the following line are compressed into a single space before the input line is examined by PMake. .xH 2 Dependency Lines .LP As mentioned in the introduction, in any system, there are dependencies between the files that make up the system. For instance, in a program made up of several C source files and one header file, the C files will need to be re-compiled should the header file be changed. For a document of several chapters and one macro file, the chapters will need to be reprocessed if any of the macros changes. .Ix 0 def "dependency" These are dependencies and are specified by means of dependency lines in the makefile. .LP .Ix 0 def "dependency line" On a dependency line, there are targets and sources, separated by a one- or two-character operator. The targets ``depend'' on the sources and are usually created from them. .Ix 0 def target .Ix 0 def source .Ix 0 ref operator Any number of targets and sources may be specified on a dependency line. All the targets in the line are made to depend on all the sources. Targets and sources need not be actual files, but every source must be either an actual file or another target in the makefile. If you run out of room, use a backslash at the end of the line to continue onto the next one. .LP Any file may be a target and any file may be a source, but the relationship between the two (or however many) is determined by the ``operator'' that separates them. .Ix 0 def operator Three types of operators exist: one specifies that the datedness of a target is determined by the state of its sources, while another specifies other files (the sources) that need to be dealt with before the target can be re-created. The third operator is very similar to the first, with the additional condition that the target is out-of-date if it has no sources. These operations are represented by the colon, the exclamation point and the double-colon, respectively, and are mutually exclusive. Their exact semantics are as follows: .IP ":" .Ix 0 def operator colon .Ix 0 def : If a colon is used, a target on the line is considered to be ``out-of-date'' (and in need of creation) if .RS .IP \(bu 2 any of the sources has been modified more recently than the target, or .IP \(bu 2 the target doesn't exist. .RE .Ix 0 def out-of-date .IP "\&" Under this operation, steps will be taken to re-create the target only if it is found to be out-of-date by using these two rules. .IP "!" .Ix 0 def operator force .Ix 0 def ! If an exclamation point is used, the target will always be re-created, but this will not happen until all of its sources have been examined and re-created, if necessary. .IP "::" .Ix 0 def operator double-colon .Ix 0 def :: If a double-colon is used, a target is out-of-date if: .RS .IP \(bu 2 any of the sources has been modified more recently than the target, or .IP \(bu 2 the target doesn't exist, or .IP \(bu 2 the target has no sources. .RE .IP "\&" If the target is out-of-date according to these rules, it will be re-created. This operator also does something else to the targets, but I'll go into that in the next section (``Shell Commands''). .LP Enough words, now for an example. Take that C program I mentioned earlier. Say there are three C files .CW a.c , ( .CW b.c and .CW c.c ) each of which includes the file .CW defs.h . The dependencies between the files could then be expressed as follows: .DS program : a.o b.o c.o a.o b.o c.o : defs.h a.o : a.c b.o : b.c c.o : c.c .DE .LP You may be wondering at this point, where .CW a.o , .CW b.o and .CW c.o came in and why .I they depend on .CW defs.h and the C files don't. The reason is quite simple: .CW program cannot be made by linking together .c files \*- it must be made from .o files. Likewise, if you change .CW defs.h , it isn't the .c files that need to be re-created, it's the .o files. If you think of dependencies in these terms \*- which files (targets) need to be created from which files (sources) \*- you should have no problems. .LP An important thing to notice about the above example, is that all the \&.o files appear as targets on more than one line. This is perfectly all right: the target is made to depend on all the sources mentioned on all the dependency lines. E.g. .CW a.o depends on both .CW defs.h and .CW a.c . .Ix 0 ref dependency .No .LP The order of the dependency lines in the makefile is important: the first target on the first dependency line in the makefile will be the one that gets made if you don't say otherwise. That's why .CW program comes first in the example makefile, above. .LP Both targets and sources may contain the standard C-Shell wildcard characters .CW { , ( .CW } , .CW * , .CW ? , .CW [ , and .CW ] ), but the non-curly-brace ones may only appear in the final component (the file portion) of the target or source. The characters mean the following things: .IP \fB{}\fP These enclose a comma-separated list of options and cause the pattern to be expanded once for each element of the list. Each expansion contains a different element. For example, .CW src/{whiffle,beep,fish}.c expands to the three words .CW src/whiffle.c , .CW src/beep.c , and .CW src/fish.c . These braces may be nested and, unlike the other wildcard characters, the resulting words need not be actual files. All other wildcard characters are expanded using the files that exist when PMake is started. .IP \fB*\fP This matches zero or more characters of any sort. .CW src/*.c will expand to the same three words as above as long as .CW src contains those three files (and no other files that end in .CW .c ). .IP \fB?\fP Matches any single character. .IP \fB[]\fP This is known as a character class and contains either a list of single characters, or a series of character ranges .CW a-z , ( for example means all characters between a and z), or both. It matches any single character contained in the list. E.g. .CW [A-Za-z] will match all letters, while .CW [0123456789] will match all numbers. .xH 2 Shell Commands .LP ``Isn't that nice,'' you say to yourself, ``but how are files actually `re-created,' as he likes to spell it?'' The re-creation is accomplished by commands you place in the makefile. These commands are passed to the Bourne shell (better known as ``/bin/sh'') to be executed and are .Ix 0 ref shell .Ix 0 ref re-creation .Ix 0 ref update expected to do what's necessary to update the target file (PMake doesn't actually check to see if the target was created. It just assumes it's there). .Ix 0 ref target .LP Shell commands in a makefile look a lot like shell commands you would type at a terminal, with one important exception: each command in a makefile .I must be preceded by at least one tab. .LP Each target has associated with it a shell script made up of one or more of these shell commands. The creation script for a target should immediately follow the dependency line for that target. While any given target may appear on more than one dependency line, only one of these dependency lines may be followed by a creation script, unless the `::' operator was used on the dependency line. .Ix 0 ref operator double-colon .Ix 0 ref :: .No .LP If the double-colon was used, each dependency line for the target may be followed by a shell script. That script will only be executed if the target on the associated dependency line is out-of-date with respect to the sources on that line, according to the rules I gave earlier. I'll give you a good example of this later on. .LP To expand on the earlier makefile, you might add commands as follows: .DS program : a.o b.o c.o cc a.o b.o c.o \-o program a.o b.o c.o : defs.h a.o : a.c cc \-c a.c b.o : b.c cc \-c b.c c.o : c.c cc \-c c.c .DE .LP Something you should remember when writing a makefile is, the commands will be executed if the .I target on the dependency line is out-of-date, not the sources. .Ix 0 ref target .Ix 0 ref source .Ix 0 ref out-of-date In this example, the command .CW "cc \-c a.c" '' `` will be executed if .CW a.o is out-of-date. Because of the `:' operator, .Ix 0 ref : .Ix 0 ref operator colon this means that should .CW a.c .I or .CW defs.h have been modified more recently than .CW a.o , the command will be executed .CW a.o "\&" ( will be considered out-of-date). .Ix 0 ref out-of-date .LP Remember how I said the only difference between a makefile shell command and a regular shell command was the leading tab? I lied. There is another way in which makefile commands differ from regular ones. The first two characters after the initial whitespace are treated specially. If they are any combination of `@' and `\-', they cause PMake to do different things. .LP In most cases, shell commands are printed before they're actually executed. This is to keep you informed of what's going on. If an `@' appears, however, this echoing is suppressed. In the case of an .CW echo command, say .CW "echo Linking index" ,'' `` it would be rather silly to see .DS echo Linking index Linking index .DE .LP so PMake allows you to place an `@' before the command .CW "@echo Linking index" '') (`` to prevent the command from being printed. .LP The other special character is the `\-'. In case you didn't know, shell commands finish with a certain ``exit status.'' This status is made available by the operating system to whatever program invoked the command. Normally this status will be 0 if everything went ok and non-zero if something went wrong. For this reason, PMake will consider an error to have occurred if one of the shells it invokes returns a non-zero status. When it detects an error, PMake's usual action is to abort whatever it's doing and exit with a non-zero status itself (any other targets that were being created will continue being made, but nothing new will be started. PMake will exit after the last job finishes). This behavior can be altered, however, by placing a `\-' at the front of a command .CW "\-mv index index.old" ''), (`` certain command-line arguments, or doing other things, to be detailed later. In such a case, the non-zero status is simply ignored and PMake keeps chugging along. .No .LP Because all the commands are given to a single shell to execute, such things as setting shell variables, changing directories, etc., last beyond the command in which they are found. This also allows shell compound commands (like .CW for loops) to be entered in a natural manner. Since this could cause problems for some makefiles that depend on each command being executed by a single shell, PMake has a .B \-B .Ix 0 ref compatibility .Ix 0 ref flags -B flag (it stands for backwards-compatible) that forces each command to be given to a separate shell. It also does several other things, all of which I discourage since they are now old-fashioned.\|.\|.\|. .No .LP A target's shell script is fed to the shell on its (the shell's) input stream. This means that any commands, such as .CW ci that need to get input from the terminal won't work right \*- they'll get the shell's input, something they probably won't find to their liking. A simple way around this is to give a command like this: .DS ci $(SRCS) < /dev/tty .DE This would force the program's input to come from the terminal. If you can't do this for some reason, your only other alternative is to use PMake in its fullest compatibility mode. See .B Compatibility in chapter 4. .Ix 0 ref compatibility .LP .xH 2 Variables .LP PMake, like Make before it, has the ability to save text in variables to be recalled later at your convenience. Variables in PMake are used much like variables in the shell and, by tradition, consist of all upper-case letters (you don't .I have to use all upper-case letters. In fact there's nothing to stop you from calling a variable .CW @^&$%$ . Just tradition). Variables are assigned-to using lines of the form .Ix 0 def variable assignment .DS VARIABLE = value .DE .Ix 0 def variable assignment appended-to by .DS VARIABLE += value .DE .Ix 0 def variable appending .Ix 0 def variable assignment appended .Ix 0 def += conditionally assigned-to (if the variable isn't already defined) by .DS VARIABLE ?= value .DE .Ix 0 def variable assignment conditional .Ix 0 def ?= and assigned-to with expansion (i.e. the value is expanded (see below) before being assigned to the variable\*-useful for placing a value at the beginning of a variable, or other things) by .DS VARIABLE := value .DE .Ix 0 def variable assignment expanded .Ix 0 def := .LP Any whitespace before .I value is stripped off. When appending, a space is placed between the old value and the stuff being appended. .LP The final way a variable may be assigned to is using .DS VARIABLE != shell-command .DE .Ix 0 def variable assignment shell-output .Ix 0 def != In this case, .I shell-command has all its variables expanded (see below) and is passed off to a shell to execute. The output of the shell is then placed in the variable. Any newlines (other than the final one) are replaced by spaces before the assignment is made. This is typically used to find the current directory via a line like: .DS CWD != pwd .DE .LP .B Note: this is intended to be used to execute commands that produce small amounts of output (e.g. ``pwd''). The implementation is less than intelligent and will likely freeze if you execute something that produces thousands of bytes of output (8 Kb is the limit on many UNIX systems). .LP The value of a variable may be retrieved by enclosing the variable name in parentheses or curly braces and preceding the whole thing with a dollar sign. .LP For example, to set the variable CFLAGS to the string .CW "\-I/sprite/src/lib/libc \-O" ,'' `` you would place a line .DS CFLAGS = \-I/sprite/src/lib/libc \-O .DE in the makefile and use the word .CW "$(CFLAGS)" wherever you would like the string .CW "\-I/sprite/src/lib/libc \-O" to appear. This is called variable expansion. .Ix 0 def variable expansion .No .LP Unlike Make, PMake will not expand a variable unless it knows the variable exists. E.g. if you have a .CW "${i}" in a shell command and you have not assigned a value to the variable .CW i (the empty string is considered a value, by the way), where Make would have substituted the empty string, PMake will leave the .CW "${i}" alone. To keep PMake from substituting for a variable it knows, precede the dollar sign with another dollar sign. (e.g. to pass .CW "${HOME}" to the shell, use .CW "$${HOME}" ). This causes PMake, in effect, to expand the .CW $ macro, which expands to a single .CW $ . For compatibility, Make's style of variable expansion will be used if you invoke PMake with any of the compatibility flags (\c .B \-V , .B \-B or .B \-M . The .B \-V flag alters just the variable expansion). .Ix 0 ref flags -V .Ix 0 ref flags -B .Ix 0 ref flags -M .Ix 0 ref compatibility .LP .Ix 0 ref variable expansion There are two different times at which variable expansion occurs: When parsing a dependency line, the expansion occurs immediately upon reading the line. If any variable used on a dependency line is undefined, PMake will print a message and exit. Variables in shell commands are expanded when the command is executed. Variables used inside another variable are expanded whenever the outer variable is expanded (the expansion of an inner variable has no effect on the outer variable. I.e. if the outer variable is used on a dependency line and in a shell command, and the inner variable changes value between when the dependency line is read and the shell command is executed, two different values will be substituted for the outer variable). .Ix 0 def variable types .LP Variables come in four flavors, though they are all expanded the same and all look about the same. They are (in order of expanding scope): .RS .IP \(bu 2 Local variables. .Ix 0 ref variable local .IP \(bu 2 Command-line variables. .Ix 0 ref variable command-line .IP \(bu 2 Global variables. .Ix 0 ref variable global .IP \(bu 2 Environment variables. .Ix 0 ref variable environment .RE .LP The classification of variables doesn't matter much, except that the classes are searched from the top (local) to the bottom (environment) when looking up a variable. The first one found wins. .xH 3 Local Variables .LP .Ix 0 def variable local Each target can have as many as seven local variables. These are variables that are only ``visible'' within that target's shell script and contain such things as the target's name, all of its sources (from all its dependency lines), those sources that were out-of-date, etc. Four local variables are defined for all targets. They are: .RS .IP ".TARGET" .Ix 0 def variable local .TARGET .Ix 0 def .TARGET The name of the target. .IP ".OODATE" .Ix 0 def variable local .OODATE .Ix 0 def .OODATE The list of the sources for the target that were considered out-of-date. The order in the list is not guaranteed to be the same as the order in which the dependencies were given. .IP ".ALLSRC" .Ix 0 def variable local .ALLSRC .Ix 0 def .ALLSRC The list of all sources for this target in the order in which they were given. .IP ".PREFIX" .Ix 0 def variable local .PREFIX .Ix 0 def .PREFIX The target without its suffix and without any leading path. E.g. for the target .CW ../../lib/compat/fsRead.c , this variable would contain .CW fsRead . .RE .LP Three other local variables are set only for certain targets under special circumstances. These are the ``.IMPSRC,'' .Ix 0 ref variable local .IMPSRC .Ix 0 ref .IMPSRC ``.ARCHIVE,'' .Ix 0 ref variable local .ARCHIVE .Ix 0 ref .ARCHIVE and ``.MEMBER'' .Ix 0 ref variable local .MEMBER .Ix 0 ref .MEMBER variables. When they are set and how they are used is described later. .LP Four of these variables may be used in sources as well as in shell scripts. .Ix 0 def "dynamic source" .Ix 0 def source dynamic These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The variables in the sources are expanded once for each target on the dependency line, providing what is known as a ``dynamic source,'' .Rd 0 allowing you to specify several dependency lines at once. For example, .DS $(OBJS) : $(.PREFIX).c .DE will create a dependency between each object file and its corresponding C source file. .xH 3 Command-line Variables .LP .Ix 0 def variable command-line Command-line variables are set when PMake is first invoked by giving a variable assignment as one of the arguments. For example, .DS pmake "CFLAGS = -I/sprite/src/lib/libc -O" .DE would make .CW CFLAGS be a command-line variable with the given value. Any assignments to .CW CFLAGS in the makefile will have no effect, because once it is set, there is (almost) nothing you can do to change a command-line variable (the search order, you see). Command-line variables may be set using any of the four assignment operators, though only .CW = and .CW ?= behave as you would expect them to, mostly because assignments to command-line variables are performed before the makefile is read, thus the values set in the makefile are unavailable at the time. .CW += .Ix 0 ref += .Ix 0 ref variable assignment appended is the same as .CW = , because the old value of the variable is sought only in the scope in which the assignment is taking place (for reasons of efficiency that I won't get into here). .CW := and .CW ?= .Ix 0 ref := .Ix 0 ref ?= .Ix 0 ref variable assignment expanded .Ix 0 ref variable assignment conditional will work if the only variables used are in the environment. .CW != is sort of pointless to use from the command line, since the same effect can no doubt be accomplished using the shell's own command substitution mechanisms (backquotes and all that). .xH 3 Global Variables .LP .Ix 0 def variable global Global variables are those set or appended-to in the makefile. There are two classes of global variables: those you set and those PMake sets. As I said before, the ones you set can have any name you want them to have, except they may not contain a colon or an exclamation point. The variables PMake sets (almost) always begin with a period and always contain upper-case letters, only. The variables are as follows: .RS .IP .PMAKE .Ix 0 def variable global .PMAKE .Ix 0 def .PMAKE .Ix 0 def variable global MAKE .Ix 0 def MAKE The name by which PMake was invoked is stored in this variable. For compatibility, the name is also stored in the MAKE variable. .IP .MAKEFLAGS .Ix 0 def variable global .MAKEFLAGS .Ix 0 def .MAKEFLAGS variable .Ix 0 def variable global MFLAGS .Ix 0 def MFLAGS All the relevant flags with which PMake was invoked. This does not include such things as .B \-f or variable assignments. Again for compatibility, this value is stored in the MFLAGS variable as well. .RE .LP Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the section on special targets in chapter 3. .Ix 0 ref variable global .INCLUDES .Ix 0 ref variable global .LIBS .LP Global variables may be deleted using lines of the form: .Ix 0 def #undef .Ix 0 def variable deletion .DS #undef \fIvariable\fP .DE The .CW # ' ` must be the first character on the line. Note that this may only be done on global variables. .xH 3 Environment Variables .LP .Ix 0 def variable environment Environment variables are passed by the shell that invoked PMake and are given by PMake to each shell it invokes. They are expanded like any other variable, but they cannot be altered in any way. .LP One special environment variable, .CW PMAKE , .Ix 0 def variable environment PMAKE is examined by PMake for command-line flags, variable assignments, etc., it should always use. This variable is examined before the actual arguments to PMake are. In addition, all flags given to PMake, either through the .CW PMAKE variable or on the command line, are placed in this environment variable and exported to each shell PMake executes. Thus recursive invocations of PMake automatically receive the same flags as the top-most one. .LP Using all these variables, you can compress the sample makefile even more: .DS OBJS = a.o b.o c.o program : $(OBJS) cc $(.ALLSRC) \-o $(.TARGET) $(OBJS) : defs.h a.o : a.c cc \-c a.c b.o : b.c cc \-c b.c c.o : c.c cc \-c c.c .DE .Ix 0 ref variable local .ALLSRC .Ix 0 ref .ALLSRC .Ix 0 ref variable local .TARGET .Ix 0 ref .TARGET .Rd 3 .xH 2 Comments .LP .Ix 0 def comments Comments in a makefile start with a `#' character and extend to the end of the line. They may appear anywhere you want them, except in a shell command (though the shell will treat it as a comment, too). If, for some reason, you need to use the `#' in a variable or on a dependency line, put a backslash in front of it. PMake will compress the two into a single `#' (Note: this isn't true if PMake is operating in full-compatibility mode). .Ix 0 ref flags -M .Ix 0 ref compatibility .xH 2 Parallelism .No .LP PMake was specifically designed to re-create several targets at once, when possible. You do not have to do anything special to cause this to happen (unless PMake was configured to not act in parallel, in which case you will have to make use of the .B \-L and .B \-J flags (see below)), .Ix 0 ref flags -L .Ix 0 ref flags -J but you do have to be careful at times. .LP There are several problems you are likely to encounter. One is that some makefiles (and programs) are written in such a way that it is impossible for two targets to be made at once. The program .CW xstr , for example, always modifies the files .CW strings and .CW x.c . There is no way to change it. Thus you cannot run two of them at once without something being trashed. Similarly, if you have commands in the makefile that always send output to the same file, you will not be able to make more than one target at once unless you change the file you use. You can, for instance, add a .CW $$$$ to the end of the file name to tack on the process ID of the shell executing the command (each .CW $$ expands to a single .CW $ , thus giving you the shell variable .CW $$ ). Since only one shell is used for all the commands, you'll get the same file name for each command in the script. .LP The other problem comes from improperly-specified dependencies that worked in Make because of its sequential, depth-first way of examining them. While I don't want to go into depth on how PMake works (look in chapter 4 if you're interested), I will warn you that files in two different ``levels'' of the dependency tree may be examined in a different order in PMake than they were in Make. For example, given the makefile .DS a : b c b : d .DE PMake will examine the targets in the order .CW c , .CW d , .CW b , .CW a . If the makefile's author expected PMake to abort before making .CW c if an error occurred while making .CW b , or if .CW b needed to exist before .CW c was made, s/he will be sorely disappointed. The dependencies are incomplete, since in both these cases, .CW c would depend on .CW b . So watch out. .LP Another problem you may face is that, while PMake is set up to handle the output from multiple jobs in a graceful fashion, the same is not so for input. It has no way to regulate input to different jobs, so if you use the redirection from .CW /dev/tty I mentioned earlier, you must be careful not to run two of the jobs at once. .xH 2 Writing and Debugging a Makefile .LP Now you know most of what's in a makefile, what do you do next? There are two choices: (1) use one of the uncommonly-available makefile generators or (2) write your own makefile (I leave out the third choice of ignoring PMake and doing everything by hand as being beyond the bounds of common sense). .LP When faced with the writing of a makefile, it is usually best to start from first principles: just what .I are you trying to do? What do you want the makefile finally to produce? .LP To begin with a somewhat traditional example, let's say you need to write a makefile to create a program, .CW expr , that takes standard infix expressions and converts them to prefix form (for no readily apparent reason). You've got three source files, in C, that make up the program: .CW main.c , .CW parse.c , and .CW output.c . Harking back to my pithy advice about dependency lines, you write the first line of the file: .DS expr : main.o parse.o output.o .DE because you remember .CW expr is made from .CW .o files, not .CW .c files. Similarly for the .CW .o files you produce the lines: .DS main.o : main.c parse.o : parse.c output.o : output.c main.o parse.o output.o : defs.h .DE .LP Great. You've now got the dependencies specified. What you need now is commands. These commands, remember, must produce the target on the dependency line, usually by using the sources you've listed. You remember about local variables? Good, so it should come to you as no surprise when you write .DS expr : main.o parse.o output.o cc -o $(.TARGET) $(.ALLSRC) .DE Why use the variables? If your program grows to produce postfix expressions too (which, of course, requires a name change or two), it is one fewer place you have to change the file. You cannot do this for the object files, however, because they depend on their corresponding source files .I and .CW defs.h , thus if you said .DS cc -c $(.ALLSRC) .DE you'd get (for .CW main.o ): .DS cc -c main.c defs.h .DE which is wrong. So you round out the makefile with these lines: .DS main.o : main.c cc -c main.c parse.o : parse.c cc -c parse.c output.o : output.c cc -c output.c .DE .LP The makefile is now complete and will, in fact, create the program you want it to without unnecessary compilations or excessive typing on your part. There are two things wrong with it, however (aside from it being altogether too long, something I'll address in chapter 3): .IP 1) The string .CW "main.o parse.o output.o" '' `` is repeated twice, necessitating two changes when you add postfix (you were planning on that, weren't you?). This is in direct violation of de Boor's First Rule of writing makefiles: .QP .I Anything that needs to be written more than once should be placed in a variable. .IP "\&" I cannot emphasize this enough as being very important to the maintenance of a makefile and its program. .IP 2) There is no way to alter the way compilations are performed short of editing the makefile and making the change in all places. This is evil and violates de Boor's Second Rule, which follows directly from the first: .QP .I Any flags or programs used inside a makefile should be placed in a variable so they may be changed, temporarily or permanently, with the greatest ease. .LP The makefile should more properly read: .DS OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) main.o : main.c $(CC) $(CFLAGS) -c main.c parse.o : parse.c $(CC) $(CFLAGS) -c parse.c output.o : output.c $(CC) $(CFLAGS) -c output.c $(OBJS) : defs.h .DE Alternatively, if you like the idea of dynamic sources mentioned in section 2.3.1, .Rm 0 2.3.1 .Rd 4 .Ix 0 ref "dynamic source" .Ix 0 ref source dynamic you could write it like this: .DS OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) $(OBJS) : $(.PREFIX).c defs.h $(CC) $(CFLAGS) -c $(.PREFIX).c .DE These two rules and examples lead to de Boor's First Corollary: .QP .I Variables are your friends. .LP Once you've written the makefile comes the sometimes-difficult task of .Ix 0 ref debugging making sure the darn thing works. Your most helpful tool to make sure the makefile is at least syntactically correct is the .B \-n .Ix 0 ref flags -n flag, which allows you to see if PMake will choke on the makefile. The second thing the .B \-n flag lets you do is see what PMake would do without it actually doing it, thus you can make sure the right commands would be executed were you to give PMake its head. .LP When you find your makefile isn't behaving as you hoped, the first question that comes to mind (after ``What time is it, anyway?'') is ``Why not?'' In answering this, two flags will serve you well: .CW "-d m" '' `` .Ix 0 ref flags -d and .CW "-p 2" .'' `` .Ix 0 ref flags -p The first causes PMake to tell you as it examines each target in the makefile and indicate why it is deciding whatever it is deciding. You can then use the information printed for other targets to see where you went wrong. The .CW "-p 2" '' `` flag makes PMake print out its internal state when it is done, allowing you to see that you forgot to make that one chapter depend on that file of macros you just got a new version of. The output from .CW "-p 2" '' `` is intended to resemble closely a real makefile, but with additional information provided and with variables expanded in those commands PMake actually printed or executed. .LP Something to be especially careful about is circular dependencies. .Ix 0 def dependency circular E.g. .DS a : b b : c d d : a .DE In this case, because of how PMake works, .CW c is the only thing PMake will examine, because .CW d and .CW a will effectively fall off the edge of the universe, making it impossible to examine .CW b (or them, for that matter). PMake will tell you (if run in its normal mode) all the targets involved in any cycle it looked at (i.e. if you have two cycles in the graph (naughty, naughty), but only try to make a target in one of them, PMake will only tell you about that one. You'll have to try to make the other to find the second cycle). When run as Make, it will only print the first target in the cycle. .xH 2 Invoking PMake .LP .Ix 0 ref flags .Ix 0 ref arguments .Ix 0 ref usage PMake comes with a wide variety of flags to choose from. They may appear in any order, interspersed with command-line variable assignments and targets to create. The flags are as follows: .IP "\fB\-d\fP \fIwhat\fP" .Ix 0 def flags -d .Ix 0 ref debugging This causes PMake to spew out debugging information that may prove useful to you. If you can't figure out why PMake is doing what it's doing, you might try using this flag. The .I what parameter is a string of single characters that tell PMake what aspects you are interested in. Most of what I describe will make little sense to you, unless you've dealt with Make before. Just remember where this table is and come back to it as you read on. The characters and the information they produce are as follows: .RS .IP a Archive searching and caching. .IP c Conditional evaluation. .IP d The searching and caching of directories. .IP j Various snippets of information related to the running of the multiple shells. Not particularly interesting. .IP m The making of each target: what target is being examined; when it was last modified; whether it is out-of-date; etc. .IP p Makefile parsing. .IP r Remote execution. .IP s The application of suffix-transformation rules. (See chapter 3) .IP t The maintenance of the list of targets. .IP v Variable assignment. .RE .IP "\&" Of these all, the .CW m and .CW s letters will be most useful to you. If the .B \-d is the final argument or the argument from which it would get these key letters (see below for a note about which argument would be used) begins with a .B \- , all of these debugging flags will be set, resulting in massive amounts of output. .IP "\fB\-f\fP \fImakefile\fP" .Ix 0 def flags -f Specify a makefile to read different from the standard makefiles .CW Makefile "\&" ( or .CW makefile ). .Ix 0 ref makefile default .Ix 0 ref makefile other If .I makefile is ``\-'', PMake uses the standard input. This is useful for making quick and dirty makefiles.\|.\|. .Ix 0 ref makefile "quick and dirty" .IP \fB\-h\fP .Ix 0 def flags -h Prints out a summary of the various flags PMake accepts. It can also be used to find out what level of concurrency was compiled into the version of PMake you are using (look at .B \-J and .B \-L ) and various other information on how PMake was configured. .Ix 0 ref configuration .Ix 0 ref makefile system .IP \fB\-i\fP .Ix 0 def flags -i If you give this flag, PMake will ignore non-zero status returned by any of its shells. It's like placing a `\-' before all the commands in the makefile. .IP \fB\-k\fP .Ix 0 def flags -k This is similar to .B \-i in that it allows PMake to continue when it sees an error, but unlike .B \-i , where PMake continues blithely as if nothing went wrong, .B \-k causes it to recognize the error and only continue work on those things that don't depend on the target, either directly or indirectly (through depending on something that depends on it), whose creation returned the error. The `k' is for ``keep going''.\|.\|. .Ix 0 ref target .IP \fB\-l\fP .Ix 0 def flags -l PMake has the ability to lock a directory against other people executing it in the same directory (by means of a file called ``LOCK.make'' that it creates and checks for in the directory). This is a Good Thing because two people doing the same thing in the same place can be disastrous for the final product (too many cooks and all that). Whether this locking is the default is up to your system administrator. If locking is on, .B \-l will turn it off, and vice versa. Note that this locking will not prevent \fIyou\fP from invoking PMake twice in the same place \*- if you own the lock file, PMake will warn you about it but continue to execute. .IP "\fB\-m\fP \fIdirectory\fP" .Ix 0 def flags -m Tells PMake another place to search for included makefiles via the <...> style. Several .B \-m options can be given to form a search path. If this construct is used the default system makefile search path is completely overridden. To be explained in chapter 3, section 3.2. .Rm 2 3.2 .IP \fB\-n\fP .Ix 0 def flags -n This flag tells PMake not to execute the commands needed to update the out-of-date targets in the makefile. Rather, PMake will simply print the commands it would have executed and exit. This is particularly useful for checking the correctness of a makefile. If PMake doesn't do what you expect it to, it's a good chance the makefile is wrong. .IP "\fB\-p\fP \fInumber\fP" .Ix 0 def flags -p .Ix 0 ref debugging This causes PMake to print its input in a reasonable form, though not necessarily one that would make immediate sense to anyone but me. The .I number is a bitwise-or of 1 and 2 where 1 means it should print the input before doing any processing and 2 says it should print it after everything has been re-created. Thus .CW "\-p 3" would print it twice\*-once before processing and once after (you might find the difference between the two interesting). This is mostly useful to me, but you may find it informative in some bizarre circumstances. .IP \fB\-q\fP .Ix 0 def flags -q If you give PMake this flag, it will not try to re-create anything. It will just see if anything is out-of-date and exit non-zero if so. .IP \fB\-r\fP .Ix 0 def flags -r When PMake starts up, it reads a default makefile that tells it what sort of system it's on and gives it some idea of what to do if you don't tell it anything. I'll tell you about it in chapter 3. If you give this flag, PMake won't read the default makefile. .IP \fB\-s\fP .Ix 0 def flags -s This causes PMake to not print commands before they're executed. It is the equivalent of putting an `@' before every command in the makefile. .IP \fB\-t\fP .Ix 0 def flags -t Rather than try to re-create a target, PMake will simply ``touch'' it so as to make it appear up-to-date. If the target didn't exist before, it will when PMake finishes, but if the target did exist, it will appear to have been updated. .IP \fB\-v\fP .Ix 0 def flags -v This is a mixed-compatibility flag intended to mimic the System V version of Make. It is the same as giving .B \-B , and .B \-V as well as turning off directory locking. Targets can still be created in parallel, however. This is the mode PMake will enter if it is invoked either as .CW smake '' `` or .CW vmake ''. `` .IP \fB\-x\fP .Ix 0 def flags -x This tells PMake it's ok to export jobs to other machines, if they're available. It is used when running in Make mode, as exporting in this mode tends to make things run slower than if the commands were just executed locally. .IP \fB\-B\fP .Ix 0 ref compatibility .Ix 0 def flags -B Forces PMake to be as backwards-compatible with Make as possible while still being itself. This includes: .RS .IP \(bu 2 Executing one shell per shell command .IP \(bu 2 Expanding anything that looks even vaguely like a variable, with the empty string replacing any variable PMake doesn't know. .IP \(bu 2 Refusing to allow you to escape a `#' with a backslash. .IP \(bu 2 Permitting undefined variables on dependency lines and conditionals (see below). Normally this causes PMake to abort. .RE .IP \fB\-C\fP .Ix 0 def flags -C This nullifies any and all compatibility mode flags you may have given or implied up to the time the .B \-C is encountered. It is useful mostly in a makefile that you wrote for PMake to avoid bad things happening when someone runs PMake as .CW make '' `` or has things set in the environment that tell it to be compatible. .B \-C is .I not placed in the .CW PMAKE environment variable or the .CW .MAKEFLAGS or .CW MFLAGS global variables. .Ix 0 ref variable environment PMAKE .Ix 0 ref variable global .MAKEFLAGS .Ix 0 ref variable global MFLAGS .Ix 0 ref .MAKEFLAGS variable .Ix 0 ref MFLAGS .IP "\fB\-D\fP \fIvariable\fP" .Ix 0 def flags -D Allows you to define a variable to have .CW 1 '' `` as its value. The variable is a global variable, not a command-line variable. This is useful mostly for people who are used to the C compiler arguments and those using conditionals, which I'll get into in section 4.3 .Rm 1 4.3 .IP "\fB\-I\fP \fIdirectory\fP" .Ix 0 def flags -I Tells PMake another place to search for included makefiles. Yet another thing to be explained in chapter 3 (section 3.2, to be precise). .Rm 2 3.2 .IP "\fB\-J\fP \fInumber\fP" .Ix 0 def flags -J Gives the absolute maximum number of targets to create at once on both local and remote machines. .IP "\fB\-L\fP \fInumber\fP" .Ix 0 def flags -L This specifies the maximum number of targets to create on the local machine at once. This may be 0, though you should be wary of doing this, as PMake may hang until a remote machine becomes available, if one is not available when it is started. .IP \fB\-M\fP .Ix 0 ref compatibility .Ix 0 def flags -M This is the flag that provides absolute, complete, full compatibility with Make. It still allows you to use all but a few of the features of PMake, but it is non-parallel. This is the mode PMake enters if you call it .CW make .'' `` .IP \fB\-P\fP .Ix 0 def flags -P .Ix 0 ref "output control" When creating targets in parallel, several shells are executing at once, each wanting to write its own two cent's-worth to the screen. This output must be captured by PMake in some way in order to prevent the screen from being filled with garbage even more indecipherable than you usually see. PMake has two ways of doing this, one of which provides for much cleaner output and a clear separation between the output of different jobs, the other of which provides a more immediate response so one can tell what is really happening. The former is done by notifying you when the creation of a target starts, capturing the output and transferring it to the screen all at once when the job finishes. The latter is done by catching the output of the shell (and its children) and buffering it until an entire line is received, then printing that line preceded by an indication of which job produced the output. Since I prefer this second method, it is the one used by default. The first method will be used if you give the .B \-P flag to PMake. .IP \fB\-V\fP .Ix 0 def flags -V As mentioned before, the .B \-V flag tells PMake to use Make's style of expanding variables, substituting the empty string for any variable it doesn't know. .IP \fB\-W\fP .Ix 0 def flags -W There are several times when PMake will print a message at you that is only a warning, i.e. it can continue to work in spite of your having done something silly (such as forgotten a leading tab for a shell command). Sometimes you are well aware of silly things you have done and would like PMake to stop bothering you. This flag tells it to shut up about anything non-fatal. .IP \fB\-X\fP .Ix 0 def flags -X This flag causes PMake to not attempt to export any jobs to another machine. .LP Several flags may follow a single `\-'. Those flags that require arguments take them from successive parameters. E.g. .DS pmake -fDnI server.mk DEBUG /chip2/X/server/include .DE will cause PMake to read .CW server.mk as the input makefile, define the variable .CW DEBUG as a global variable and look for included makefiles in the directory .CW /chip2/X/server/include . .xH 2 Summary .LP A makefile is made of four types of lines: .RS .IP \(bu 2 Dependency lines .IP \(bu 2 Creation commands .IP \(bu 2 Variable assignments .IP \(bu 2 Comments, include statements and conditional directives .RE .LP A dependency line is a list of one or more targets, an operator .CW : ', (` .CW :: ', ` or .CW ! '), ` and a list of zero or more sources. Sources may contain wildcards and certain local variables. .LP A creation command is a regular shell command preceded by a tab. In addition, if the first two characters after the tab (and other whitespace) are a combination of .CW @ ' ` or .CW - ', ` PMake will cause the command to not be printed (if the character is .CW @ ') ` or errors from it to be ignored (if .CW - '). ` A blank line, dependency line or variable assignment terminates a creation script. There may be only one creation script for each target with a .CW : ' ` or .CW ! ' ` operator. .LP Variables are places to store text. They may be unconditionally assigned-to using the .CW = ' ` .Ix 0 ref = .Ix 0 ref variable assignment operator, appended-to using the .CW += ' ` .Ix 0 ref += .Ix 0 ref variable assignment appended operator, conditionally (if the variable is undefined) assigned-to with the .CW ?= ' ` .Ix 0 ref ?= .Ix 0 ref variable assignment conditional operator, and assigned-to with variable expansion with the .CW := ' ` .Ix 0 ref := .Ix 0 ref variable assignment expanded operator. The output of a shell command may be assigned to a variable using the .CW != ' ` .Ix 0 ref != .Ix 0 ref variable assignment shell-output operator. Variables may be expanded (their value inserted) by enclosing their name in parentheses or curly braces, preceded by a dollar sign. A dollar sign may be escaped with another dollar sign. Variables are not expanded if PMake doesn't know about them. There are seven local variables: .CW .TARGET , .CW .ALLSRC , .CW .OODATE , .CW .PREFIX , .CW .IMPSRC , .CW .ARCHIVE , and .CW .MEMBER . Four of them .CW .TARGET , ( .CW .PREFIX , .CW .ARCHIVE , and .CW .MEMBER ) may be used to specify ``dynamic sources.'' .Ix 0 ref "dynamic source" .Ix 0 ref source dynamic Variables are good. Know them. Love them. Live them. .LP Debugging of makefiles is best accomplished using the .B \-n , .B "\-d m" , and .B "\-p 2" flags. .xH 2 Exercises .ce \s+4\fBTBA\fP\s0 .xH 1 Short-cuts and Other Nice Things .LP Based on what I've told you so far, you may have gotten the impression that PMake is just a way of storing away commands and making sure you don't forget to compile something. Good. That's just what it is. However, the ways I've described have been inelegant, at best, and painful, at worst. This chapter contains things that make the writing of makefiles easier and the makefiles themselves shorter and easier to modify (and, occasionally, simpler). In this chapter, I assume you are somewhat more familiar with Sprite (or UNIX, if that's what you're using) than I did in chapter 2, just so you're on your toes. So without further ado... .xH 2 Transformation Rules .LP As you know, a file's name consists of two parts: a base name, which gives some hint as to the contents of the file, and a suffix, which usually indicates the format of the file. Over the years, as .UX has developed, naming conventions, with regard to suffixes, have also developed that have become almost as incontrovertible as Law. E.g. a file ending in .CW .c is assumed to contain C source code; one with a .CW .o suffix is assumed to be a compiled, relocatable object file that may be linked into any program; a file with a .CW .ms suffix is usually a text file to be processed by Troff with the \-ms macro package, and so on. One of the best aspects of both Make and PMake comes from their understanding of how the suffix of a file pertains to its contents and their ability to do things with a file based solely on its suffix. This ability comes from something known as a transformation rule. A transformation rule specifies how to change a file with one suffix into a file with another suffix. .LP A transformation rule looks much like a dependency line, except the target is made of two known suffixes stuck together. Suffixes are made known to PMake by placing them as sources on a dependency line whose target is the special target .CW .SUFFIXES . E.g. .DS \&.SUFFIXES : .o .c \&.c.o : $(CC) $(CFLAGS) -c $(.IMPSRC) .DE The creation script attached to the target is used to transform a file with the first suffix (in this case, .CW .c ) into a file with the second suffix (here, .CW .o ). In addition, the target inherits whatever attributes have been applied to the transformation rule. The simple rule given above says that to transform a C source file into an object file, you compile it using .CW cc with the .CW \-c flag. This rule is taken straight from the system makefile. Many transformation rules (and suffixes) are defined there, and I refer you to it for more examples (type .CW "pmake -h" '' `` to find out where it is). .LP There are several things to note about the transformation rule given above: .RS .IP 1) The .CW .IMPSRC variable. .Ix 0 def variable local .IMPSRC .Ix 0 def .IMPSRC This variable is set to the ``implied source'' (the file from which the target is being created; the one with the first suffix), which, in this case, is the .c file. .IP 2) The .CW CFLAGS variable. Almost all of the transformation rules in the system makefile are set up using variables that you can alter in your makefile to tailor the rule to your needs. In this case, if you want all your C files to be compiled with the .B \-g flag, to provide information for .CW dbx , you would set the .CW CFLAGS variable to contain .CW -g .CW "CFLAGS = -g" '') (`` and PMake would take care of the rest. .RE .LP To give you a quick example, the makefile in 2.3.4 .Rm 3 2.3.4 could be changed to this: .DS OBJS = a.o b.o c.o program : $(OBJS) $(CC) -o $(.TARGET) $(.ALLSRC) $(OBJS) : defs.h .DE The transformation rule I gave above takes the place of the 6 lines\** .FS This is also somewhat cleaner, I think, than the dynamic source solution presented in 2.6 .FE .Rm 4 2.6 .DS a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c .DE .LP Now you may be wondering about the dependency between the .CW .o and .CW .c files \*- it's not mentioned anywhere in the new makefile. This is because it isn't needed: one of the effects of applying a transformation rule is the target comes to depend on the implied source. That's why it's called the implied .I source . .LP For a more detailed example. Say you have a makefile like this: .DS a.out : a.o b.o $(CC) $(.ALLSRC) .DE and a directory set up like this: .DS total 4 -rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile -rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c -rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o -rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c .DE While just typing .CW pmake '' `` will do the right thing, it's much more informative to type .CW "pmake -d s" ''. `` This will show you what PMake is up to as it processes the files. In this case, PMake prints the following: .DS Suff_FindDeps (a.out) using existing source a.o applying .o -> .out to "a.o" Suff_FindDeps (a.o) trying a.c...got it applying .c -> .o to "a.c" Suff_FindDeps (b.o) trying b.c...got it applying .c -> .o to "b.c" Suff_FindDeps (a.c) trying a.y...not there trying a.l...not there trying a.c,v...not there trying a.y,v...not there trying a.l,v...not there Suff_FindDeps (b.c) trying b.y...not there trying b.l...not there trying b.c,v...not there trying b.y,v...not there trying b.l,v...not there --- a.o --- cc -c a.c --- b.o --- cc -c b.c --- a.out --- cc a.o b.o .DE .LP .CW Suff_FindDeps is the name of a function in PMake that is called to check for implied sources for a target using transformation rules. The transformations it tries are, naturally enough, limited to the ones that have been defined (a transformation may be defined multiple times, by the way, but only the most recent one will be used). You will notice, however, that there is a definite order to the suffixes that are tried. This order is set by the relative positions of the suffixes on the .CW .SUFFIXES line \*- the earlier a suffix appears, the earlier it is checked as the source of a transformation. Once a suffix has been defined, the only way to change its position in the pecking order is to remove all the suffixes (by having a .CW .SUFFIXES dependency line with no sources) and redefine them in the order you want. (Previously-defined transformation rules will be automatically redefined as the suffixes they involve are re-entered.) .LP Another way to affect the search order is to make the dependency explicit. In the above example, .CW a.out depends on .CW a.o and .CW b.o . Since a transformation exists from .CW .o to .CW .out , PMake uses that, as indicated by the .CW "using existing source a.o" '' `` message. .LP The search for a transformation starts from the suffix of the target and continues through all the defined transformations, in the order dictated by the suffix ranking, until an existing file with the same base (the target name minus the suffix and any leading directories) is found. At that point, one or more transformation rules will have been found to change the one existing file into the target. .LP For example, ignoring what's in the system makefile for now, say you have a makefile like this: .DS \&.SUFFIXES : .out .o .c .y .l \&.l.c : lex $(.IMPSRC) mv lex.yy.c $(.TARGET) \&.y.c : yacc $(.IMPSRC) mv y.tab.c $(.TARGET) \&.c.o : cc -c $(.IMPSRC) \&.o.out : cc -o $(.TARGET) $(.IMPSRC) .DE and the single file .CW jive.l . If you were to type .CW "pmake -rd ms jive.out" ,'' `` you would get the following output for .CW jive.out : .DS Suff_FindDeps (jive.out) trying jive.o...not there trying jive.c...not there trying jive.y...not there trying jive.l...got it applying .l -> .c to "jive.l" applying .c -> .o to "jive.c" applying .o -> .out to "jive.o" .DE and this is why: PMake starts with the target .CW jive.out , figures out its suffix .CW .out ) ( and looks for things it can transform to a .CW .out file. In this case, it only finds .CW .o , so it looks for the file .CW jive.o . It fails to find it, so it looks for transformations into a .CW .o file. Again it has only one choice: .CW .c . So it looks for .CW jive.c and, as you know, fails to find it. At this point it has two choices: it can create the .CW .c file from either a .CW .y file or a .CW .l file. Since .CW .y came first on the .CW .SUFFIXES line, it checks for .CW jive.y first, but can't find it, so it looks for .CW jive.l and, lo and behold, there it is. At this point, it has defined a transformation path as follows: .CW .l \(-> .CW .c \(-> .CW .o \(-> .CW .out and applies the transformation rules accordingly. For completeness, and to give you a better idea of what PMake actually did with this three-step transformation, this is what PMake printed for the rest of the process: .DS Suff_FindDeps (jive.o) using existing source jive.c applying .c -> .o to "jive.c" Suff_FindDeps (jive.c) using existing source jive.l applying .l -> .c to "jive.l" Suff_FindDeps (jive.l) Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date Examining jive.c...non-existent...out-of-date --- jive.c --- lex jive.l \&.\|.\|. meaningless lex output deleted .\|.\|. mv lex.yy.c jive.c Examining jive.o...non-existent...out-of-date --- jive.o --- cc -c jive.c Examining jive.out...non-existent...out-of-date --- jive.out --- cc -o jive.out jive.o .DE .LP One final question remains: what does PMake do with targets that have no known suffix? PMake simply pretends it actually has a known suffix and searches for transformations accordingly. The suffix it chooses is the source for the .CW .NULL .Ix 0 ref .NULL target mentioned later. In the system makefile, .CW .out is chosen as the ``null suffix'' .Ix 0 def suffix null .Ix 0 def "null suffix" because most people use PMake to create programs. You are, however, free and welcome to change it to a suffix of your own choosing. The null suffix is ignored, however, when PMake is in compatibility mode (see chapter 4). .xH 2 Including Other Makefiles .Ix 0 def makefile inclusion .Rd 2 .LP Just as for programs, it is often useful to extract certain parts of a makefile into another file and just include it in other makefiles somehow. Many compilers allow you say something like .DS #include "defs.h" .DE to include the contents of .CW defs.h in the source file. PMake allows you to do the same thing for makefiles, with the added ability to use variables in the filenames. An include directive in a makefile looks either like this: .DS #include .DE or this .DS #include "file" .DE The difference between the two is where PMake searches for the file: the first way, PMake will look for the file only in the system makefile directory (or directories) (to find out what that directory is, give PMake the .B \-h flag). .Ix 0 ref flags -h The system makefile directory search path can be overridden via the .B \-m option. .Ix 0 ref flags -m For files in double-quotes, the search is more complex: .RS .IP 1) The directory of the makefile that's including the file. .IP 2) The current directory (the one in which you invoked PMake). .IP 3) The directories given by you using .B \-I flags, in the order in which you gave them. .IP 4) Directories given by .CW .PATH dependency lines (see chapter 4). .IP 5) The system makefile directory. .RE .LP in that order. .LP You are free to use PMake variables in the filename\*-PMake will expand them before searching for the file. You must specify the searching method with either angle brackets or double-quotes .I outside of a variable expansion. I.e. the following .DS SYSTEM = #include $(SYSTEM) .DE won't work. .xH 2 Saving Commands .LP .Ix 0 def ... There may come a time when you will want to save certain commands to be executed when everything else is done. For instance: you're making several different libraries at one time and you want to create the members in parallel. Problem is, .CW ranlib is another one of those programs that can't be run more than once in the same directory at the same time (each one creates a file called .CW __.SYMDEF into which it stuffs information for the linker to use. Two of them running at once will overwrite each other's file and the result will be garbage for both parties). You might want a way to save the ranlib commands til the end so they can be run one after the other, thus keeping them from trashing each other's file. PMake allows you to do this by inserting an ellipsis (``.\|.\|.'') as a command between commands to be run at once and those to be run later. .LP So for the .CW ranlib case above, you might do this: .Rd 5 .DS lib1.a : $(LIB1OBJS) rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) lib2.a : $(LIB2OBJS) rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) .DE .Ix 0 ref variable local .TARGET .Ix 0 ref variable local .ALLSRC This would save both .DS ranlib $(.TARGET) .DE commands until the end, when they would run one after the other (using the correct value for the .CW .TARGET variable, of course). .LP Commands saved in this manner are only executed if PMake manages to re-create everything without an error. .xH 2 Target Attributes .LP PMake allows you to give attributes to targets by means of special sources. Like everything else PMake uses, these sources begin with a period and are made up of all upper-case letters. There are various reasons for using them, and I will try to give examples for most of them. Others you'll have to find uses for yourself. Think of it as ``an exercise for the reader.'' By placing one (or more) of these as a source on a dependency line, you are ``marking the target(s) with that attribute.'' That's just the way I phrase it, so you know. .LP Any attributes given as sources for a transformation rule are applied to the target of the transformation rule when the rule is applied. .Ix 0 def attributes .Ix 0 ref source .Ix 0 ref target .nr pw 12 .IP .DONTCARE \n(pw .Ix 0 def attributes .DONTCARE .Ix 0 def .DONTCARE If a target is marked with this attribute and PMake can't figure out how to create it, it will ignore this fact and assume the file isn't really needed or actually exists and PMake just can't find it. This may prove wrong, but the error will be noted later on, not when PMake tries to create the target so marked. This attribute also prevents PMake from attempting to touch the target if it is given the .B \-t flag. .Ix 0 ref flags -t .IP .EXEC \n(pw .Ix 0 def attributes .EXEC .Ix 0 def .EXEC This attribute causes its shell script to be executed while having no effect on targets that depend on it. This makes the target into a sort of subroutine. An example. Say you have some LISP files that need to be compiled and loaded into a LISP process. To do this, you echo LISP commands into a file and execute a LISP with this file as its input when everything's done. Say also that you have to load other files from another system before you can compile your files and further, that you don't want to go through the loading and dumping unless one of .I your files has changed. Your makefile might look a little bit like this (remember, this is an educational example, and don't worry about the .CW COMPILE rule, all will soon become clear, grasshopper): .DS system : init a.fasl b.fasl c.fasl for i in $(.ALLSRC); do echo -n '(load "' >> input echo -n ${i} >> input echo '")' >> input done echo '(dump "$(.TARGET)")' >> input lisp < input a.fasl : a.l init COMPILE b.fasl : b.l init COMPILE c.fasl : c.l init COMPILE COMPILE : .USE echo '(compile "$(.ALLSRC)")' >> input init : .EXEC echo '(load-system)' > input .DE .Ix 0 ref .USE .Ix 0 ref attributes .USE .Ix 0 ref variable local .ALLSRC .IP "\&" .CW .EXEC sources, don't appear in the local variables of targets that depend on them (nor are they touched if PMake is given the .B \-t flag). .Ix 0 ref flags -t Note that all the rules, not just that for .CW system , include .CW init as a source. This is because none of the other targets can be made until .CW init has been made, thus they depend on it. .IP .EXPORT \n(pw .Ix 0 def attributes .EXPORT .Ix 0 def .EXPORT This is used to mark those targets whose creation should be sent to another machine if at all possible. This may be used by some exportation schemes if the exportation is expensive. You should ask your system administrator if it is necessary. .IP .EXPORTSAME \n(pw .Ix 0 def attributes .EXPORTSAME .Ix 0 def .EXPORTSAME Tells the export system that the job should be exported to a machine of the same architecture as the current one. Certain operations (e.g. running text through .CW nroff ) can be performed the same on any architecture (CPU and operating system type), while others (e.g. compiling a program with .CW cc ) must be performed on a machine with the same architecture. Not all export systems will support this attribute. .IP .IGNORE \n(pw .Ix 0 def attributes .IGNORE .Ix 0 def .IGNORE attribute Giving a target the .CW .IGNORE attribute causes PMake to ignore errors from any of the target's commands, as if they all had `\-' before them. .IP .INVISIBLE \n(pw .Ix 0 def attributes .INVISIBLE .Ix 0 def .INVISIBLE This allows you to specify one target as a source for another without the one affecting the other's local variables. Useful if, say, you have a makefile that creates two programs, one of which is used to create the other, so it must exist before the other is created. You could say .DS prog1 : $(PROG1OBJS) prog2 MAKEINSTALL prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL .DE where .CW MAKEINSTALL is some complex .USE rule (see below) that depends on the .Ix 0 ref .USE .CW .ALLSRC variable containing the right things. Without the .CW .INVISIBLE attribute for .CW prog2 , the .CW MAKEINSTALL rule couldn't be applied. This is not as useful as it should be, and the semantics may change (or the whole thing go away) in the not-too-distant future. .IP .JOIN \n(pw .Ix 0 def attributes .JOIN .Ix 0 def .JOIN This is another way to avoid performing some operations in parallel while permitting everything else to be done so. Specifically it forces the target's shell script to be executed only if one or more of the sources was out-of-date. In addition, the target's name, in both its .CW .TARGET variable and all the local variables of any target that depends on it, is replaced by the value of its .CW .ALLSRC variable. As an example, suppose you have a program that has four libraries that compile in the same directory along with, and at the same time as, the program. You again have the problem with .CW ranlib that I mentioned earlier, only this time it's more severe: you can't just put the ranlib off to the end since the program will need those libraries before it can be re-created. You can do something like this: .DS program : $(OBJS) libraries cc -o $(.TARGET) $(.ALLSRC) libraries : lib1.a lib2.a lib3.a lib4.a .JOIN ranlib $(.OODATE) .DE .Ix 0 ref variable local .TARGET .Ix 0 ref variable local .ALLSRC .Ix 0 ref variable local .OODATE .Ix 0 ref .TARGET .Ix 0 ref .ALLSRC .Ix 0 ref .OODATE In this case, PMake will re-create the .CW $(OBJS) as necessary, along with .CW lib1.a , .CW lib2.a , .CW lib3.a and .CW lib4.a . It will then execute .CW ranlib on any library that was changed and set .CW program 's .CW .ALLSRC variable to contain what's in .CW $(OBJS) followed by .CW "lib1.a lib2.a lib3.a lib4.a" .'' `` In case you're wondering, it's called .CW .JOIN because it joins together different threads of the ``input graph'' at the target marked with the attribute. Another aspect of the .JOIN attribute is it keeps the target from being created if the .B \-t flag was given. .Ix 0 ref flags -t .IP .MAKE \n(pw .Ix 0 def attributes .MAKE .Ix 0 def .MAKE The .CW .MAKE attribute marks its target as being a recursive invocation of PMake. This forces PMake to execute the script associated with the target (if it's out-of-date) even if you gave the .B \-n or .B \-t flag. By doing this, you can start at the top of a system and type .DS pmake -n .DE and have it descend the directory tree (if your makefiles are set up correctly), printing what it would have executed if you hadn't included the .B \-n flag. .IP .NOEXPORT \n(pw .Ix 0 def attributes .NOEXPORT .Ix 0 def .NOEXPORT attribute If possible, PMake will attempt to export the creation of all targets to another machine (this depends on how PMake was configured). Sometimes, the creation is so simple, it is pointless to send it to another machine. If you give the target the .CW .NOEXPORT attribute, it will be run locally, even if you've given PMake the .B "\-L 0" flag. .IP .NOTMAIN \n(pw .Ix 0 def attributes .NOTMAIN .Ix 0 def .NOTMAIN Normally, if you do not specify a target to make in any other way, PMake will take the first target on the first dependency line of a makefile as the target to create. That target is known as the ``Main Target'' and is labeled as such if you print the dependencies out using the .B \-p flag. .Ix 0 ref flags -p Giving a target this attribute tells PMake that the target is definitely .I not the Main Target. This allows you to place targets in an included makefile and have PMake create something else by default. .IP .PRECIOUS \n(pw .Ix 0 def attributes .PRECIOUS .Ix 0 def .PRECIOUS attribute When PMake is interrupted (you type control-C at the keyboard), it will attempt to clean up after itself by removing any half-made targets. If a target has the .CW .PRECIOUS attribute, however, PMake will leave it alone. An additional side effect of the `::' operator is to mark the targets as .CW .PRECIOUS . .Ix 0 ref operator double-colon .Ix 0 ref :: .IP .SILENT \n(pw .Ix 0 def attributes .SILENT .Ix 0 def .SILENT attribute Marking a target with this attribute keeps its commands from being printed when they're executed, just as if they had an `@' in front of them. .IP .USE \n(pw .Ix 0 def attributes .USE .Ix 0 def .USE By giving a target this attribute, you turn it into PMake's equivalent of a macro. When the target is used as a source for another target, the other target acquires the commands, sources and attributes (except .CW .USE ) of the source. If the target already has commands, the .CW .USE target's commands are added to the end. If more than one .USE-marked source is given to a target, the rules are applied sequentially. .IP "\&" \n(pw The typical .USE rule (as I call them) will use the sources of the target to which it is applied (as stored in the .CW .ALLSRC variable for the target) as its ``arguments,'' if you will. For example, you probably noticed that the commands for creating .CW lib1.a and .CW lib2.a in the example in section 3.3 .Rm 5 3.3 were exactly the same. You can use the .CW .USE attribute to eliminate the repetition, like so: .DS lib1.a : $(LIB1OBJS) MAKELIB lib2.a : $(LIB2OBJS) MAKELIB MAKELIB : .USE rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) .DE .Ix 0 ref variable local .TARGET .Ix 0 ref variable local .ALLSRC .IP "\&" \n(pw Several system makefiles (not to be confused with The System Makefile) make use of these .USE rules to make your life easier (they're in the default, system makefile directory...take a look). Note that the .USE rule source itself .CW MAKELIB ) ( does not appear in any of the targets's local variables. There is no limit to the number of times I could use the .CW MAKELIB rule. If there were more libraries, I could continue with .CW "lib3.a : $(LIB3OBJS) MAKELIB" '' `` and so on and so forth. .xH 2 Special Targets .LP As there were in Make, so there are certain targets that have special meaning to PMake. When you use one on a dependency line, it is the only target that may appear on the left-hand-side of the operator. .Ix 0 ref target .Ix 0 ref operator As for the attributes and variables, all the special targets begin with a period and consist of upper-case letters only. I won't describe them all in detail because some of them are rather complex and I'll describe them in more detail than you'll want in chapter 4. The targets are as follows: .nr pw 10 .IP .BEGIN \n(pw .Ix 0 def .BEGIN Any commands attached to this target are executed before anything else is done. You can use it for any initialization that needs doing. .IP .DEFAULT \n(pw .Ix 0 def .DEFAULT This is sort of a .USE rule for any target (that was used only as a source) that PMake can't figure out any other way to create. It's only ``sort of'' a .USE rule because only the shell script attached to the .CW .DEFAULT target is used. The .CW .IMPSRC variable of a target that inherits .CW .DEFAULT 's commands is set to the target's own name. .Ix 0 ref .IMPSRC .Ix 0 ref variable local .IMPSRC .IP .END \n(pw .Ix 0 def .END This serves a function similar to .CW .BEGIN , in that commands attached to it are executed once everything has been re-created (so long as no errors occurred). It also serves the extra function of being a place on which PMake can hang commands you put off to the end. Thus the script for this target will be executed before any of the commands you save with the ``.\|.\|.''. .Ix 0 ref ... .IP .EXPORT \n(pw The sources for this target are passed to the exportation system compiled into PMake. Some systems will use these sources to configure themselves. You should ask your system administrator about this. .IP .IGNORE \n(pw .Ix 0 def .IGNORE target .Ix 0 ref .IGNORE attribute .Ix 0 ref attributes .IGNORE This target marks each of its sources with the .CW .IGNORE attribute. If you don't give it any sources, then it is like giving the .B \-i flag when you invoke PMake \*- errors are ignored for all commands. .Ix 0 ref flags -i .IP .INCLUDES \n(pw .Ix 0 def .INCLUDES target .Ix 0 def variable global .INCLUDES .Ix 0 def .INCLUDES variable The sources for this target are taken to be suffixes that indicate a file that can be included in a program source file. The suffix must have already been declared with .CW .SUFFIXES (see below). Any suffix so marked will have the directories on its search path (see .CW .PATH , below) placed in the .CW .INCLUDES variable, each preceded by a .B \-I flag. This variable can then be used as an argument for the compiler in the normal fashion. The .CW .h suffix is already marked in this way in the system makefile. .Ix 0 ref makefile system E.g. if you have .DS \&.SUFFIXES : .bitmap \&.PATH.bitmap : /usr/local/X/lib/bitmaps \&.INCLUDES : .bitmap .DE PMake will place .CW "-I/usr/local/X/lib/bitmaps" '' `` in the .CW .INCLUDES variable and you can then say .DS cc $(.INCLUDES) -c xprogram.c .DE (Note: the .CW .INCLUDES variable is not actually filled in until the entire makefile has been read.) .IP .INTERRUPT \n(pw .Ix 0 def .INTERRUPT When PMake is interrupted, it will execute the commands in the script for this target, if it exists. .IP .LIBS \n(pw .Ix 0 def .LIBS target .Ix 0 def .LIBS variable .Ix 0 def variable global .LIBS This does for libraries what .CW .INCLUDES does for include files, except the flag used is .B \-L , as required by those linkers that allow you to tell them where to find libraries. The variable used is .CW .LIBS . Be forewarned that PMake may not have been compiled to do this if the linker on your system doesn't accept the .B \-L flag, though the .CW .LIBS variable will always be defined once the makefile has been read. .IP .MAIN \n(pw .Ix 0 def .MAIN If you didn't give a target (or targets) to create when you invoked PMake, it will take the sources of this target as the targets to create. .IP .MAKEFLAGS \n(pw .Ix 0 def .MAKEFLAGS target This target provides a way for you to always specify flags for PMake when the makefile is used. The flags are just as they would be typed to the shell (except you can't use shell variables unless they're in the environment), though the .B \-f and .B \-r flags have no effect. .IP .NULL \n(pw .Ix 0 def .NULL .Ix 0 ref suffix null .Ix 0 ref "null suffix" This allows you to specify what suffix PMake should pretend a file has if, in fact, it has no known suffix. Only one suffix may be so designated. The last source on the dependency line is the suffix that is used (you should, however, only give one suffix.\|.\|.). .IP .PATH \n(pw .Ix 0 def .PATH If you give sources for this target, PMake will take them as directories in which to search for files it cannot find in the current directory. If you give no sources, it will clear out any directories added to the search path before. Since the effects of this all get very complex, I'll leave it til chapter four to give you a complete explanation. .IP .PATH\fIsuffix\fP \n(pw .Ix 0 ref .PATH This does a similar thing to .CW .PATH , but it does it only for files with the given suffix. The suffix must have been defined already. Look at .B "Search Paths" (section 4.1) .Rm 6 4.1 for more information. .IP .PRECIOUS \n(pw .Ix 0 def .PRECIOUS target .Ix 0 ref .PRECIOUS attribute .Ix 0 ref attributes .PRECIOUS Similar to .CW .IGNORE , this gives the .CW .PRECIOUS attribute to each source on the dependency line, unless there are no sources, in which case the .CW .PRECIOUS attribute is given to every target in the file. .IP .RECURSIVE \n(pw .Ix 0 def .RECURSIVE .Ix 0 ref attributes .MAKE .Ix 0 ref .MAKE This target applies the .CW .MAKE attribute to all its sources. It does nothing if you don't give it any sources. .IP .SHELL \n(pw .Ix 0 def .SHELL PMake is not constrained to only using the Bourne shell to execute the commands you put in the makefile. You can tell it some other shell to use with this target. Check out .B "A Shell is a Shell is a Shell" (section 4.4) .Rm 7 4.4 for more information. .IP .SILENT \n(pw .Ix 0 def .SILENT target .Ix 0 ref .SILENT attribute .Ix 0 ref attributes .SILENT When you use .CW .SILENT as a target, it applies the .CW .SILENT attribute to each of its sources. If there are no sources on the dependency line, then it is as if you gave PMake the .B \-s flag and no commands will be echoed. .IP .SUFFIXES \n(pw .Ix 0 def .SUFFIXES This is used to give new file suffixes for PMake to handle. Each source is a suffix PMake should recognize. If you give a .CW .SUFFIXES dependency line with no sources, PMake will forget about all the suffixes it knew (this also nukes the null suffix). For those targets that need to have suffixes defined, this is how you do it. .LP In addition to these targets, a line of the form .DS \fIattribute\fP : \fIsources\fP .DE applies the .I attribute to all the targets listed as .I sources . .xH 2 Modifying Variable Expansion .LP .Ix 0 def variable expansion modified .Ix 0 ref variable expansion .Ix 0 def variable modifiers Variables need not always be expanded verbatim. PMake defines several modifiers that may be applied to a variable's value before it is expanded. You apply a modifier by placing it after the variable name with a colon between the two, like so: .DS ${\fIVARIABLE\fP:\fImodifier\fP} .DE Each modifier is a single character followed by something specific to the modifier itself. You may apply as many modifiers as you want \*- each one is applied to the result of the previous and is separated from the previous by another colon. .LP There are seven ways to modify a variable's expansion, most of which come from the C shell variable modification characters: .RS .IP "M\fIpattern\fP" .Ix 0 def :M .Ix 0 def modifier match This is used to select only those words (a word is a series of characters that are neither spaces nor tabs) that match the given .I pattern . The pattern is a wildcard pattern like that used by the shell, where .CW * means 0 or more characters of any sort; .CW ? is any single character; .CW [abcd] matches any single character that is either `a', `b', `c' or `d' (there may be any number of characters between the brackets); .CW [0-9] matches any single character that is between `0' and `9' (i.e. any digit. This form may be freely mixed with the other bracket form), and `\\' is used to escape any of the characters `*', `?', `[' or `:', leaving them as regular characters to match themselves in a word. For example, the system makefile .CW uses .CW "$(CFLAGS:M-[ID]*)" '' `` to extract all the .CW \-I and .CW \-D flags that would be passed to the C compiler. This allows it to properly locate include files and generate the correct dependencies. .IP "N\fIpattern\fP" .Ix 0 def :N .Ix 0 def modifier nomatch This is identical to .CW :M except it substitutes all words that don't match the given pattern. .IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]" .Ix 0 def :S .Ix 0 def modifier substitute Causes the first occurrence of .I search-string in the variable to be replaced by .I replacement-string , unless the .CW g flag is given at the end, in which case all occurrences of the string are replaced. The substitution is performed on each word in the variable in turn. If .I search-string begins with a .CW ^ , the string must match starting at the beginning of the word. If .I search-string ends with a .CW $ , the string must match to the end of the word (these two may be combined to force an exact match). If a backslash precedes these two characters, however, they lose their special meaning. Variable expansion also occurs in the normal fashion inside both the .I search-string and the .I replacement-string , .B except that a backslash is used to prevent the expansion of a .CW $ , not another dollar sign, as is usual. Note that .I search-string is just a string, not a pattern, so none of the usual regular-expression/wildcard characters have any special meaning save .CW ^ and .CW $ . In the replacement string, the .CW & character is replaced by the .I search-string unless it is preceded by a backslash. You are allowed to use any character except colon or exclamation point to separate the two strings. This so-called delimiter character may be placed in either string by preceding it with a backslash. .IP T .Ix 0 def :T .Ix 0 def modifier tail Replaces each word in the variable expansion by its last component (its ``tail''). For example, given .DS OBJS = ../lib/a.o b /usr/lib/libm.a TAILS = $(OBJS:T) .DE the variable .CW TAILS would expand to .CW "a.o b libm.a" .'' `` .IP H .Ix 0 def :H .Ix 0 def modifier head This is similar to .CW :T , except that every word is replaced by everything but the tail (the ``head''). Using the same definition of .CW OBJS , the string .CW "$(OBJS:H)" '' `` would expand to .CW "../lib /usr/lib" .'' `` Note that the final slash on the heads is removed and anything without a head is replaced by the empty string. .IP E .Ix 0 def :E .Ix 0 def modifier extension .Ix 0 def modifier suffix .Ix 0 ref suffix "variable modifier" .CW :E replaces each word by its suffix (``extension''). So .CW "$(OBJS:E)" '' `` would give you .CW ".o .a" .'' `` .IP R .Ix 0 def :R .Ix 0 def modifier root .Ix 0 def modifier base This replaces each word by everything but the suffix (the ``root'' of the word). .CW "$(OBJS:R)" '' `` expands to `` .CW "../lib/a b /usr/lib/libm" .'' .RE .LP In addition, the System V style of substitution is also supported. This looks like: .DS $(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP) .DE It must be the last modifier in the chain. The search is anchored at the end of each word, so only suffixes or whole words may be replaced. .xH 2 More on Debugging .xH 2 More Exercises .IP (3.1) You've got a set programs, each of which is created from its own assembly-language source file (suffix .CW .asm ). Each program can be assembled into two versions, one with error-checking code assembled in and one without. You could assemble them into files with different suffixes .CW .eobj \& ( and .CW .obj , for instance), but your linker only understands files that end in .CW .obj . To top it all off, the final executables .I must have the suffix .CW .exe . How can you still use transformation rules to make your life easier (Hint: assume the error-checking versions have .CW ec tacked onto their prefix)? .IP (3.2) Assume, for a moment or two, you want to perform a sort of ``indirection'' by placing the name of a variable into another one, then you want to get the value of the first by expanding the second somehow. Unfortunately, PMake doesn't allow constructs like .DS I $($(FOO)) .DE What do you do? Hint: no further variable expansion is performed after modifiers are applied, thus if you cause a $ to occur in the expansion, that's what will be in the result. .xH 1 PMake for Gods .LP This chapter is devoted to those facilities in PMake that allow you to do a great deal in a makefile with very little work, as well as do some things you couldn't do in Make without a great deal of work (and perhaps the use of other programs). The problem with these features, is they must be handled with care, or you will end up with a mess. .LP Once more, I assume a greater familiarity with .UX or Sprite than I did in the previous two chapters. .xH 2 Search Paths .Rd 6 .LP PMake supports the dispersal of files into multiple directories by allowing you to specify places to look for sources with .CW .PATH targets in the makefile. The directories you give as sources for these targets make up a ``search path.'' Only those files used exclusively as sources are actually sought on a search path, the assumption being that anything listed as a target in the makefile can be created by the makefile and thus should be in the current directory. .LP There are two types of search paths in PMake: one is used for all types of files (including included makefiles) and is specified with a plain .CW .PATH target (e.g. .CW ".PATH : RCS" ''), `` while the other is specific to a certain type of file, as indicated by the file's suffix. A specific search path is indicated by immediately following the .CW .PATH with the suffix of the file. For instance .DS \&.PATH.h : /sprite/lib/include /sprite/att/lib/include .DE would tell PMake to look in the directories .CW /sprite/lib/include and .CW /sprite/att/lib/include for any files whose suffix is .CW .h . .LP The current directory is always consulted first to see if a file exists. Only if it cannot be found there are the directories in the specific search path, followed by those in the general search path, consulted. .LP A search path is also used when expanding wildcard characters. If the pattern has a recognizable suffix on it, the path for that suffix will be used for the expansion. Otherwise the default search path is employed. .LP When a file is found in some directory other than the current one, all local variables that would have contained the target's name .CW .ALLSRC , ( and .CW .IMPSRC ) will instead contain the path to the file, as found by PMake. Thus if you have a file .CW ../lib/mumble.c and a makefile .DS \&.PATH.c : ../lib mumble : mumble.c $(CC) -o $(.TARGET) $(.ALLSRC) .DE the command executed to create .CW mumble would be .CW "cc -o mumble ../lib/mumble.c" .'' `` (As an aside, the command in this case isn't strictly necessary, since it will be found using transformation rules if it isn't given. This is because .CW .out is the null suffix by default and a transformation exists from .CW .c to .CW .out . Just thought I'd throw that in.) .LP If a file exists in two directories on the same search path, the file in the first directory on the path will be the one PMake uses. So if you have a large system spread over many directories, it would behoove you to follow a naming convention that avoids such conflicts. .LP Something you should know about the way search paths are implemented is that each directory is read, and its contents cached, exactly once \&\*- when it is first encountered \*- so any changes to the directories while PMake is running will not be noted when searching for implicit sources, nor will they be found when PMake attempts to discover when the file was last modified, unless the file was created in the current directory. While people have suggested that PMake should read the directories each time, my experience suggests that the caching seldom causes problems. In addition, not caching the directories slows things down enormously because of PMake's attempts to apply transformation rules through non-existent files \*- the number of extra file-system searches is truly staggering, especially if many files without suffixes are used and the null suffix isn't changed from .CW .out . .xH 2 Archives and Libraries .LP .UX and Sprite allow you to merge files into an archive using the .CW ar command. Further, if the files are relocatable object files, you can run .CW ranlib on the archive and get yourself a library that you can link into any program you want. The main problem with archives is they double the space you need to store the archived files, since there's one copy in the archive and one copy out by itself. The problem with libraries is you usually think of them as .CW -lm rather than .CW /usr/lib/libm.a and the linker thinks they're out-of-date if you so much as look at them. .LP PMake solves the problem with archives by allowing you to tell it to examine the files in the archives (so you can remove the individual files without having to regenerate them later). To handle the problem with libraries, PMake adds an additional way of deciding if a library is out-of-date: .IP \(bu 2 If the table of contents is older than the library, or is missing, the library is out-of-date. .LP A library is any target that looks like .CW \-l name'' `` or that ends in a suffix that was marked as a library using the .CW .LIBS target. .CW .a is so marked in the system makefile. .LP Members of an archive are specified as ``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''. Thus .CW libdix.a(window.o) '' ``' specifies the file .CW window.o in the archive .CW libdix.a . You may also use wildcards to specify the members of the archive. Just remember that most the wildcard characters will only find .I existing files. .LP A file that is a member of an archive is treated specially. If the file doesn't exist, but it is in the archive, the modification time recorded in the archive is used for the file when determining if the file is out-of-date. When figuring out how to make an archived member target (not the file itself, but the file in the archive \*- the \fIarchive\fP(\fImember\fP) target), special care is taken with the transformation rules, as follows: .IP \(bu 2 \&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP. .IP \(bu 2 The transformation from the \fImember\fP's suffix to the \fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target. .IP \(bu 2 The \fIarchive\fP(\fImember\fP)'s .CW .TARGET variable is set to the name of the \fImember\fP if \fImember\fP is actually a target, or the path to the member file if \fImember\fP is only a source. .IP \(bu 2 The .CW .ARCHIVE variable for the \fIarchive\fP(\fImember\fP) target is set to the name of the \fIarchive\fP. .Ix 0 def variable local .ARCHIVE .Ix 0 def .ARCHIVE .IP \(bu 2 The .CW .MEMBER variable is set to the actual string inside the parentheses. In most cases, this will be the same as the .CW .TARGET variable. .Ix 0 def variable local .MEMBER .Ix 0 def .MEMBER .IP \(bu 2 The \fIarchive\fP(\fImember\fP)'s place in the local variables of the targets that depend on it is taken by the value of its .CW .TARGET variable. .LP Thus, a program library could be created with the following makefile: .DS \&.o.a : ... rm -f $(.TARGET:T) OBJS = obj1.o obj2.o obj3.o libprog.a : libprog.a($(OBJS)) ar cru $(.TARGET) $(.OODATE) ranlib $(.TARGET) .DE This will cause the three object files to be compiled (if the corresponding source files were modified after the object file or, if that doesn't exist, the archived object file), the out-of-date ones archived in .CW libprog.a , a table of contents placed in the archive and the newly-archived object files to be removed. .LP All this is used in the .CW makelib.mk system makefile to create a single library with ease. This makefile looks like this: .DS .SM # # Rules for making libraries. The object files that make up the library # are removed once they are archived. # # To make several libraries in parallel, you should define the variable # "many_libraries". This will serialize the invocations of ranlib. # # To use, do something like this: # # OBJECTS = # # fish.a: fish.a($(OBJECTS)) MAKELIB # # #ifndef _MAKELIB_MK _MAKELIB_MK = #include \&.po.a .o.a : ... rm -f $(.MEMBER) ARFLAGS ?= crl # # Re-archive the out-of-date members and recreate the library's table of # contents using ranlib. If many_libraries is defined, put the ranlib # off til the end so many libraries can be made at once. # MAKELIB : .USE .PRECIOUS ar $(ARFLAGS) $(.TARGET) $(.OODATE) #ifndef no_ranlib # ifdef many_libraries ... # endif /* many_libraries */ ranlib $(.TARGET) #endif /* no_ranlib */ #endif /* _MAKELIB_MK */ .DE .xH 2 On the Condition... .Rd 1 .LP Like the C compiler before it, PMake allows you to configure the makefile, based on the current environment, using conditional statements. A conditional looks like this: .DS #if \fIboolean expression\fP \fIlines\fP #elif \fIanother boolean expression\fP \fImore lines\fP #else \fIstill more lines\fP #endif .DE They may be nested to a maximum depth of 30 and may occur anywhere (except in a comment, of course). The .CW # '' `` must the very first character on the line. .LP Each .I "boolean expression" is made up of terms that look like function calls, the standard C boolean operators .CW && , .CW || , and .CW ! , and the standard relational operators .CW == , .CW != , .CW > , .CW >= , .CW < , and .CW <= , with .CW == and .CW != being overloaded to allow string comparisons as well. .CW && represents logical AND; .CW || is logical OR and .CW ! is logical NOT. The arithmetic and string operators take precedence over all three of these operators, while NOT takes precedence over AND, which takes precedence over OR. This precedence may be overridden with parentheses, and an expression may be parenthesized to your heart's content. Each term looks like a call on one of four functions: .nr pw 9 .Ix 0 def make .Ix 0 def conditional make .Ix 0 def if make .IP make \n(pw The syntax is .CW make( \fItarget\fP\c .CW ) where .I target is a target in the makefile. This is true if the given target was specified on the command line, or as the source for a .CW .MAIN target (note that the sources for .CW .MAIN are only used if no targets were given on the command line). .IP defined \n(pw .Ix 0 def defined .Ix 0 def conditional defined .Ix 0 def if defined The syntax is .CW defined( \fIvariable\fP\c .CW ) and is true if .I variable is defined. Certain variables are defined in the system makefile that identify the system on which PMake is being run. .IP exists \n(pw .Ix 0 def exists .Ix 0 def conditional exists .Ix 0 def if exists The syntax is .CW exists( \fIfile\fP\c .CW ) and is true if the file can be found on the global search path (i.e. that defined by .CW .PATH targets, not by .CW .PATH \fIsuffix\fP targets). .IP empty \n(pw .Ix 0 def empty .Ix 0 def conditional empty .Ix 0 def if empty This syntax is much like the others, except the string inside the parentheses is of the same form as you would put between parentheses when expanding a variable, complete with modifiers and everything. The function returns true if the resulting string is empty (NOTE: an undefined variable in this context will cause at the very least a warning message about a malformed conditional, and at the worst will cause the process to stop once it has read the makefile. If you want to check for a variable being defined or empty, use the expression .CW !defined( \fIvar\fP\c `` .CW ") || empty(" \fIvar\fP\c .CW ) '' as the definition of .CW || will prevent the .CW empty() from being evaluated and causing an error, if the variable is undefined). This can be used to see if a variable contains a given word, for example: .DS #if !empty(\fIvar\fP:M\fIword\fP) .DE .LP The arithmetic and string operators may only be used to test the value of a variable. The lefthand side must contain the variable expansion, while the righthand side contains either a string, enclosed in double-quotes, or a number. The standard C numeric conventions (except for specifying an octal number) apply to both sides. E.g. .DS #if $(OS) == 4.3 #if $(MACHINE) == "sun3" #if $(LOAD_ADDR) < 0xc000 .DE are all valid conditionals. In addition, the numeric value of a variable can be tested as a boolean as follows: .DS #if $(LOAD) .DE would see if .CW LOAD contains a non-zero value and .DS #if !$(LOAD) .DE would test if .CW LOAD contains a zero value. .LP In addition to the bare .CW #if ,'' `` there are other forms that apply one of the first two functions to each term. They are as follows: .DS ifdef \fRdefined\fP ifndef \fR!defined\fP ifmake \fRmake\fP ifnmake \fR!make\fP .DE There are also the ``else if'' forms: .CW elif , .CW elifdef , .CW elifndef , .CW elifmake , and .CW elifnmake . .LP For instance, if you wish to create two versions of a program, one of which is optimized (the production version) and the other of which is for debugging (has symbols for dbx), you have two choices: you can create two makefiles, one of which uses the .CW \-g flag for the compilation, while the other uses the .CW \-O flag, or you can use another target (call it .CW debug ) to create the debug version. The construct below will take care of this for you. I have also made it so defining the variable .CW DEBUG (say with .CW "pmake -D DEBUG" ) will also cause the debug version to be made. .DS #if defined(DEBUG) || make(debug) CFLAGS += -g #else CFLAGS += -O #endif .DE There are, of course, problems with this approach. The most glaring annoyance is that if you want to go from making a debug version to making a production version, you have to remove all the object files, or you will get some optimized and some debug versions in the same program. Another annoyance is you have to be careful not to make two targets that ``conflict'' because of some conditionals in the makefile. For instance .DS #if make(print) FORMATTER = ditroff -Plaser_printer #endif #if make(draft) FORMATTER = nroff -Pdot_matrix_printer #endif .DE would wreak havoc if you tried .CW "pmake draft print" '' `` since you would use the same formatter for each target. As I said, this all gets somewhat complicated. .xH 2 A Shell is a Shell is a Shell .Rd 7 .LP In normal operation, the Bourne Shell (better known as .CW sh '') `` is used to execute the commands to re-create targets. PMake also allows you to specify a different shell for it to use when executing these commands. There are several things PMake must know about the shell you wish to use. These things are specified as the sources for the .CW .SHELL .Ix 0 ref .SHELL .Ix 0 ref target .SHELL target by keyword, as follows: .IP "\fBpath=\fP\fIpath\fP" PMake needs to know where the shell actually resides, so it can execute it. If you specify this and nothing else, PMake will use the last component of the path and look in its table of the shells it knows and use the specification it finds, if any. Use this if you just want to use a different version of the Bourne or C Shell (yes, PMake knows how to use the C Shell too). .IP "\fBname=\fP\fIname\fP" This is the name by which the shell is to be known. It is a single word and, if no other keywords are specified (other than .B path ), it is the name by which PMake attempts to find a specification for it (as mentioned above). You can use this if you would just rather use the C Shell than the Bourne Shell .CW ".SHELL: name=csh" '' (`` will do it). .IP "\fBquiet=\fP\fIecho-off command\fP" As mentioned before, PMake actually controls whether commands are printed by introducing commands into the shell's input stream. This keyword, and the next two, control what those commands are. The .B quiet keyword is the command used to turn echoing off. Once it is turned off, echoing is expected to remain off until the echo-on command is given. .IP "\fBecho=\fP\fIecho-on command\fP" The command PMake should give to turn echoing back on again. .IP "\fBfilter=\fP\fIprinted echo-off command\fP" Many shells will echo the echo-off command when it is given. This keyword tells PMake in what format the shell actually prints the echo-off command. Wherever PMake sees this string in the shell's output, it will delete it and any following whitespace, up to and including the next newline. See the example at the end of this section for more details. .IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP" Unless a target has been marked .CW .SILENT , PMake wants to start the shell running with echoing on. To do this, it passes this flag to the shell as one of its arguments. If either this or the next flag begins with a `\-', the flags will be passed to the shell as separate arguments. Otherwise, the two will be concatenated (if they are used at the same time, of course). .IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP" Likewise, unless a target is marked .CW .IGNORE , PMake wishes error-checking to be on from the very start. To this end, it will pass this flag to the shell as an argument. The same rules for an initial `\-' apply as for the .B echoFlag . .IP "\fBcheck=\fP\fIcommand to turn error checking on\fP" Just as for echo-control, error-control is achieved by inserting commands into the shell's input stream. This is the command to make the shell check for errors. It also serves another purpose if the shell doesn't have error-control as commands, but I'll get into that in a minute. Again, once error checking has been turned on, it is expected to remain on until it is turned off again. .IP "\fBignore=\fP\fIcommand to turn error checking off\fP" This is the command PMake uses to turn error checking off. It has another use if the shell doesn't do error-control, but I'll tell you about that.\|.\|.\|now. .IP "\fBhasErrCtl=\fP\fIyes or no\fP" This takes a value that is either .B yes or .B no . Now you might think that the existence of the .B check and .B ignore keywords would be enough to tell PMake if the shell can do error-control, but you'd be wrong. If .B hasErrCtl is .B yes , PMake uses the check and ignore commands in a straight-forward manner. If this is .B no , however, their use is rather different. In this case, the check command is used as a template, in which the string .B %s is replaced by the command that's about to be executed, to produce a command for the shell that will echo the command to be executed. The ignore command is also used as a template, again with .B %s replaced by the command to be executed, to produce a command that will execute the command to be executed and ignore any error it returns. When these strings are used as templates, you must provide newline(s) .CW \en '') (`` in the appropriate place(s). .LP The strings that follow these keywords may be enclosed in single or double quotes (the quotes will be stripped off) and may contain the usual C backslash-characters (\en is newline, \er is return, \eb is backspace, \e' escapes a single-quote inside single-quotes, \e" escapes a double-quote inside double-quotes). Now for an example. .LP This is actually the contents of the .CW system makefile, and causes PMake to use the Bourne Shell in such a way that each command is printed as it is executed. That is, if more than one command is given on a line, each will be printed separately. Similarly, each time the body of a loop is executed, the commands within that loop will be printed, etc. The specification runs like this: .DS # # This is a shell specification to have the Bourne shell echo # the commands just before executing them, rather than when it reads # them. Useful if you want to see how variables are being expanded, etc. # \&.SHELL : path=/bin/sh \e quiet="set -" \e echo="set -x" \e filter="+ set - " \e echoFlag=x \e errFlag=e \e hasErrCtl=yes \e check="set -e" \e ignore="set +e" .DE .LP It tells PMake the following: .Bp The shell is located in the file .CW /bin/sh . It need not tell PMake that the name of the shell is .CW sh as PMake can figure that out for itself (it's the last component of the path). .Bp The command to stop echoing is .CW "set -" . .Bp The command to start echoing is .CW "set -x" . .Bp When the echo off command is executed, the shell will print .CW "+ set - " (The `+' comes from using the .CW \-x flag (rather than the .CW \-v flag PMake usually uses)). PMake will remove all occurrences of this string from the output, so you don't notice extra commands you didn't put there. .Bp The flag the Bourne Shell will take to start echoing in this way is the .CW \-x flag. The Bourne Shell will only take its flag arguments concatenated as its first argument, so neither this nor the .B errFlag specification begins with a \-. .Bp The flag to use to turn error-checking on from the start is .CW \-e . .Bp The shell can turn error-checking on and off, and the commands to do so are .CW "set +e" and .CW "set -e" , respectively. .LP I should note that this specification is for Bourne Shells that are not part of Berkeley .UX , as shells from Berkeley don't do error control. You can get a similar effect, however, by changing the last three lines to be: .DS hasErrCtl=no \e check="echo \e"+ %s\e"\en" \e ignore="sh -c '%s || exit 0\en" .DE .LP This will cause PMake to execute the two commands .DS echo "+ \fIcmd\fP" sh -c '\fIcmd\fP || true' .DE for each command for which errors are to be ignored. (In case you are wondering, the thing for .CW ignore tells the shell to execute another shell without error checking on and always exit 0, since the .B || causes the .CW "exit 0" to be executed only if the first command exited non-zero, and if the first command exited zero, the shell will also exit zero, since that's the last command it executed). .xH 2 Compatibility .Ix 0 ref compatibility .LP There are three (well, 3 \(12) levels of backwards-compatibility built into PMake. Most makefiles will need none at all. Some may need a little bit of work to operate correctly when run in parallel. Each level encompasses the previous levels (e.g. .B \-B (one shell per command) implies .B \-V ) The three levels are described in the following three sections. .xH 3 DEFCON 3 \*- Variable Expansion .Ix 0 ref compatibility .LP As noted before, PMake will not expand a variable unless it knows of a value for it. This can cause problems for makefiles that expect to leave variables undefined except in special circumstances (e.g. if more flags need to be passed to the C compiler or the output from a text processor should be sent to a different printer). If the variables are enclosed in curly braces .CW ${PRINTER} ''), (`` the shell will let them pass. If they are enclosed in parentheses, however, the shell will declare a syntax error and the make will come to a grinding halt. .LP You have two choices: change the makefile to define the variables (their values can be overridden on the command line, since that's where they would have been set if you used Make, anyway) or always give the .B \-V flag (this can be done with the .CW .MAKEFLAGS target, if you want). .xH 3 DEFCON 2 \*- The Number of the Beast .Ix 0 ref compatibility .LP Then there are the makefiles that expect certain commands, such as changing to a different directory, to not affect other commands in a target's creation script. You can solve this is either by going back to executing one shell per command (which is what the .B \-B flag forces PMake to do), which slows the process down a good bit and requires you to use semicolons and escaped newlines for shell constructs, or by changing the makefile to execute the offending command(s) in a subshell (by placing the line inside parentheses), like so: .DS install :: .MAKE (cd src; $(.PMAKE) install) (cd lib; $(.PMAKE) install) (cd man; $(.PMAKE) install) .DE .Ix 0 ref operator double-colon .Ix 0 ref variable global .PMAKE .Ix 0 ref .PMAKE .Ix 0 ref .MAKE .Ix 0 ref attribute .MAKE This will always execute the three makes (even if the .B \-n flag was given) because of the combination of the ``::'' operator and the .CW .MAKE attribute. Each command will change to the proper directory to perform the install, leaving the main shell in the directory in which it started. .xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery" .Ix 0 ref compatibility .LP The final category of makefile is the one where every command requires input, the dependencies are incompletely specified, or you simply cannot create more than one target at a time, as mentioned earlier. In addition, you may not have the time or desire to upgrade the makefile to run smoothly with PMake. If you are the conservative sort, this is the compatibility mode for you. It is entered either by giving PMake the .B \-M flag (for Make), or by executing PMake as .CW make .'' `` In either case, PMake performs things exactly like Make (while still supporting most of the nice new features PMake provides). This includes: .IP \(bu 2 No parallel execution. .IP \(bu 2 Targets are made in the exact order specified by the makefile. The sources for each target are made in strict left-to-right order, etc. .IP \(bu 2 A single Bourne shell is used to execute each command, thus the shell's .CW $$ variable is useless, changing directories doesn't work across command lines, etc. .IP \(bu 2 If no special characters exist in a command line, PMake will break the command into words itself and execute the command directly, without executing a shell first. The characters that cause PMake to execute a shell are: .CW # , .CW = , .CW | , .CW ^ , .CW ( , .CW ) , .CW { , .CW } , .CW ; , .CW & , .CW < , .CW > , .CW * , .CW ? , .CW [ , .CW ] , .CW : , .CW $ , .CW ` , and .CW \e . You should notice that these are all the characters that are given special meaning by the shell (except .CW ' and .CW " , which PMake deals with all by its lonesome). .IP \(bu 2 The use of the null suffix is turned off. .Ix 0 ref "null suffix" .Ix 0 ref suffix null .xH 2 The Way Things Work .LP When PMake reads the makefile, it parses sources and targets into nodes in a graph. The graph is directed only in the sense that PMake knows which way is up. Each node contains not only links to all its parents and children (the nodes that depend on it and those on which it depends, respectively), but also a count of the number of its children that have already been processed. .LP The most important thing to know about how PMake uses this graph is that the traversal is breadth-first and occurs in two passes. .LP After PMake has parsed the makefile, it begins with the nodes the user has told it to make (either on the command line, or via a .CW .MAIN target, or by the target being the first in the file not labeled with the .CW .NOTMAIN attribute) placed in a queue. It continues to take the node off the front of the queue, mark it as something that needs to be made, pass the node to .CW Suff_FindDeps (mentioned earlier) to find any implicit sources for the node, and place all the node's children that have yet to be marked at the end of the queue. If any of the children is a .CW .USE rule, its attributes are applied to the parent, then its commands are appended to the parent's list of commands and its children are linked to its parent. The parent's unmade children counter is then decremented (since the .CW .USE node has been processed). You will note that this allows a .CW .USE node to have children that are .CW .USE nodes and the rules will be applied in sequence. If the node has no children, it is placed at the end of another queue to be examined in the second pass. This process continues until the first queue is empty. .LP At this point, all the leaves of the graph are in the examination queue. PMake removes the node at the head of the queue and sees if it is out-of-date. If it is, it is passed to a function that will execute the commands for the node asynchronously. When the commands have completed, all the node's parents have their unmade children counter decremented and, if the counter is then 0, they are placed on the examination queue. Likewise, if the node is up-to-date. Only those parents that were marked on the downward pass are processed in this way. Thus PMake traverses the graph back up to the nodes the user instructed it to create. When the examination queue is empty and no shells are running to create a target, PMake is finished. .LP Once all targets have been processed, PMake executes the commands attached to the .CW .END target, either explicitly or through the use of an ellipsis in a shell script. If there were no errors during the entire process but there are still some targets unmade (PMake keeps a running count of how many targets are left to be made), there is a cycle in the graph. PMake does a depth-first traversal of the graph to find all the targets that weren't made and prints them out one by one. .xH 1 Answers to Exercises .IP (3.1) This is something of a trick question, for which I apologize. The trick comes from the UNIX definition of a suffix, which PMake doesn't necessarily share. You will have noticed that all the suffixes used in this tutorial (and in UNIX in general) begin with a period .CW .ms , ( .CW .c , etc.). Now, PMake's idea of a suffix is more like English's: it's the characters at the end of a word. With this in mind, one possible .Ix 0 def suffix solution to this problem goes as follows: .DS I \&.SUFFIXES : ec.exe .exe ec.obj .obj .asm ec.objec.exe .obj.exe : link -o $(.TARGET) $(.IMPSRC) \&.asmec.obj : asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC) \&.asm.obj : asm -o $(.TARGET) $(.IMPSRC) .DE .IP (3.2) The trick to this one lies in the ``:='' variable-assignment operator and the ``:S'' variable-expansion modifier. .Ix 0 ref variable assignment expanded .Ix 0 ref variable expansion modified .Ix 0 ref modifier substitute .Ix 0 ref :S .Ix 0 ref := Basically what you want is to take the pointer variable, so to speak, and transform it into an invocation of the variable at which it points. You might try something like .DS I $(PTR:S/^/\e$(/:S/$/)) .DE which places .CW $( '' `` at the front of the variable name and .CW ) '' `` at the end, thus transforming .CW VAR ,'' `` for example, into .CW $(VAR) ,'' `` which is just what we want. Unfortunately (as you know if you've tried it), since, as it says in the hint, PMake does no further substitution on the result of a modified expansion, that's \fIall\fP you get. The solution is to make use of ``:='' to place that string into yet another variable, then invoke the other variable directly: .DS I *PTR := $(PTR:S/^/\e$(/:S/$/)/) .DE You can then use .CW $(*PTR) '' `` to your heart's content. .de Gp .XP \&\fB\\$1:\fP .. .xH 1 Glossary of Jargon .Gp "attribute" A property given to a target that causes PMake to treat it differently. .Gp "command script" The lines immediately following a dependency line that specify commands to execute to create each of the targets on the dependency line. Each line in the command script must begin with a tab. .Gp "command-line variable" A variable defined in an argument when PMake is first executed. Overrides all assignments to the same variable name in the makefile. .Gp "conditional" A construct much like that used in C that allows a makefile to be configured on the fly based on the local environment, or on what is being made by that invocation of PMake. .Gp "creation script" Commands used to create a target. See ``command script.'' .Gp "dependency" The relationship between a source and a target. This comes in three flavors, as indicated by the operator between the target and the source. `:' gives a straight time-wise dependency (if the target is older than the source, the target is out-of-date), while `!' provides simply an ordering and always considers the target out-of-date. `::' is much like `:', save it creates multiple instances of a target each of which depends on its own list of sources. .Gp "dynamic source" This refers to a source that has a local variable invocation in it. It allows a single dependency line to specify a different source for each target on the line. .Gp "global variable" Any variable defined in a makefile. Takes precedence over variables defined in the environment, but not over command-line or local variables. .Gp "input graph" What PMake constructs from a makefile. Consists of nodes made of the targets in the makefile, and the links between them (the dependencies). The links are directed (from source to target) and there may not be any cycles (loops) in the graph. .Gp "local variable" A variable defined by PMake visible only in a target's shell script. There are seven local variables, not all of which are defined for every target: .CW .TARGET , .CW .ALLSRC , .CW .OODATE , .CW .PREFIX , .CW .IMPSRC , .CW .ARCHIVE , and .CW .MEMBER . .CW .TARGET , .CW .PREFIX , .CW .ARCHIVE , and .CW .MEMBER may be used on dependency lines to create ``dynamic sources.'' .Gp "makefile" A file that describes how a system is built. If you don't know what it is after reading this tutorial.\|.\|.\|. .Gp "modifier" A letter, following a colon, used to alter how a variable is expanded. It has no effect on the variable itself. .Gp "operator" What separates a source from a target (on a dependency line) and specifies the relationship between the two. There are three: .CW : ', ` .CW :: ', ` and .CW ! '. ` .Gp "search path" A list of directories in which a file should be sought. PMake's view of the contents of directories in a search path does not change once the makefile has been read. A file is sought on a search path only if it is exclusively a source. .Gp "shell" A program to which commands are passed in order to create targets. .Gp "source" Anything to the right of an operator on a dependency line. Targets on the dependency line are usually created from the sources. .Gp "special target" A target that causes PMake to do special things when it's encountered. .Gp "suffix" The tail end of a file name. Usually begins with a period, .CW .c or .CW .ms , e.g. .Gp "target" A word to the left of the operator on a dependency line. More generally, any file that PMake might create. A file may be (and often is) both a target and a source (what it is depends on how PMake is looking at it at the time \*- sort of like the wave/particle duality of light, you know). .Gp "transformation rule" A special construct in a makefile that specifies how to create a file of one type from a file of another, as indicated by their suffixes. .Gp "variable expansion" The process of substituting the value of a variable for a reference to it. Expansion may be altered by means of modifiers. .Gp "variable" A place in which to store text that may be retrieved later. Also used to define the local environment. Conditionals exist that test whether a variable is defined or not. .bp .\" Output table of contents last, with an entry for the index, making .\" sure to save and restore the last real page number for the index... .nr @n \n(PN+1 .\" We are not generating an index .\" .XS \n(@n .\" Index .\" .XE .nr %% \n% .PX .nr % \n(%% Index: head/contrib/bmake/bmake.1 =================================================================== --- head/contrib/bmake/bmake.1 (revision 281811) +++ head/contrib/bmake/bmake.1 (revision 281812) @@ -1,2171 +1,2279 @@ -.\" $NetBSD: make.1,v 1.229 2014/01/19 10:23:29 apb Exp $ +.\" $NetBSD: make.1,v 1.247 2015/04/10 08:43:32 wiz 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 February 14, 2014 +.Dd April 9, 2015 .Dt MAKE 1 .Os .Sh NAME .Nm bmake .Nd maintain program dependencies .Sh SYNOPSIS .Nm .Op Fl BeikNnqrstWwX .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 w -Print entering and leaving directory messages, pre and post processing. .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 w +Print entering and leaving directory messages, pre and post processing. .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 +Each target may have associated with it one or more lines of shell +commands, normally used to create the target. -Each of the commands in this script +Each of the lines 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 +(For historical reasons, spaces are not accepted.) +While targets can appear in many dependency lines if desired, by +default only one of these rules may be followed by a creation +script. +If the .Ql Ic \&:: -operator is used. +operator is used, however, all rules may include scripts and the +scripts are executed in the order found. .Pp -If the first characters of the command line are any combination of +Each line is treated as a separate shell command, unless the end of +line is escaped with a backslash +.Pq Ql \e +in which case that line and the next are combined. +.\" The escaped newline is retained and passed to the shell, which +.\" normally ignores it. +.\" However, the tab at the beginning of the following line is removed. +If the first characters of the command 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 \- +in compatibility mode 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 +it will be passed to the shell; otherwise .Nm will attempt direct execution. +If a line starts with +.Ql Ic \- +and the shell has ErrCtl enabled then failure of the command line +will be ignored as in compatibility mode. +Otherwise +.Ql Ic \- +affects the entire job; +the script will stop at the first command line that fails, +but the target will not be deemed to have failed. .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: +.Dq chdir +without potentially changing the directory for subsequent commands +should be put in parentheses so it executes in a subshell. +To force the use of one shell, escape the line breaks so as to make +the whole script one command. +For example: .Bd -literal -offset indent - avoid-chdir-side-effects: @echo Building $@ in `pwd` - @(cd ${.CURDIR} && ${.MAKE} $@) + @(cd ${.CURDIR} && ${MAKE} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: - @echo Building $@ in `pwd`; \\ - (cd ${.CURDIR} && ${.MAKE} $@); \\ + @echo Building $@ in `pwd`; \e + (cd ${.CURDIR} && ${MAKE} $@); \e echo Back in `pwd` .Ed +.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. .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. +.El +.Pp +Local variables are all built in and their values vary magically from +target to target. +It is not currently possible to define new local variables. The seven local variables are as follows: -.Bl -tag -width ".ARCHIVE" +.Bl -tag -width ".ARCHIVE" -offset indent .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. +The name of the archive file; also known as +.Ql Va \&! . .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. +The name of the archive member; also known as +.Ql Va % . .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 * . +The suffix must be one of the known suffixes declared with +.Ic .SUFFIXES +or it will not be recognized. .It Va .TARGET The name of the target; also known as .Ql Va @ . .El .Pp The shorter forms -.Ql Va @ , +.Ql ( Va \*[Gt] , +.Ql Va \&! , +.Ql Va \*[Lt] , +.Ql Va % , .Ql Va \&? , -.Ql Va \&\*[Lt] , -.Ql Va \&\*[Gt] , +.Ql Va * , and -.Ql Va * +.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" , +compatibility with historical makefiles and legacy POSIX make and are +not recommended. +.Pp +Variants of these variables with the punctuation followed immediately by +.Ql D +or +.Ql F , +e.g. +.Ql Va $(@D) , +are legacy forms equivalent to using the +.Ql :H and -.Ql Va "*D" -are permitted for compatibility with +.Ql :T +modifiers. +These forms are accepted for compatibility with .At V -makefiles and are not recommended. +makefiles and POSIX but 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 Va .INCLUDEDFROMDIR The directory of the file this Makefile was included from. .It Va .INCLUDEDFROMFILE The filename of the file this Makefile was included from. .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 . If .Va .MAKE.JOB.PREFIX is empty, no token is printed. .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.IGNORE_PATHS Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: .Ql Pa /dev /etc /proc /tmp /var/run /var/tmp .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 . As a consequence of the way values are split into words, matched, and then joined, a construct like .Dl ${VAR:M*} will normalise the inter-word spacing, removing all leading and trailing space, and converting multiple consecutive spaces to single spaces. . .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 an extended 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. .Pp As for the .Cm \&:S modifier, the .Ar pattern and .Ar replacement are subjected to variable expansion before being parsed as regular expressions. .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 variable 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 .PATH. Ns Va suffix Like .Ic .PATH but applies only to files with a particular suffix. The suffix must have been previously declared with .Ic .SUFFIXES . .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, +The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. +.Ss Older versions +An incomplete list of changes in older versions of +.Nm : .Pp -The way that parallel makes are scheduled changed in -NetBSD 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 NetBSD 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. +.Pp +The way that parallel makes are scheduled changed in +NetBSD 4.0 +so that .ORDER and .WAIT apply recursively to the dependent nodes. +The algorithms used may change again in the future. +.Ss Other make dialects +Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not +support most of the features of +.Nm +as described in this manual. +Most notably: +.Bl -bullet -offset indent +.It +The +.Ic .WAIT +and +.Ic .ORDER +declarations and most functionality pertaining to parallelization. +(GNU make supports parallelization but lacks these features needed to +control it effectively.) +.It +Directives, including for loops and conditionals and most of the +forms of include files. +(GNU make has its own incompatible and less powerful syntax for +conditionals.) +.It +All built-in variables that begin with a dot. +.It +Most of the special sources and targets that begin with a dot, +with the notable exception of +.Ic .PHONY , +.Ic .PRECIOUS , +and +.Ic .SUFFIXES . +.It +Variable modifiers, except for the +.Dl :old=new +string substitution, which does not portably support globbing with +.Ql % +and historically only works on declared suffixes. +.It +The +.Ic $> +variable even in its short form; most makes support this functionality +but its name varies. +.El +.Pp +Some features are somewhat more portable, such as assignment with +.Ic += , +.Ic ?= , +and +.Ic != . +The +.Ic .PATH +functionality is based on an older feature +.Ic VPATH +found in GNU make and many versions of SVR4 make; however, +historically its behavior is too ill-defined (and too buggy) to rely +upon. +.Pp +The +.Ic $@ +and +.Ic $< +variables are more or less universally portable, as is the +.Ic $(MAKE) +variable. +Basic use of suffix rules (for files only in the current directory, +not trying to chain transformations together, etc.) is also reasonably +portable. .Sh SEE ALSO .Xr mkdep 1 .Sh HISTORY .Nm is derived from NetBSD .Xr make 1 . It uses autoconf to facilitate portability to other platforms. .Pp A make command appeared in .At v7 . This make 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 make 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 make 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: head/contrib/bmake/bmake.cat1 =================================================================== --- head/contrib/bmake/bmake.cat1 (revision 281811) +++ head/contrib/bmake/bmake.cat1 (revision 281812) @@ -1,1397 +1,1452 @@ MAKE(1) NetBSD General Commands Manual MAKE(1) NNAAMMEE bbmmaakkee -- maintain program dependencies SSYYNNOOPPSSIISS bbmmaakkee [--BBeeiikkNNnnqqrrssttWWwwXX] [--CC _d_i_r_e_c_t_o_r_y] [--DD _v_a_r_i_a_b_l_e] [--dd _f_l_a_g_s] [--ff _m_a_k_e_f_i_l_e] [--II _d_i_r_e_c_t_o_r_y] [--JJ _p_r_i_v_a_t_e] [--jj _m_a_x___j_o_b_s] [--mm _d_i_r_e_c_t_o_r_y] [--TT _f_i_l_e] [--VV _v_a_r_i_a_b_l_e] [_v_a_r_i_a_b_l_e_=_v_a_l_u_e] [_t_a_r_g_e_t _._._.] DDEESSCCRRIIPPTTIIOONN bbmmaakkee is a program designed to simplify the maintenance of other pro- grams. Its input is a list of specifications as to the files upon which programs and other files depend. If no --ff _m_a_k_e_f_i_l_e makefile option is given, bbmmaakkee will try to open `_m_a_k_e_f_i_l_e' then `_M_a_k_e_f_i_l_e' in order to find the specifications. If the file `_._d_e_p_e_n_d' exists, it is read (see mkdep(1)). This manual page is intended as a reference document only. For a more thorough description of bbmmaakkee and makefiles, please refer to _P_M_a_k_e _- _A _T_u_t_o_r_i_a_l. bbmmaakkee will prepend the contents of the _M_A_K_E_F_L_A_G_S environment variable to the command line arguments before parsing them. The options are as follows: --BB 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. --CC _d_i_r_e_c_t_o_r_y Change to _d_i_r_e_c_t_o_r_y before reading the makefiles or doing any- thing else. If multiple --CC options are specified, each is inter- preted relative to the previous one: --CC _/ --CC _e_t_c is equivalent to --CC _/_e_t_c. --DD _v_a_r_i_a_b_l_e Define _v_a_r_i_a_b_l_e to be 1, in the global context. --dd _[_-_]_f_l_a_g_s Turn on debugging, and specify which portions of bbmmaakkee are to print debugging information. Unless the flags are preceded by `-' they are added to the _M_A_K_E_F_L_A_G_S 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 _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 out- put is line buffered. _F_l_a_g_s is one or more of the following: _A Print all possible debugging information; equivalent to specifying all of the debugging flags. _a Print debugging information about archive searching and caching. _C Print debugging information about current working direc- tory. _c Print debugging information about conditional evaluation. _d Print debugging information about directory searching and caching. _e Print debugging information about failed commands and targets. _F[++]_f_i_l_e_n_a_m_e 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 `F' flag is `+', then the file will be opened in append mode; otherwise the file will be overwritten. If the file name is `stdout' or `stderr' then debugging output will be written to the standard output or standard error output file descriptors respectively (and the `+' option has no effect). Otherwise, the output will be written to the named file. If the file name ends `.%d' then the `%d' is replaced by the pid. _f Print debugging information about loop evaluation. _g_1 Print the input graph before making anything. _g_2 Print the input graph after making everything, or before exiting on error. _g_3 Print the input graph before exiting on error. _j Print debugging information about running multiple shells. _l Print commands in Makefiles regardless of whether or not they are prefixed by `@' or other "quiet" flags. Also known as "loud" behavior. _M Print debugging information about "meta" mode decisions about targets. _m Print debugging information about making targets, includ- ing modification dates. _n Don't delete the temporary command scripts created when running commands. These temporary scripts are created in the directory referred to by the TMPDIR environment vari- able, or in _/_t_m_p if TMPDIR is unset or set to the empty string. The temporary scripts are created by mkstemp(3), and have names of the form _m_a_k_e_X_X_X_X_X_X. _N_O_T_E: This can create many files in TMPDIR or _/_t_m_p, so use with care. _p Print debugging information about makefile parsing. _s Print debugging information about suffix-transformation rules. _t Print debugging information about target list mainte- nance. _V Force the --VV option to print raw values of variables. _v Print debugging information about variable assignment. - _w Print entering and leaving directory messages, pre and - post processing. - _x Run shell commands with --xx so the actual commands are printed as they are executed. --ee Specify that environment variables override macro assignments within makefiles. --ff _m_a_k_e_f_i_l_e Specify a makefile to read instead of the default `_m_a_k_e_f_i_l_e'. If _m_a_k_e_f_i_l_e is `--', standard input is read. Multiple makefiles may be specified, and are read in the order specified. --II _d_i_r_e_c_t_o_r_y Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the --mm option) is automatically included as part of this list. --ii Ignore non-zero exit of shell commands in the makefile. Equiva- lent to specifying `--' before each command line in the makefile. --JJ _p_r_i_v_a_t_e This option should _n_o_t be specified by the user. When the _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. --jj _m_a_x___j_o_b_s Specify the maximum number of jobs that bbmmaakkee may have running at any one time. The value is saved in _._M_A_K_E_._J_O_B_S. Turns compati- bility mode off, unless the _B flag is also specified. When com- patibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the tradi- tional 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. --kk Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. --mm _d_i_r_e_c_t_o_r_y Specify a directory in which to search for sys.mk and makefiles included via the <_f_i_l_e>-style include statement. The --mm option can be used multiple times to form a search path. This path will override the default system include path: /usr/share/mk. Fur- thermore the system include path will be appended to the search path used for "_f_i_l_e"-style include statements (see the --II option). If a file or directory name in the --mm argument (or the MAKESYSPATH environment variable) starts with the string ".../" then bbmmaakkee 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 ".../" specification in the --mm argument. If used, this feature allows bbmmaakkee to easily search in the current source tree for customized sys.mk files (e.g., by using ".../mk/sys.mk" as an argument). --nn Display the commands that would have been executed, but do not actually execute them unless the target depends on the .MAKE spe- cial source (see below). --NN 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. --qq Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. --rr Do not use the built-in rules specified in the system makefile. --ss Do not echo any commands as they are executed. Equivalent to specifying `@@' before each command line in the makefile. --TT _t_r_a_c_e_f_i_l_e When used with the --jj flag, append a trace record to _t_r_a_c_e_f_i_l_e for each job started and completed. --tt 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. --VV _v_a_r_i_a_b_l_e Print bbmmaakkee's idea of the value of _v_a_r_i_a_b_l_e, in the global con- text. 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 _v_a_r_i_a_b_l_e contains a `$' then the value will be expanded before printing. --WW Treat any warnings during makefile parsing as errors. + --ww Print entering and leaving directory messages, pre and post pro- + cessing. + --XX Don't export variables passed on the command line to the environ- ment individually. Variables passed on the command line are still exported via the _M_A_K_E_F_L_A_G_S environment variable. This option may be useful on systems which have a small limit on the size of command arguments. _v_a_r_i_a_b_l_e_=_v_a_l_u_e Set the value of the variable _v_a_r_i_a_b_l_e to _v_a_l_u_e. Normally, all values passed on the command line are also exported to sub-makes in the environment. The --XX flag disables this behavior. Vari- able assignments should follow options for POSIX compatibility but no ordering is enforced. 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. In general, lines may be continued from one line to the next by ending them with a backslash (`\'). The trailing newline character and initial whitespace on the following line are compressed into a single space. FFIILLEE DDEEPPEENNDDEENNCCYY SSPPEECCIIFFIICCAATTIIOONNSS Dependency lines consist of one or more targets, an operator, and zero or more sources. This creates a relationship where the targets ``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 sep- arates them. The three operators are as follows: :: 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 bbmmaakkee is interrupted. !! Targets are always re-created, but not until all sources have been examined and re-created as necessary. Sources for a target accumu- late over dependency lines when this operator is used. The target is removed if bbmmaakkee is interrupted. :::: If no sources are specified, the target is always re-created. Oth- erwise, 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 bbmmaakkee is interrupted. Targets and sources may contain the shell wildcard values `?', `*', `[]', and `{}'. The values `?', `*', and `[]' 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 `{}' need not necessarily be used to describe existing files. Expansion is in directory order, not alphabetically as done in the shell. SSHHEELLLL CCOOMMMMAANNDDSS - Each target may have associated with it a series of shell commands, nor- - mally used to create the target. Each of the commands in this script - _m_u_s_t 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 `::::' operator is used. + Each target may have associated with it one or more lines of shell com- + mands, normally used to create the target. Each of the lines in this + script _m_u_s_t be preceded by a tab. (For historical reasons, spaces are + not accepted.) While targets can appear in many dependency lines if + desired, by default only one of these rules may be followed by a creation + script. If the `::::' operator is used, however, all rules may include + scripts and the scripts are executed in the order found. - If the first characters of the command line are any combination of `@@', - `++', or `--', the command is treated specially. A `@@' causes the command - not to be echoed before it is executed. A `++' causes the command to be - executed even when --nn 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 `--' causes any non-zero exit status of the command - line to be ignored. + Each line is treated as a separate shell command, unless the end of line + is escaped with a backslash (`\') in which case that line and the next + are combined. If the first characters of the command are any combination + of `@@', `++', or `--', the command is treated specially. A `@@' causes the + command not to be echoed before it is executed. A `++' causes the command + to be executed even when --nn is given. This is similar to the effect of + the .MAKE special source, except that the effect can be limited to a sin- + gle line of a script. A `--' in compatibility mode causes any non-zero + exit status of the command line to be ignored. When bbmmaakkee is run in jobs mode with --jj _m_a_x___j_o_b_s, the entire script for - the target is fed to a single instance of the shell. + the target is fed to a single instance of the shell. In compatibility + (non-jobs) mode, each command is run in a separate process. If the com- + mand contains any shell meta characters (`#=|^(){};&<>*?[]:$`\\n') it + will be passed to the shell; otherwise bbmmaakkee will attempt direct execu- + tion. If a line starts with `--' and the shell has ErrCtl enabled then + failure of the command line will be ignored as in compatibility mode. + Otherwise `--' affects the entire job; the script will stop at the first + command line that fails, but the target will not be deemed to have + failed. - In compatibility (non-jobs) mode, each command is run in a separate - process. If the command contains any shell meta characters - (`#=|^(){};&<>*?[]:$`\\n') it will be passed to the shell, otherwise - bbmmaakkee will attempt direct execution. - - Since bbmmaakkee will chdir(2) to `_._O_B_J_D_I_R' before executing any targets, each - child process starts with that as its current working directory. - Makefiles should be written so that the mode of bbmmaakkee operation does not change their behavior. For example, any command which needs to use - ``cd'' or ``chdir'', without side-effect should be put in parenthesis: + ``cd'' or ``chdir'' without potentially changing the directory for subse- + quent commands should be put in parentheses so it executes in a subshell. + To force the use of one shell, escape the line breaks so as to make the + whole script one command. For example: - avoid-chdir-side-effects: @echo Building $@ in `pwd` - @(cd ${.CURDIR} && ${.MAKE} $@) + @(cd ${.CURDIR} && ${MAKE} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: @echo Building $@ in `pwd`; \ - (cd ${.CURDIR} && ${.MAKE} $@); \ + (cd ${.CURDIR} && ${MAKE} $@); \ echo Back in `pwd` + Since bbmmaakkee will chdir(2) to `_._O_B_J_D_I_R' before executing any targets, each + child process starts with that as its current working directory. + VVAARRIIAABBLLEE AASSSSIIGGNNMMEENNTTSS Variables in make are much like variables in the shell, and, by tradi- tion, consist of all upper-case letters. VVaarriiaabbllee aassssiiggnnmmeenntt mmooddiiffiieerrss The five operators that can be used to assign values to variables are as follows: == Assign the value to the variable. Any previous value is overrid- den. ++== Append the value to the current value of the variable. ??== Assign the value to the variable if it is not already defined. ::== Assign with expansion, i.e. expand the value before assigning it to the variable. Normally, expansion is not done until the vari- able is referenced. _N_O_T_E: References to undefined variables are _n_o_t expanded. This can cause problems when variable modifiers are used. !!== 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. Any white-space before the assigned _v_a_l_u_e is removed; if the value is being appended, a single space is inserted between the previous contents of the variable and the appended value. Variables are expanded by surrounding the variable name with either curly braces (`{}') or parentheses (`()') and preceding it with a dollar sign (`$'). If the variable name contains only a single letter, the surround- ing braces or parentheses are not required. This shorter form is not recommended. If the variable name contains a dollar, then the name itself is expanded first. This allows almost arbitrary variable names, however names con- taining dollar, braces, parenthesis, or whitespace are really best avoided! If the result of expanding a variable contains a dollar sign (`$') the string is expanded again. Variable substitution occurs at three distinct times, depending on where the variable is being used. 1. Variables in dependency lines are expanded as the line is read. 2. Variables in shell commands are expanded when the shell command is executed. 3. ``.for'' loop index variables are expanded on each loop iteration. Note that other variables are not expanded inside loops so the fol- lowing example code: .for i in 1 2 3 a+= ${i} j= ${i} b+= ${j} .endfor all: @echo ${a} @echo ${b} will print: 1 2 3 3 3 3 Because while ${a} contains ``1 2 3'' after the loop is executed, ${b} contains ``${j} ${j} ${j}'' which expands to ``3 3 3'' since after the loop completes ${j} contains ``3''. VVaarriiaabbllee ccllaasssseess The four different classes of variables (in order of increasing prece- dence) are: Environment variables Variables defined as part of bbmmaakkee's environment. Global variables Variables defined in the makefile or in included makefiles. Command line variables Variables defined as part of the command line. Local variables - Variables that are defined specific to a certain target. The - seven local variables are as follows: + Variables that are defined specific to a certain target. - _._A_L_L_S_R_C The list of all sources for this target; also known as - `_>'. + Local variables are all built in and their values vary magically from + target to target. It is not currently possible to define new local vari- + ables. The seven local variables are as follows: - _._A_R_C_H_I_V_E The name of the archive file. + _._A_L_L_S_R_C The list of all sources for this target; also known as + `_>'. - _._I_M_P_S_R_C In suffix-transformation rules, the name/path of the - source from which the target is to be transformed (the - ``implied'' source); also known as `_<'. It is not - defined in explicit rules. + _._A_R_C_H_I_V_E The name of the archive file; also known as `_!'. - _._M_E_M_B_E_R The name of the archive member. + _._I_M_P_S_R_C In suffix-transformation rules, the name/path of the + source from which the target is to be transformed (the + ``implied'' source); also known as `_<'. It is not + defined in explicit rules. - _._O_O_D_A_T_E The list of sources for this target that were deemed - out-of-date; also known as `_?'. + _._M_E_M_B_E_R The name of the archive member; also known as `_%'. - _._P_R_E_F_I_X The file prefix of the target, containing only the file - portion, no suffix or preceding directory components; - also known as `_*'. + _._O_O_D_A_T_E The list of sources for this target that were deemed out- + of-date; also known as `_?'. - _._T_A_R_G_E_T The name of the target; also known as `_@'. + _._P_R_E_F_I_X The file prefix of the target, containing only the file + portion, no suffix or preceding directory components; + also known as `_*'. The suffix must be one of the known + suffixes declared with ..SSUUFFFFIIXXEESS or it will not be recog- + nized. - The shorter forms `_@', `_?', `_<', `_>', and `_*' are permitted for - backward compatibility with historical makefiles and are not rec- - ommended. The six variables `_@_F', `_@_D', `_<_F', `_<_D', `_*_F', and - `_*_D' are permitted for compatibility with AT&T System V UNIX - makefiles and are not recommended. + _._T_A_R_G_E_T The name of the target; also known as `_@'. - 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 `_._T_A_R_G_E_T', `_._P_R_E_F_I_X', `_._A_R_C_H_I_V_E', - and `_._M_E_M_B_E_R'. + The shorter forms (`_>', `_!', `_<', `_%', `_?', `_*', and `_@') are permitted + for backward compatibility with historical makefiles and legacy POSIX + make and are not recommended. + Variants of these variables with the punctuation followed immediately by + `D' or `F', e.g. `_$_(_@_D_)', are legacy forms equivalent to using the `:H' + and `:T' modifiers. These forms are accepted for compatibility with AT&T + System V UNIX makefiles and POSIX but are not recommended. + + 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 `_._T_A_R_G_E_T', `_._P_R_E_F_I_X', `_._A_R_C_H_I_V_E', and `_._M_E_M_B_E_R'. + AAddddiittiioonnaall bbuuiilltt--iinn vvaarriiaabblleess In addition, bbmmaakkee sets or knows about the following variables: _$ A single dollar sign `$', i.e. `$$' expands to a single dollar sign. _._A_L_L_T_A_R_G_E_T_S The list of all targets encountered in the Makefile. If evaluated during Makefile parsing, lists only those tar- gets encountered thus far. _._C_U_R_D_I_R A path to the directory where bbmmaakkee was executed. Refer to the description of `PWD' for more details. _._I_N_C_L_U_D_E_D_F_R_O_M_D_I_R The directory of the file this Makefile was included from. _._I_N_C_L_U_D_E_D_F_R_O_M_F_I_L_E The filename of the file this Makefile was included from. MAKE The name that bbmmaakkee was executed with (_a_r_g_v_[_0_]). For compatibility bbmmaakkee also sets _._M_A_K_E with the same value. The preferred variable to use is the environment variable MAKE because it is more compatible with other versions of bbmmaakkee and cannot be confused with the special target with the same name. _._M_A_K_E_._D_E_P_E_N_D_F_I_L_E Names the makefile (default `_._d_e_p_e_n_d') from which gener- ated dependencies are read. _._M_A_K_E_._E_X_P_A_N_D___V_A_R_I_A_B_L_E_S A boolean that controls the default behavior of the --VV option. _._M_A_K_E_._E_X_P_O_R_T_E_D The list of variables exported by bbmmaakkee. _._M_A_K_E_._J_O_B_S The argument to the --jj option. _._M_A_K_E_._J_O_B_._P_R_E_F_I_X If bbmmaakkee is run with _j then output for each target is prefixed with a token `--- target ---' the first part of which can be controlled via _._M_A_K_E_._J_O_B_._P_R_E_F_I_X. If _._M_A_K_E_._J_O_B_._P_R_E_F_I_X is empty, no token is printed. For example: .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}] would produce tokens like `---make[1234] target ---' mak- ing it easier to track the degree of parallelism being achieved. MAKEFLAGS The environment variable `MAKEFLAGS' may contain anything that may be specified on bbmmaakkee's command line. Anything specified on bbmmaakkee's command line is appended to the `MAKEFLAGS' variable which is then entered into the envi- ronment for all programs which bbmmaakkee executes. _._M_A_K_E_._L_E_V_E_L The recursion depth of bbmmaakkee. The initial instance of bbmmaakkee will be 0, and an incremented value is put into the environment to be seen by the next generation. This allows tests like: .if ${.MAKE.LEVEL} == 0 to protect things which should only be evaluated in the initial instance of bbmmaakkee. _._M_A_K_E_._M_A_K_E_F_I_L_E___P_R_E_F_E_R_E_N_C_E The ordered list of makefile names (default `_m_a_k_e_f_i_l_e', `_M_a_k_e_f_i_l_e') that bbmmaakkee will look for. _._M_A_K_E_._M_A_K_E_F_I_L_E_S The list of makefiles read by bbmmaakkee, which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. _._M_A_K_E_._M_O_D_E Processed after reading all makefiles. Can affect the mode that bbmmaakkee runs in. It can contain a number of key- words: _c_o_m_p_a_t Like --BB, puts bbmmaakkee into "compat" mode. _m_e_t_a Puts bbmmaakkee into "meta" mode, where meta files are created for each target to capture the command run, the output generated and if filemon(4) is available, the system calls which are of interest to bbmmaakkee. The captured output can be very useful when diagnosing errors. _c_u_r_d_i_r_O_k_= _b_f Normally bbmmaakkee will not create .meta files in `_._C_U_R_D_I_R'. This can be overridden by set- ting _b_f to a value which represents True. _e_n_v For debugging, it can be useful to inlcude the environment in the .meta file. _v_e_r_b_o_s_e 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: _._M_A_K_E_._M_E_T_A_._P_R_E_F_I_X. _i_g_n_o_r_e_-_c_m_d 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 ..NNOOMMEETTAA__CCMMPP. _s_i_l_e_n_t_= _b_f If _b_f is True, when a .meta file is created, mark the target ..SSIILLEENNTT. _._M_A_K_E_._M_E_T_A_._B_A_I_L_I_W_I_C_K In "meta" mode, provides a list of prefixes which match the directories controlled by bbmmaakkee. If a file that was generated outside of _._O_B_J_D_I_R but within said bailiwick is missing, the current target is considered out-of-date. _._M_A_K_E_._M_E_T_A_._C_R_E_A_T_E_D 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 _._M_A_K_E_._M_E_T_A_._F_I_L_E_S. _._M_A_K_E_._M_E_T_A_._F_I_L_E_S 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 informa- tion. _._M_A_K_E_._M_E_T_A_._I_G_N_O_R_E___P_A_T_H_S Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: `_/_d_e_v _/_e_t_c _/_p_r_o_c _/_t_m_p _/_v_a_r_/_r_u_n _/_v_a_r_/_t_m_p' _._M_A_K_E_._M_E_T_A_._P_R_E_F_I_X Defines the message printed for each meta file updated in "meta verbose" mode. The default value is: Building ${.TARGET:H:tA}/${.TARGET:T} _._M_A_K_E_O_V_E_R_R_I_D_E_S 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 `MAKEFLAGS'. This behaviour can be disabled by assigning an empty value to `_._M_A_K_E_O_V_E_R_R_I_D_E_S' within a makefile. Extra variables can be exported from a makefile by appending their names to `_._M_A_K_E_O_V_E_R_R_I_D_E_S'. `MAKEFLAGS' is re-exported whenever `_._M_A_K_E_O_V_E_R_R_I_D_E_S' is modified. _._M_A_K_E_._P_A_T_H___F_I_L_E_M_O_N If bbmmaakkee was built with filemon(4) support, this is set to the path of the device node. This allows makefiles to test for this support. _._M_A_K_E_._P_I_D The process-id of bbmmaakkee. _._M_A_K_E_._P_P_I_D The parent process-id of bbmmaakkee. _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R When bbmmaakkee stops due to an error, it prints its name and the value of `_._C_U_R_D_I_R' as well as the value of any vari- ables named in `_M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R'. _._n_e_w_l_i_n_e This variable is simply assigned a newline character as its value. This allows expansions using the ::@@ modifier to put a newline between iterations of the loop rather than a space. For example, the printing of `_M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R' could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}. _._O_B_J_D_I_R A path to the directory where the targets are built. Its value is determined by trying to chdir(2) to the follow- ing directories in order and using the first match: 1. ${MAKEOBJDIRPREFIX}${.CURDIR} (Only if `MAKEOBJDIRPREFIX' is set in the environ- ment or on the command line.) 2. ${MAKEOBJDIR} (Only if `MAKEOBJDIR' is set in the environment or on the command line.) 3. ${.CURDIR}_/_o_b_j_.${MACHINE} 4. ${.CURDIR}_/_o_b_j 5. _/_u_s_r_/_o_b_j_/${.CURDIR} 6. ${.CURDIR} Variable expansion is performed on the value before it's used, so expressions such as ${.CURDIR:S,^/usr/src,/var/obj,} may be used. This is especially useful with `MAKEOBJDIR'. `_._O_B_J_D_I_R' may be modified in the makefile as a global variable. In all cases, bbmmaakkee will chdir(2) to `_._O_B_J_D_I_R' and set `PWD' to that directory before executing any tar- gets. _._P_A_R_S_E_D_I_R A path to the directory of the current `_M_a_k_e_f_i_l_e' being parsed. _._P_A_R_S_E_F_I_L_E The basename of the current `_M_a_k_e_f_i_l_e' being parsed. This variable and `_._P_A_R_S_E_D_I_R' are both set only while the `_M_a_k_e_f_i_l_e_s' are being parsed. If you want to retain their current values, assign them to a variable using assignment with expansion: (`::=='). _._P_A_T_H A variable that represents the list of directories that bbmmaakkee will search for files. The search list should be updated using the target `_._P_A_T_H' rather than the vari- able. PWD Alternate path to the current directory. bbmmaakkee normally sets `_._C_U_R_D_I_R' to the canonical path given by getcwd(3). However, if the environment variable `PWD' is set and gives a path to the current directory, then bbmmaakkee sets `_._C_U_R_D_I_R' to the value of `PWD' instead. This behaviour is disabled if `MAKEOBJDIRPREFIX' is set or `MAKEOBJDIR' contains a variable transform. `PWD' is set to the value of `_._O_B_J_D_I_R' for all programs which bbmmaakkee executes. .TARGETS The list of targets explicitly specified on the command line, if any. VPATH Colon-separated (``:'') lists of directories that bbmmaakkee will search for files. The variable is supported for compatibility with old make programs only, use `_._P_A_T_H' instead. VVaarriiaabbllee mmooddiiffiieerrss Variable expansion may be modified to select or modify each word of the variable (where a ``word'' is white-space delimited sequence of charac- ters). The general format of a variable expansion is as follows: ${variable[:modifier[:...]]} Each modifier begins with a colon, which may be escaped with a backslash (`\'). A set of modifiers can be specified via a variable, as follows: modifier_variable=modifier[:...] ${variable:${modifier_variable}[:...]} 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 (`$'), these must be doubled to avoid early expansion. The supported modifiers are: ::EE Replaces each word in the variable with its suffix. ::HH Replaces each word in the variable with everything but the last com- ponent. ::MM_p_a_t_t_e_r_n Select only those words that match _p_a_t_t_e_r_n. The standard shell wildcard characters (`*', `?', and `[]') may be used. The wildcard characters may be escaped with a backslash (`\'). As a consequence of the way values are split into words, matched, and then joined, a construct like ${VAR:M*} will normalise the inter-word spacing, removing all leading and trailing space, and converting multiple consecutive spaces to single spaces. ::NN_p_a_t_t_e_r_n This is identical to `::MM', but selects all words which do not match _p_a_t_t_e_r_n. ::OO Order every word in variable alphabetically. To sort words in reverse order use the `::OO::[[--11....11]]' combination of modifiers. ::OOxx Randomize words in variable. The results will be different each time you are referring to the modified variable; use the assignment with expansion (`::==') to prevent such behaviour. For example, 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}" may produce output similar to: quattro due tre uno tre due quattro uno due uno quattro tre due uno quattro tre ::QQ Quotes every shell meta-character in the variable, so that it can be passed safely through recursive invocations of bbmmaakkee. ::RR Replaces each word in the variable with everything but its suffix. ::ggmmttiimmee The value is a format string for strftime(3), using the current gmtime(3). ::hhaasshh Compute a 32bit hash of the value and encode it as hex digits. ::llooccaallttiimmee The value is a format string for strftime(3), using the current localtime(3). ::ttAA Attempt to convert variable to an absolute path using realpath(3), if that fails, the value is unchanged. ::ttll Converts variable to lower-case letters. ::ttss_c Words in the variable are normally separated by a space on expan- sion. This modifier sets the separator to the character _c. If _c is omitted, then no separator is used. The common escapes (including octal numeric codes), work as expected. ::ttuu Converts variable to upper-case letters. ::ttWW Causes the value to be treated as a single word (possibly containing embedded white space). See also `::[[**]]'. ::ttww Causes the value to be treated as a sequence of words delimited by white space. See also `::[[@@]]'. ::SS/_o_l_d___s_t_r_i_n_g/_n_e_w___s_t_r_i_n_g/[11ggWW] Modify the first occurrence of _o_l_d___s_t_r_i_n_g in the variable's value, replacing it with _n_e_w___s_t_r_i_n_g. If a `g' is appended to the last slash of the pattern, all occurrences in each word are replaced. If a `1' is appended to the last slash of the pattern, only the first word is affected. If a `W' is appended to the last slash of the pattern, then the value is treated as a single word (possibly con- taining embedded white space). If _o_l_d___s_t_r_i_n_g begins with a caret (`^'), _o_l_d___s_t_r_i_n_g is anchored at the beginning of each word. If _o_l_d___s_t_r_i_n_g ends with a dollar sign (`$'), it is anchored at the end of each word. Inside _n_e_w___s_t_r_i_n_g, an ampersand (`&') is replaced by _o_l_d___s_t_r_i_n_g (without any `^' or `$'). 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 (`\'). Variable expansion occurs in the normal fashion inside both _o_l_d___s_t_r_i_n_g and _n_e_w___s_t_r_i_n_g with the single exception that a backslash is used to prevent the expansion of a dollar sign (`$'), not a pre- ceding dollar sign as is usual. ::CC/_p_a_t_t_e_r_n/_r_e_p_l_a_c_e_m_e_n_t/[11ggWW] The ::CC modifier is just like the ::SS modifier except that the old and new strings, instead of being simple strings, are an extended regu- lar expression (see regex(3)) string _p_a_t_t_e_r_n and an ed(1)-style string _r_e_p_l_a_c_e_m_e_n_t. Normally, the first occurrence of the pattern _p_a_t_t_e_r_n in each word of the value is substituted with _r_e_p_l_a_c_e_m_e_n_t. The `1' modifier causes the substitution to apply to at most one word; the `g' modifier causes the substitution to apply to as many instances of the search pattern _p_a_t_t_e_r_n as occur in the word or words it is found in; the `W' modifier causes the value to be treated as a single word (possibly containing embedded white space). Note that `1' and `g' are orthogonal; the former specifies whether multiple words are potentially affected, the latter whether multiple substitutions can potentially occur within each affected word. As for the ::SS modifier, the _p_a_t_t_e_r_n and _r_e_p_l_a_c_e_m_e_n_t are subjected to variable expansion before being parsed as regular expressions. ::TT Replaces each word in the variable with its last component. ::uu Remove adjacent duplicate words (like uniq(1)). ::??_t_r_u_e___s_t_r_i_n_g::_f_a_l_s_e___s_t_r_i_n_g If the variable name (not its value), when parsed as a .if condi- tional expression, evaluates to true, return as its value the _t_r_u_e___s_t_r_i_n_g, otherwise return the _f_a_l_s_e___s_t_r_i_n_g. 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 ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS), to determine is any words match "42" you need to use something like: ${"${NUMBERS:M42}" != "":?match:no}. _:_o_l_d___s_t_r_i_n_g_=_n_e_w___s_t_r_i_n_g This is the AT&T System V UNIX style variable substitution. It must be the last modifier specified. If _o_l_d___s_t_r_i_n_g or _n_e_w___s_t_r_i_n_g do not contain the pattern matching character _% then it is assumed that they are anchored at the end of each word, so only suffixes or entire words may be replaced. Otherwise _% is the substring of _o_l_d___s_t_r_i_n_g to be replaced in _n_e_w___s_t_r_i_n_g. Variable expansion occurs in the normal fashion inside both _o_l_d___s_t_r_i_n_g and _n_e_w___s_t_r_i_n_g with the single exception that a backslash is used to prevent the expansion of a dollar sign (`$'), not a pre- ceding dollar sign as is usual. ::@@_t_e_m_p@@_s_t_r_i_n_g@@ This is the loop expansion mechanism from the OSF Development Envi- ronment (ODE) make. Unlike ..ffoorr loops expansion occurs at the time of reference. Assign _t_e_m_p to each word in the variable and evaluate _s_t_r_i_n_g. The ODE convention is that _t_e_m_p should start and end with a period. For example. ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} However a single character variable is often more readable: ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} ::UU_n_e_w_v_a_l If the variable is undefined _n_e_w_v_a_l 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: ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: ${VAR:D:Unewval} ::DD_n_e_w_v_a_l If the variable is defined _n_e_w_v_a_l is the value. ::LL The name of the variable is the value. ::PP 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. ::!!_c_m_d!! The output of running _c_m_d is the value. ::sshh If the variable is non-empty it is run as a command and the output becomes the new value. ::::==_s_t_r The variable is assigned the value _s_t_r 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 bbmmaakkee happy. The `::::' helps avoid false matches with the AT&T System V UNIX style ::== modifier and since substitution always occurs the ::::== form is vaguely appropriate. ::::??==_s_t_r As for ::::== but only if the variable does not already have a value. ::::++==_s_t_r Append _s_t_r to the variable. ::::!!==_c_m_d Assign the output of _c_m_d to the variable. ::[[_r_a_n_g_e]] Selects one or more words from the value, or performs other opera- tions related to the way in which the value is divided into words. 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 `::[[]]' 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). The _r_a_n_g_e is subjected to variable expansion, and the expanded result is then interpreted as follows: _i_n_d_e_x Selects a single word from the value. _s_t_a_r_t...._e_n_d Selects all words from _s_t_a_r_t to _e_n_d, inclusive. For example, `::[[22....--11]]' selects all words from the second word to the last word. If _s_t_a_r_t is greater than _e_n_d, then the words are out- put in reverse order. For example, `::[[--11....11]]' selects all the words from last to first. ** 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 Means the same as `::[[**]]'. @@ Causes subsequent modifiers to treat the value as a sequence of words delimited by white space. Analogous to the effect of "$@" in Bourne shell. ## Returns the number of words in the value. IINNCCLLUUDDEE SSTTAATTEEMMEENNTTSS,, CCOONNDDIITTIIOONNAALLSS AANNDD FFOORR LLOOOOPPSS Makefile inclusion, conditional structures and for loops reminiscent of the C programming language are provided in bbmmaakkee. All such structures are identified by a line beginning with a single dot (`.') character. Files are included with either ..iinncclluuddee <_f_i_l_e> or ..iinncclluuddee "_f_i_l_e". Vari- ables 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 --II option are searched before the system makefile directory. For compatibility with other versions of bbmmaakkee `include file ...' is also accepted. If the include statement is written as ..--iinncclluuddee or as ..ssiinncclluuddee then errors locating and/or opening include files are ignored. Conditional expressions are also preceded by a single dot as the first character of a line. The possible conditionals are as follows: ..eerrrroorr _m_e_s_s_a_g_e The message is printed along with the name of the makefile and line number, then bbmmaakkee will exit. ..eexxppoorrtt _v_a_r_i_a_b_l_e _._._. Export the specified global variable. If no variable list is provided, all globals are exported except for internal variables (those that start with `.'). This is not affected by the --XX flag, so should be used with caution. For compatibility with other bbmmaakkee programs `export variable=value' is also accepted. Appending a variable name to _._M_A_K_E_._E_X_P_O_R_T_E_D is equivalent to exporting a variable. ..eexxppoorrtt--eennvv _v_a_r_i_a_b_l_e _._._. The same as `.export', except that the variable is not appended to _._M_A_K_E_._E_X_P_O_R_T_E_D. This allows exporting a value to the environ- ment which is different from that used by bbmmaakkee internally. ..iinnffoo _m_e_s_s_a_g_e The message is printed along with the name of the makefile and line number. ..uunnddeeff _v_a_r_i_a_b_l_e Un-define the specified global variable. Only global variables may be un-defined. ..uunneexxppoorrtt _v_a_r_i_a_b_l_e _._._. The opposite of `.export'. The specified global _v_a_r_i_a_b_l_e will be removed from _._M_A_K_E_._E_X_P_O_R_T_E_D. If no variable list is provided, all globals are unexported, and _._M_A_K_E_._E_X_P_O_R_T_E_D deleted. ..uunneexxppoorrtt--eennvv Unexport all globals previously exported and clear the environ- ment inherited from the parent. This operation will cause a mem- ory leak of the original environment, so should be used spar- ingly. Testing for _._M_A_K_E_._L_E_V_E_L being 0, would make sense. Also note that any variables which originated in the parent environ- ment should be explicitly preserved if desired. For example: .if ${.MAKE.LEVEL} == 0 PATH := ${PATH} .unexport-env .export PATH .endif Would result in an environment containing only `PATH', which is the minimal useful environment. Actually `.MAKE.LEVEL' will also be pushed into the new environment. ..wwaarrnniinngg _m_e_s_s_a_g_e The message prefixed by `_w_a_r_n_i_n_g_:' is printed along with the name of the makefile and line number. ..iiff [!]_e_x_p_r_e_s_s_i_o_n [_o_p_e_r_a_t_o_r _e_x_p_r_e_s_s_i_o_n _._._.] Test the value of an expression. ..iiffddeeff [!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e _._._.] Test the value of a variable. ..iiffnnddeeff [!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e _._._.] Test the value of a variable. ..iiffmmaakkee [!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t _._._.] Test the target being built. ..iiffnnmmaakkee [!] _t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t _._._.] Test the target being built. ..eellssee Reverse the sense of the last conditional. ..eelliiff [!] _e_x_p_r_e_s_s_i_o_n [_o_p_e_r_a_t_o_r _e_x_p_r_e_s_s_i_o_n _._._.] A combination of `..eellssee' followed by `..iiff'. ..eelliiffddeeff [!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e _._._.] A combination of `..eellssee' followed by `..iiffddeeff'. ..eelliiffnnddeeff [!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e _._._.] A combination of `..eellssee' followed by `..iiffnnddeeff'. ..eelliiffmmaakkee [!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t _._._.] A combination of `..eellssee' followed by `..iiffmmaakkee'. ..eelliiffnnmmaakkee [!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t _._._.] A combination of `..eellssee' followed by `..iiffnnmmaakkee'. ..eennddiiff End the body of the conditional. The _o_p_e_r_a_t_o_r may be any one of the following: |||| Logical OR. &&&& Logical AND; of higher precedence than ``||''. As in C, bbmmaakkee 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 `!!' may be used to logically negate an entire conditional. It is of higher precedence than `&&&&'. The value of _e_x_p_r_e_s_s_i_o_n may be any of the following: ddeeffiinneedd Takes a variable name as an argument and evaluates to true if the variable has been defined. mmaakkee Takes a target name as an argument and evaluates to true if the target was specified as part of bbmmaakkee's command line or was declared the default target (either implicitly or explicitly, see _._M_A_I_N) before the line containing the conditional. eemmppttyy Takes a variable, with possible modifiers, and evaluates to true if the expansion of the variable would result in an empty string. eexxiissttss 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 _._P_A_T_H). ttaarrggeett Takes a target name as an argument and evaluates to true if the target has been defined. ccoommmmaannddss Takes a target name as an argument and evaluates to true if the target has been defined and has commands associated with it. _E_x_p_r_e_s_s_i_o_n 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 sup- ported. The standard C relational operators are all supported. If after variable expansion, either the left or right hand side of a `====' or `!!==' 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. When bbmmaakkee is evaluating one of these conditional expressions, and it encounters a (white-space separated) word it doesn't recognize, either the ``make'' or ``defined'' expression is applied to it, depending on the form of the conditional. If the form is `..iiffddeeff', `..iiffnnddeeff', or `..iiff' the ``defined'' expression is applied. Similarly, if the form is `..iiffmmaakkee' or `..iiffnnmmaakkee, tthhee' ``make'' expression is applied. If the conditional evaluates to true the parsing of the makefile contin- ues as before. If it evaluates to false, the following lines are skipped. In both cases this continues until a `..eellssee' or `..eennddiiff' is found. For loops are typically used to apply a set of rules to a list of files. The syntax of a for loop is: ..ffoorr _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e _._._.] iinn _e_x_p_r_e_s_s_i_o_n ..eennddffoorr After the for eexxpprreessssiioonn is evaluated, it is split into words. On each iteration of the loop, one word is taken and assigned to each vvaarriiaabbllee, in order, and these vvaarriiaabblleess are substituted into the mmaakkee--rruulleess 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. CCOOMMMMEENNTTSS Comments begin with a hash (`#') character, anywhere but in a shell com- mand line, and continue to the end of an unescaped new line. SSPPEECCIIAALL SSOOUURRCCEESS ((AATTTTRRIIBBUUTTEESS)) ..EEXXEECC Target is never out of date, but always execute commands any- way. ..IIGGNNOORREE Ignore any errors from the commands associated with this tar- get, exactly as if they all were preceded by a dash (`-'). ..MMAADDEE Mark all sources of this target as being up-to-date. ..MMAAKKEE Execute the commands associated with this target even if the --nn or --tt options were specified. Normally used to mark recursive bbmmaakkees. ..MMEETTAA Create a meta file for the target, even if it is flagged as ..PPHHOONNYY, ..MMAAKKEE, or ..SSPPEECCIIAALL. Usage in conjunction with ..MMAAKKEE is the most likely case. In "meta" mode, the target is out-of- date if the meta file is missing. ..NNOOMMEETTAA Do not create a meta file for the target. Meta files are also not created for ..PPHHOONNYY, ..MMAAKKEE, or ..SSPPEECCIIAALL targets. ..NNOOMMEETTAA__CCMMPP 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 _._O_O_D_A_T_E, which can be used for that purpose even when not otherwise needed or desired: skip-compare-for-some: @echo this will be compared @echo this will not ${.OODATE:M.NOMETA_CMP} @echo this will also be compared The ::MM pattern suppresses any expansion of the unwanted vari- able. ..NNOOPPAATTHH Do not search for the target in the directories specified by ..PPAATTHH. ..NNOOTTMMAAIINN Normally bbmmaakkee 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. ..OOPPTTIIOONNAALL If a target is marked with this attribute and bbmmaakkee can't fig- ure out how to create it, it will ignore this fact and assume the file isn't needed or already exists. ..PPHHOONNYY 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 --tt option. Suffix-transformation rules are not applied to ..PPHHOONNYY targets. ..PPRREECCIIOOUUSS When bbmmaakkee is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. ..RREECCUURRSSIIVVEE Synonym for ..MMAAKKEE. ..SSIILLEENNTT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign (`@'). ..UUSSEE Turn the target into bbmmaakkee's version of a macro. When the tar- get is used as a source for another target, the other target acquires the commands, sources, and attributes (except for ..UUSSEE) of the source. If the target already has commands, the ..UUSSEE target's commands are appended to them. ..UUSSEEBBEEFFOORREE Exactly like ..UUSSEE, but prepend the ..UUSSEEBBEEFFOORREE target commands to the target. ..WWAAIITT If ..WWAAIITT 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 depen- dency tree. So given: x: a .WAIT b echo x a: echo a b: b1 echo b b1: echo b1 the output is always `a', `b1', `b', `x'. The ordering imposed by ..WWAAIITT is only relevant for parallel makes. SSPPEECCIIAALL TTAARRGGEETTSS Special targets may not be included with other targets, i.e. they must be the only target specified. ..BBEEGGIINN Any command lines attached to this target are executed before anything else is done. ..DDEEFFAAUULLTT This is sort of a ..UUSSEE rule for any target (that was used only as a source) that bbmmaakkee can't figure out any other way to cre- ate. Only the shell script is used. The ..IIMMPPSSRRCC variable of a target that inherits ..DDEEFFAAUULLTT's commands is set to the target's own name. ..EENNDD Any command lines attached to this target are executed after everything else is done. ..EERRRROORR Any command lines attached to this target are executed when another target fails. The ..EERRRROORR__TTAARRGGEETT variable is set to the target that failed. See also MMAAKKEE__PPRRIINNTT__VVAARR__OONN__EERRRROORR. ..IIGGNNOORREE Mark each of the sources with the ..IIGGNNOORREE attribute. If no sources are specified, this is the equivalent of specifying the --ii option. ..IINNTTEERRRRUUPPTT If bbmmaakkee is interrupted, the commands for this target will be executed. ..MMAAIINN If no target is specified when bbmmaakkee is invoked, this target will be built. ..MMAAKKEEFFLLAAGGSS This target provides a way to specify flags for bbmmaakkee when the makefile is used. The flags are as if typed to the shell, though the --ff option will have no effect. ..NNOOPPAATTHH Apply the ..NNOOPPAATTHH attribute to any specified sources. ..NNOOTTPPAARRAALLLLEELL Disable parallel mode. ..NNOO__PPAARRAALLLLEELL Synonym for ..NNOOTTPPAARRAALLLLEELL, for compatibility with other pmake variants. ..OORRDDEERR The named targets are made in sequence. This ordering does not add targets to the list of targets to be made. Since the depen- dents of a target do not get built until the target itself could be built, unless `a' is built by another part of the dependency graph, the following is a dependency loop: .ORDER: b a b: a The ordering imposed by ..OORRDDEERR is only relevant for parallel makes. ..PPAATTHH The sources are directories which are to be searched for files not found in the current directory. If no sources are speci- fied, any previously specified directories are deleted. If the source is the special ..DDOOTTLLAASSTT target, then the current working directory is searched last. ..PPAATTHH.._s_u_f_f_i_x Like ..PPAATTHH but applies only to files with a particular suffix. The suffix must have been previously declared with ..SSUUFFFFIIXXEESS. ..PPHHOONNYY Apply the ..PPHHOONNYY attribute to any specified sources. ..PPRREECCIIOOUUSS Apply the ..PPRREECCIIOOUUSS attribute to any specified sources. If no sources are specified, the ..PPRREECCIIOOUUSS attribute is applied to every target in the file. ..SSHHEELLLL Sets the shell that bbmmaakkee will use to execute commands. The sources are a set of _f_i_e_l_d_=_v_a_l_u_e pairs. _n_a_m_e This is the minimal specification, used to select one of the builtin shell specs; _s_h, _k_s_h, and _c_s_h. _p_a_t_h Specifies the path to the shell. _h_a_s_E_r_r_C_t_l Indicates whether the shell supports exit on error. _c_h_e_c_k The command to turn on error checking. _i_g_n_o_r_e The command to disable error checking. _e_c_h_o The command to turn on echoing of commands executed. _q_u_i_e_t The command to turn off echoing of commands exe- cuted. _f_i_l_t_e_r The output to filter after issuing the _q_u_i_e_t com- mand. It is typically identical to _q_u_i_e_t. _e_r_r_F_l_a_g The flag to pass the shell to enable error checking. _e_c_h_o_F_l_a_g The flag to pass the shell to enable command echo- ing. _n_e_w_l_i_n_e The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. Example: .SHELL: name=ksh path=/bin/ksh hasErrCtl=true \ check="set -e" ignore="set +e" \ echo="set -v" quiet="set +v" filter="set +v" \ echoFlag=v errFlag=e newline="'\n'" ..SSIILLEENNTT Apply the ..SSIILLEENNTT attribute to any specified sources. If no sources are specified, the ..SSIILLEENNTT attribute is applied to every command in the file. ..SSTTAALLEE This target gets run when a dependency file contains stale entries, having _._A_L_L_S_R_C set to the name of that dependency file. ..SSUUFFFFIIXXEESS Each source specifies a suffix to bbmmaakkee. If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. Example: .SUFFIXES: .o .c.o: cc -o ${.TARGET} -c ${.IMPSRC} EENNVVIIRROONNMMEENNTT bbmmaakkee uses the following environment variables, if they exist: MACHINE, MACHINE_ARCH, MAKE, MAKEFLAGS, MAKEOBJDIR, MAKEOBJDIRPREFIX, MAKESYSPATH, PWD, and TMPDIR. MAKEOBJDIRPREFIX and MAKEOBJDIR may only be set in the environment or on the command line to bbmmaakkee and not as makefile variables; see the descrip- tion of `_._O_B_J_D_I_R' for more details. FFIILLEESS .depend list of dependencies Makefile list of dependencies makefile list of dependencies sys.mk system makefile /usr/share/mk system makefile directory CCOOMMPPAATTIIBBIILLIITTYY - The basic make syntax is compatible between different versions of make, + The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. - The way that parallel makes are scheduled changed in NetBSD 4.0 so that - .ORDER and .WAIT apply recursively to the dependent nodes. The algo- - rithms used may change again in the future. + OOllddeerr vveerrssiioonnss + An incomplete list of changes in older versions of bbmmaakkee: The way that .for loop variables are substituted changed after NetBSD 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. + The way that parallel makes are scheduled changed in NetBSD 4.0 so that + .ORDER and .WAIT apply recursively to the dependent nodes. The algo- + rithms used may change again in the future. + + OOtthheerr mmaakkee ddiiaalleeccttss + Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not sup- + port most of the features of bbmmaakkee as described in this manual. Most + notably: + + ++oo The ..WWAAIITT and ..OORRDDEERR declarations and most functionality per- + taining to parallelization. (GNU make supports parallelization + but lacks these features needed to control it effectively.) + + ++oo Directives, including for loops and conditionals and most of + the forms of include files. (GNU make has its own incompatible + and less powerful syntax for conditionals.) + + ++oo All built-in variables that begin with a dot. + + ++oo Most of the special sources and targets that begin with a dot, + with the notable exception of ..PPHHOONNYY, ..PPRREECCIIOOUUSS, and ..SSUUFFFFIIXXEESS. + + ++oo Variable modifiers, except for the + :old=new + string substitution, which does not portably support globbing + with `%' and historically only works on declared suffixes. + + ++oo The $$>> variable even in its short form; most makes support this + functionality but its name varies. + + Some features are somewhat more portable, such as assignment with ++==, ??==, + and !!==. The ..PPAATTHH functionality is based on an older feature VVPPAATTHH found + in GNU make and many versions of SVR4 make; however, historically its + behavior is too ill-defined (and too buggy) to rely upon. + + The $$@@ and $$<< variables are more or less universally portable, as is the + $$((MMAAKKEE)) variable. Basic use of suffix rules (for files only in the cur- + rent directory, not trying to chain transformations together, etc.) is + also reasonably portable. + SSEEEE AALLSSOO mkdep(1) HHIISSTTOORRYY bbmmaakkee is derived from NetBSD make(1). It uses autoconf to facilitate portability to other platforms. A make command appeared in Version 7 AT&T UNIX. This make 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 ``customs''. Historically the target/dependency ``FRC'' has been used to FoRCe rebuilding (since the target/dependency does not exist... unless someone creates an ``FRC'' file). BBUUGGSS The make 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 make just counts {} and () in order to find the end of a variable expansion. There is no way of escaping a space character in a filename. -NetBSD 5.1 February 14, 2014 NetBSD 5.1 +NetBSD 5.1 April 9, 2015 NetBSD 5.1 Index: head/contrib/bmake/compat.c =================================================================== --- head/contrib/bmake/compat.c (revision 281811) +++ head/contrib/bmake/compat.c (revision 281812) @@ -1,767 +1,767 @@ -/* $NetBSD: compat.c,v 1.94 2014/01/03 00:02:01 sjg Exp $ */ +/* $NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: compat.c,v 1.94 2014/01/03 00:02:01 sjg Exp $"; +static char rcsid[] = "$NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: compat.c,v 1.94 2014/01/03 00:02:01 sjg Exp $"); +__RCSID("$NetBSD: compat.c,v 1.96 2014/09/07 20:55:34 joerg Exp $"); #endif #endif /* not lint */ #endif /*- * compat.c -- * The routines in this file implement the full-compatibility * mode of PMake. Most of the special functionality of PMake * is available in this mode. Things not supported: * - different shells. * - friendly variable substitution. * * Interface: * Compat_Run Initialize things for this module and recreate * thems as need creatin' */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "wait.h" #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" /* * The following array is used to make a fast determination of which * characters are interpreted specially by the shell. If a command * contains any of these characters, it is executed by the shell, not * directly by us. */ static char meta[256]; static GNode *curTarg = NULL; static GNode *ENDNode; static void CompatInterrupt(int); static void Compat_Init(void) { const char *cp; Shell_Init(); /* setup default shell */ for (cp = "~#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) { meta[(unsigned char) *cp] = 1; } /* * The null character serves as a sentinel in the string. */ meta[0] = 1; } /*- *----------------------------------------------------------------------- * CompatInterrupt -- * Interrupt the creation of the current target and remove it if * it ain't precious. * * Results: * None. * * Side Effects: * The target is removed and the process exits. If .INTERRUPT exists, * its commands are run first WITH INTERRUPTS IGNORED.. * *----------------------------------------------------------------------- */ static void CompatInterrupt(int signo) { GNode *gn; if ((curTarg != NULL) && !Targ_Precious (curTarg)) { char *p1; char *file = Var_Value(TARGET, curTarg, &p1); if (!noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } if (p1) free(p1); /* * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); if (gn != NULL) { Compat_Make(gn, gn); } } } if (signo == SIGQUIT) _exit(signo); bmake_signal(signo, SIG_DFL); kill(myPid, signo); } /*- *----------------------------------------------------------------------- * CompatRunCommand -- * Execute the next command for a target. If the command returns an * error, the node's made field is set to ERROR and creation stops. * * Input: * cmdp Command to execute * gnp Node from which the command came * * Results: * 0 if the command succeeded, 1 if an error occurred. * * Side Effects: * The node's 'made' field may be set to ERROR. * *----------------------------------------------------------------------- */ int CompatRunCommand(void *cmdp, void *gnp) { char *cmdStart; /* Start of expanded command */ char *cp, *bp; Boolean silent, /* Don't print command */ doIt; /* Execute even if -n */ volatile Boolean errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ int status; /* Description of child's death */ pid_t cpid; /* Child actually found */ pid_t retstat; /* Result of wait */ LstNode cmdNode; /* Node where current command is located */ const char ** volatile av; /* Argument vector for thing to exec */ char ** volatile mav;/* Copy of the argument vector for freeing */ int argc; /* Number of arguments in av or 0 if not * dynamically allocated */ Boolean local; /* TRUE if command should be executed * locally */ Boolean useShell; /* TRUE if command should be executed * using a shell */ char * volatile cmd = (char *)cmdp; GNode *gn = (GNode *)gnp; silent = gn->type & OP_SILENT; errCheck = !(gn->type & OP_IGNORE); doIt = FALSE; cmdNode = Lst_Member(gn->commands, cmd); cmdStart = Var_Subst(NULL, cmd, gn, FALSE); /* * brk_string will return an argv with a NULL in av[0], thus causing * execvp to choke and die horribly. Besides, how can we execute a null * command? In any case, we warn the user that the command expanded to * nothing (is this the right thing to do?). */ if (*cmdStart == '\0') { free(cmdStart); return(0); } cmd = cmdStart; Lst_Replace(cmdNode, cmdStart); if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { (void)Lst_AtEnd(ENDNode->commands, cmdStart); return(0); } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; return(0); } while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) { switch (*cmd) { case '@': silent = DEBUG(LOUD) ? FALSE : TRUE; break; case '-': errCheck = FALSE; break; case '+': doIt = TRUE; if (!meta[0]) /* we came here from jobs */ Compat_Init(); break; } cmd++; } while (isspace((unsigned char)*cmd)) cmd++; /* * If we did not end up with a command, just skip it. */ if (!*cmd) return (0); #if !defined(MAKE_NATIVE) /* * In a non-native build, the host environment might be weird enough * that it's necessary to go through a shell to get the correct * behaviour. Or perhaps the shell has been replaced with something * that does extra logging, and that should not be bypassed. */ useShell = TRUE; #else /* * Search for meta characters in the command. If there are no meta * characters, there's no need to execute a shell to execute the * command. */ for (cp = cmd; !meta[(unsigned char)*cp]; cp++) { continue; } useShell = (*cp != '\0'); #endif /* * Print the command before echoing if we're not supposed to be quiet for * this one. We also print the command if -n given. */ if (!silent || NoExecute(gn)) { printf("%s\n", cmd); fflush(stdout); } /* * If we're not supposed to execute any commands, this is as far as * we go... */ if (!doIt && NoExecute(gn)) { return (0); } if (DEBUG(JOB)) fprintf(debug_file, "Execute: '%s'\n", cmd); again: if (useShell) { /* * We need to pass the command off to the shell, typically * because the command contains a "meta" character. */ static const char *shargv[5]; int shargc; shargc = 0; shargv[shargc++] = shellPath; /* * The following work for any of the builtin shell specs. */ if (errCheck && shellErrFlag) { shargv[shargc++] = shellErrFlag; } if (DEBUG(SHELL)) shargv[shargc++] = "-xc"; else shargv[shargc++] = "-c"; shargv[shargc++] = cmd; shargv[shargc++] = NULL; av = shargv; argc = 0; bp = NULL; mav = NULL; } else { /* * No meta-characters, so no need to exec a shell. Break the command * into words to form an argument vector we can execute. */ mav = brk_string(cmd, &argc, TRUE, &bp); if (mav == NULL) { useShell = 1; goto again; } av = (void *)mav; } local = TRUE; #ifdef USE_META if (useMeta) { meta_compat_start(); } #endif /* * Fork and execute the single command. If the fork fails, we abort. */ cpid = vFork(); if (cpid < 0) { Fatal("Could not fork"); } if (cpid == 0) { Var_ExportVars(); #ifdef USE_META if (useMeta) { meta_compat_child(); } #endif if (local) (void)execvp(av[0], (char *const *)UNCONST(av)); else (void)execv(av[0], (char *const *)UNCONST(av)); execError("exec", av[0]); _exit(1); } if (mav) free(mav); if (bp) free(bp); Lst_Replace(cmdNode, NULL); #ifdef USE_META if (useMeta) { meta_compat_parent(); } #endif /* * The child is off and running. Now all we can do is wait... */ while (1) { while ((retstat = wait(&reason)) != cpid) { if (retstat > 0) JobReapChild(retstat, reason, FALSE); /* not ours? */ if (retstat == -1 && errno != EINTR) { break; } } if (retstat > -1) { if (WIFSTOPPED(reason)) { status = WSTOPSIG(reason); /* stopped */ } else if (WIFEXITED(reason)) { status = WEXITSTATUS(reason); /* exited */ #if defined(USE_META) && defined(USE_FILEMON_ONCE) if (useMeta) { meta_cmd_finish(NULL); } #endif if (status != 0) { if (DEBUG(ERROR)) { fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ", gn->name); for (cp = cmd; *cp; ) { if (isspace((unsigned char)*cp)) { fprintf(debug_file, " "); while (isspace((unsigned char)*cp)) cp++; } else { fprintf(debug_file, "%c", *cp); cp++; } } fprintf(debug_file, "\n"); } printf("*** Error code %d", status); } } else { status = WTERMSIG(reason); /* signaled */ printf("*** Signal %d", status); } if (!WIFEXITED(reason) || (status != 0)) { if (errCheck) { #ifdef USE_META if (useMeta) { meta_job_error(NULL, gn, 0, status); } #endif gn->made = ERROR; if (keepgoing) { /* * Abort the current target, but let others * continue. */ printf(" (continuing)\n"); } } else { /* * Continue executing commands for this target. * If we return 0, this will happen... */ printf(" (ignored)\n"); status = 0; } } break; } else { Fatal("error in wait: %d: %s", retstat, strerror(errno)); /*NOTREACHED*/ } } free(cmdStart); return (status); } /*- *----------------------------------------------------------------------- * Compat_Make -- * Make a target. * * Input: * gnp The node to make * pgnp Parent to abort if necessary * * Results: * 0 * * Side Effects: * If an error is detected and not being ignored, the process exits. * *----------------------------------------------------------------------- */ int Compat_Make(void *gnp, void *pgnp) { GNode *gn = (GNode *)gnp; GNode *pgn = (GNode *)pgnp; if (!meta[0]) /* we came here from jobs */ Compat_Init(); if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) { /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can * descend and make all our children. If any of them has an error * but the -k flag was given, our 'make' field will be set FALSE again. * This is our signal to not attempt to do anything but abort our * parent as well. */ gn->flags |= REMAKE; gn->made = BEINGMADE; if ((gn->type & OP_MADE) == 0) Suff_FindDeps(gn); Lst_ForEach(gn->children, Compat_Make, gn); if ((gn->flags & REMAKE) == 0) { gn->made = ABORTED; pgn->flags &= ~REMAKE; goto cohorts; } if (Lst_Member(gn->iParents, pgn) != NULL) { char *p1; Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); if (p1) free(p1); } /* * All the children were made ok. Now cmgn->mtime contains the * modification time of the newest child, we need to find out if we * exist and when we were modified last. The criteria for datedness * are defined by the Make_OODate function. */ if (DEBUG(MAKE)) { fprintf(debug_file, "Examining %s...", gn->name); } if (! Make_OODate(gn)) { gn->made = UPTODATE; if (DEBUG(MAKE)) { fprintf(debug_file, "up-to-date.\n"); } goto cohorts; } else if (DEBUG(MAKE)) { fprintf(debug_file, "out-of-date.\n"); } /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ if (queryFlag) { exit(1); } /* * We need to be re-made. We also have to make sure we've got a $? * variable. To be nice, we also define the $> variable using * Make_DoAllVar(). */ Make_DoAllVar(gn); /* * Alter our type to tell if errors should be ignored or things * should not be printed so CompatRunCommand knows what to do. */ if (Targ_Ignore(gn)) { gn->type |= OP_IGNORE; } if (Targ_Silent(gn)) { gn->type |= OP_SILENT; } if (Job_CheckCommands(gn, Fatal)) { /* * Our commands are ok, but we still have to worry about the -t * flag... */ if (!touchFlag || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META if (useMeta && !NoExecute(gn)) { meta_job_start(NULL, gn); } #endif Lst_ForEach(gn->commands, CompatRunCommand, gn); curTarg = NULL; } else { Job_Touch(gn, gn->type & OP_SILENT); } } else { gn->made = ERROR; } #ifdef USE_META if (useMeta && !NoExecute(gn)) { meta_job_finish(NULL); } #endif if (gn->made != ERROR) { /* * If the node was made successfully, mark it so, update * its modification time and timestamp all its parents. Note * that for .ZEROTIME targets, the timestamping isn't done. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; Make_TimeStamp(pgn, gn); } } else if (keepgoing) { pgn->flags &= ~REMAKE; } else { PrintOnError(gn, "\n\nStop."); exit(1); } } else if (gn->made == ERROR) { /* * Already had an error when making this beastie. Tell the parent * to abort. */ pgn->flags &= ~REMAKE; } else { if (Lst_Member(gn->iParents, pgn) != NULL) { char *p1; Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); if (p1) free(p1); } switch(gn->made) { case BEINGMADE: Error("Graph cycles through %s", gn->name); gn->made = ERROR; pgn->flags &= ~REMAKE; break; case MADE: if ((gn->type & OP_EXEC) == 0) { pgn->flags |= CHILDMADE; Make_TimeStamp(pgn, gn); } break; case UPTODATE: if ((gn->type & OP_EXEC) == 0) { Make_TimeStamp(pgn, gn); } break; default: break; } } cohorts: Lst_ForEach(gn->cohorts, Compat_Make, pgnp); return (0); } /*- *----------------------------------------------------------------------- * Compat_Run -- * Initialize this mode and start making. * * Input: * targs List of target nodes to re-create * * Results: * None. * * Side Effects: * Guess what? * *----------------------------------------------------------------------- */ void Compat_Run(Lst targs) { GNode *gn = NULL;/* Current root target */ int errors; /* Number of targets not remade due to errors */ Compat_Init(); if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) { bmake_signal(SIGINT, CompatInterrupt); } if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) { bmake_signal(SIGTERM, CompatInterrupt); } if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) { bmake_signal(SIGHUP, CompatInterrupt); } if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) { bmake_signal(SIGQUIT, CompatInterrupt); } ENDNode = Targ_FindNode(".END", TARG_CREATE); ENDNode->type = OP_SPECIAL; /* * If the user has defined a .BEGIN target, execute the commands attached * to it. */ if (!queryFlag) { gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); if (gn != NULL) { Compat_Make(gn, gn); if (gn->made == ERROR) { PrintOnError(gn, "\n\nStop."); exit(1); } } } /* * Expand .USE nodes right now, because they can modify the structure * of the tree. */ Make_ExpandUse(targs); /* * For each entry in the list of targets to create, call Compat_Make on * it to create the thing. Compat_Make will leave the 'made' field of gn * in one of several states: * UPTODATE gn was already up-to-date * MADE gn was recreated successfully * ERROR An error occurred while gn was being created * ABORTED gn was not remade because one of its inferiors * could not be made due to errors. */ errors = 0; while (!Lst_IsEmpty (targs)) { gn = (GNode *)Lst_DeQueue(targs); Compat_Make(gn, gn); if (gn->made == UPTODATE) { printf("`%s' is up to date.\n", gn->name); } else if (gn->made == ABORTED) { printf("`%s' not remade because of errors.\n", gn->name); errors += 1; } } /* * If the user has defined a .END target, run its commands. */ if (errors == 0) { Compat_Make(ENDNode, ENDNode); if (gn->made == ERROR) { PrintOnError(gn, "\n\nStop."); exit(1); } } } Index: head/contrib/bmake/configure =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/x-shellscript Index: head/contrib/bmake/configure.in =================================================================== --- head/contrib/bmake/configure.in (revision 281811) +++ head/contrib/bmake/configure.in (revision 281812) @@ -1,386 +1,388 @@ dnl dnl RCSid: -dnl $Id: configure.in,v 1.52 2014/02/15 22:27:59 sjg Exp $ +dnl $Id: configure.in,v 1.53 2014/11/06 01:49:40 sjg Exp $ dnl dnl Process this file with autoconf to produce a configure script dnl AC_PREREQ(2.50) AC_INIT([bmake], [20140214], [sjg@NetBSD.org]) AC_CONFIG_HEADERS(config.h) dnl make srcdir absolute case "$srcdir" in /*) ;; *) srcdir=`cd $srcdir && pwd`;; esac dnl AC_ARG_WITH(defshell, [ --with-defshell=SHELL use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions], [case "${withval}" in yes) AC_MSG_ERROR(bad value ${withval} given for bmake DEFSHELL) ;; no) ;; *) case "$with_defshell" in sh) DEFSHELL_INDEX=DEFSHELL_INDEX_SH;; # it's the default anyway ksh) DEFSHELL_INDEX=DEFSHELL_INDEX_KSH;; csh) DEFSHELL_INDEX=DEFSHELL_INDEX_CSH;; # kidding right? *) defshell_path=$with_defshell;; # better be sh compatible! esac ;; esac]) dnl use_meta=yes AC_ARG_WITH(meta, [ --without-meta dissable use of meta-mode], [case "${withval}" in yes|no) use_meta=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for meta) ;; esac]) dnl AC_ARG_WITH(filemon, [ --with-filemon=path/filemon.h indicate path to filemon.h for meta-mode], [ case "/${withval}" in /no|*/filemon.h) filemon_h="${withval}";; */filemon*) filemon_h="${withval}/filemon.h";; *) AC_MSG_ERROR(bad value ${withval} given for filemon) ;; esac], [ OS=`uname -s` for d in "/usr/include/dev/filemon" "$prefix/include/dev/filemon" "$srcdir/filemon" "$srcdir/../filemon" "$srcdir/../../sys/dev/filemon" do for x in "/$OS" "" do filemon_h="$d$x/filemon.h" test -s "$filemon_h" && break done test -s "$filemon_h" && break done test -s "${filemon_h:-/dev/null}" || filemon_h=no ]) dnl echo "Note: use_meta=$use_meta filemon_h=$filemon_h" >&6 case "$use_meta" in yes) case "$filemon_h" in *.h) echo "Using: filemon=$filemon_h" >&6;; esac ;; esac dnl dnl Check for OS problems dnl Solaris's signal.h only privides sigset_t etc if one of dnl _EXTENSIONS_ _POSIX_C_SOURCE or _XOPEN_SOURCE are defined. dnl The later two seem to cause more problems than they solve so if we dnl see _EXTENSIONS_ we use it. AC_USE_SYSTEM_EXTENSIONS dnl Checks for programs. AC_PROG_CC AC_PROG_GCC_TRADITIONAL AC_PROG_INSTALL dnl Executable suffix - normally empty; .exe on os2. AC_SUBST(ac_exe_suffix)dnl dnl dnl Hurd refuses to define PATH_MAX or MAXPATHLEN if test -x /usr/bin/getconf; then bmake_path_max=`getconf PATH_MAX / 2> /dev/null` + # only a numeric response is useful + test ${bmake_path_max:-0} -gt 0 2> /dev/null || bmake_path_max= fi bmake_path_max=${bmake_path_max:-1024} if test $bmake_path_max -gt 1024; then # this is all we expect bmake_path_max=1024 fi echo "Using: BMAKE_PATH_MAX=$bmake_path_max" >&6 AC_SUBST(bmake_path_max)dnl dnl dnl AC_C_CROSS dnl dnl Checks for header files. AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_HEADER_DIRENT dnl Keep this list sorted AC_CHECK_HEADERS( \ ar.h \ err.h \ fcntl.h \ paths.h \ poll.h \ ranlib.h \ string.h \ sys/mman.h \ sys/select.h \ sys/socket.h \ sys/sysctl.h \ sys/time.h \ sys/uio.h \ unistd.h \ utime.h \ ) dnl Both *BSD and Linux have sys/cdefs.h, most do not. dnl If it is missing, we add -I${srcdir}/missing to CFLAGS dnl also if sys/cdefs.h does not have __RCSID we need to use ours dnl but we need to include the host's one too *sigh* AC_CHECK_HEADER(sys/cdefs.h, echo $ECHO_N "checking whether sys/cdefs.h is compatible... $ECHO_C" >&6 AC_EGREP_CPP(yes, [#include #ifdef __RCSID yes #endif ], echo yes >&6, echo no >&6; CPPFLAGS="${CPPFLAGS} -I`cd ${srcdir}/missing && pwd` -DNEED_HOST_CDEFS_H"), CPPFLAGS="${CPPFLAGS} -I`cd ${srcdir}/missing && pwd`") dnl Checks for typedefs, structures, and compiler characteristics. AC_C___ATTRIBUTE__ AC_C_BIGENDIAN AC_C_CONST AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_DECL_SYS_SIGLIST AC_HEADER_TIME AC_STRUCT_TM dnl Checks for library functions. AC_TYPE_SIGNAL AC_FUNC_VFORK AC_FUNC_VPRINTF AC_FUNC_WAIT3 dnl Keep this list sorted AC_CHECK_FUNCS( \ err \ errx \ getcwd \ getenv \ getopt \ getwd \ killpg \ mmap \ putenv \ select \ setenv \ setpgid \ setsid \ sigaction \ sigvec \ snprintf \ strerror \ strftime \ strsep \ strtod \ strtol \ sysctl \ unsetenv \ vsnprintf \ wait3 \ wait4 \ waitpid \ warn \ warnx \ ) dnl functions which we may need to provide AC_REPLACE_FUNCS( \ realpath \ dirname \ stresep \ strlcpy \ ) AC_CHECK_LIB([util], [emalloc], [ AC_CHECK_LIB([util], [erealloc], [ AC_CHECK_LIB([util], [estrdup], [ AC_CHECK_LIB([util], [estrndup], [ LIBS="$LIBS -lutil" CPPFLAGS="$CPPFLAGS -DUSE_EMALLOC" ])])])]) dnl dnl Structures dnl AC_HEADER_STAT AC_STRUCT_ST_RDEV dnl echo "checking if compiler supports __func__" >&6 AC_LANG(C) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[[const char *func = __func__;]])],, AC_DEFINE(__func__, __FUNCTION__, C99 function name)) dnl dnl we want this for unit-tests/Makefile echo $ECHO_N "checking if diff -u works... $ECHO_C" >&6 if diff -u /dev/null /dev/null > /dev/null 2>&1; then diff_u=-u echo yes >&6 else diff_u= echo no >&6 fi dnl dnl AC_* don't quite cut it. dnl echo "checking for MACHINE & MACHINE_ARCH..." >&6 cat > conftest.$ac_ext < #ifdef MACHINE machine=MACHINE #endif #ifdef MACHINE_ARCH machine_arch=MACHINE_ARCH #endif EOF default_machine=`(eval "$ac_cpp conftest.$ac_ext") 2>&5 | egrep machine= | tr -d ' "'` rm -rf conftest* if test "$default_machine"; then eval "$default_machine" fi machine=${machine:-`$srcdir/machine.sh`} machine_arch=${machine_arch:-`$srcdir/machine.sh arch`} echo "defaults: MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 dnl dnl now allow overrides dnl AC_ARG_WITH(machine, [ --with-machine=MACHINE explicitly set MACHINE], [case "${withval}" in yes) AC_MSG_ERROR(bad value ${withval} given for bmake MACHINE) ;; no) ;; generic) machine=`$srcdir/machine.sh`;; *) machine=$with_machine;; esac]) force_machine= AC_ARG_WITH(force_machine, [ --with-force-machine=MACHINE set FORCE_MACHINE], [case "${withval}" in yes) force_machine=FORCE_;; no) ;; *) force_machine=FORCE_; machine=$with_force_machine;; esac]) dnl force_machine_arch= AC_ARG_WITH(force_machine_arch, [ --with-force-machine-arch=MACHINE set FORCE_MACHINE_ARCH], [case "${withval}" in yes) force_machine_arch=FORCE_;; no) ;; *) force_machine_arch=FORCE_; machine_arch=$with_force_machine;; esac]) dnl AC_ARG_WITH(machine_arch, [ --with-machine_arch=MACHINE_ARCH explicitly set MACHINE_ARCH], [case "${withval}" in yes) AC_MSG_ERROR(bad value ${withval} given for bmake MACHINE_ARCH) ;; no) ;; *) machine_arch=$with_machine_arch;; esac]) dnl dnl Tell them what we ended up with dnl echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 dnl dnl Allow folk to control _PATH_DEFSYSPATH dnl default_sys_path=\${prefix}/share/mk AC_ARG_WITH(default-sys-path, [ --with-default-sys-path=PATH:DIR:LIST use an explicit _PATH_DEFSYSPATH MAKESYSPATH is a ':' separated list of directories that bmake will search for system .mk files. _PATH_DEFSYSPATH is its default value.], [case "${withval}" in yes) AC_MSG_ERROR(bad value ${withval} given for bmake _PATH_DEFSYSPATH) ;; no) ;; *) default_sys_path="$with_default_sys_path" ;; esac]) dnl dnl Some folk don't like this one dnl AC_ARG_WITH(path-objdirprefix, [ --with-path-objdirprefix=PATH override _PATH_OBJDIRPREFIX], [case "${withval}" in yes) AC_MSG_ERROR(bad value ${withval} given for bmake _PATH_OBJDIRPREFIX) ;; no) CPPFLAGS="$CPPFLAGS -DNO_PATH_OBJDIRPREFIX" ;; *) CPPFLAGS="$CPPFLAGS \"-D_PATH_OBJDIRPREFIX=\\\"$with_path-objdir\\\"\"" ;; esac]) dnl dnl And this can be handy to do with out. dnl AC_ARG_ENABLE(pwd-override, [ --disable-pwd-override disable \$PWD overriding getcwd()], [case "${enableval}" in yes) ;; no) CPPFLAGS="$CPPFLAGS -DNO_PWD_OVERRIDE" ;; *) AC_MSG_ERROR(bad value ${enableval} given for pwd-override option) ;; esac]) dnl dnl Just for grins dnl AC_ARG_ENABLE(check-make-chdir, [ --disable-check-make-chdir disable make trying to guess when it should automatically cd \${.CURDIR}], [case "${enableval}" in yes) ;; no) CPPFLAGS="$CPPFLAGS -DNO_CHECK_MAKE_CHDIR" ;; *) AC_MSG_ERROR(bad value ${enableval} given for check-make-chdir option) ;; esac]) dnl dnl On non-BSD systems, bootstrap won't work without mk dnl AC_ARG_WITH(mksrc, [ --with-mksrc=PATH tell makefile.boot where to find mk src], [case "${withval}" in ""|yes|no) ;; *) test -s $withval/install-mk && mksrc=$withval || AC_MSG_ERROR(bad value ${withval} given for mksrc cannot find install-mk) ;; esac ]) dnl dnl Now make sure we have a value dnl srcdir=`cd $srcdir && pwd` for mksrc in $mksrc $srcdir/mk $srcdir/../mk mk do test -s $mksrc/install-mk || continue mksrc=`cd $mksrc && pwd` break done mksrc=`echo $mksrc | sed "s,$srcdir,\\\${srcdir},"` echo "Using: MKSRC=$mksrc" 1>&6 dnl On some systems we want a different default shell by default if test -x /usr/xpg4/bin/sh; then defshell_path=${defshell_path:-/usr/xpg4/bin/sh} fi if test -n "$defshell_path"; then echo "Using: SHELL=$defshell_path" >&6 AC_DEFINE_UNQUOTED(DEFSHELL_CUSTOM, "$defshell_path", Path of default shell) fi if test -n "$DEFSHELL_INDEX"; then AC_DEFINE_UNQUOTED(DEFSHELL_INDEX, $DEFSHELL_INDEX, Shell spec to use by default) fi dnl AC_SUBST(machine) AC_SUBST(force_machine) AC_SUBST(machine_arch) AC_SUBST(mksrc) AC_SUBST(default_sys_path) AC_SUBST(INSTALL) AC_SUBST(GCC) AC_SUBST(diff_u) AC_SUBST(use_meta) AC_SUBST(filemon_h) AC_OUTPUT(makefile Makefile.config make-bootstrap.sh unit-tests/Makefile) cat < #ifndef lint #if 0 static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: job.c,v 1.176 2013/08/04 16:48:15 sjg Exp $"); +__RCSID("$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg 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 /* * FreeBSD: traditionally .MAKE is not required to * pass jobs queue to sub-makes. * Use .MAKE.ALWAYS_PASS_JOB_QUEUE=no to disable. */ #define MAKE_ALWAYS_PASS_JOB_QUEUE ".MAKE.ALWAYS_PASS_JOB_QUEUE" static int Always_pass_job_queue = TRUE; /* * FreeBSD: aborting entire parallel make isn't always * desired. When doing tinderbox for example, failure of * one architecture should not stop all. * We still want to bail on interrupt though. */ #define MAKE_JOB_ERROR_TOKEN "MAKE_JOB_ERROR_TOKEN" static int Job_error_token = TRUE; /* * error handling variables */ static int errors = 0; /* number of errors reported */ static int aborting = 0; /* why is the make aborting? */ #define ABORT_ERROR 1 /* Because of an error */ #define ABORT_INTERRUPT 2 /* Because it was interrupted */ #define ABORT_WAIT 3 /* Waiting for jobs to finish */ #define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ /* * this tracks the number of tokens currently "out" to build jobs. */ int jobTokensRunning = 0; int not_parallel = 0; /* set if .NOT_PARALLEL */ /* * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file * is a char! So when we go above 127 we turn negative! */ #define FILENO(a) ((unsigned) fileno(a)) /* * post-make command processing. The node postCommands is really just the * .END target but we keep it around to avoid having to search for it * all the time. */ static GNode *postCommands = NULL; /* node containing commands to execute when * everything else is done */ static int numCommands; /* The number of commands actually printed * for a target. Should this number be * 0, no shell will be executed. */ /* * Return values from JobStart. */ #define JOB_RUNNING 0 /* Job is running */ #define JOB_ERROR 1 /* Error in starting the job */ #define JOB_FINISHED 2 /* The job is already finished */ /* * Descriptions for various shells. * * The build environment may set DEFSHELL_INDEX to one of * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to * select one of the prefedined shells as the default shell. * * Alternatively, the build environment may set DEFSHELL_CUSTOM to the * name or the full path of a sh-compatible shell, which will be used as * the default shell. * * ".SHELL" lines in Makefiles can choose the default shell from the # set defined here, or add additional shells. */ #ifdef DEFSHELL_CUSTOM #define DEFSHELL_INDEX_CUSTOM 0 #define DEFSHELL_INDEX_SH 1 #define DEFSHELL_INDEX_KSH 2 #define DEFSHELL_INDEX_CSH 3 #else /* !DEFSHELL_CUSTOM */ #define DEFSHELL_INDEX_SH 0 #define DEFSHELL_INDEX_KSH 1 #define DEFSHELL_INDEX_CSH 2 #endif /* !DEFSHELL_CUSTOM */ #ifndef DEFSHELL_INDEX #define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ #endif /* !DEFSHELL_INDEX */ static Shell shells[] = { #ifdef DEFSHELL_CUSTOM /* * An sh-compatible shell with a non-standard name. * * Keep this in sync with the "sh" description below, but avoid * non-portable features that might not be supplied by all * sh-compatible shells. */ { DEFSHELL_CUSTOM, FALSE, "", "", "", 0, FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', "", "", }, #endif /* DEFSHELL_CUSTOM */ /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ { "sh", FALSE, "", "", "", 0, FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', #if defined(MAKE_NATIVE) && defined(__NetBSD__) "q", #else "", #endif "", }, /* * KSH description. */ { "ksh", TRUE, "set +v", "set -v", "set +v", 6, FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', "v", "", }, /* * CSH description. The csh can do echo control by playing * with the setting of the 'echo' shell variable. Sadly, * however, it is unable to do error control nicely. */ { "csh", TRUE, "unset verbose", "set verbose", "unset verbose", 10, FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#', "v", "e", }, /* * UNKNOWN. */ { NULL, FALSE, NULL, NULL, NULL, 0, FALSE, NULL, NULL, NULL, NULL, 0, NULL, NULL, } }; static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to * which we pass all * commands in the Makefile. * It is set by the * Job_ParseShell function */ const char *shellPath = NULL, /* full pathname of * executable image */ *shellName = NULL; /* last component of shell */ char *shellErrFlag = NULL; static const char *shellArgv = NULL; /* Custom shell args */ STATIC Job *job_table; /* The structures that describe them */ STATIC Job *job_table_end; /* job_table + maxJobs */ static int wantToken; /* we want a token */ static int lurking_children = 0; static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to * the output channels of children */ static struct pollfd *fds = NULL; static Job **jobfds = NULL; static int nfds = 0; static void watchfd(Job *); static void clearfd(Job *); static int readyfd(Job *); STATIC GNode *lastNode; /* The node for which output was most recently * produced. */ static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */ static Job tokenWaitJob; /* token wait pseudo-job */ static Job childExitJob; /* child exit pseudo-job */ #define CHILD_EXIT "." #define DO_JOB_RESUME "R" #define TARG_FMT "%s %s ---\n" /* Default format */ #define MESSAGE(fp, gn) \ if (maxJobs != 1 && targPrefix && *targPrefix) \ (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) static sigset_t caught_signals; /* Set of signals we handle */ #if defined(SYSV) #define KILLPG(pid, sig) kill(-(pid), (sig)) #else #define KILLPG(pid, sig) killpg((pid), (sig)) #endif static void JobChildSig(int); static void JobContinueSig(int); static Job *JobFindPid(int, int, Boolean); static int JobPrintCommand(void *, void *); static int JobSaveCommand(void *, void *); static void JobClose(Job *); static void JobExec(Job *, char **); static void JobMakeArgv(Job *, char **); static int JobStart(GNode *, int); static char *JobOutput(Job *, char *, char *, int); static void JobDoOutput(Job *, Boolean); static Shell *JobMatchShell(const char *); static void JobInterrupt(int, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobTokenAdd(void); static void JobSigLock(sigset_t *); static void JobSigUnlock(sigset_t *); static void JobSigReset(void); const char *malloc_options="A"; static void job_table_dump(const char *where) { Job *job; fprintf(debug_file, "job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n", (int)(job - job_table), job->job_state, job->flags, job->pid); } } /* * JobSigLock/JobSigUnlock * * Signal lock routines to get exclusive access. Currently used to * protect `jobs' and `stoppedJobs' list manipulations. */ static void JobSigLock(sigset_t *omaskp) { if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) { Punt("JobSigLock: sigprocmask: %s", strerror(errno)); sigemptyset(omaskp); } } static void JobSigUnlock(sigset_t *omaskp) { (void)sigprocmask(SIG_SETMASK, omaskp, NULL); } static void JobCreatePipe(Job *job, int minfd) { int i, fd; if (pipe(job->jobPipe) == -1) Punt("Cannot create pipe: %s", strerror(errno)); for (i = 0; i < 2; i++) { /* Avoid using low numbered fds */ fd = fcntl(job->jobPipe[i], F_DUPFD, minfd); if (fd != -1) { close(job->jobPipe[i]); job->jobPipe[i] = fd; } } /* Set close-on-exec flag for both */ (void)fcntl(job->jobPipe[0], F_SETFD, 1); (void)fcntl(job->jobPipe[1], F_SETFD, 1); /* * We mark the input side of the pipe non-blocking; we poll(2) the * pipe when we're waiting for a job token, but we might lose the * race for the token when a new one becomes available, so the read * from the pipe should not block. */ fcntl(job->jobPipe[0], F_SETFL, fcntl(job->jobPipe[0], F_GETFL, 0) | O_NONBLOCK); } /*- *----------------------------------------------------------------------- * JobCondPassSig -- * Pass a signal to a job * * Input: * signop Signal to send it * * Side Effects: * None, except the job may bite it. * *----------------------------------------------------------------------- */ static void JobCondPassSig(int signo) { Job *job; if (DEBUG(JOB)) { (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo); } for (job = job_table; job < job_table_end; job++) { if (job->job_state != JOB_ST_RUNNING) continue; if (DEBUG(JOB)) { (void)fprintf(debug_file, "JobCondPassSig passing signal %d to child %d.\n", signo, job->pid); } KILLPG(job->pid, signo); } } /*- *----------------------------------------------------------------------- * JobChldSig -- * SIGCHLD handler. * * Input: * signo The signal number we've received * * Results: * None. * * Side Effects: * Sends a token on the child exit pipe to wake us up from * select()/poll(). * *----------------------------------------------------------------------- */ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) continue; } /*- *----------------------------------------------------------------------- * JobContinueSig -- * Resume all stopped jobs. * * Input: * signo The signal number we've received * * Results: * None. * * Side Effects: * Jobs start running again. * *----------------------------------------------------------------------- */ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { /* * Defer sending to SIGCONT to our stopped children until we return * from the signal handler. */ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && errno == EAGAIN) continue; } /*- *----------------------------------------------------------------------- * JobPassSig -- * Pass a signal on to all jobs, then resend to ourselves. * * Input: * signo The signal number we've received * * Results: * None. * * Side Effects: * We die by the same signal. * *----------------------------------------------------------------------- */ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { /* Run .INTERRUPT target then exit */ JobInterrupt(TRUE, signo); } MAKE_ATTR_DEAD static void JobPassSig_term(int signo) { /* Dont run .INTERRUPT target then exit */ JobInterrupt(FALSE, signo); } static void JobPassSig_suspend(int signo) { sigset_t nmask, omask; struct sigaction act; /* Suppress job started/continued messages */ make_suspended = 1; /* Pass the signal onto every job */ JobCondPassSig(signo); /* * Send ourselves the signal now we've given the message to everyone else. * Note we block everything else possible while we're getting the signal. * This ensures that all our jobs get continued when we wake up before * we take any other signal. */ sigfillset(&nmask); sigdelset(&nmask, signo); (void)sigprocmask(SIG_SETMASK, &nmask, &omask); act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; (void)sigaction(signo, &act, NULL); if (DEBUG(JOB)) { (void)fprintf(debug_file, "JobPassSig passing signal %d to self.\n", signo); } (void)kill(getpid(), signo); /* * We've been continued. * * A whole host of signals continue to happen! * SIGCHLD for any processes that actually suspended themselves. * SIGCHLD for any processes that exited while we were alseep. * The SIGCONT that actually caused us to wakeup. * * Since we defer passing the SIGCONT on to our children until * the main processing loop, we can be sure that all the SIGCHLD * events will have happened by then - and that the waitpid() will * collect the child 'suspended' events. * For correct sequencing we just need to ensure we process the * waitpid() before passign on the SIGCONT. * * In any case nothing else is needed here. */ /* Restore handler and signal mask */ act.sa_handler = JobPassSig_suspend; (void)sigaction(signo, &act, NULL); (void)sigprocmask(SIG_SETMASK, &omask, NULL); } /*- *----------------------------------------------------------------------- * JobFindPid -- * Compare the pid of the job with the given pid and return 0 if they * are equal. This function is called from Job_CatchChildren * to find the job descriptor of the finished job. * * Input: * job job to examine * pid process id desired * * Results: * Job with matching pid * * Side Effects: * None *----------------------------------------------------------------------- */ static Job * JobFindPid(int pid, int status, Boolean isJobs) { Job *job; for (job = job_table; job < job_table_end; job++) { if ((job->job_state == status) && job->pid == pid) return job; } if (DEBUG(JOB) && isJobs) job_table_dump("no pid"); return NULL; } /*- *----------------------------------------------------------------------- * JobPrintCommand -- * Put out another command for the given job. If the command starts * with an @ or a - we process it specially. In the former case, * so long as the -s and -n flags weren't given to make, we stick * a shell-specific echoOff command in the script. In the latter, * we ignore errors for the entire job, unless the shell has error * control. * If the command is just "..." we take all future commands for this * job to be commands to be executed once the entire graph has been * made and return non-zero to signal that the end of the commands * was reached. These commands are later attached to the postCommands * node and executed by Job_End when all things are done. * This function is called from JobStart via Lst_ForEach. * * Input: * cmdp command string to print * jobp job for which to print it * * Results: * Always 0, unless the command was "..." * * Side Effects: * If the command begins with a '-' and the shell has no error control, * the JOB_IGNERR flag is set in the job descriptor. * If the command is "..." and we're not ignoring such things, * tailCmds is set to the successor node of the cmd. * numCommands is incremented if the command is actually printed. *----------------------------------------------------------------------- */ static int JobPrintCommand(void *cmdp, void *jobp) { Boolean noSpecials; /* true if we shouldn't worry about * inserting special commands into * the input stream. */ Boolean shutUp = FALSE; /* true if we put a no echo command * into the command file */ Boolean errOff = FALSE; /* true if we turned error checking * off before printing the command * and need to turn it back on */ const char *cmdTemplate; /* Template to use when printing the * command */ char *cmdStart; /* Start of expanded command */ char *escCmd = NULL; /* Command with quotes/backticks escaped */ char *cmd = (char *)cmdp; Job *job = (Job *)jobp; int i, j; noSpecials = NoExecute(job->node); if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; if ((job->flags & JOB_IGNDOTS) == 0) { job->tailCmds = Lst_Succ(Lst_Member(job->node->commands, cmd)); return 1; } return 0; } #define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ (void)fprintf(debug_file, fmt, arg); \ } \ (void)fprintf(job->cmdFILE, fmt, arg); \ (void)fflush(job->cmdFILE); numCommands += 1; cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE); 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. */ + job->flags |= JOB_IGNERR; if (!(job->flags & JOB_SILENT) && !shutUp) { if (commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); } DBPRINTF(commandShell->errCheck, escCmd); shutUp = TRUE; } else { if (!shutUp) { DBPRINTF(commandShell->errCheck, escCmd); } } cmdTemplate = commandShell->ignErr; /* * The error ignoration (hee hee) is already taken care * of by the ignErr template, so pretend error checking * is still on. */ errOff = FALSE; } else { errOff = FALSE; } } else { errOff = FALSE; } } else { /* * If errors are being checked and the shell doesn't have error control * but does supply an errOut template, then setup commands to run * through it. */ if (!commandShell->hasErrCtl && commandShell->errOut && (*commandShell->errOut != '\0')) { if (!(job->flags & JOB_SILENT) && !shutUp) { if (commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); } DBPRINTF(commandShell->errCheck, escCmd); shutUp = TRUE; } /* If it's a comment line or blank, treat as an ignored error */ if ((escCmd[0] == commandShell->commentChar) || (escCmd[0] == 0)) cmdTemplate = commandShell->ignErr; else cmdTemplate = commandShell->errOut; errOff = FALSE; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && (job->flags & JOB_TRACED) == 0) { DBPRINTF("set -%s\n", "x"); job->flags |= JOB_TRACED; } DBPRINTF(cmdTemplate, cmd); free(cmdStart); if (escCmd) free(escCmd); if (errOff) { /* * If echoing is already off, there's no point in issuing the * echoOff command. Otherwise we issue it and pretend it was on * for the whole command... */ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){ DBPRINTF("%s\n", commandShell->echoOff); shutUp = TRUE; } DBPRINTF("%s\n", commandShell->errCheck); } if (shutUp && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOn); } return 0; } /*- *----------------------------------------------------------------------- * JobSaveCommand -- * Save a command to be executed when everything else is done. * Callback function for JobFinish... * * Results: * Always returns 0 * * Side Effects: * The command is tacked onto the end of postCommands's commands list. * *----------------------------------------------------------------------- */ static int JobSaveCommand(void *cmd, void *gn) { cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, FALSE); (void)Lst_AtEnd(postCommands->commands, cmd); return(0); } /*- *----------------------------------------------------------------------- * JobClose -- * Called to close both input and output pipes when a job is finished. * * Results: * Nada * * Side Effects: * The file descriptors associated with the job are closed. * *----------------------------------------------------------------------- */ static void JobClose(Job *job) { clearfd(job); (void)close(job->outPipe); job->outPipe = -1; JobDoOutput(job, TRUE); (void)close(job->inPipe); job->inPipe = -1; } /*- *----------------------------------------------------------------------- * JobFinish -- * Do final processing for the given job including updating * parents and starting new jobs as available/necessary. Note * that we pay no attention to the JOB_IGNERR flag here. * This is because when we're called because of a noexecute flag * or something, jstat.w_status is 0 and when called from * Job_CatchChildren, the status is zeroed if it s/b ignored. * * Input: * job job to finish * status sub-why job went away * * Results: * None * * Side Effects: * Final commands for the job are placed on postCommands. * * If we got an error and are aborting (aborting == ABORT_ERROR) and * the job list is now empty, we are done for the day. * If we recognized an error (errors !=0), we set the aborting flag * to ABORT_ERROR so no more jobs will be started. *----------------------------------------------------------------------- */ /*ARGSUSED*/ static void JobFinish (Job *job, WAIT_T status) { Boolean done, return_job_token; if (DEBUG(JOB)) { fprintf(debug_file, "Jobfinish: %d [%s], status %d\n", job->pid, job->node->name, status); } if ((WIFEXITED(status) && (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || WIFSIGNALED(status)) { /* * If it exited non-zero and either we're doing things our * way or we're not ignoring errors, the job is finished. * Similarly, if the shell died because of a signal * the job is also finished. In these * cases, finish out the job's output before printing the exit * status... */ JobClose(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } done = TRUE; } else if (WIFEXITED(status)) { /* * Deal with ignored errors in -B mode. We need to print a message * telling of the ignored error as well as setting status.w_status * to 0 so the next command gets run. To do this, we set done to be * TRUE if in -B mode and the job exited non-zero. */ done = WEXITSTATUS(status) != 0; /* * Old comment said: "Note we don't * want to close down any of the streams until we know we're at the * end." * But we do. Otherwise when are we going to print the rest of the * stuff? */ JobClose(job); } else { /* * No need to close things down or anything. */ done = FALSE; } if (done) { if (WIFEXITED(status)) { if (DEBUG(JOB)) { (void)fprintf(debug_file, "Process %d [%s] exited.\n", job->pid, job->node->name); } if (WEXITSTATUS(status) != 0) { if (job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } #ifdef USE_META if (useMeta) { meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); } #endif (void)printf("*** [%s] Error code %d%s\n", job->node->name, WEXITSTATUS(status), (job->flags & JOB_IGNERR) ? " (ignored)" : ""); if (job->flags & JOB_IGNERR) { WAIT_STATUS(status) = 0; } else { PrintOnError(job->node, NULL); } } else if (DEBUG(JOB)) { if (job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } (void)printf("*** [%s] Completed successfully\n", job->node->name); } } else { if (job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); } (void)fflush(stdout); } #ifdef USE_META if (useMeta) { meta_job_finish(job); } #endif return_job_token = FALSE; Trace_Log(JOBEND, job); if (!(job->flags & JOB_SPECIAL)) { if ((WAIT_STATUS(status) != 0) || (aborting == ABORT_ERROR) || (aborting == ABORT_INTERRUPT)) return_job_token = TRUE; } if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (WAIT_STATUS(status) == 0)) { /* * As long as we aren't aborting and the job didn't return a non-zero * status that we shouldn't ignore, we call Make_Update to update * the parents. In addition, any saved commands for the node are placed * on the .END target. */ if (job->tailCmds != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; if (!(job->flags & JOB_SPECIAL)) return_job_token = TRUE; Make_Update(job->node); job->job_state = JOB_ST_FREE; } else if (WAIT_STATUS(status)) { errors += 1; job->job_state = JOB_ST_FREE; } /* * Set aborting if any error. */ if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { /* * If we found any errors in this batch of children and the -k flag * wasn't given, we set the aborting flag so no more jobs get * started. */ aborting = ABORT_ERROR; } if (return_job_token) Job_TokenReturn(); if (aborting == ABORT_ERROR && jobTokensRunning == 0) { /* * If we are aborting and the job table is now empty, we finish. */ Finish(errors); } } /*- *----------------------------------------------------------------------- * Job_Touch -- * Touch the given target. Called by JobStart when the -t flag was * given * * Input: * gn the node of the file to touch * silent TRUE if should not print message * * Results: * None * * Side Effects: * The data modification of the file is changed. In addition, if the * file did not exist, it is created. *----------------------------------------------------------------------- */ void Job_Touch(GNode *gn, Boolean silent) { int streamID; /* ID of stream opened to do the touch */ struct utimbuf times; /* Times for utime() call */ if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| OP_SPECIAL|OP_PHONY)) { /* * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets * and, as such, shouldn't really be created. */ return; } if (!silent || NoExecute(gn)) { (void)fprintf(stdout, "touch %s\n", gn->name); (void)fflush(stdout); } if (NoExecute(gn)) { return; } if (gn->type & OP_ARCHV) { Arch_Touch(gn); } else if (gn->type & OP_LIB) { Arch_TouchLib(gn); } else { char *file = gn->path ? gn->path : gn->name; times.actime = times.modtime = now; if (utime(file, ×) < 0){ streamID = open(file, O_RDWR | O_CREAT, 0666); if (streamID >= 0) { char c; /* * Read and write a byte to the file to change the * modification time, then close the file. */ if (read(streamID, &c, 1) == 1) { (void)lseek(streamID, (off_t)0, SEEK_SET); while (write(streamID, &c, 1) == -1 && errno == EAGAIN) continue; } (void)close(streamID); } else { (void)fprintf(stdout, "*** couldn't touch %s: %s", file, strerror(errno)); (void)fflush(stdout); } } } } /*- *----------------------------------------------------------------------- * Job_CheckCommands -- * Make sure the given node has all the commands it needs. * * Input: * gn The target whose commands need verifying * abortProc Function to abort with message * * Results: * TRUE if the commands list is/was ok. * * Side Effects: * The node will have commands from the .DEFAULT rule added to it * if it needs them. *----------------------------------------------------------------------- */ Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) && ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) { /* * No commands. Look for .DEFAULT rule from which we might infer * commands */ if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && (gn->type & OP_SPECIAL) == 0) { char *p1; /* * Make only looks for a .DEFAULT if the node was never the * target of an operator, so that's what we do too. If * a .DEFAULT was given, we substitute its commands for gn's * commands and set the IMPSRC variable to be the target's name * The DEFAULT node acts like a transformation rule, in that * gn also inherits any attributes or sources attached to * .DEFAULT itself. */ Make_HandleUse(DEFAULT, gn); Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0); if (p1) free(p1); } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) { /* * The node wasn't the target of an operator we have no .DEFAULT * rule to go on and the target doesn't already exist. There's * nothing more we can do for this branch. If the -k flag wasn't * given, we stop in our tracks, otherwise we just don't update * this node's parents so they never get examined. */ static const char msg[] = ": don't know how to make"; if (gn->flags & FROM_DEPEND) { if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name); return TRUE; } if (gn->type & OP_OPTIONAL) { (void)fprintf(stdout, "%s%s %s (ignored)\n", progname, msg, gn->name); (void)fflush(stdout); } else if (keepgoing) { (void)fprintf(stdout, "%s%s %s (continuing)\n", progname, msg, gn->name); (void)fflush(stdout); return FALSE; } else { (*abortProc)("%s%s %s. Stop", progname, msg, gn->name); return FALSE; } } } return TRUE; } /*- *----------------------------------------------------------------------- * JobExec -- * Execute the shell for the given job. Called from JobStart * * Input: * job Job to execute * * Results: * None. * * Side Effects: * A shell is executed, outputs is altered and the Job structure added * to the job table. * *----------------------------------------------------------------------- */ static void JobExec(Job *job, char **argv) { int cpid; /* ID of new child */ sigset_t mask; job->flags &= ~JOB_TRACED; if (DEBUG(JOB)) { int i; (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local"); (void)fprintf(debug_file, "\tCommand: "); for (i = 0; argv[i] != NULL; i++) { (void)fprintf(debug_file, "%s ", argv[i]); } (void)fprintf(debug_file, "\n"); } /* * Some jobs produce no output and it's disconcerting to have * no feedback of their running (since they produce no output, the * banner with their name in it never appears). This is an attempt to * provide that feedback, even if nothing follows it. */ if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) { MESSAGE(stdout, job->node); lastNode = job->node; } /* No interruptions until this job is on the `jobs' list */ JobSigLock(&mask); /* Pre-emptively mark job running, pid still zero though */ job->job_state = JOB_ST_RUNNING; cpid = vFork(); if (cpid == -1) Punt("Cannot vfork: %s", strerror(errno)); if (cpid == 0) { /* Child */ sigset_t tmask; #ifdef USE_META if (useMeta) { meta_job_child(job); } #endif /* * Reset all signal handlers; this is necessary because we also * need to unblock signals before we exec(2). */ JobSigReset(); /* Now unblock signals */ sigemptyset(&tmask); JobSigUnlock(&tmask); /* * Must duplicate the input stream down to the child's input and * reset it to the beginning (again). Since the stream was marked * close-on-exec, we must clear that bit in the new input. */ if (dup2(FILENO(job->cmdFILE), 0) == -1) { execError("dup2", "job->cmdFILE"); _exit(1); } (void)fcntl(0, F_SETFD, 0); (void)lseek(0, (off_t)0, SEEK_SET); - if (Always_pass_job_queue || (job->node->type & OP_MAKE)) { + if (Always_pass_job_queue || + (job->node->type & (OP_MAKE | OP_SUBMAKE))) { /* * Pass job token pipe to submakes. */ fcntl(tokenWaitJob.inPipe, F_SETFD, 0); fcntl(tokenWaitJob.outPipe, F_SETFD, 0); } /* * Set up the child's output to be routed through the pipe * we've created for it. */ if (dup2(job->outPipe, 1) == -1) { execError("dup2", "job->outPipe"); _exit(1); } /* * The output channels are marked close on exec. This bit was * duplicated by the dup2(on some systems), so we have to clear * it before routing the shell's error output to the same place as * its standard output. */ (void)fcntl(1, F_SETFD, 0); if (dup2(1, 2) == -1) { execError("dup2", "1, 2"); _exit(1); } /* * We want to switch the child into a different process family so * we can kill it and all its descendants in one fell swoop, * by killing its process family, but not commit suicide. */ #if defined(HAVE_SETPGID) (void)setpgid(0, getpid()); #else #if defined(HAVE_SETSID) /* XXX: dsl - I'm sure this should be setpgrp()... */ (void)setsid(); #else (void)setpgrp(0, getpid()); #endif #endif Var_ExportVars(); (void)execv(shellPath, argv); execError("exec", shellPath); _exit(1); } /* Parent, continuing after the child exec */ job->pid = cpid; Trace_Log(JOBSTART, job); /* * Set the current position in the buffer to the beginning * and mark another stream to watch in the outputs mask */ job->curPos = 0; watchfd(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } /* * Now the job is actually running, add it to the table. */ if (DEBUG(JOB)) { fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n", job->node->name, job->pid); job_table_dump("job started"); } JobSigUnlock(&mask); } /*- *----------------------------------------------------------------------- * JobMakeArgv -- * Create the argv needed to execute the shell for a given job. * * * Results: * * Side Effects: * *----------------------------------------------------------------------- */ static void JobMakeArgv(Job *job, char **argv) { int argc; static char args[10]; /* For merged arguments */ argv[0] = UNCONST(shellName); argc = 1; if ((commandShell->exit && (*commandShell->exit != '-')) || (commandShell->echo && (*commandShell->echo != '-'))) { /* * At least one of the flags doesn't have a minus before it, so * merge them together. Have to do this because the *(&(@*#*&#$# * Bourne shell thinks its second argument is a file to source. * Grrrr. Note the ten-character limitation on the combined arguments. */ (void)snprintf(args, sizeof(args), "-%s%s", ((job->flags & JOB_IGNERR) ? "" : (commandShell->exit ? commandShell->exit : "")), ((job->flags & JOB_SILENT) ? "" : (commandShell->echo ? commandShell->echo : ""))); if (args[1]) { argv[argc] = args; argc++; } } else { if (!(job->flags & JOB_IGNERR) && commandShell->exit) { argv[argc] = UNCONST(commandShell->exit); argc++; } if (!(job->flags & JOB_SILENT) && commandShell->echo) { argv[argc] = UNCONST(commandShell->echo); argc++; } } argv[argc] = NULL; } /*- *----------------------------------------------------------------------- * JobStart -- * Start a target-creation process going for the target described * by the graph node gn. * * Input: * gn target to create * flags flags for the job to override normal ones. * e.g. JOB_SPECIAL or JOB_IGNDOTS * previous The previous Job structure for this node, if any. * * Results: * JOB_ERROR if there was an error in the commands, JOB_FINISHED * if there isn't actually anything left to do for the job and * JOB_RUNNING if the job has been started. * * Side Effects: * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. * * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set * JOB_IGNDOTS is never set (dsl) * Also the return value is ignored by everyone. *----------------------------------------------------------------------- */ static int JobStart(GNode *gn, int flags) { Job *job; /* new job descriptor */ char *argv[10]; /* Argument vector to shell */ Boolean cmdsOK; /* true if the nodes commands were all right */ Boolean noExec; /* Set true if we decide not to run the job */ int tfd; /* File descriptor to the temp file */ for (job = job_table; job < job_table_end; job++) { if (job->job_state == JOB_ST_FREE) break; } if (job >= job_table_end) Punt("JobStart no job slots vacant"); memset(job, 0, sizeof *job); job->job_state = JOB_ST_SETUP; if (gn->type & OP_SPECIAL) flags |= JOB_SPECIAL; job->node = gn; job->tailCmds = NULL; /* * Set the initial value of the flags for this job based on the global * ones and the node's attributes... Any flags supplied by the caller * are also added to the field. */ job->flags = 0; if (Targ_Ignore(gn)) { job->flags |= JOB_IGNERR; } if (Targ_Silent(gn)) { job->flags |= JOB_SILENT; } job->flags |= flags; /* * Check the commands now so any attributes from .DEFAULT have a chance * to migrate to the node */ cmdsOK = Job_CheckCommands(gn, Error); job->inPollfd = NULL; /* * If the -n flag wasn't given, we open up OUR (not the child's) * temporary file to stuff commands in it. The thing is rd/wr so we don't * need to reopen it to feed it to the shell. If the -n flag *was* given, * we just set the file to be stdout. Cute, huh? */ if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) || (!noExecute && !touchFlag)) { /* * tfile is the name of a file into which all shell commands are * put. It is removed before the child shell is executed, unless * DEBUG(SCRIPT) is set. */ char *tfile; sigset_t mask; /* * We're serious here, but if the commands were bogus, we're * also dead... */ if (!cmdsOK) { PrintOnError(gn, NULL); /* provide some clue */ DieHorribly(); } JobSigLock(&mask); tfd = mkTempFile(TMPPAT, &tfile); if (!DEBUG(SCRIPT)) (void)eunlink(tfile); JobSigUnlock(&mask); job->cmdFILE = fdopen(tfd, "w+"); if (job->cmdFILE == NULL) { Punt("Could not fdopen %s", tfile); } (void)fcntl(FILENO(job->cmdFILE), F_SETFD, 1); /* * Send the commands to the command file, flush all its buffers then * rewind and remove the thing. */ noExec = FALSE; #ifdef USE_META if (useMeta) { meta_job_start(job, gn); if (Targ_Silent(gn)) { /* might have changed */ job->flags |= JOB_SILENT; } } #endif /* * We can do all the commands at once. hooray for sanity */ numCommands = 0; Lst_ForEach(gn->commands, JobPrintCommand, job); /* * If we didn't print out any commands to the shell script, * there's not much point in executing the shell, is there? */ if (numCommands == 0) { noExec = TRUE; } free(tfile); } else if (NoExecute(gn)) { /* * Not executing anything -- just print all the commands to stdout * in one fell swoop. This will still set up job->tailCmds correctly. */ if (lastNode != gn) { MESSAGE(stdout, gn); lastNode = gn; } job->cmdFILE = stdout; /* * Only print the commands if they're ok, but don't die if they're * not -- just let the user know they're bad and keep going. It * doesn't do any harm in this case and may do some good. */ if (cmdsOK) { Lst_ForEach(gn->commands, JobPrintCommand, job); } /* * Don't execute the shell, thank you. */ noExec = TRUE; } else { /* * Just touch the target and note that no shell should be executed. * Set cmdFILE to stdout to make life easier. Check the commands, too, * but don't die if they're no good -- it does no harm to keep working * up the graph. */ job->cmdFILE = stdout; Job_Touch(gn, job->flags&JOB_SILENT); noExec = TRUE; } /* Just in case it isn't already... */ (void)fflush(job->cmdFILE); /* * If we're not supposed to execute a shell, don't. */ if (noExec) { if (!(job->flags & JOB_SPECIAL)) Job_TokenReturn(); /* * Unlink and close the command file if we opened one */ if (job->cmdFILE != stdout) { if (job->cmdFILE != NULL) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } } /* * We only want to work our way up the graph if we aren't here because * the commands for the job were no good. */ if (cmdsOK && aborting == 0) { if (job->tailCmds != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; Make_Update(job->node); } job->job_state = JOB_ST_FREE; return cmdsOK ? JOB_FINISHED : JOB_ERROR; } /* * Set up the control arguments to the shell. This is based on the flags * set earlier for this job. */ JobMakeArgv(job, argv); /* Create the pipe by which we'll get the shell's output. */ JobCreatePipe(job, 3); JobExec(job, argv); return(JOB_RUNNING); } static char * JobOutput(Job *job, char *cp, char *endp, int msg) { char *ecp; if (commandShell->noPrint) { ecp = Str_FindSubstring(cp, commandShell->noPrint); while (ecp != NULL) { if (cp != ecp) { *ecp = '\0'; if (!beSilent && msg && job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } /* * The only way there wouldn't be a newline after * this line is if it were the last in the buffer. * however, since the non-printable comes after it, * there must be a newline, so we don't print one. */ (void)fprintf(stdout, "%s", cp); (void)fflush(stdout); } cp = ecp + commandShell->noPLen; if (cp != endp) { /* * Still more to print, look again after skipping * the whitespace following the non-printable * command.... */ cp++; while (*cp == ' ' || *cp == '\t' || *cp == '\n') { cp++; } ecp = Str_FindSubstring(cp, commandShell->noPrint); } else { return cp; } } } return cp; } /*- *----------------------------------------------------------------------- * JobDoOutput -- * This function is called at different times depending on * whether the user has specified that output is to be collected * via pipes or temporary files. In the former case, we are called * whenever there is something to read on the pipe. We collect more * output from the given job and store it in the job's outBuf. If * this makes up a line, we print it tagged by the job's identifier, * as necessary. * If output has been collected in a temporary file, we open the * file and read it line by line, transfering it to our own * output channel until the file is empty. At which point we * remove the temporary file. * In both cases, however, we keep our figurative eye out for the * 'noPrint' line for the shell from which the output came. If * we recognize a line, we don't print it. If the command is not * alone on the line (the character after it is not \0 or \n), we * do print whatever follows it. * * Input: * job the job whose output needs printing * finish TRUE if this is the last time we'll be called * for this job * * Results: * None * * Side Effects: * curPos may be shifted as may the contents of outBuf. *----------------------------------------------------------------------- */ STATIC void JobDoOutput(Job *job, Boolean finish) { Boolean gotNL = FALSE; /* true if got a newline */ Boolean fbuf; /* true if our buffer filled up */ int nr; /* number of bytes read */ int i; /* auxiliary index into outBuf */ int max; /* limit for i (end of current data) */ int nRead; /* (Temporary) number of bytes read */ /* * Read as many bytes as will fit in the buffer. */ end_loop: gotNL = FALSE; fbuf = FALSE; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); if (nRead < 0) { if (errno == EAGAIN) return; if (DEBUG(JOB)) { perror("JobDoOutput(piperead)"); } nr = 0; } else { nr = nRead; } /* * If we hit the end-of-file (the job is dead), we must flush its * remaining output, so pretend we read a newline if there's any * output remaining in the buffer. * Also clear the 'finish' flag so we stop looping. */ if ((nr == 0) && (job->curPos != 0)) { job->outBuf[job->curPos] = '\n'; nr = 1; finish = FALSE; } else if (nr == 0) { finish = FALSE; } /* * Look for the last newline in the bytes we just got. If there is * one, break out of the loop with 'i' as its index and gotNL set * TRUE. */ max = job->curPos + nr; for (i = job->curPos + nr - 1; i >= job->curPos; i--) { if (job->outBuf[i] == '\n') { gotNL = TRUE; break; } else if (job->outBuf[i] == '\0') { /* * Why? */ job->outBuf[i] = ' '; } } if (!gotNL) { job->curPos += nr; if (job->curPos == JOB_BUFSIZE) { /* * If we've run out of buffer space, we have no choice * but to print the stuff. sigh. */ fbuf = TRUE; i = job->curPos; } } if (gotNL || fbuf) { /* * Need to send the output to the screen. Null terminate it * first, overwriting the newline character if there was one. * So long as the line isn't one we should filter (according * to the shell description), we print the line, preceded * by a target banner if this target isn't the same as the * one for which we last printed something. * The rest of the data in the buffer are then shifted down * to the start of the buffer and curPos is set accordingly. */ job->outBuf[i] = '\0'; if (i >= job->curPos) { char *cp; cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); /* * There's still more in that thar buffer. This time, though, * we know there's no newline at the end, so we add one of * our own free will. */ if (*cp != '\0') { if (!beSilent && job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } #ifdef USE_META if (useMeta) { meta_job_output(job, cp, gotNL ? "\n" : ""); } #endif (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); (void)fflush(stdout); } } - if (i < max - 1) { - /* shift the remaining characters down */ - (void)memcpy(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); + /* + * max is the last offset still in the buffer. Move any remaining + * characters to the start of the buffer and update the end marker + * curPos. + */ + if (i < max) { + (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); - } else { - /* - * We have written everything out, so we just start over - * from the start of the buffer. No copying. No nothing. - */ + assert(i == max); job->curPos = 0; } } if (finish) { /* * If the finish flag is true, we must loop until we hit * end-of-file on the pipe. This is guaranteed to happen * eventually since the other end of the pipe is now closed * (we closed it explicitly and the child has exited). When * we do get an EOF, finish will be set FALSE and we'll fall * through and out. */ goto end_loop; } } static void JobRun(GNode *targ) { #ifdef notyet /* * Unfortunately it is too complicated to run .BEGIN, .END, * and .INTERRUPT job in the parallel job module. This has * the nice side effect that it avoids a lot of other problems. */ Lst lst = Lst_Init(FALSE); Lst_AtEnd(lst, targ); (void)Make_Run(lst); Lst_Destroy(lst, NULL); JobStart(targ, JOB_SPECIAL); while (jobTokensRunning) { Job_CatchOutput(); } #else Compat_Make(targ, targ); if (targ->made == ERROR) { PrintOnError(targ, "\n\nStop."); exit(1); } #endif } /*- *----------------------------------------------------------------------- * Job_CatchChildren -- * Handle the exit of a child. Called from Make_Make. * * Input: * block TRUE if should block on the wait * * Results: * none. * * Side Effects: * The job descriptor is removed from the list of children. * * Notes: * We do waits, blocking or not, according to the wisdom of our * caller, until there are no more children to report. For each * job, call JobFinish to finish things off. * *----------------------------------------------------------------------- */ void Job_CatchChildren(void) { int pid; /* pid of dead child */ WAIT_T status; /* Exit/termination status */ /* * Don't even bother if we know there's no one around. */ if (jobTokensRunning == 0) return; while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) { if (DEBUG(JOB)) { (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid, WAIT_STATUS(status)); } JobReapChild(pid, status, TRUE); } } /* * It is possible that wait[pid]() was called from elsewhere, * this lets us reap jobs regardless. */ void JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) { Job *job; /* job descriptor for dead child */ /* * Don't even bother if we know there's no one around. */ if (jobTokensRunning == 0) return; job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); if (job == NULL) { if (isJobs) { if (!lurking_children) Error("Child (%d) status %x not in table?", pid, status); } return; /* not ours */ } if (WIFSTOPPED(status)) { if (DEBUG(JOB)) { (void)fprintf(debug_file, "Process %d (%s) stopped.\n", job->pid, job->node->name); } if (!make_suspended) { switch (WSTOPSIG(status)) { case SIGTSTP: (void)printf("*** [%s] Suspended\n", job->node->name); break; case SIGSTOP: (void)printf("*** [%s] Stopped\n", job->node->name); break; default: (void)printf("*** [%s] Stopped -- signal %d\n", job->node->name, WSTOPSIG(status)); } job->job_suspended = 1; } (void)fflush(stdout); return; } job->job_state = JOB_ST_FINISHED; job->exit_status = WAIT_STATUS(status); JobFinish(job, status); } /*- *----------------------------------------------------------------------- * Job_CatchOutput -- * Catch the output from our children, if we're using * pipes do so. Otherwise just block time until we get a * signal(most likely a SIGCHLD) since there's no point in * just spinning when there's nothing to do and the reaping * of a child can wait for a while. * * Results: * None * * Side Effects: * Output is read from pipes if we're piping. * ----------------------------------------------------------------------- */ void Job_CatchOutput(void) { int nready; Job *job; int i; (void)fflush(stdout); /* The first fd in the list is the job token pipe */ do { nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC); } while (nready < 0 && errno == EINTR); if (nready < 0) Punt("poll: %s", strerror(errno)); if (nready > 0 && readyfd(&childExitJob)) { char token = 0; ssize_t count; count = read(childExitJob.inPipe, &token, 1); switch (count) { case 0: Punt("unexpected eof on token pipe"); case -1: Punt("token pipe read: %s", strerror(errno)); case 1: if (token == DO_JOB_RESUME[0]) /* Complete relay requested from our SIGCONT handler */ JobRestartJobs(); break; default: abort(); } --nready; } Job_CatchChildren(); if (nready == 0) return; for (i = 2; i < nfds; i++) { if (!fds[i].revents) continue; job = jobfds[i]; if (job->job_state == JOB_ST_RUNNING) JobDoOutput(job, FALSE); if (--nready == 0) return; } } /*- *----------------------------------------------------------------------- * Job_Make -- * Start the creation of a target. Basically a front-end for * JobStart used by the Make module. * * Results: * None. * * Side Effects: * Another job is started. * *----------------------------------------------------------------------- */ void Job_Make(GNode *gn) { (void)JobStart(gn, 0); } void Shell_Init(void) { if (shellPath == NULL) { /* * We are using the default shell, which may be an absolute * path if DEFSHELL_CUSTOM is defined. */ shellName = commandShell->name; #ifdef DEFSHELL_CUSTOM if (*shellName == '/') { shellPath = shellName; shellName = strrchr(shellPath, '/'); shellName++; } else #endif shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH); } if (commandShell->exit == NULL) { commandShell->exit = ""; } if (commandShell->echo == NULL) { commandShell->echo = ""; } if (commandShell->hasErrCtl && *commandShell->exit) { if (shellErrFlag && strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { free(shellErrFlag); shellErrFlag = NULL; } if (!shellErrFlag) { int n = strlen(commandShell->exit) + 2; shellErrFlag = bmake_malloc(n); if (shellErrFlag) { snprintf(shellErrFlag, n, "-%s", commandShell->exit); } } } else if (shellErrFlag) { free(shellErrFlag); shellErrFlag = NULL; } } /*- * Returns the string literal that is used in the current command shell * to produce a newline character. */ const char * Shell_GetNewline(void) { return commandShell->newline; } void Job_SetPrefix(void) { if (targPrefix) { free(targPrefix); } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) { Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0); } targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}", VAR_GLOBAL, 0); } /*- *----------------------------------------------------------------------- * Job_Init -- * Initialize the process module * * Input: * * Results: * none * * Side Effects: * lists and counters are initialized *----------------------------------------------------------------------- */ void Job_Init(void) { Job_SetPrefix(); /* Allocate space for all the job info */ job_table = bmake_malloc(maxJobs * sizeof *job_table); memset(job_table, 0, maxJobs * sizeof *job_table); job_table_end = job_table + maxJobs; wantToken = 0; aborting = 0; errors = 0; lastNode = NULL; Always_pass_job_queue = getBoolean(MAKE_ALWAYS_PASS_JOB_QUEUE, Always_pass_job_queue); Job_error_token = getBoolean(MAKE_JOB_ERROR_TOKEN, Job_error_token); /* * There is a non-zero chance that we already have children. * eg after 'make -f- < 0) continue; if (rval == 0) lurking_children = 1; break; } Shell_Init(); JobCreatePipe(&childExitJob, 3); /* We can only need to wait for tokens, children and output from each job */ fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs)); jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs)); /* These are permanent entries and take slots 0 and 1 */ watchfd(&tokenWaitJob); watchfd(&childExitJob); sigemptyset(&caught_signals); /* * Install a SIGCHLD handler. */ (void)bmake_signal(SIGCHLD, JobChildSig); sigaddset(&caught_signals, SIGCHLD); #define ADDSIG(s,h) \ if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \ sigaddset(&caught_signals, s); \ (void)bmake_signal(s, h); \ } /* * Catch the four signals that POSIX specifies if they aren't ignored. * JobPassSig will take care of calling JobInterrupt if appropriate. */ ADDSIG(SIGINT, JobPassSig_int) ADDSIG(SIGHUP, JobPassSig_term) ADDSIG(SIGTERM, JobPassSig_term) ADDSIG(SIGQUIT, JobPassSig_term) /* * There are additional signals that need to be caught and passed if * either the export system wants to be told directly of signals or if * we're giving each job its own process group (since then it won't get * signals from the terminal driver as we own the terminal) */ ADDSIG(SIGTSTP, JobPassSig_suspend) ADDSIG(SIGTTOU, JobPassSig_suspend) ADDSIG(SIGTTIN, JobPassSig_suspend) ADDSIG(SIGWINCH, JobCondPassSig) ADDSIG(SIGCONT, JobContinueSig) #undef ADDSIG (void)Job_RunTarget(".BEGIN", NULL); postCommands = Targ_FindNode(".END", TARG_CREATE); } static void JobSigReset(void) { #define DELSIG(s) \ if (sigismember(&caught_signals, s)) { \ (void)bmake_signal(s, SIG_DFL); \ } DELSIG(SIGINT) DELSIG(SIGHUP) DELSIG(SIGQUIT) DELSIG(SIGTERM) DELSIG(SIGTSTP) DELSIG(SIGTTOU) DELSIG(SIGTTIN) DELSIG(SIGWINCH) DELSIG(SIGCONT) #undef DELSIG (void)bmake_signal(SIGCHLD, SIG_DFL); } /*- *----------------------------------------------------------------------- * JobMatchShell -- * Find a shell in 'shells' given its name. * * Results: * A pointer to the Shell structure. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Shell * JobMatchShell(const char *name) { Shell *sh; for (sh = shells; sh->name != NULL; sh++) { if (strcmp(name, sh->name) == 0) return (sh); } return NULL; } /*- *----------------------------------------------------------------------- * Job_ParseShell -- * Parse a shell specification and set up commandShell, shellPath * and shellName appropriately. * * Input: * line The shell spec * * Results: * FAILURE if the specification was incorrect. * * Side Effects: * commandShell points to a Shell structure (either predefined or * created from the shell spec), shellPath is the full path of the * shell described by commandShell, while shellName is just the * final component of shellPath. * * Notes: * A shell specification consists of a .SHELL target, with dependency * operator, followed by a series of blank-separated words. Double * quotes can be used to use blanks in words. A backslash escapes * anything (most notably a double-quote and a space) and * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: * name Name of shell. * path Location of shell. * quiet Command to turn off echoing. * echo Command to turn echoing on * filter Result of turning off echoing that shouldn't be * printed. * echoFlag Flag to turn echoing on at the start * errFlag Flag to turn error checking on at the start * hasErrCtl True if shell has error checking control * newline String literal to represent a newline char * check Command to turn on error checking if hasErrCtl * is TRUE or template of command to echo a command * for which error checking is off if hasErrCtl is * FALSE. * ignore Command to turn off error checking if hasErrCtl * is TRUE or template of command to execute a * command so as to ignore any errors it returns if * hasErrCtl is FALSE. * *----------------------------------------------------------------------- */ ReturnStatus Job_ParseShell(char *line) { char **words; char **argv; int argc; char *path; Shell newShell; Boolean fullSpec = FALSE; Shell *sh; while (isspace((unsigned char)*line)) { line++; } if (shellArgv) free(UNCONST(shellArgv)); memset(&newShell, 0, sizeof(newShell)); /* * Parse the specification by keyword */ words = brk_string(line, &argc, TRUE, &path); if (words == NULL) { Error("Unterminated quoted string [%s]", line); return FAILURE; } shellArgv = path; for (path = NULL, argv = words; argc != 0; argc--, argv++) { if (strncmp(*argv, "path=", 5) == 0) { path = &argv[0][5]; } else if (strncmp(*argv, "name=", 5) == 0) { newShell.name = &argv[0][5]; } else { if (strncmp(*argv, "quiet=", 6) == 0) { newShell.echoOff = &argv[0][6]; } else if (strncmp(*argv, "echo=", 5) == 0) { newShell.echoOn = &argv[0][5]; } else if (strncmp(*argv, "filter=", 7) == 0) { newShell.noPrint = &argv[0][7]; newShell.noPLen = strlen(newShell.noPrint); } else if (strncmp(*argv, "echoFlag=", 9) == 0) { newShell.echo = &argv[0][9]; } else if (strncmp(*argv, "errFlag=", 8) == 0) { newShell.exit = &argv[0][8]; } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) { char c = argv[0][10]; newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && (c != 'T') && (c != 't')); } else if (strncmp(*argv, "newline=", 8) == 0) { newShell.newline = &argv[0][8]; } else if (strncmp(*argv, "check=", 6) == 0) { newShell.errCheck = &argv[0][6]; } else if (strncmp(*argv, "ignore=", 7) == 0) { newShell.ignErr = &argv[0][7]; } else if (strncmp(*argv, "errout=", 7) == 0) { newShell.errOut = &argv[0][7]; } else if (strncmp(*argv, "comment=", 8) == 0) { newShell.commentChar = argv[0][8]; } else { Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", *argv); free(words); return(FAILURE); } fullSpec = TRUE; } } if (path == NULL) { /* * If no path was given, the user wants one of the pre-defined shells, * yes? So we find the one s/he wants with the help of JobMatchShell * and set things up the right way. shellPath will be set up by * Shell_Init. */ if (newShell.name == NULL) { Parse_Error(PARSE_FATAL, "Neither path nor name specified"); free(words); return(FAILURE); } else { if ((sh = JobMatchShell(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); return(FAILURE); } commandShell = sh; shellName = newShell.name; if (shellPath) { /* Shell_Init has already been called! Do it again. */ free(UNCONST(shellPath)); shellPath = NULL; Shell_Init(); } } } else { /* * The user provided a path. If s/he gave nothing else (fullSpec is * FALSE), try and find a matching shell in the ones we know of. * Else we just take the specification at its word and copy it * to a new location. In either case, we need to record the * path the user gave for the shell. */ shellPath = path; path = strrchr(path, '/'); if (path == NULL) { path = UNCONST(shellPath); } else { path += 1; } if (newShell.name != NULL) { shellName = newShell.name; } else { shellName = path; } if (!fullSpec) { if ((sh = JobMatchShell(shellName)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", shellName); free(words); return(FAILURE); } commandShell = sh; } else { commandShell = bmake_malloc(sizeof(Shell)); *commandShell = newShell; } /* this will take care of shellErrFlag */ Shell_Init(); } if (commandShell->echoOn && commandShell->echoOff) { commandShell->hasEchoCtl = TRUE; } if (!commandShell->hasErrCtl) { if (commandShell->errCheck == NULL) { commandShell->errCheck = ""; } if (commandShell->ignErr == NULL) { commandShell->ignErr = "%s\n"; } } /* * Do not free up the words themselves, since they might be in use by the * shell specification. */ free(words); return SUCCESS; } /*- *----------------------------------------------------------------------- * JobInterrupt -- * Handle the receipt of an interrupt. * * Input: * runINTERRUPT Non-zero if commands for the .INTERRUPT target * should be executed * signo signal received * * Results: * None * * Side Effects: * All children are killed. Another job will be started if the * .INTERRUPT target was given. *----------------------------------------------------------------------- */ static void JobInterrupt(int runINTERRUPT, int signo) { Job *job; /* job descriptor in that element */ GNode *interrupt; /* the node describing the .INTERRUPT target */ sigset_t mask; GNode *gn; aborting = ABORT_INTERRUPT; JobSigLock(&mask); for (job = job_table; job < job_table_end; job++) { if (job->job_state != JOB_ST_RUNNING) continue; gn = job->node; if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) { char *file = (gn->path == NULL ? gn->name : gn->path); if (!noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } } if (job->pid) { if (DEBUG(JOB)) { (void)fprintf(debug_file, "JobInterrupt passing signal %d to child %d.\n", signo, job->pid); } KILLPG(job->pid, signo); } } JobSigUnlock(&mask); if (runINTERRUPT && !touchFlag) { interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); if (interrupt != NULL) { ignoreErrors = FALSE; JobRun(interrupt); } } Trace_Log(MAKEINTR, 0); exit(signo); } /* *----------------------------------------------------------------------- * Job_Finish -- * Do final processing such as the running of the commands * attached to the .END target. * * Results: * Number of errors reported. * * Side Effects: * None. *----------------------------------------------------------------------- */ int Job_Finish(void) { if (postCommands != NULL && (!Lst_IsEmpty(postCommands->commands) || !Lst_IsEmpty(postCommands->children))) { if (errors) { Error("Errors reported so .END ignored"); } else { JobRun(postCommands); } } return(errors); } /*- *----------------------------------------------------------------------- * Job_End -- * Cleanup any memory used by the jobs module * * Results: * None. * * Side Effects: * Memory is freed *----------------------------------------------------------------------- */ void Job_End(void) { #ifdef CLEANUP if (shellArgv) free(shellArgv); #endif } /*- *----------------------------------------------------------------------- * Job_Wait -- * Waits for all running jobs to finish and returns. Sets 'aborting' * to ABORT_WAIT to prevent other jobs from starting. * * Results: * None. * * Side Effects: * Currently running jobs finish. * *----------------------------------------------------------------------- */ void Job_Wait(void) { aborting = ABORT_WAIT; while (jobTokensRunning != 0) { Job_CatchOutput(); } aborting = 0; } /*- *----------------------------------------------------------------------- * Job_AbortAll -- * Abort all currently running jobs without handling output or anything. * This function is to be called only in the event of a major * error. Most definitely NOT to be called from JobInterrupt. * * Results: * None * * Side Effects: * All children are killed, not just the firstborn *----------------------------------------------------------------------- */ void Job_AbortAll(void) { Job *job; /* the job descriptor in that element */ WAIT_T foo; aborting = ABORT_ERROR; if (jobTokensRunning) { for (job = job_table; job < job_table_end; job++) { if (job->job_state != JOB_ST_RUNNING) continue; /* * kill the child process with increasingly drastic signals to make * darn sure it's dead. */ KILLPG(job->pid, SIGINT); KILLPG(job->pid, SIGKILL); } } /* * Catch as many children as want to report in at first, then give up */ while (waitpid((pid_t) -1, &foo, WNOHANG) > 0) continue; } /*- *----------------------------------------------------------------------- * JobRestartJobs -- * Tries to restart stopped jobs if there are slots available. * Called in process context in response to a SIGCONT. * * Results: * None. * * Side Effects: * Resumes jobs. * *----------------------------------------------------------------------- */ static void JobRestartJobs(void) { Job *job; for (job = job_table; job < job_table_end; job++) { if (job->job_state == JOB_ST_RUNNING && (make_suspended || job->job_suspended)) { if (DEBUG(JOB)) { (void)fprintf(debug_file, "Restarting stopped job pid %d.\n", job->pid); } if (job->job_suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } job->job_suspended = 0; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid); } } if (job->job_state == JOB_ST_FINISHED) /* Job exit deferred after calling waitpid() in a signal handler */ JobFinish(job, job->exit_status); } make_suspended = 0; } static void watchfd(Job *job) { if (job->inPollfd != NULL) Punt("Watching watched job"); fds[nfds].fd = job->inPipe; fds[nfds].events = POLLIN; jobfds[nfds] = job; job->inPollfd = &fds[nfds]; nfds++; } static void clearfd(Job *job) { int i; if (job->inPollfd == NULL) Punt("Unwatching unwatched job"); i = job->inPollfd - fds; nfds--; /* * Move last job in table into hole made by dead job. */ if (nfds != i) { fds[i] = fds[nfds]; jobfds[i] = jobfds[nfds]; jobfds[i]->inPollfd = &fds[i]; } job->inPollfd = NULL; } static int readyfd(Job *job) { if (job->inPollfd == NULL) Punt("Polling unwatched job"); return (job->inPollfd->revents & POLLIN) != 0; } /*- *----------------------------------------------------------------------- * JobTokenAdd -- * Put a token into the job pipe so that some make process can start * another job. * * Side Effects: * Allows more build jobs to be spawned somewhere. * *----------------------------------------------------------------------- */ static void JobTokenAdd(void) { char tok = JOB_TOKENS[aborting], tok1; if (!Job_error_token && aborting == ABORT_ERROR) { if (jobTokensRunning == 0) return; tok = '+'; /* no error token */ } /* If we are depositing an error token flush everything else */ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; if (DEBUG(JOB)) fprintf(debug_file, "(%d) aborting %d, deposit token %c\n", getpid(), aborting, tok); while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; } /*- *----------------------------------------------------------------------- * Job_ServerStartTokenAdd -- * Prep the job token pipe in the root make process. * *----------------------------------------------------------------------- */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { int i; char jobarg[64]; if (jp_0 >= 0 && jp_1 >= 0) { /* Pipe passed in from parent */ tokenWaitJob.inPipe = jp_0; tokenWaitJob.outPipe = jp_1; (void)fcntl(jp_0, F_SETFD, 1); (void)fcntl(jp_1, F_SETFD, 1); return; } JobCreatePipe(&tokenWaitJob, 15); snprintf(jobarg, sizeof(jobarg), "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); /* * Preload the job pipe with one token per job, save the one * "extra" token for the primary job. * * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is * larger than the write buffer size of the pipe, we will * deadlock here. */ for (i = 1; i < max_tokens; i++) JobTokenAdd(); } /*- *----------------------------------------------------------------------- * Job_TokenReturn -- * Return a withdrawn token to the pool. * *----------------------------------------------------------------------- */ void Job_TokenReturn(void) { jobTokensRunning--; if (jobTokensRunning < 0) Punt("token botch"); if (jobTokensRunning || JOB_TOKENS[aborting] != '+') JobTokenAdd(); } /*- *----------------------------------------------------------------------- * Job_TokenWithdraw -- * Attempt to withdraw a token from the pool. * * Results: * Returns TRUE if a token was withdrawn, and FALSE if the pool * is currently empty. * * Side Effects: * If pool is empty, set wantToken so that we wake up * when a token is released. * *----------------------------------------------------------------------- */ Boolean Job_TokenWithdraw(void) { char tok, tok1; int count; wantToken = 0; if (DEBUG(JOB)) fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n", getpid(), aborting, jobTokensRunning); if (aborting || (jobTokensRunning >= maxJobs)) return FALSE; count = read(tokenWaitJob.inPipe, &tok, 1); if (count == 0) Fatal("eof on job pipe!"); if (count < 0 && jobTokensRunning != 0) { if (errno != EAGAIN) { Fatal("job pipe read: %s", strerror(errno)); } if (DEBUG(JOB)) fprintf(debug_file, "(%d) blocked for token\n", getpid()); wantToken = 1; return FALSE; } if (count == 1 && tok != '+') { /* make being abvorted - remove any other job tokens */ if (DEBUG(JOB)) fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok); while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; /* And put the stopper back */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; Fatal("A failure has been detected in another branch of the parallel make"); } if (count == 1 && jobTokensRunning == 0) /* We didn't want the token really */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; jobTokensRunning++; if (DEBUG(JOB)) fprintf(debug_file, "(%d) withdrew token\n", getpid()); return TRUE; } /*- *----------------------------------------------------------------------- * Job_RunTarget -- * Run the named target if found. If a filename is specified, then * set that to the sources. * * Results: * None * * Side Effects: * exits if the target fails. * *----------------------------------------------------------------------- */ Boolean Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target, TARG_NOCREATE); if (gn == NULL) return FALSE; if (fname) Var_Set(ALLSRC, fname, gn, 0); JobRun(gn); if (gn->made == ERROR) { PrintOnError(gn, "\n\nStop."); exit(1); } return TRUE; } #ifdef USE_SELECT int emul_poll(struct pollfd *fd, int nfd, int timeout) { fd_set rfds, wfds; int i, maxfd, nselect, npoll; struct timeval tv, *tvp; long usecs; FD_ZERO(&rfds); FD_ZERO(&wfds); maxfd = -1; for (i = 0; i < nfd; i++) { fd[i].revents = 0; if (fd[i].events & POLLIN) FD_SET(fd[i].fd, &rfds); if (fd[i].events & POLLOUT) FD_SET(fd[i].fd, &wfds); if (fd[i].fd > maxfd) maxfd = fd[i].fd; } if (maxfd >= FD_SETSIZE) { Punt("Ran out of fd_set slots; " "recompile with a larger FD_SETSIZE."); } if (timeout < 0) { tvp = NULL; } else { usecs = timeout * 1000; tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; tvp = &tv; } nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); if (nselect <= 0) return nselect; npoll = 0; for (i = 0; i < nfd; i++) { if (FD_ISSET(fd[i].fd, &rfds)) fd[i].revents |= POLLIN; if (FD_ISSET(fd[i].fd, &wfds)) fd[i].revents |= POLLOUT; if (fd[i].revents) npoll++; } return npoll; } #endif /* USE_SELECT */ Index: head/contrib/bmake/lst.h =================================================================== --- head/contrib/bmake/lst.h (revision 281811) +++ head/contrib/bmake/lst.h (revision 281812) @@ -1,189 +1,189 @@ -/* $NetBSD: lst.h,v 1.18 2009/01/23 21:58:27 dsl Exp $ */ +/* $NetBSD: lst.h,v 1.20 2014/09/07 20:55:34 joerg 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. * * from: @(#)lst.h 8.1 (Berkeley) 6/6/93 */ /* * 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. * * from: @(#)lst.h 8.1 (Berkeley) 6/6/93 */ /*- * lst.h -- * Header for using the list library */ #ifndef _LST_H_ #define _LST_H_ #include #include #include "sprite.h" /* * basic typedef. This is what the Lst_ functions handle */ typedef struct List *Lst; typedef struct ListNode *LstNode; typedef void *DuplicateProc(void *); typedef void FreeProc(void *); #define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */ #define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */ /* * Creation/destruction functions */ /* Create a new list */ Lst Lst_Init(Boolean); /* Duplicate an existing list */ Lst Lst_Duplicate(Lst, DuplicateProc *); /* Destroy an old one */ void Lst_Destroy(Lst, FreeProc *); /* True if list is empty */ Boolean Lst_IsEmpty(Lst); /* * Functions to modify a list */ /* Insert an element before another */ ReturnStatus Lst_InsertBefore(Lst, LstNode, void *); /* Insert an element after another */ ReturnStatus Lst_InsertAfter(Lst, LstNode, void *); /* Place an element at the front of a lst. */ ReturnStatus Lst_AtFront(Lst, void *); /* Place an element at the end of a lst. */ ReturnStatus Lst_AtEnd(Lst, void *); /* Remove an element */ ReturnStatus Lst_Remove(Lst, LstNode); /* Replace a node with a new value */ ReturnStatus Lst_Replace(LstNode, void *); /* Concatenate two lists */ ReturnStatus Lst_Concat(Lst, Lst, int); /* * Node-specific functions */ /* Return first element in list */ LstNode Lst_First(Lst); /* Return last element in list */ LstNode Lst_Last(Lst); /* Return successor to given element */ LstNode Lst_Succ(LstNode); /* Return predecessor to given element */ LstNode Lst_Prev(LstNode); /* Get datum from LstNode */ void *Lst_Datum(LstNode); /* * Functions for entire lists */ /* Find an element in a list */ LstNode Lst_Find(Lst, const void *, int (*)(const void *, const void *)); /* Find an element starting from somewhere */ LstNode Lst_FindFrom(Lst, LstNode, const void *, int (*cProc)(const void *, const void *)); /* * See if the given datum is on the list. Returns the LstNode containing * the datum */ LstNode Lst_Member(Lst, void *); /* Apply a function to all elements of a lst */ int Lst_ForEach(Lst, int (*)(void *, void *), void *); /* * Apply a function to all elements of a lst starting from a certain point. * If the list is circular, the application will wrap around to the * beginning of the list again. */ int Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *), void *); /* * these functions are for dealing with a list as a table, of sorts. * An idea of the "current element" is kept and used by all the functions * between Lst_Open() and Lst_Close(). */ /* Open the list */ ReturnStatus Lst_Open(Lst); /* Next element please */ LstNode Lst_Next(Lst); /* Done yet? */ Boolean Lst_IsAtEnd(Lst); /* Finish table access */ void Lst_Close(Lst); /* * for using the list as a queue */ /* Place an element at tail of queue */ ReturnStatus Lst_EnQueue(Lst, void *); /* Remove an element from head of queue */ void *Lst_DeQueue(Lst); #endif /* _LST_H_ */ Index: head/contrib/bmake/lst.lib/lstInt.h =================================================================== --- head/contrib/bmake/lst.lib/lstInt.h (revision 281811) +++ head/contrib/bmake/lst.lib/lstInt.h (revision 281812) @@ -1,105 +1,105 @@ -/* $NetBSD: lstInt.h,v 1.20 2009/01/24 14:43:29 dsl Exp $ */ +/* $NetBSD: lstInt.h,v 1.22 2014/09/07 20:55:34 joerg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)lstInt.h 8.1 (Berkeley) 6/6/93 */ /*- * lstInt.h -- * Internals for the list library */ #ifndef _LSTINT_H_ #define _LSTINT_H_ #include "../lst.h" #include "../make_malloc.h" typedef struct ListNode { struct ListNode *prevPtr; /* previous element in list */ struct ListNode *nextPtr; /* next in list */ unsigned int useCount:8, /* Count of functions using the node. * node may not be deleted until count * goes to 0 */ flags:8; /* Node status flags */ void *datum; /* datum associated with this element */ } *ListNode; /* * Flags required for synchronization */ #define LN_DELETED 0x0001 /* List node should be removed when done */ typedef enum { Head, Middle, Tail, Unknown } Where; typedef struct List { ListNode firstPtr; /* first node in list */ ListNode lastPtr; /* last node in list */ Boolean isCirc; /* true if the list should be considered * circular */ /* * fields for sequential access */ Where atEnd; /* Where in the list the last access was */ Boolean isOpen; /* true if list has been Lst_Open'ed */ ListNode curPtr; /* current node, if open. NULL if * *just* opened */ ListNode prevPtr; /* Previous node, if open. Used by * Lst_Remove */ } *List; /* * PAlloc (var, ptype) -- * Allocate a pointer-typedef structure 'ptype' into the variable 'var' */ #define PAlloc(var,ptype) var = (ptype) bmake_malloc(sizeof *(var)) /* * LstValid (l) -- * Return TRUE if the list l is valid */ #define LstValid(l) ((Lst)(l) != NULL) /* * LstNodeValid (ln, l) -- * Return TRUE if the LstNode ln is valid with respect to l */ #define LstNodeValid(ln, l) ((ln) != NULL) /* * LstIsEmpty (l) -- * TRUE if the list l is empty. */ #define LstIsEmpty(l) (((List)(l))->firstPtr == NULL) #endif /* _LSTINT_H_ */ Index: head/contrib/bmake/lst.lib/lstRemove.c =================================================================== --- head/contrib/bmake/lst.lib/lstRemove.c (revision 281811) +++ head/contrib/bmake/lst.lib/lstRemove.c (revision 281812) @@ -1,136 +1,136 @@ -/* $NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $ */ +/* $NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg 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. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $"; +static char rcsid[] = "$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)lstRemove.c 8.1 (Berkeley) 6/6/93"; #else -__RCSID("$NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $"); +__RCSID("$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"); #endif #endif /* not lint */ #endif /*- * LstRemove.c -- * Remove an element from a list */ #include "lstInt.h" /*- *----------------------------------------------------------------------- * Lst_Remove -- * Remove the given node from the given list. * * Results: * SUCCESS or FAILURE. * * Side Effects: * The list's firstPtr will be set to NULL if ln is the last * node on the list. firsPtr and lastPtr will be altered if ln is * either the first or last node, respectively, on the list. * *----------------------------------------------------------------------- */ ReturnStatus Lst_Remove(Lst l, LstNode ln) { List list = l; ListNode lNode = ln; if (!LstValid (l) || !LstNodeValid (ln, l)) { return (FAILURE); } /* * unlink it from the list */ if (lNode->nextPtr != NULL) { lNode->nextPtr->prevPtr = lNode->prevPtr; } if (lNode->prevPtr != NULL) { lNode->prevPtr->nextPtr = lNode->nextPtr; } /* * if either the firstPtr or lastPtr of the list point to this node, * adjust them accordingly */ if (list->firstPtr == lNode) { list->firstPtr = lNode->nextPtr; } if (list->lastPtr == lNode) { list->lastPtr = lNode->prevPtr; } /* * Sequential access stuff. If the node we're removing is the current * node in the list, reset the current node to the previous one. If the * previous one was non-existent (prevPtr == NULL), we set the * end to be Unknown, since it is. */ if (list->isOpen && (list->curPtr == lNode)) { list->curPtr = list->prevPtr; if (list->curPtr == NULL) { list->atEnd = Unknown; } } /* * the only way firstPtr can still point to ln is if ln is the last * node on the list (the list is circular, so lNode->nextptr == lNode in * this case). The list is, therefore, empty and is marked as such */ if (list->firstPtr == lNode) { list->firstPtr = NULL; } /* * note that the datum is unmolested. The caller must free it as * necessary and as expected. */ if (lNode->useCount == 0) { free(ln); } else { lNode->flags |= LN_DELETED; } return (SUCCESS); } Index: head/contrib/bmake/main.c =================================================================== --- head/contrib/bmake/main.c (revision 281811) +++ head/contrib/bmake/main.c (revision 281812) @@ -1,2019 +1,2023 @@ -/* $NetBSD: main.c,v 1.226 2014/02/07 17:23:35 pooka Exp $ */ +/* $NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: main.c,v 1.226 2014/02/07 17:23:35 pooka Exp $"; +static char rcsid[] = "$NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $"; #else #include #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\ The Regents of the University of California. All rights reserved."); #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: main.c,v 1.226 2014/02/07 17:23:35 pooka Exp $"); +__RCSID("$NetBSD: main.c,v 1.232 2015/03/26 22:20:42 sjg Exp $"); #endif #endif /* not lint */ #endif /*- * main.c -- * The main file for this entire program. Exit routines etc * reside here. * * Utility functions defined in this file: * Main_ParseArgLine Takes a line of arguments, breaks them and * treats them as if they were given when first * invoked. Used by the parse module to implement * the .MFLAGS target. * * Error Print a tagged error message. The global * MAKE variable must have been defined. This - * takes a format string and two optional - * arguments for it. + * takes a format string and optional arguments + * for it. * * Fatal Print an error message and exit. Also takes - * a format string and two arguments. + * a format string and arguments for it. * * Punt Aborts all jobs and exits with a message. Also - * takes a format string and two arguments. + * takes a format string and arguments for it. * * Finish Finish things up by printing the number of * errors which occurred, as passed to it, and * exiting. */ #include #include #include #include #include #if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) #include #endif #include #include "wait.h" #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" #ifdef USE_IOVEC #include #endif #ifndef DEFMAXLOCAL #define DEFMAXLOCAL DEFMAXJOBS #endif /* DEFMAXLOCAL */ #ifndef __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #endif Lst create; /* Targets to be made */ time_t now; /* Time at start of make */ GNode *DEFAULT; /* .DEFAULT node */ Boolean allPrecious; /* .PRECIOUS given on line by itself */ static Boolean noBuiltins; /* -r flag */ static Lst makefiles; /* ordered list of makefiles to read */ static Boolean printVars; /* print value of one or more vars */ static Lst variables; /* list of variables to print */ int maxJobs; /* -j argument */ static int maxJobTokens; /* -j argument */ Boolean compatMake; /* -B argument */ int debug; /* -d argument */ Boolean debugVflag; /* -dV */ Boolean noExecute; /* -n flag */ Boolean noRecursiveExecute; /* -N flag */ Boolean keepgoing; /* -k flag */ Boolean queryFlag; /* -q flag */ Boolean touchFlag; /* -t flag */ Boolean enterFlag; /* -w flag */ Boolean ignoreErrors; /* -i flag */ Boolean beSilent; /* -s flag */ Boolean oldVars; /* variable substitution style */ Boolean checkEnvFirst; /* -e flag */ Boolean parseWarnFatal; /* -W flag */ Boolean jobServer; /* -J flag */ static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ Boolean varNoExportEnv; /* -X flag */ Boolean doing_depend; /* Set while reading .depend */ static Boolean jobsRunning; /* TRUE if the jobs might be running */ static const char * tracefile; static void MainParseArgs(int, char **); static int ReadMakefile(const void *, const void *); static void usage(void) MAKE_ATTR_DEAD; static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ char curdir[MAXPATHLEN + 1]; /* Startup directory */ char *progname; /* the program name */ char *makeDependfile; pid_t myPid; int makelevel; Boolean forceJobs = FALSE; /* * On some systems MACHINE is defined as something other than * what we want. */ #ifdef FORCE_MACHINE # undef MACHINE # define MACHINE FORCE_MACHINE #endif extern Lst parseIncPath; /* * For compatibility with the POSIX version of MAKEFLAGS that includes * all the options with out -, convert flags to -f -l -a -g -s. */ static char * explode(const char *flags) { size_t len; char *nf, *st; const char *f; if (flags == NULL) return NULL; for (f = flags; *f; f++) if (!isalpha((unsigned char)*f)) break; if (*f) return bmake_strdup(flags); len = strlen(flags); st = nf = bmake_malloc(len * 3 + 1); while (*flags) { *nf++ = '-'; *nf++ = *flags++; *nf++ = ' '; } *nf = '\0'; return st; } static void parse_debug_options(const char *argvalue) { const char *modules; const char *mode; char *fname; int len; for (modules = argvalue; *modules; ++modules) { switch (*modules) { case 'A': debug = ~0; break; case 'a': debug |= DEBUG_ARCH; break; case 'C': debug |= DEBUG_CWD; break; case 'c': debug |= DEBUG_COND; break; case 'd': debug |= DEBUG_DIR; break; case 'e': debug |= DEBUG_ERROR; break; case 'f': debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; ++modules; } else if (modules[1] == '2') { debug |= DEBUG_GRAPH2; ++modules; } else if (modules[1] == '3') { debug |= DEBUG_GRAPH3; ++modules; } break; case 'j': debug |= DEBUG_JOB; break; case 'l': debug |= DEBUG_LOUD; break; case 'M': debug |= DEBUG_META; break; case 'm': debug |= DEBUG_MAKE; break; case 'n': debug |= DEBUG_SCRIPT; break; case 'p': debug |= DEBUG_PARSE; break; case 's': debug |= DEBUG_SUFF; break; case 't': debug |= DEBUG_TARG; break; case 'V': debugVflag = TRUE; break; case 'v': debug |= DEBUG_VAR; break; case 'x': debug |= DEBUG_SHELL; break; case 'F': if (debug_file != stdout && debug_file != stderr) fclose(debug_file); if (*++modules == '+') { modules++; mode = "a"; } else mode = "w"; if (strcmp(modules, "stdout") == 0) { debug_file = stdout; goto debug_setbuf; } if (strcmp(modules, "stderr") == 0) { debug_file = stderr; goto debug_setbuf; } len = strlen(modules); fname = malloc(len + 20); memcpy(fname, modules, len + 1); /* Let the filename be modified by the pid */ if (strcmp(fname + len - 3, ".%d") == 0) snprintf(fname + len - 2, 20, "%d", getpid()); debug_file = fopen(fname, mode); if (!debug_file) { fprintf(stderr, "Cannot open debug file %s\n", fname); usage(); } free(fname); goto debug_setbuf; default: (void)fprintf(stderr, "%s: illegal argument to d option -- %c\n", progname, *modules); usage(); } } debug_setbuf: /* * Make the debug_file unbuffered, and make * stdout line buffered (unless debugfile == stdout). */ setvbuf(debug_file, NULL, _IONBF, 0); if (debug_file != stdout) { setvbuf(stdout, NULL, _IOLBF, 0); } } /*- * MainParseArgs -- * Parse a given argument vector. Called from main() and from * Main_ParseArgLine() when the .MAKEFLAGS target is used. * * XXX: Deal with command line overriding .MAKEFLAGS in makefile * * Results: * None * * Side Effects: * Various global and local flags will be set depending on the flags * given */ static void MainParseArgs(int argc, char **argv) { char *p; int c = '?'; int arginc; char *argvalue; const char *getopt_def; char *optscan; Boolean inOption, dashDash = FALSE; char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ #define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstw" /* Can't actually use getopt(3) because rescanning is not portable */ getopt_def = OPTFLAGS; rearg: inOption = FALSE; optscan = NULL; while(argc > 1) { char *getopt_spec; if(!inOption) optscan = argv[1]; c = *optscan++; arginc = 0; if(inOption) { if(c == '\0') { ++argv; --argc; inOption = FALSE; continue; } } else { if (c != '-' || dashDash) break; inOption = TRUE; c = *optscan++; } /* '-' found at some earlier point */ getopt_spec = strchr(getopt_def, c); if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') { /* - found, and should have an arg */ inOption = FALSE; arginc = 1; argvalue = optscan; if(*argvalue == '\0') { if (argc < 3) goto noarg; argvalue = argv[2]; arginc = 2; } } else { argvalue = NULL; } switch(c) { case '\0': arginc = 1; inOption = FALSE; break; case 'B': compatMake = TRUE; Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0); break; case 'C': if (chdir(argvalue) == -1) { (void)fprintf(stderr, "%s: chdir %s: %s\n", progname, argvalue, strerror(errno)); exit(1); } if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); exit(2); } ignorePWD = TRUE; break; case 'D': if (argvalue == NULL || argvalue[0] == 0) goto noarg; Var_Set(argvalue, "1", VAR_GLOBAL, 0); Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'I': if (argvalue == NULL) goto noarg; Parse_AddIncludeDir(argvalue); Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'J': if (argvalue == NULL) goto noarg; if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { (void)fprintf(stderr, "%s: internal error -- J option malformed (%s)\n", progname, argvalue); usage(); } if ((fcntl(jp_0, F_GETFD, 0) < 0) || (fcntl(jp_1, F_GETFD, 0) < 0)) { #if 0 (void)fprintf(stderr, "%s: ###### warning -- J descriptors were closed!\n", progname); exit(2); #endif jp_0 = -1; jp_1 = -1; compatMake = TRUE; } else { Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); jobServer = TRUE; } break; case 'N': noExecute = TRUE; noRecursiveExecute = TRUE; Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); break; case 'S': keepgoing = FALSE; Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); break; case 'T': if (argvalue == NULL) goto noarg; tracefile = bmake_strdup(argvalue); Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'V': if (argvalue == NULL) goto noarg; printVars = TRUE; (void)Lst_AtEnd(variables, argvalue); Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'W': parseWarnFatal = TRUE; break; case 'X': varNoExportEnv = TRUE; Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); break; case 'd': if (argvalue == NULL) goto noarg; /* If '-d-opts' don't pass to children */ if (argvalue[0] == '-') argvalue++; else { Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); } parse_debug_options(argvalue); break; case 'e': checkEnvFirst = TRUE; Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); break; case 'f': if (argvalue == NULL) goto noarg; (void)Lst_AtEnd(makefiles, argvalue); break; case 'i': ignoreErrors = TRUE; Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); break; case 'j': if (argvalue == NULL) goto noarg; forceJobs = TRUE; maxJobs = strtol(argvalue, &p, 0); if (*p != '\0' || maxJobs < 1) { (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n", progname); exit(1); } Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0); maxJobTokens = maxJobs; break; case 'k': keepgoing = TRUE; Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); break; case 'm': if (argvalue == NULL) goto noarg; /* look for magic parent directory search string */ if (strncmp(".../", argvalue, 4) == 0) { if (!Dir_FindHereOrAbove(curdir, argvalue+4, found_path, sizeof(found_path))) break; /* nothing doing */ (void)Dir_AddDir(sysIncPath, found_path); } else { (void)Dir_AddDir(sysIncPath, argvalue); } Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); break; case 'n': noExecute = TRUE; Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); break; case 'q': queryFlag = TRUE; /* Kind of nonsensical, wot? */ Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); break; case 'r': noBuiltins = TRUE; Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); break; case 's': beSilent = TRUE; Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); break; case 't': touchFlag = TRUE; Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); break; case 'w': enterFlag = TRUE; Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); break; case '-': dashDash = TRUE; break; default: case '?': #ifndef MAKE_NATIVE fprintf(stderr, "getopt(%s) -> %d (%c)\n", OPTFLAGS, c, c); #endif usage(); } argv += arginc; argc -= arginc; } oldVars = TRUE; /* * See if the rest of the arguments are variable assignments and * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ for (; argc > 1; ++argv, --argc) if (Parse_IsVar(argv[1])) { Parse_DoVar(argv[1], VAR_CMD); } else { if (!*argv[1]) Punt("illegal (null) argument."); if (*argv[1] == '-' && !dashDash) goto rearg; (void)Lst_AtEnd(create, bmake_strdup(argv[1])); } return; noarg: (void)fprintf(stderr, "%s: option requires an argument -- %c\n", progname, c); usage(); } /*- * Main_ParseArgLine -- * Used by the parse module when a .MFLAGS or .MAKEFLAGS target * is encountered and by main() when reading the .MAKEFLAGS envariable. * Takes a line of arguments and breaks it into its * component words and passes those words and the number of them to the * MainParseArgs function. * The line should have all its leading whitespace removed. * * Input: * line Line to fracture * * Results: * None * * Side Effects: * Only those that come from the various arguments. */ void Main_ParseArgLine(const char *line) { char **argv; /* Manufactured argument vector */ int argc; /* Number of arguments in argv */ char *args; /* Space used by the args */ char *buf, *p1; char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); size_t len; if (line == NULL) return; for (; *line == ' '; ++line) continue; if (!*line) return; #ifndef POSIX { /* * $MAKE may simply be naming the make(1) binary */ char *cp; if (!(cp = strrchr(line, '/'))) cp = line; if ((cp = strstr(cp, "make")) && strcmp(cp, "make") == 0) return; } #endif buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2); (void)snprintf(buf, len, "%s %s", argv0, line); if (p1) free(p1); argv = brk_string(buf, &argc, TRUE, &args); if (argv == NULL) { Error("Unterminated quoted string [%s]", buf); free(buf); return; } free(buf); MainParseArgs(argc, argv); free(args); free(argv); } Boolean Main_SetObjdir(const char *path) { struct stat sb; char *p = NULL; char buf[MAXPATHLEN + 1]; Boolean rc = FALSE; /* expand variable substitutions */ if (strchr(path, '$') != 0) { snprintf(buf, MAXPATHLEN, "%s", path); path = p = Var_Subst(NULL, buf, VAR_GLOBAL, 0); } if (path[0] != '/') { snprintf(buf, MAXPATHLEN, "%s/%s", curdir, path); path = buf; } /* look for the directory and try to chdir there */ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { if (chdir(path)) { (void)fprintf(stderr, "make warning: %s: %s.\n", path, strerror(errno)); } else { strncpy(objdir, path, MAXPATHLEN); Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0); setenv("PWD", objdir, 1); Dir_InitDot(); rc = TRUE; } } if (p) free(p); return rc; } /*- * ReadAllMakefiles -- * wrapper around ReadMakefile() to read all. * * Results: * TRUE if ok, FALSE on error */ static int ReadAllMakefiles(const void *p, const void *q) { return (ReadMakefile(p, q) == 0); } int str2Lst_Append(Lst lp, char *str, const char *sep) { char *cp; int n; if (!sep) sep = " \t"; for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { (void)Lst_AtEnd(lp, cp); n++; } return (n); } #ifdef SIGINFO /*ARGSUSED*/ static void siginfo(int signo MAKE_ATTR_UNUSED) { char dir[MAXPATHLEN]; char str[2 * MAXPATHLEN]; int len; if (getcwd(dir, sizeof(dir)) == NULL) return; len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir); if (len > 0) (void)write(STDERR_FILENO, str, (size_t)len); } #endif /* * Allow makefiles some control over the mode we run in. */ void MakeMode(const char *mode) { char *mp = NULL; if (!mode) mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}", VAR_GLOBAL, 0); if (mode && *mode) { if (strstr(mode, "compat")) { compatMake = TRUE; forceJobs = FALSE; } #if USE_META if (strstr(mode, "meta")) meta_mode_init(mode); #endif } if (mp) free(mp); } /*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the .MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { Lst targs; /* target nodes to create -- passed to Make_Init */ Boolean outOfDate = FALSE; /* FALSE if all targets up to date */ struct stat sb, sa; char *p1, *path; char mdpath[MAXPATHLEN]; #ifdef FORCE_MACHINE const char *machine = FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); #endif const char *machine_arch = getenv("MACHINE_ARCH"); char *syspath = getenv("MAKESYSPATH"); Lst sysMkPath; /* Path of sys.mk */ char *cp = NULL, *start; /* avoid faults on read-only strings */ static char defsyspath[] = _PATH_DEFSYSPATH; char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ struct timeval rightnow; /* to initialize random seed */ struct utsname utsname; /* default to writing debug to stderr */ debug_file = stderr; #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif /* * Set the seed to produce a different random sequence * on each program execution. */ gettimeofday(&rightnow, NULL); srandom(rightnow.tv_sec + rightnow.tv_usec); if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; #if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) /* * get rid of resource limit on file descriptors */ { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { rl.rlim_cur = rl.rlim_max; (void)setrlimit(RLIMIT_NOFILE, &rl); } } #endif if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, strerror(errno)); exit(2); } /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ if (!machine) { #ifdef MAKE_NATIVE machine = utsname.machine; #else #ifdef MAKE_MACHINE machine = MAKE_MACHINE; #else machine = "unknown"; #endif #endif } if (!machine_arch) { #if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) && defined(CTL_HW) && defined(HW_MACHINE_ARCH) static char machine_arch_buf[sizeof(utsname.machine)]; int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; size_t len = sizeof(machine_arch_buf); if (sysctl(mib, __arraycount(mib), machine_arch_buf, &len, NULL, 0) < 0) { (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, strerror(errno)); exit(2); } machine_arch = machine_arch_buf; #else #ifndef MACHINE_ARCH #ifdef MAKE_MACHINE_ARCH machine_arch = MAKE_MACHINE_ARCH; #else machine_arch = "unknown"; #endif #else machine_arch = MACHINE_ARCH; #endif #endif } myPid = getpid(); /* remember this for vFork() */ /* * Just in case MAKEOBJDIR wants us to do something tricky. */ Var_Init(); /* Initialize the lists of variables for * parsing arguments */ Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0); Var_Set("MACHINE", machine, VAR_GLOBAL, 0); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0); #endif Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */ /* * This is the traditional preference for makefiles. */ #ifndef MAKEFILE_PREFERENCE_LIST # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, VAR_GLOBAL, 0); Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0); create = Lst_Init(FALSE); makefiles = Lst_Init(FALSE); printVars = FALSE; debugVflag = FALSE; variables = Lst_Init(FALSE); beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ maxJobTokens = maxJobs; compatMake = FALSE; /* No compat mode */ ignorePWD = FALSE; /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) { /* * Leave alone if it is an absolute path, or if it does * not contain a '/' in which case we need to find it in * the path, like execvp(3) and the shells do. */ p1 = argv[0]; } else { /* * A relative path, canonicalize it. */ p1 = realpath(argv[0], mdpath); if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) { p1 = argv[0]; /* realpath failed */ } } Var_Set("MAKE", p1, VAR_GLOBAL, 0); Var_Set(".MAKE", p1, VAR_GLOBAL, 0); Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0); Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0); Var_Set("MFLAGS", "", VAR_GLOBAL, 0); Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0); /* some makefiles need to know this */ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0); /* * Set some other useful macros */ { char tmp[64], *ep; makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0; if (makelevel < 0) makelevel = 0; snprintf(tmp, sizeof(tmp), "%d", makelevel); Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0); snprintf(tmp, sizeof(tmp), "%u", myPid); Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0); snprintf(tmp, sizeof(tmp), "%u", getppid()); Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0); } if (makelevel > 0) { char pn[1024]; snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } #ifdef USE_META meta_init(); #endif /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ #ifdef POSIX p1 = explode(getenv("MAKEFLAGS")); Main_ParseArgLine(p1); free(p1); #else Main_ParseArgLine(getenv("MAKE")); #endif /* * Find where we are (now). * We take care of PWD for the automounter below... */ if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: getcwd: %s.\n", progname, strerror(errno)); exit(2); } MainParseArgs(argc, argv); if (enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); /* * Verify that cwd is sane. */ if (stat(curdir, &sa) == -1) { (void)fprintf(stderr, "%s: %s: %s.\n", progname, curdir, strerror(errno)); exit(2); } /* * All this code is so that we know where we are when we start up * on a different machine with pmake. * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX * since the value of curdir can vary depending on how we got * here. Ie sitting at a shell prompt (shell that provides $PWD) * or via subdir.mk in which case its likely a shell which does * not provide it. * So, to stop it breaking this case only, we ignore PWD if * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. */ #ifndef NO_PWD_OVERRIDE if (!ignorePWD) { char *pwd, *ptmp1 = NULL, *ptmp2 = NULL; if ((pwd = getenv("PWD")) != NULL && Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) { const char *makeobjdir = Var_Value("MAKEOBJDIR", VAR_CMD, &ptmp2); if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) (void)strncpy(curdir, pwd, MAXPATHLEN); } } free(ptmp1); free(ptmp2); } #endif Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0); /* * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, * MAKEOBJDIR is set in the environment, try only that value * and fall back to .CURDIR if it does not exist. * * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none * of these paths exist, just use .CURDIR. */ Dir_Init(curdir); (void)Main_SetObjdir(curdir); if ((path = Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &p1)) != NULL) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir); (void)Main_SetObjdir(mdpath); free(p1); } else if ((path = Var_Value("MAKEOBJDIR", VAR_CMD, &p1)) != NULL) { (void)Main_SetObjdir(path); free(p1); } else { (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine); if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) { (void)snprintf(mdpath, MAXPATHLEN, "%s%s", _PATH_OBJDIRPREFIX, curdir); (void)Main_SetObjdir(mdpath); } } /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ if (!compatMake && !forceJobs) { compatMake = TRUE; } /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); Trace_Init(tracefile); DEFAULT = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(create)) { LstNode ln; for (ln = Lst_First(create); ln != NULL; ln = Lst_Succ(ln)) { char *name = (char *)Lst_Datum(ln); Var_Append(".TARGETS", name, VAR_GLOBAL); } } else Var_Set(".TARGETS", "", VAR_GLOBAL, 0); /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (syspath == NULL || *syspath == '\0') syspath = defsyspath; else syspath = bmake_strdup(syspath); for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; if (*cp == ':') { *cp++ = '\0'; } /* look for magic parent directory search string */ if (strncmp(".../", start, 4) != 0) { (void)Dir_AddDir(defIncPath, start); } else { if (Dir_FindHereOrAbove(curdir, start+4, found_path, sizeof(found_path))) { (void)Dir_AddDir(defIncPath, found_path); } } } if (syspath != defsyspath) free(syspath); /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != NULL), or the default * makefile and Makefile, in that order, if it wasn't. */ if (!noBuiltins) { LstNode ln; sysMkPath = Lst_Init(FALSE); Dir_Expand(_PATH_DEFSYSMK, Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); ln = Lst_Find(sysMkPath, NULL, ReadMakefile); if (ln == NULL) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } if (!Lst_IsEmpty(makefiles)) { LstNode ln; ln = Lst_Find(makefiles, NULL, ReadAllMakefiles); if (ln != NULL) Fatal("%s: cannot open %s.", progname, (char *)Lst_Datum(ln)); } else { p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}", VAR_CMD, 0); if (p1) { (void)str2Lst_Append(makefiles, p1, NULL); (void)Lst_Find(makefiles, NULL, ReadMakefile); free(p1); } } /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ if (!noBuiltins || !printVars) { makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}", VAR_CMD, 0); doing_depend = TRUE; (void)ReadMakefile(makeDependfile, NULL); doing_depend = FALSE; } MakeMode(NULL); Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); if (p1) free(p1); if (!compatMake) Job_ServerStart(maxJobTokens, jp_0, jp_1); if (DEBUG(JOB)) fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", jp_0, jp_1, maxJobs, maxJobTokens, compatMake); Main_ExportMAKEFLAGS(TRUE); /* initial export */ /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * ::... */ if (Var_Exists("VPATH", VAR_CMD)) { char *vpath, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++) continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ (void)Dir_AddDir(dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* * Propagate attributes through :: dependency lists. */ Targ_Propagate(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ if (printVars) { LstNode ln; Boolean expandVars; if (debugVflag) expandVars = FALSE; else expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); for (ln = Lst_First(variables); ln != NULL; ln = Lst_Succ(ln)) { char *var = (char *)Lst_Datum(ln); char *value; if (strchr(var, '$')) { value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, 0); } else if (expandVars) { char tmp[128]; if (snprintf(tmp, sizeof(tmp), "${%s}", var) >= (int)(sizeof(tmp))) Fatal("%s: variable name too big: %s", progname, var); value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); } else { value = Var_Value(var, VAR_GLOBAL, &p1); } printf("%s\n", value ? value : ""); if (p1) free(p1); } } else { /* * Have now read the entire graph and need to make a list of * targets to create. If none was given on the command line, * we consult the parsing module to find the main target(s) * to create. */ if (Lst_IsEmpty(create)) targs = Parse_MainName(); else targs = Targ_FindList(create, TARG_CREATE); if (!compatMake) { /* * Initialize job module before traversing the graph * now that any .BEGIN and .END targets have been read. * This is done only if the -q flag wasn't given * (to prevent the .BEGIN from being executed should * it exist). */ if (!queryFlag) { Job_Init(); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(targs); } else { /* * Compat_Init will take care of creating all the * targets as well as initializing the module. */ Compat_Run(targs); } } #ifdef CLEANUP Lst_Destroy(targs, NULL); Lst_Destroy(variables, NULL); Lst_Destroy(makefiles, NULL); Lst_Destroy(create, (FreeProc *)free); #endif /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEEND, 0); if (enterFlag) printf("%s: Leaving directory `%s'\n", progname, curdir); Suff_End(); Targ_End(); Arch_End(); Var_End(); Parse_End(); Dir_End(); Job_End(); Trace_End(); return outOfDate ? 1 : 0; } /*- * ReadMakefile -- * Open and parse the given makefile. * * Results: * 0 if ok. -1 if couldn't open file. * * Side Effects: * lots */ static int ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED) { const char *fname = p; /* makefile to read */ int fd; size_t len = MAXPATHLEN; char *name, *path = bmake_malloc(len); if (!strcmp(fname, "-")) { Parse_File(NULL /*stdin*/, -1); Var_Set("MAKEFILE", "", VAR_INTERNAL, 0); } else { /* if we've chdir'd, rebuild the path name */ if (strcmp(curdir, objdir) && *fname != '/') { size_t plen = strlen(curdir) + strlen(fname) + 2; if (len < plen) path = bmake_realloc(path, len = 2 * plen); (void)snprintf(path, len, "%s/%s", curdir, fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } /* If curdir failed, try objdir (ala .depend) */ plen = strlen(objdir) + strlen(fname) + 2; if (len < plen) path = bmake_realloc(path, len = 2 * plen); (void)snprintf(path, len, "%s/%s", objdir, fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } } else { fd = open(fname, O_RDONLY); if (fd != -1) goto found; } /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); if (!name) name = Dir_FindFile(fname, Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); if (!name || (fd = open(name, O_RDONLY)) == -1) { if (name) free(name); free(path); return(-1); } fname = name; /* * set the MAKEFILE variable desired by System V fans -- the * placement of the setting here means it gets set to the last * makefile specified, as it is set by SysV make. */ found: if (!doing_depend) Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0); Parse_File(fname, fd); } free(path); return(0); } /*- * Cmd_Exec -- * Execute the command in cmd, and return the output of that command * in a string. * * Results: * A string containing the output of the command, or the empty string * If errnum is not NULL, it contains the reason for the command failure * * Side Effects: * The string must be freed by the caller. */ char * Cmd_Exec(const char *cmd, const char **errnum) { const char *args[4]; /* Args for invoking the shell */ int fds[2]; /* Pipe streams */ int cpid; /* Child PID */ int pid; /* PID from wait() */ char *res; /* result */ WAIT_T status; /* command exit status */ Buffer buf; /* buffer to store the result */ char *cp; - int cc; + int cc; /* bytes read, or -1 */ + int savederr; /* saved errno */ *errnum = NULL; if (!shellName) Shell_Init(); /* * Set up arguments for shell */ args[0] = shellName; args[1] = "-c"; args[2] = cmd; args[3] = NULL; /* * Open a pipe for fetching its output */ if (pipe(fds) == -1) { *errnum = "Couldn't create pipe for \"%s\""; goto bad; } /* * Fork */ switch (cpid = vFork()) { case 0: /* * Close input side of pipe */ (void)close(fds[0]); /* * Duplicate the output stream to the shell's output, then * shut the extra thing down. Note we don't fetch the error * stream...why not? Why? */ (void)dup2(fds[1], 1); (void)close(fds[1]); Var_ExportVars(); (void)execv(shellPath, UNCONST(args)); _exit(1); /*NOTREACHED*/ case -1: *errnum = "Couldn't exec \"%s\""; goto bad; default: /* * No need for the writing half */ (void)close(fds[1]); + savederr = 0; Buf_Init(&buf, 0); do { char result[BUFSIZ]; cc = read(fds[0], result, sizeof(result)); if (cc > 0) Buf_AddBytes(&buf, cc, result); } while (cc > 0 || (cc == -1 && errno == EINTR)); + if (cc == -1) + savederr = errno; /* * Close the input side of the pipe. */ (void)close(fds[0]); /* * Wait for the process to exit. */ while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) { JobReapChild(pid, status, FALSE); continue; } cc = Buf_Size(&buf); res = Buf_Destroy(&buf, FALSE); - if (cc == 0) + if (savederr != 0) *errnum = "Couldn't read shell's output for \"%s\""; if (WIFSIGNALED(status)) *errnum = "\"%s\" exited on a signal"; else if (WEXITSTATUS(status) != 0) *errnum = "\"%s\" returned non-zero status"; /* * Null-terminate the result, convert newlines to spaces and * install it in the variable. */ res[cc] = '\0'; cp = &res[cc]; if (cc > 0 && *--cp == '\n') { /* * A final newline is just stripped */ *cp-- = '\0'; } while (cp >= res) { if (*cp == '\n') { *cp = ' '; } cp--; } break; } return res; bad: res = bmake_malloc(1); *res = '\0'; return res; } /*- * Error -- * Print an error message given its format. * * Results: * None. * * Side Effects: * The message is printed. */ /* VARARGS */ void Error(const char *fmt, ...) { va_list ap; FILE *err_file; err_file = debug_file; if (err_file == stdout) err_file = stderr; (void)fflush(stdout); for (;;) { va_start(ap, fmt); fprintf(err_file, "%s: ", progname); (void)vfprintf(err_file, fmt, ap); va_end(ap); (void)fprintf(err_file, "\n"); (void)fflush(err_file); if (err_file == stderr) break; err_file = stderr; } } /*- * Fatal -- * Produce a Fatal error message. If jobs are running, waits for them * to finish. * * Results: * None * * Side Effects: * The program exits */ /* VARARGS */ void Fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (jobsRunning) Job_Wait(); (void)fflush(stdout); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); PrintOnError(NULL, NULL); if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) Targ_PrintGraph(2); Trace_Log(MAKEERROR, 0); exit(2); /* Not 1 so -q can distinguish error */ } /* * Punt -- * Major exception once jobs are being created. Kills all jobs, prints * a message and exits. * * Results: * None * * Side Effects: * All children are killed indiscriminately and the program Lib_Exits */ /* VARARGS */ void Punt(const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void)fflush(stdout); (void)fprintf(stderr, "%s: ", progname); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); PrintOnError(NULL, NULL); DieHorribly(); } /*- * DieHorribly -- * Exit without giving a message. * * Results: * None * * Side Effects: * A big one... */ void DieHorribly(void) { if (jobsRunning) Job_AbortAll(); if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEERROR, 0); exit(2); /* Not 1, so -q can distinguish error */ } /* * Finish -- * Called when aborting due to errors in child shell to signal * abnormal exit. * * Results: * None * * Side Effects: * The program exits */ void Finish(int errors) /* number of errors encountered in Make_Make */ { Fatal("%d error%s", errors, errors == 1 ? "" : "s"); } /* * eunlink -- * Remove a file carefully, avoiding directories. */ int eunlink(const char *file) { struct stat st; if (lstat(file, &st) == -1) return -1; if (S_ISDIR(st.st_mode)) { errno = EISDIR; return -1; } return unlink(file); } /* * execError -- * Print why exec failed, avoiding stdio. */ void execError(const char *af, const char *av) { #ifdef USE_IOVEC int i = 0; struct iovec iov[8]; #define IOADD(s) \ (void)(iov[i].iov_base = UNCONST(s), \ iov[i].iov_len = strlen(iov[i].iov_base), \ i++) #else #define IOADD(s) (void)write(2, s, strlen(s)) #endif IOADD(progname); IOADD(": "); IOADD(af); IOADD("("); IOADD(av); IOADD(") failed ("); IOADD(strerror(errno)); IOADD(")\n"); #ifdef USE_IOVEC while (writev(2, iov, 8) == -1 && errno == EAGAIN) continue; #endif } /* * usage -- * exit with usage message */ static void usage(void) { char *p; if ((p = strchr(progname, '[')) != NULL) *p = '\0'; (void)fprintf(stderr, "usage: %s [-BeikNnqrstWwX] \n\ [-C directory] [-D variable] [-d flags] [-f makefile]\n\ [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\ [-V variable] [variable=value] [target ...]\n", progname); exit(2); } int PrintAddr(void *a, void *b) { printf("%lx ", (unsigned long) a); return b ? 0 : 0; } void PrintOnError(GNode *gn, const char *s) { static GNode *en = NULL; char tmp[64]; char *cp; if (s) printf("%s", s); printf("\n%s: stopped in %s\n", progname, curdir); if (en) return; /* we've been here! */ if (gn) { /* * We can print this even if there is no .ERROR target. */ Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0); } strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", sizeof(tmp) - 1); cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); if (cp) { if (*cp) printf("%s", cp); free(cp); } /* * Finally, see if there is a .ERROR target, and run it if so. */ en = Targ_FindNode(".ERROR", TARG_NOCREATE); if (en) { en->type |= OP_SPECIAL; Compat_Make(en, en); } } void Main_ExportMAKEFLAGS(Boolean first) { static int once = 1; char tmp[64]; char *s; if (once != first) return; once = 0; strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", sizeof(tmp)); s = Var_Subst(NULL, tmp, VAR_CMD, 0); if (s && *s) { #ifdef POSIX setenv("MAKEFLAGS", s, 1); #else setenv("MAKE", s, 1); #endif } } char * getTmpdir(void) { static char *tmpdir = NULL; if (!tmpdir) { struct stat st; /* * Honor $TMPDIR but only if it is valid. * Ensure it ends with /. */ tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, 0); if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { free(tmpdir); tmpdir = bmake_strdup(_PATH_TMP); } } return tmpdir; } /* * Create and open a temp file using "pattern". * If "fnamep" is provided set it to a copy of the filename created. * Otherwise unlink the file once open. */ int mkTempFile(const char *pattern, char **fnamep) { static char *tmpdir = NULL; char tfile[MAXPATHLEN]; int fd; if (!pattern) pattern = TMPPAT; if (!tmpdir) tmpdir = getTmpdir(); if (pattern[0] == '/') { snprintf(tfile, sizeof(tfile), "%s", pattern); } else { snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern); } if ((fd = mkstemp(tfile)) < 0) Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); if (fnamep) { *fnamep = bmake_strdup(tfile); } else { unlink(tfile); /* we just want the descriptor */ } return fd; } /* * Return a Boolean based on setting of a knob. * * If the knob is not set, the supplied default is the return value. * If set, anything that looks or smells like "No", "False", "Off", "0" etc, * is FALSE, otherwise TRUE. */ Boolean getBoolean(const char *name, Boolean bf) { char tmp[64]; char *cp; if (snprintf(tmp, sizeof(tmp), "${%s:tl}", name) < (int)(sizeof(tmp))) { cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); if (cp) { switch(*cp) { case '\0': /* not set - the default wins */ break; case '0': case 'f': case 'n': bf = FALSE; break; case 'o': switch (cp[1]) { case 'f': bf = FALSE; break; default: bf = TRUE; break; } break; default: bf = TRUE; break; } free(cp); } } return (bf); } Index: head/contrib/bmake/make.1 =================================================================== --- head/contrib/bmake/make.1 (revision 281811) +++ head/contrib/bmake/make.1 (revision 281812) @@ -1,2177 +1,2285 @@ -.\" $NetBSD: make.1,v 1.230 2014/02/15 18:55:30 sjg Exp $ +.\" $NetBSD: make.1,v 1.247 2015/04/10 08:43:32 wiz 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 February 14, 2014 +.Dd April 9, 2015 .Dt MAKE 1 .Os .Sh NAME .Nm make .Nd maintain program dependencies .Sh SYNOPSIS .Nm .Op Fl BeikNnqrstWwX .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 w -Print entering and leaving directory messages, pre and post processing. .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 w +Print entering and leaving directory messages, pre and post processing. .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 +Each target may have associated with it one or more lines of shell +commands, normally used to create the target. -Each of the commands in this script +Each of the lines 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 +(For historical reasons, spaces are not accepted.) +While targets can appear in many dependency lines if desired, by +default only one of these rules may be followed by a creation +script. +If the .Ql Ic \&:: -operator is used. +operator is used, however, all rules may include scripts and the +scripts are executed in the order found. .Pp -If the first characters of the command line are any combination of +Each line is treated as a separate shell command, unless the end of +line is escaped with a backslash +.Pq Ql \e +in which case that line and the next are combined. +.\" The escaped newline is retained and passed to the shell, which +.\" normally ignores it. +.\" However, the tab at the beginning of the following line is removed. +If the first characters of the command 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 \- +in compatibility mode 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 +it will be passed to the shell; otherwise .Nm will attempt direct execution. +If a line starts with +.Ql Ic \- +and the shell has ErrCtl enabled then failure of the command line +will be ignored as in compatibility mode. +Otherwise +.Ql Ic \- +affects the entire job; +the script will stop at the first command line that fails, +but the target will not be deemed to have failed. .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: +.Dq chdir +without potentially changing the directory for subsequent commands +should be put in parentheses so it executes in a subshell. +To force the use of one shell, escape the line breaks so as to make +the whole script one command. +For example: .Bd -literal -offset indent - avoid-chdir-side-effects: @echo Building $@ in `pwd` - @(cd ${.CURDIR} && ${.MAKE} $@) + @(cd ${.CURDIR} && ${MAKE} $@) @echo Back in `pwd` ensure-one-shell-regardless-of-mode: - @echo Building $@ in `pwd`; \\ - (cd ${.CURDIR} && ${.MAKE} $@); \\ + @echo Building $@ in `pwd`; \e + (cd ${.CURDIR} && ${MAKE} $@); \e echo Back in `pwd` .Ed +.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. .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. +.El +.Pp +Local variables are all built in and their values vary magically from +target to target. +It is not currently possible to define new local variables. The seven local variables are as follows: -.Bl -tag -width ".ARCHIVE" +.Bl -tag -width ".ARCHIVE" -offset indent .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. +The name of the archive file; also known as +.Ql Va \&! . .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. +The name of the archive member; also known as +.Ql Va % . .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 * . +The suffix must be one of the known suffixes declared with +.Ic .SUFFIXES +or it will not be recognized. .It Va .TARGET The name of the target; also known as .Ql Va @ . .El .Pp The shorter forms -.Ql Va @ , +.Ql ( Va \*[Gt] , +.Ql Va \&! , +.Ql Va \*[Lt] , +.Ql Va % , .Ql Va \&? , -.Ql Va \&\*[Lt] , -.Ql Va \&\*[Gt] , +.Ql Va * , and -.Ql Va * +.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" , +compatibility with historical makefiles and legacy POSIX make and are +not recommended. +.Pp +Variants of these variables with the punctuation followed immediately by +.Ql D +or +.Ql F , +e.g. +.Ql Va $(@D) , +are legacy forms equivalent to using the +.Ql :H and -.Ql Va "*D" -are permitted for compatibility with +.Ql :T +modifiers. +These forms are accepted for compatibility with .At V -makefiles and are not recommended. +makefiles and POSIX but 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 Va .INCLUDEDFROMDIR The directory of the file this Makefile was included from. .It Va .INCLUDEDFROMFILE The filename of the file this Makefile was included from. .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.ALWAYS_PASS_JOB_QUEUE Tells .Nm whether to pass the descriptors of the job token queue even if the target is not tagged with .Ic .MAKE The default is .Ql Pa yes for backwards compatability with .Fx 9.0 and earlier. .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 . If .Va .MAKE.JOB.PREFIX is empty, no token is printed. .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.IGNORE_PATHS Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: .Ql Pa /dev /etc /proc /tmp /var/run /var/tmp .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 . As a consequence of the way values are split into words, matched, and then joined, a construct like .Dl ${VAR:M*} will normalise the inter-word spacing, removing all leading and trailing space, and converting multiple consecutive spaces to single spaces. . .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 an extended 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. .Pp As for the .Cm \&:S modifier, the .Ar pattern and .Ar replacement are subjected to variable expansion before being parsed as regular expressions. .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 variable 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 .PATH. Ns Va suffix Like .Ic .PATH but applies only to files with a particular suffix. The suffix must have been previously declared with .Ic .SUFFIXES . .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, +The basic make syntax is compatible between different versions of make; however the special variables, variable modifiers and conditionals are not. +.Ss Older versions +An incomplete list of changes in older versions of +.Nm : .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. +.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. +.Ss Other make dialects +Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not +support most of the features of +.Nm +as described in this manual. +Most notably: +.Bl -bullet -offset indent +.It +The +.Ic .WAIT +and +.Ic .ORDER +declarations and most functionality pertaining to parallelization. +(GNU make supports parallelization but lacks these features needed to +control it effectively.) +.It +Directives, including for loops and conditionals and most of the +forms of include files. +(GNU make has its own incompatible and less powerful syntax for +conditionals.) +.It +All built-in variables that begin with a dot. +.It +Most of the special sources and targets that begin with a dot, +with the notable exception of +.Ic .PHONY , +.Ic .PRECIOUS , +and +.Ic .SUFFIXES . +.It +Variable modifiers, except for the +.Dl :old=new +string substitution, which does not portably support globbing with +.Ql % +and historically only works on declared suffixes. +.It +The +.Ic $> +variable even in its short form; most makes support this functionality +but its name varies. +.El +.Pp +Some features are somewhat more portable, such as assignment with +.Ic += , +.Ic ?= , +and +.Ic != . +The +.Ic .PATH +functionality is based on an older feature +.Ic VPATH +found in GNU make and many versions of SVR4 make; however, +historically its behavior is too ill-defined (and too buggy) to rely +upon. +.Pp +The +.Ic $@ +and +.Ic $< +variables are more or less universally portable, as is the +.Ic $(MAKE) +variable. +Basic use of suffix rules (for files only in the current directory, +not trying to chain transformations together, etc.) is also reasonably +portable. .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: head/contrib/bmake/make.c =================================================================== --- head/contrib/bmake/make.c (revision 281811) +++ head/contrib/bmake/make.c (revision 281812) @@ -1,1561 +1,1561 @@ -/* $NetBSD: make.c,v 1.88 2012/11/09 18:53:05 sjg Exp $ */ +/* $NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: make.c,v 1.88 2012/11/09 18:53:05 sjg Exp $"; +static char rcsid[] = "$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93"; #else -__RCSID("$NetBSD: make.c,v 1.88 2012/11/09 18:53:05 sjg Exp $"); +__RCSID("$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $"); #endif #endif /* not lint */ #endif /*- * make.c -- * The functions which perform the examination of targets and * their suitability for creation * * Interface: * Make_Run Initialize things for the module and recreate * whatever needs recreating. Returns TRUE if * work was (or would have been) done and FALSE * otherwise. * * Make_Update Update all parents of a given child. Performs * various bookkeeping chores like the updating * of the cmgn field of the parent, filling * of the IMPSRC context variable, etc. It will * place the parent on the toBeMade queue if it * should be. * * Make_TimeStamp Function to set the parent's cmgn field * based on a child's modification time. * * Make_DoAllVar Set up the various local variables for a * target, including the .ALLSRC variable, making * sure that any variable that needs to exist * at the very least has the empty value. * * Make_OODate Determine if a target is out-of-date. * * Make_HandleUse See if a child is a .USE node for a parent * and perform the .USE actions if so. * * Make_ExpandUse Expand .USE nodes */ #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" static unsigned int checked = 1;/* Sequence # to detect recursion */ static Lst toBeMade; /* The current fringe of the graph. These * are nodes which await examination by * MakeOODate. It is added to by * Make_Update and subtracted from by * MakeStartJobs */ static int MakeAddChild(void *, void *); static int MakeFindChild(void *, void *); static int MakeUnmark(void *, void *); static int MakeAddAllSrc(void *, void *); static int MakeTimeStamp(void *, void *); static int MakeHandleUse(void *, void *); static Boolean MakeStartJobs(void); static int MakePrintStatus(void *, void *); static int MakeCheckOrder(void *, void *); static int MakeBuildChild(void *, void *); static int MakeBuildParent(void *, void *); MAKE_ATTR_DEAD static void make_abort(GNode *gn, int line) { static int two = 2; fprintf(debug_file, "make_abort from line %d\n", line); Targ_PrintNode(gn, &two); Lst_ForEach(toBeMade, Targ_PrintNode, &two); Targ_PrintGraph(3); abort(); } /*- *----------------------------------------------------------------------- * Make_TimeStamp -- * Set the cmgn field of a parent node based on the mtime stamp in its * child. Called from MakeOODate via Lst_ForEach. * * Input: * pgn the current parent * cgn the child we've just examined * * Results: * Always returns 0. * * Side Effects: * The cmgn of the parent node will be changed if the mtime * field of the child is greater than it. *----------------------------------------------------------------------- */ int Make_TimeStamp(GNode *pgn, GNode *cgn) { if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) { pgn->cmgn = cgn; } return (0); } /* * Input: * pgn the current parent * cgn the child we've just examined * */ static int MakeTimeStamp(void *pgn, void *cgn) { return Make_TimeStamp((GNode *)pgn, (GNode *)cgn); } /*- *----------------------------------------------------------------------- * Make_OODate -- * See if a given node is out of date with respect to its sources. * Used by Make_Run when deciding which nodes to place on the * toBeMade queue initially and by Make_Update to screen out USE and * EXEC nodes. In the latter case, however, any other sort of node * must be considered out-of-date since at least one of its children * will have been recreated. * * Input: * gn the node to check * * Results: * TRUE if the node is out of date. FALSE otherwise. * * Side Effects: * The mtime field of the node and the cmgn field of its parents * will/may be changed. *----------------------------------------------------------------------- */ Boolean Make_OODate(GNode *gn) { Boolean oodate; /* * Certain types of targets needn't even be sought as their datedness * doesn't depend on their modification time... */ if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) { (void)Dir_MTime(gn, 1); if (DEBUG(MAKE)) { if (gn->mtime != 0) { fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime)); } else { fprintf(debug_file, "non-existent..."); } } } /* * A target is remade in one of the following circumstances: * its modification time is smaller than that of its youngest child * and it would actually be run (has commands or type OP_NOP) * it's the object of a force operator * it has no children, was on the lhs of an operator and doesn't exist * already. * * Libraries are only considered out-of-date if the archive module says * they are. * * These weird rules are brought to you by Backward-Compatibility and * the strange people who wrote 'Make'. */ if (gn->type & (OP_USE|OP_USEBEFORE)) { /* * If the node is a USE node it is *never* out of date * no matter *what*. */ if (DEBUG(MAKE)) { fprintf(debug_file, ".USE node..."); } oodate = FALSE; } else if ((gn->type & OP_LIB) && ((gn->mtime==0) || Arch_IsLib(gn))) { if (DEBUG(MAKE)) { fprintf(debug_file, "library..."); } /* * always out of date if no children and :: target * or non-existent. */ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP))); } else if (gn->type & OP_JOIN) { /* * A target with the .JOIN attribute is only considered * out-of-date if any of its children was out-of-date. */ if (DEBUG(MAKE)) { fprintf(debug_file, ".JOIN node..."); } if (DEBUG(MAKE)) { fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); } oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { /* * A node which is the object of the force (!) operator or which has * the .EXEC attribute is always considered out-of-date. */ if (DEBUG(MAKE)) { if (gn->type & OP_FORCE) { fprintf(debug_file, "! operator..."); } else if (gn->type & OP_PHONY) { fprintf(debug_file, ".PHONY node..."); } else { fprintf(debug_file, ".EXEC node..."); } } oodate = TRUE; } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) || (gn->cmgn == NULL && ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) || gn->type & OP_DOUBLEDEP))) { /* * A node whose modification time is less than that of its * youngest child or that has no children (cmgn == NULL) and * either doesn't exist (mtime == 0) and it isn't optional * or was the object of a * :: operator is out-of-date. * Why? Because that's the way Make does it. */ if (DEBUG(MAKE)) { if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) { fprintf(debug_file, "modified before source %s...", gn->cmgn->path); } else if (gn->mtime == 0) { fprintf(debug_file, "non-existent and no sources..."); } else { fprintf(debug_file, ":: operator and no sources..."); } } oodate = TRUE; } else { /* * When a non-existing child with no sources * (such as a typically used FORCE source) has been made and * the target of the child (usually a directory) has the same * timestamp as the timestamp just given to the non-existing child * after it was considered made. */ if (DEBUG(MAKE)) { if (gn->flags & FORCE) fprintf(debug_file, "non existing child..."); } oodate = (gn->flags & FORCE) ? TRUE : FALSE; } #ifdef USE_META if (useMeta) { oodate = meta_oodate(gn, oodate); } #endif /* * If the target isn't out-of-date, the parents need to know its * modification time. Note that targets that appear to be out-of-date * but aren't, because they have no commands and aren't of type OP_NOP, * have their mtime stay below their children's mtime to keep parents from * thinking they're out-of-date. */ if (!oodate) { Lst_ForEach(gn->parents, MakeTimeStamp, gn); } return (oodate); } /*- *----------------------------------------------------------------------- * MakeAddChild -- * Function used by Make_Run to add a child to the list l. * It will only add the child if its make field is FALSE. * * Input: * gnp the node to add * lp the list to which to add it * * Results: * Always returns 0 * * Side Effects: * The given list is extended *----------------------------------------------------------------------- */ static int MakeAddChild(void *gnp, void *lp) { GNode *gn = (GNode *)gnp; Lst l = (Lst) lp; if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { if (DEBUG(MAKE)) fprintf(debug_file, "MakeAddChild: need to examine %s%s\n", gn->name, gn->cohort_num); (void)Lst_EnQueue(l, gn); } return (0); } /*- *----------------------------------------------------------------------- * MakeFindChild -- * Function used by Make_Run to find the pathname of a child * that was already made. * * Input: * gnp the node to find * * Results: * Always returns 0 * * Side Effects: * The path and mtime of the node and the cmgn of the parent are * updated; the unmade children count of the parent is decremented. *----------------------------------------------------------------------- */ static int MakeFindChild(void *gnp, void *pgnp) { GNode *gn = (GNode *)gnp; GNode *pgn = (GNode *)pgnp; (void)Dir_MTime(gn, 0); Make_TimeStamp(pgn, gn); pgn->unmade--; return (0); } /*- *----------------------------------------------------------------------- * Make_HandleUse -- * Function called by Make_Run and SuffApplyTransform on the downward * pass to handle .USE and transformation nodes. It implements the * .USE and transformation functionality by copying the node's commands, * type flags and children to the parent node. * * A .USE node is much like an explicit transformation rule, except * its commands are always added to the target node, even if the * target already has commands. * * Input: * cgn The .USE node * pgn The target of the .USE node * * Results: * none * * Side Effects: * Children and commands may be added to the parent and the parent's * type may be changed. * *----------------------------------------------------------------------- */ void Make_HandleUse(GNode *cgn, GNode *pgn) { LstNode ln; /* An element in the children list */ #ifdef DEBUG_SRC if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name); return; } #endif if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) { if (cgn->type & OP_USEBEFORE) { /* * .USEBEFORE -- * prepend the child's commands to the parent. */ Lst cmds = pgn->commands; pgn->commands = Lst_Duplicate(cgn->commands, NULL); (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW); Lst_Destroy(cmds, NULL); } else { /* * .USE or target has no commands -- * append the child's commands to the parent. */ (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW); } } if (Lst_Open(cgn->children) == SUCCESS) { while ((ln = Lst_Next(cgn->children)) != NULL) { GNode *tgn, *gn = (GNode *)Lst_Datum(ln); /* * Expand variables in the .USE node's name * and save the unexpanded form. * We don't need to do this for commands. * They get expanded properly when we execute. */ if (gn->uname == NULL) { gn->uname = gn->name; } else { if (gn->name) free(gn->name); } gn->name = Var_Subst(NULL, gn->uname, pgn, FALSE); if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) { /* See if we have a target for this node. */ tgn = Targ_FindNode(gn->name, TARG_NOCREATE); if (tgn != NULL) gn = tgn; } (void)Lst_AtEnd(pgn->children, gn); (void)Lst_AtEnd(gn->parents, pgn); pgn->unmade += 1; } Lst_Close(cgn->children); } pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM); } /*- *----------------------------------------------------------------------- * MakeHandleUse -- * Callback function for Lst_ForEach, used by Make_Run on the downward * pass to handle .USE nodes. Should be called before the children * are enqueued to be looked at by MakeAddChild. * This function calls Make_HandleUse to copy the .USE node's commands, * type flags and children to the parent node. * * Input: * cgnp the child we've just examined * pgnp the current parent * * Results: * returns 0. * * Side Effects: * After expansion, .USE child nodes are removed from the parent * *----------------------------------------------------------------------- */ static int MakeHandleUse(void *cgnp, void *pgnp) { GNode *cgn = (GNode *)cgnp; GNode *pgn = (GNode *)pgnp; LstNode ln; /* An element in the children list */ int unmarked; unmarked = ((cgn->type & OP_MARK) == 0); cgn->type |= OP_MARK; if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) return (0); if (unmarked) Make_HandleUse(cgn, pgn); /* * This child node is now "made", so we decrement the count of * unmade children in the parent... We also remove the child * from the parent's list to accurately reflect the number of decent * children the parent has. This is used by Make_Run to decide * whether to queue the parent or examine its children... */ if ((ln = Lst_Member(pgn->children, cgn)) != NULL) { Lst_Remove(pgn->children, ln); pgn->unmade--; } return (0); } /*- *----------------------------------------------------------------------- * Make_Recheck -- * Check the modification time of a gnode, and update it as described * in the comments below. * * Results: - * returns 0 if the gnode does not exist, or it's filesystem + * returns 0 if the gnode does not exist, or its filesystem * time if it does. * * Side Effects: * the gnode's modification time and path name are affected. * *----------------------------------------------------------------------- */ time_t Make_Recheck(GNode *gn) { time_t mtime = Dir_MTime(gn, 1); #ifndef RECHECK /* * We can't re-stat the thing, but we can at least take care of rules * where a target depends on a source that actually creates the * target, but only if it has changed, e.g. * * parse.h : parse.o * * parse.o : parse.y * yacc -d parse.y * cc -c y.tab.c * mv y.tab.o parse.o * cmp -s y.tab.h parse.h || mv y.tab.h parse.h * * In this case, if the definitions produced by yacc haven't changed * from before, parse.h won't have been updated and gn->mtime will * reflect the current modification time for parse.h. This is * something of a kludge, I admit, but it's a useful one.. * XXX: People like to use a rule like * * FRC: * * To force things that depend on FRC to be made, so we have to * check for gn->children being empty as well... */ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { gn->mtime = now; } #else /* * This is what Make does and it's actually a good thing, as it * allows rules like * * cmp -s y.tab.h parse.h || cp y.tab.h parse.h * * to function as intended. Unfortunately, thanks to the stateless * nature of NFS (by which I mean the loose coupling of two clients * using the same file from a common server), there are times * when the modification time of a file created on a remote * machine will not be modified before the local stat() implied by * the Dir_MTime occurs, thus leading us to believe that the file * is unchanged, wreaking havoc with files that depend on this one. * * I have decided it is better to make too much than to make too * little, so this stuff is commented out unless you're sure it's ok. * -- ardeb 1/12/88 */ /* * Christos, 4/9/92: If we are saving commands pretend that * the target is made now. Otherwise archives with ... rules * don't work! */ if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) || (mtime == 0 && !(gn->type & OP_WAIT))) { if (DEBUG(MAKE)) { fprintf(debug_file, " recheck(%s): update time from %s to now\n", gn->name, Targ_FmtTime(gn->mtime)); } gn->mtime = now; } else { if (DEBUG(MAKE)) { fprintf(debug_file, " recheck(%s): current update time: %s\n", gn->name, Targ_FmtTime(gn->mtime)); } } #endif return mtime; } /*- *----------------------------------------------------------------------- * Make_Update -- * Perform update on the parents of a node. Used by JobFinish once * a node has been dealt with and by MakeStartJobs if it finds an * up-to-date node. * * Input: * cgn the child node * * Results: * Always returns 0 * * Side Effects: * The unmade field of pgn is decremented and pgn may be placed on * the toBeMade queue if this field becomes 0. * * If the child was made, the parent's flag CHILDMADE field will be * set true. * * If the child is not up-to-date and still does not exist, * set the FORCE flag on the parents. * * If the child wasn't made, the cmgn field of the parent will be * altered if the child's mtime is big enough. * * Finally, if the child is the implied source for the parent, the * parent's IMPSRC variable is set appropriately. * *----------------------------------------------------------------------- */ void Make_Update(GNode *cgn) { GNode *pgn; /* the parent node */ char *cname; /* the child's name */ LstNode ln; /* Element in parents and iParents lists */ time_t mtime = -1; char *p1; Lst parents; GNode *centurion; /* It is save to re-examine any nodes again */ checked++; cname = Var_Value(TARGET, cgn, &p1); if (p1) free(p1); if (DEBUG(MAKE)) fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); /* * If the child was actually made, see what its modification time is * now -- some rules won't actually update the file. If the file still * doesn't exist, make its mtime now. */ if (cgn->made != UPTODATE) { mtime = Make_Recheck(cgn); } /* * If this is a `::' node, we must consult its first instance * which is where all parents are linked. */ if ((centurion = cgn->centurion) != NULL) { if (!Lst_IsEmpty(cgn->parents)) Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num); centurion->unmade_cohorts -= 1; if (centurion->unmade_cohorts < 0) Error("Graph cycles through centurion %s", centurion->name); } else { centurion = cgn; } parents = centurion->parents; /* If this was a .ORDER node, schedule the RHS */ Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade)); /* Now mark all the parents as having one less unmade child */ if (Lst_Open(parents) == SUCCESS) { while ((ln = Lst_Next(parents)) != NULL) { pgn = (GNode *)Lst_Datum(ln); if (DEBUG(MAKE)) fprintf(debug_file, "inspect parent %s%s: flags %x, " "type %x, made %d, unmade %d ", pgn->name, pgn->cohort_num, pgn->flags, pgn->type, pgn->made, pgn->unmade-1); if (!(pgn->flags & REMAKE)) { /* This parent isn't needed */ if (DEBUG(MAKE)) fprintf(debug_file, "- not needed\n"); continue; } if (mtime == 0 && !(cgn->type & OP_WAIT)) pgn->flags |= FORCE; /* * If the parent has the .MADE attribute, its timestamp got * updated to that of its newest child, and its unmake * child count got set to zero in Make_ExpandUse(). * However other things might cause us to build one of its * children - and so we mustn't do any processing here when * the child build finishes. */ if (pgn->type & OP_MADE) { if (DEBUG(MAKE)) fprintf(debug_file, "- .MADE\n"); continue; } if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) { if (cgn->made == MADE) pgn->flags |= CHILDMADE; (void)Make_TimeStamp(pgn, cgn); } /* * A parent must wait for the completion of all instances * of a `::' dependency. */ if (centurion->unmade_cohorts != 0 || centurion->made < MADE) { if (DEBUG(MAKE)) fprintf(debug_file, "- centurion made %d, %d unmade cohorts\n", centurion->made, centurion->unmade_cohorts); continue; } /* One more child of this parent is now made */ pgn->unmade -= 1; if (pgn->unmade < 0) { if (DEBUG(MAKE)) { fprintf(debug_file, "Graph cycles through %s%s\n", pgn->name, pgn->cohort_num); Targ_PrintGraph(2); } Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num); } /* We must always rescan the parents of .WAIT and .ORDER nodes. */ if (pgn->unmade != 0 && !(centurion->type & OP_WAIT) && !(centurion->flags & DONE_ORDER)) { if (DEBUG(MAKE)) fprintf(debug_file, "- unmade children\n"); continue; } if (pgn->made != DEFERRED) { /* * Either this parent is on a different branch of the tree, * or it on the RHS of a .WAIT directive * or it is already on the toBeMade list. */ if (DEBUG(MAKE)) fprintf(debug_file, "- not deferred\n"); continue; } if (pgn->order_pred && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) { /* A .ORDER rule stops us building this */ continue; } if (DEBUG(MAKE)) { static int two = 2; fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n", cgn->name, cgn->cohort_num, pgn->name, pgn->cohort_num, pgn->made); Targ_PrintNode(pgn, &two); } /* Ok, we can schedule the parent again */ pgn->made = REQUESTED; (void)Lst_EnQueue(toBeMade, pgn); } Lst_Close(parents); } /* * Set the .PREFIX and .IMPSRC variables for all the implied parents * of this node. */ if (Lst_Open(cgn->iParents) == SUCCESS) { char *cpref = Var_Value(PREFIX, cgn, &p1); while ((ln = Lst_Next(cgn->iParents)) != NULL) { pgn = (GNode *)Lst_Datum(ln); if (pgn->flags & REMAKE) { Var_Set(IMPSRC, cname, pgn, 0); if (cpref != NULL) Var_Set(PREFIX, cpref, pgn, 0); } } if (p1) free(p1); Lst_Close(cgn->iParents); } } /*- *----------------------------------------------------------------------- * MakeAddAllSrc -- * Add a child's name to the ALLSRC and OODATE variables of the given * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. * .EXEC and .USE children are very rarely going to be files, so... * If the child is a .JOIN node, its ALLSRC is propagated to the parent. * * A child is added to the OODATE variable if its modification time is * later than that of its parent, as defined by Make, except if the * parent is a .JOIN node. In that case, it is only added to the OODATE * variable if it was actually made (since .JOIN nodes don't have * modification times, the comparison is rather unfair...).. * * Results: * Always returns 0 * * Side Effects: * The ALLSRC variable for the given node is extended. *----------------------------------------------------------------------- */ static int MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED) { GNode *cgn = (GNode *)cgnp; cgn->type &= ~OP_MARK; return (0); } /* * Input: * cgnp The child to add * pgnp The parent to whose ALLSRC variable it should * be added * */ static int MakeAddAllSrc(void *cgnp, void *pgnp) { GNode *cgn = (GNode *)cgnp; GNode *pgn = (GNode *)pgnp; if (cgn->type & OP_MARK) return (0); cgn->type |= OP_MARK; if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { char *child, *allsrc; char *p1 = NULL, *p2 = NULL; if (cgn->type & OP_ARCHV) child = Var_Value(MEMBER, cgn, &p1); else child = cgn->path ? cgn->path : cgn->name; if (cgn->type & OP_JOIN) { allsrc = Var_Value(ALLSRC, cgn, &p2); } else { allsrc = child; } if (allsrc != NULL) Var_Append(ALLSRC, allsrc, pgn); if (p2) free(p2); if (pgn->type & OP_JOIN) { if (cgn->made == MADE) { Var_Append(OODATE, child, pgn); } } else if ((pgn->mtime < cgn->mtime) || (cgn->mtime >= now && cgn->made == MADE)) { /* * It goes in the OODATE variable if the parent is younger than the * child or if the child has been modified more recently than * the start of the make. This is to keep pmake from getting * confused if something else updates the parent after the * make starts (shouldn't happen, I know, but sometimes it * does). In such a case, if we've updated the kid, the parent * is likely to have a modification time later than that of * the kid and anything that relies on the OODATE variable will * be hosed. * * XXX: This will cause all made children to go in the OODATE * variable, even if they're not touched, if RECHECK isn't defined, * since cgn->mtime is set to now in Make_Update. According to * some people, this is good... */ Var_Append(OODATE, child, pgn); } if (p1) free(p1); } return (0); } /*- *----------------------------------------------------------------------- * Make_DoAllVar -- * Set up the ALLSRC and OODATE variables. Sad to say, it must be * done separately, rather than while traversing the graph. This is * because Make defined OODATE to contain all sources whose modification * times were later than that of the target, *not* those sources that * were out-of-date. Since in both compatibility and native modes, * the modification time of the parent isn't found until the child * has been dealt with, we have to wait until now to fill in the * variable. As for ALLSRC, the ordering is important and not * guaranteed when in native mode, so it must be set here, too. * * Results: * None * * Side Effects: * The ALLSRC and OODATE variables of the given node is filled in. * If the node is a .JOIN node, its TARGET variable will be set to * match its ALLSRC variable. *----------------------------------------------------------------------- */ void Make_DoAllVar(GNode *gn) { if (gn->flags & DONE_ALLSRC) return; Lst_ForEach(gn->children, MakeUnmark, gn); Lst_ForEach(gn->children, MakeAddAllSrc, gn); if (!Var_Exists (OODATE, gn)) { Var_Set(OODATE, "", gn, 0); } if (!Var_Exists (ALLSRC, gn)) { Var_Set(ALLSRC, "", gn, 0); } if (gn->type & OP_JOIN) { char *p1; Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0); if (p1) free(p1); } gn->flags |= DONE_ALLSRC; } /*- *----------------------------------------------------------------------- * MakeStartJobs -- * Start as many jobs as possible. * * Results: * If the query flag was given to pmake, no job will be started, * but as soon as an out-of-date target is found, this function * returns TRUE. At all other times, this function returns FALSE. * * Side Effects: * Nodes are removed from the toBeMade queue and job table slots * are filled. * *----------------------------------------------------------------------- */ static int MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) { GNode *bn = v_bn; if (bn->made >= MADE || !(bn->flags & REMAKE)) return 0; if (DEBUG(MAKE)) fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", bn->name, bn->cohort_num); return 1; } static int MakeBuildChild(void *v_cn, void *toBeMade_next) { GNode *cn = v_cn; if (DEBUG(MAKE)) fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n", cn->name, cn->cohort_num, cn->made, cn->type); if (cn->made > DEFERRED) return 0; /* If this node is on the RHS of a .ORDER, check LHSs. */ if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; return 0; /* but keep looking */ } if (DEBUG(MAKE)) fprintf(debug_file, "MakeBuildChild: schedule %s%s\n", cn->name, cn->cohort_num); cn->made = REQUESTED; if (toBeMade_next == NULL) Lst_AtEnd(toBeMade, cn); else Lst_InsertBefore(toBeMade, toBeMade_next, cn); if (cn->unmade_cohorts != 0) Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next); /* * If this node is a .WAIT node with unmade chlidren * then don't add the next sibling. */ return cn->type & OP_WAIT && cn->unmade > 0; } /* When a .ORDER LHS node completes we do this on each RHS */ static int MakeBuildParent(void *v_pn, void *toBeMade_next) { GNode *pn = v_pn; if (pn->made != DEFERRED) return 0; if (MakeBuildChild(pn, toBeMade_next) == 0) { /* Mark so that when this node is built we reschedule its parents */ pn->flags |= DONE_ORDER; } return 0; } static Boolean MakeStartJobs(void) { GNode *gn; int have_token = 0; while (!Lst_IsEmpty (toBeMade)) { /* Get token now to avoid cycling job-list when we only have 1 token */ if (!have_token && !Job_TokenWithdraw()) break; have_token = 1; gn = (GNode *)Lst_DeQueue(toBeMade); if (DEBUG(MAKE)) fprintf(debug_file, "Examining %s%s...\n", gn->name, gn->cohort_num); if (gn->made != REQUESTED) { if (DEBUG(MAKE)) fprintf(debug_file, "state %d\n", gn->made); make_abort(gn, __LINE__); } if (gn->checked == checked) { /* We've already looked at this node since a job finished... */ if (DEBUG(MAKE)) fprintf(debug_file, "already checked %s%s\n", gn->name, gn->cohort_num); gn->made = DEFERRED; continue; } gn->checked = checked; if (gn->unmade != 0) { /* * We can't build this yet, add all unmade children to toBeMade, * just before the current first element. */ gn->made = DEFERRED; Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade)); /* and drop this node on the floor */ if (DEBUG(MAKE)) fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num); continue; } gn->made = BEINGMADE; if (Make_OODate(gn)) { if (DEBUG(MAKE)) { fprintf(debug_file, "out-of-date\n"); } if (queryFlag) { return (TRUE); } Make_DoAllVar(gn); Job_Make(gn); have_token = 0; } else { if (DEBUG(MAKE)) { fprintf(debug_file, "up-to-date\n"); } gn->made = UPTODATE; if (gn->type & OP_JOIN) { /* * Even for an up-to-date .JOIN node, we need it to have its * context variables so references to it get the correct * value for .TARGET when building up the context variables * of its parent(s)... */ Make_DoAllVar(gn); } Make_Update(gn); } } if (have_token) Job_TokenReturn(); return (FALSE); } /*- *----------------------------------------------------------------------- * MakePrintStatus -- * Print the status of a top-level node, viz. it being up-to-date * already or not created due to an error in a lower level. * Callback function for Make_Run via Lst_ForEach. * * Input: * gnp Node to examine * cyclep True if gn->unmade being non-zero implies a * cycle in the graph, not an error in an * inferior. * * Results: * Always returns 0. * * Side Effects: * A message may be printed. * *----------------------------------------------------------------------- */ static int MakePrintStatusOrder(void *ognp, void *gnp) { GNode *ogn = ognp; GNode *gn = gnp; if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED) /* not waiting for this one */ return 0; printf(" `%s%s' has .ORDER dependency against %s%s " "(made %d, flags %x, type %x)\n", gn->name, gn->cohort_num, ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); if (DEBUG(MAKE) && debug_file != stdout) fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s " "(made %d, flags %x, type %x)\n", gn->name, gn->cohort_num, ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); return 0; } static int MakePrintStatus(void *gnp, void *v_errors) { GNode *gn = (GNode *)gnp; int *errors = v_errors; if (gn->flags & DONECYCLE) /* We've completely processed this node before, don't do it again. */ return 0; if (gn->unmade == 0) { gn->flags |= DONECYCLE; switch (gn->made) { case UPTODATE: printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num); break; case MADE: break; case UNMADE: case DEFERRED: case REQUESTED: case BEINGMADE: (*errors)++; printf("`%s%s' was not built (made %d, flags %x, type %x)!\n", gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); if (DEBUG(MAKE) && debug_file != stdout) fprintf(debug_file, "`%s%s' was not built (made %d, flags %x, type %x)!\n", gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); /* Most likely problem is actually caused by .ORDER */ Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn); break; default: /* Errors - already counted */ printf("`%s%s' not remade because of errors.\n", gn->name, gn->cohort_num); if (DEBUG(MAKE) && debug_file != stdout) fprintf(debug_file, "`%s%s' not remade because of errors.\n", gn->name, gn->cohort_num); break; } return 0; } if (DEBUG(MAKE)) fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n", gn->name, gn->cohort_num, gn->unmade); /* * If printing cycles and came to one that has unmade children, * print out the cycle by recursing on its children. */ if (!(gn->flags & CYCLE)) { /* Fist time we've seen this node, check all children */ gn->flags |= CYCLE; Lst_ForEach(gn->children, MakePrintStatus, errors); /* Mark that this node needn't be processed again */ gn->flags |= DONECYCLE; return 0; } /* Only output the error once per node */ gn->flags |= DONECYCLE; Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); if ((*errors)++ > 100) /* Abandon the whole error report */ return 1; /* Reporting for our children will give the rest of the loop */ Lst_ForEach(gn->children, MakePrintStatus, errors); return 0; } /*- *----------------------------------------------------------------------- * Make_ExpandUse -- * Expand .USE nodes and create a new targets list * * Input: * targs the initial list of targets * * Side Effects: *----------------------------------------------------------------------- */ void Make_ExpandUse(Lst targs) { GNode *gn; /* a temporary pointer */ Lst examine; /* List of targets to examine */ examine = Lst_Duplicate(targs, NULL); /* * Make an initial downward pass over the graph, marking nodes to be made * as we go down. We call Suff_FindDeps to find where a node is and * to get some children for it if it has none and also has no commands. * If the node is a leaf, we stick it on the toBeMade queue to * be looked at in a minute, otherwise we add its children to our queue * and go on about our business. */ while (!Lst_IsEmpty (examine)) { gn = (GNode *)Lst_DeQueue(examine); if (gn->flags & REMAKE) /* We've looked at this one already */ continue; gn->flags |= REMAKE; if (DEBUG(MAKE)) fprintf(debug_file, "Make_ExpandUse: examine %s%s\n", gn->name, gn->cohort_num); if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) { /* Append all the 'cohorts' to the list of things to examine */ Lst new; new = Lst_Duplicate(gn->cohorts, NULL); Lst_Concat(new, examine, LST_CONCLINK); examine = new; } /* * Apply any .USE rules before looking for implicit dependencies * to make sure everything has commands that should... * Make sure that the TARGET is set, so that we can make * expansions. */ if (gn->type & OP_ARCHV) { char *eoa, *eon; eoa = strchr(gn->name, '('); eon = strchr(gn->name, ')'); if (eoa == NULL || eon == NULL) continue; *eoa = '\0'; *eon = '\0'; Var_Set(MEMBER, eoa + 1, gn, 0); Var_Set(ARCHIVE, gn->name, gn, 0); *eoa = '('; *eon = ')'; } (void)Dir_MTime(gn, 0); Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); Lst_ForEach(gn->children, MakeUnmark, gn); Lst_ForEach(gn->children, MakeHandleUse, gn); if ((gn->type & OP_MADE) == 0) Suff_FindDeps(gn); else { /* Pretend we made all this node's children */ Lst_ForEach(gn->children, MakeFindChild, gn); if (gn->unmade != 0) printf("Warning: %s%s still has %d unmade children\n", gn->name, gn->cohort_num, gn->unmade); } if (gn->unmade != 0) Lst_ForEach(gn->children, MakeAddChild, examine); } Lst_Destroy(examine, NULL); } /*- *----------------------------------------------------------------------- * Make_ProcessWait -- * Convert .WAIT nodes into dependencies * * Input: * targs the initial list of targets * *----------------------------------------------------------------------- */ static int link_parent(void *cnp, void *pnp) { GNode *cn = cnp; GNode *pn = pnp; Lst_AtEnd(pn->children, cn); Lst_AtEnd(cn->parents, pn); pn->unmade++; return 0; } static int add_wait_dep(void *v_cn, void *v_wn) { GNode *cn = v_cn; GNode *wn = v_wn; if (cn == wn) return 1; if (cn == NULL || wn == NULL) { printf("bad wait dep %p %p\n", cn, wn); exit(4); } if (DEBUG(MAKE)) fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n", cn->name, cn->cohort_num, wn->name); Lst_AtEnd(wn->children, cn); wn->unmade++; Lst_AtEnd(cn->parents, wn); return 0; } static void Make_ProcessWait(Lst targs) { GNode *pgn; /* 'parent' node we are examining */ GNode *cgn; /* Each child in turn */ LstNode owln; /* Previous .WAIT node */ Lst examine; /* List of targets to examine */ LstNode ln; /* * We need all the nodes to have a common parent in order for the * .WAIT and .ORDER scheduling to work. * Perhaps this should be done earlier... */ pgn = Targ_NewGN(".MAIN"); pgn->flags = REMAKE; pgn->type = OP_PHONY | OP_DEPENDS; /* Get it displayed in the diag dumps */ Lst_AtFront(Targ_List(), pgn); Lst_ForEach(targs, link_parent, pgn); /* Start building with the 'dummy' .MAIN' node */ MakeBuildChild(pgn, NULL); examine = Lst_Init(FALSE); Lst_AtEnd(examine, pgn); while (!Lst_IsEmpty (examine)) { pgn = Lst_DeQueue(examine); /* We only want to process each child-list once */ if (pgn->flags & DONE_WAIT) continue; pgn->flags |= DONE_WAIT; if (DEBUG(MAKE)) fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name); if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) { /* Append all the 'cohorts' to the list of things to examine */ Lst new; new = Lst_Duplicate(pgn->cohorts, NULL); Lst_Concat(new, examine, LST_CONCLINK); examine = new; } owln = Lst_First(pgn->children); Lst_Open(pgn->children); for (; (ln = Lst_Next(pgn->children)) != NULL; ) { cgn = Lst_Datum(ln); if (cgn->type & OP_WAIT) { /* Make the .WAIT node depend on the previous children */ Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn); owln = ln; } else { Lst_AtEnd(examine, cgn); } } Lst_Close(pgn->children); } Lst_Destroy(examine, NULL); } /*- *----------------------------------------------------------------------- * Make_Run -- * Initialize the nodes to remake and the list of nodes which are * ready to be made by doing a breadth-first traversal of the graph * starting from the nodes in the given list. Once this traversal * is finished, all the 'leaves' of the graph are in the toBeMade * queue. * Using this queue and the Job module, work back up the graph, * calling on MakeStartJobs to keep the job table as full as * possible. * * Input: * targs the initial list of targets * * Results: * TRUE if work was done. FALSE otherwise. * * Side Effects: * The make field of all nodes involved in the creation of the given * targets is set to 1. The toBeMade list is set to contain all the * 'leaves' of these subgraphs. *----------------------------------------------------------------------- */ Boolean Make_Run(Lst targs) { int errors; /* Number of errors the Job module reports */ /* Start trying to make the current targets... */ toBeMade = Lst_Init(FALSE); Make_ExpandUse(targs); Make_ProcessWait(targs); if (DEBUG(MAKE)) { fprintf(debug_file, "#***# full graph\n"); Targ_PrintGraph(1); } if (queryFlag) { /* * We wouldn't do any work unless we could start some jobs in the * next loop... (we won't actually start any, of course, this is just * to see if any of the targets was out of date) */ return (MakeStartJobs()); } /* * Initialization. At the moment, no jobs are running and until some * get started, nothing will happen since the remaining upward * traversal of the graph is performed by the routines in job.c upon * the finishing of a job. So we fill the Job table as much as we can * before going into our loop. */ (void)MakeStartJobs(); /* * Main Loop: The idea here is that the ending of jobs will take * care of the maintenance of data structures and the waiting for output * will cause us to be idle most of the time while our children run as * much as possible. Because the job table is kept as full as possible, * the only time when it will be empty is when all the jobs which need * running have been run, so that is the end condition of this loop. * Note that the Job module will exit if there were any errors unless the * keepgoing flag was given. */ while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) { Job_CatchOutput(); (void)MakeStartJobs(); } errors = Job_Finish(); /* * Print the final status of each target. E.g. if it wasn't made * because some inferior reported an error. */ if (DEBUG(MAKE)) fprintf(debug_file, "done: errors %d\n", errors); if (errors == 0) { Lst_ForEach(targs, MakePrintStatus, &errors); if (DEBUG(MAKE)) { fprintf(debug_file, "done: errors %d\n", errors); if (errors) Targ_PrintGraph(4); } } return errors != 0; } Index: head/contrib/bmake/make.h =================================================================== --- head/contrib/bmake/make.h (revision 281811) +++ head/contrib/bmake/make.h (revision 281812) @@ -1,525 +1,526 @@ -/* $NetBSD: make.h,v 1.92 2013/09/04 15:38:26 sjg Exp $ */ +/* $NetBSD: make.h,v 1.95 2014/09/07 20:55:34 joerg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)make.h 8.3 (Berkeley) 6/13/95 */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)make.h 8.3 (Berkeley) 6/13/95 */ /*- * make.h -- * The global definitions for pmake */ #ifndef _MAKE_H_ #define _MAKE_H_ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifdef HAVE_STRING_H #include #else #include #endif #include #include #if defined(__GNUC__) #define MAKE_GNUC_PREREQ(x, y) \ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ (__GNUC__ > (x))) #else /* defined(__GNUC__) */ #define MAKE_GNUC_PREREQ(x, y) 0 #endif /* defined(__GNUC__) */ #if MAKE_GNUC_PREREQ(2, 7) #define MAKE_ATTR_UNUSED __attribute__((__unused__)) #else #define MAKE_ATTR_UNUSED /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 5) #define MAKE_ATTR_DEAD __attribute__((__noreturn__)) #elif defined(__GNUC__) #define MAKE_ATTR_DEAD __volatile #else #define MAKE_ATTR_DEAD /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 7) #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) \ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #else #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #endif #include "sprite.h" #include "lst.h" #include "hash.h" #include "make-conf.h" #include "buf.h" #include "make_malloc.h" /* * some vendors don't have this --sjg */ #if defined(S_IFDIR) && !defined(S_ISDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if defined(sun) && (defined(__svr4__) || defined(__SVR4)) #define POSIX_SIGNALS #endif /*- * The structure for an individual graph node. Each node has several * pieces of data associated with it. * 1) the name of the target it describes * 2) the location of the target file in the file system. * 3) the type of operator used to define its sources (qv. parse.c) * 4) whether it is involved in this invocation of make * 5) whether the target has been remade * 6) whether any of its children has been remade * 7) the number of its children that are, as yet, unmade * 8) its modification time * 9) the modification time of its youngest child (qv. make.c) * 10) a list of nodes for which this is a source (parents) * 11) a list of nodes on which this depends (children) * 12) a list of nodes that depend on this, as gleaned from the * transformation rules (iParents) * 13) a list of ancestor nodes, which includes parents, iParents, * and recursive parents of parents * 14) a list of nodes of the same name created by the :: operator * 15) a list of nodes that must be made (if they're made) before * this node can be, but that do not enter into the datedness of * this node. * 16) a list of nodes that must be made (if they're made) before * this node or any child of this node can be, but that do not * enter into the datedness of this node. * 17) a list of nodes that must be made (if they're made) after * this node is, but that do not depend on this node, in the * normal sense. * 18) a Lst of ``local'' variables that are specific to this target * and this target only (qv. var.c [$@ $< $?, etc.]) * 19) a Lst of strings that are commands to be given to a shell * to create this target. */ typedef struct GNode { char *name; /* The target's name */ char *uname; /* The unexpanded name of a .USE node */ char *path; /* The full pathname of the file */ int type; /* Its type (see the OP flags, below) */ int flags; #define REMAKE 0x1 /* this target needs to be (re)made */ #define CHILDMADE 0x2 /* children of this target were made */ #define FORCE 0x4 /* children don't exist, and we pretend made */ #define DONE_WAIT 0x8 /* Set by Make_ProcessWait() */ #define DONE_ORDER 0x10 /* Build requested by .ORDER processing */ #define FROM_DEPEND 0x20 /* Node created from .depend */ #define DONE_ALLSRC 0x40 /* We do it once only */ #define CYCLE 0x1000 /* Used by MakePrintStatus */ #define DONECYCLE 0x2000 /* Used by MakePrintStatus */ enum enum_made { UNMADE, DEFERRED, REQUESTED, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED } made; /* Set to reflect the state of processing * on this node: * UNMADE - Not examined yet * DEFERRED - Examined once (building child) * REQUESTED - on toBeMade list * BEINGMADE - Target is already being made. * Indicates a cycle in the graph. * MADE - Was out-of-date and has been made * UPTODATE - Was already up-to-date * ERROR - An error occurred while it was being * made (used only in compat mode) * ABORTED - The target was aborted due to * an error making an inferior (compat). */ int unmade; /* The number of unmade children */ time_t mtime; /* Its modification time */ struct GNode *cmgn; /* The youngest child */ Lst iParents; /* Links to parents for which this is an * implied source, if any */ Lst cohorts; /* Other nodes for the :: operator */ Lst parents; /* Nodes that depend on this one */ Lst children; /* Nodes on which this one depends */ Lst order_pred; /* .ORDER nodes we need made */ Lst order_succ; /* .ORDER nodes who need us */ char cohort_num[8]; /* #n for this cohort */ int unmade_cohorts;/* # of unmade instances on the cohorts list */ struct GNode *centurion; /* Pointer to the first instance of a :: node; only set when on a cohorts list */ unsigned int checked; /* Last time we tried to makle this node */ Hash_Table context; /* The local variables */ Lst commands; /* Creation commands */ struct _Suff *suffix; /* Suffix for the node (determined by * Suff_FindDeps and opaque to everyone * but the Suff module) */ const char *fname; /* filename where the GNode got defined */ int lineno; /* line number where the GNode got defined */ } GNode; /* * The OP_ constants are used when parsing a dependency line as a way of * communicating to other parts of the program the way in which a target * should be made. These constants are bitwise-OR'ed together and * placed in the 'type' field of each node. Any node that has * a 'type' field which satisfies the OP_NOP function was never never on * the lefthand side of an operator, though it may have been on the * righthand side... */ #define OP_DEPENDS 0x00000001 /* Execution of commands depends on * kids (:) */ #define OP_FORCE 0x00000002 /* Always execute commands (!) */ #define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids * per line (::) */ #define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) #define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't * exist and can't be created */ #define OP_USE 0x00000010 /* Use associated commands for parents */ #define OP_EXEC 0x00000020 /* Target is never out of date, but always * execute commands anyway. Its time * doesn't matter, so it has none...sort * of */ #define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ #define OP_PRECIOUS 0x00000080 /* Don't remove the target when * interrupted */ #define OP_SILENT 0x00000100 /* Don't echo commands when executed */ #define OP_MAKE 0x00000200 /* Target is a recursive make so its * commands should always be executed when * it is out of date, regardless of the * state of the -n or -t flags */ #define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its * children was out-of-date */ #define OP_MADE 0x00000800 /* Assume the children of the node have * been already made */ #define OP_SPECIAL 0x00001000 /* Special .BEGIN, .END, .INTERRUPT */ #define OP_USEBEFORE 0x00002000 /* Like .USE, only prepend commands */ #define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. * I.e. it doesn't show up in the parents's * local variables. */ #define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main * target' processing in parse.c */ #define OP_PHONY 0x00010000 /* Not a file target; run always */ #define OP_NOPATH 0x00020000 /* Don't search for file in the path */ #define OP_WAIT 0x00040000 /* .WAIT phony node */ #define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */ #define OP_META 0x00100000 /* .META we _do_ want a .meta file */ #define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */ +#define OP_SUBMAKE 0x00400000 /* Possibly a submake node */ /* Attributes applied by PMake */ #define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ #define OP_MEMBER 0x40000000 /* Target is a member of an archive */ #define OP_LIB 0x20000000 /* Target is a library */ #define OP_ARCHV 0x10000000 /* Target is an archive construct */ #define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. * Used when parsing to catch multiple * commands for a target */ #define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ #define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ #define OP_MARK 0x01000000 /* Node found while expanding .ALLSRC */ #define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute) /* * OP_NOP will return TRUE if the node with the given type was not the * object of a dependency operator */ #define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) #define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM) /* * The TARG_ constants are used when calling the Targ_FindNode and * Targ_FindList functions in targ.c. They simply tell the functions what to * do if the desired node(s) is (are) not found. If the TARG_CREATE constant * is given, a new, empty node will be created for the target, placed in the * table of all targets and its address returned. If TARG_NOCREATE is given, * a NULL pointer will be returned. */ #define TARG_NOCREATE 0x00 /* don't create it */ #define TARG_CREATE 0x01 /* create node if not found */ #define TARG_NOHASH 0x02 /* don't look in/add to hash table */ /* * These constants are all used by the Str_Concat function to decide how the * final string should look. If STR_ADDSPACE is given, a space will be * placed between the two strings. If STR_ADDSLASH is given, a '/' will * be used instead of a space. If neither is given, no intervening characters * will be placed between the two strings in the final output. If the * STR_DOFREE bit is set, the two input strings will be freed before * Str_Concat returns. */ #define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ #define STR_ADDSLASH 0x02 /* add a slash when Str_Concat'ing */ /* * Error levels for parsing. PARSE_FATAL means the process cannot continue * once the makefile has been parsed. PARSE_WARNING means it can. Passed * as the first argument to Parse_Error. */ #define PARSE_WARNING 2 #define PARSE_FATAL 1 /* * Values returned by Cond_Eval. */ #define COND_PARSE 0 /* Parse the next lines */ #define COND_SKIP 1 /* Skip the next lines */ #define COND_INVALID 2 /* Not a conditional statement */ /* * Definitions for the "local" variables. Used only for clarity. */ #define TARGET "@" /* Target of dependency */ #define OODATE "?" /* All out-of-date sources */ #define ALLSRC ">" /* All sources */ #define IMPSRC "<" /* Source implied by transformation */ #define PREFIX "*" /* Common prefix */ #define ARCHIVE "!" /* Archive in "archive(member)" syntax */ #define MEMBER "%" /* Member in "archive(member)" syntax */ #define FTARGET "@F" /* file part of TARGET */ #define DTARGET "@D" /* directory part of TARGET */ #define FIMPSRC " b) ? a : b) #endif #ifndef MAXPATHLEN #define MAXPATHLEN BMAKE_PATH_MAX #endif #endif /* _MAKE_H_ */ Index: head/contrib/bmake/meta.c =================================================================== --- head/contrib/bmake/meta.c (revision 281811) +++ head/contrib/bmake/meta.c (revision 281812) @@ -1,1421 +1,1462 @@ -/* $NetBSD: meta.c,v 1.33 2013/10/01 05:37:17 sjg Exp $ */ +/* $NetBSD: meta.c,v 1.38 2015/04/11 05:24:30 sjg Exp $ */ /* * Implement 'meta' mode. * Adapted from John Birrell's patches to FreeBSD make. * --sjg */ /* * Copyright (c) 2009-2010, Juniper Networks, Inc. * Portions Copyright (c) 2009, John Birrell. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(USE_META) #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H) #include #endif #include "make.h" #include "job.h" #ifdef HAVE_FILEMON_H # include #endif #if !defined(USE_FILEMON) && defined(FILEMON_SET_FD) # define USE_FILEMON #endif static BuildMon Mybm; /* for compat */ static Lst metaBailiwick; /* our scope of control */ static Lst metaIgnorePaths; /* paths we deliberately ignore */ #ifndef MAKE_META_IGNORE_PATHS #define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS" #endif Boolean useMeta = FALSE; static Boolean useFilemon = FALSE; static Boolean writeMeta = FALSE; static Boolean metaEnv = FALSE; /* don't save env unless asked */ static Boolean metaVerbose = FALSE; static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */ static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ extern Boolean forceJobs; extern Boolean comatMake; extern char **environ; #define MAKE_META_PREFIX ".MAKE.META.PREFIX" #ifndef N2U # define N2U(n, u) (((n) + ((u) - 1)) / (u)) #endif #ifndef ROUNDUP # define ROUNDUP(n, u) (N2U((n), (u)) * (u)) #endif #if !defined(HAVE_STRSEP) # define strsep(s, d) stresep((s), (d), 0) #endif /* * Filemon is a kernel module which snoops certain syscalls. * * C chdir * E exec * F [v]fork * L [sym]link * M rename * R read * W write * S stat * * See meta_oodate below - we mainly care about 'E' and 'R'. * * We can still use meta mode without filemon, but * the benefits are more limited. */ #ifdef USE_FILEMON # ifndef _PATH_FILEMON # define _PATH_FILEMON "/dev/filemon" # endif /* * Open the filemon device. */ static void filemon_open(BuildMon *pbm) { int retry; pbm->mon_fd = pbm->filemon_fd = -1; if (!useFilemon) return; for (retry = 5; retry >= 0; retry--) { if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0) break; } if (pbm->filemon_fd < 0) { useFilemon = FALSE; warn("Could not open %s", _PATH_FILEMON); return; } /* * We use a file outside of '.' * to avoid a FreeBSD kernel bug where unlink invalidates * cwd causing getcwd to do a lot more work. * We only care about the descriptor. */ pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL); if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) { err(1, "Could not set filemon file descriptor!"); } /* we don't need these once we exec */ (void)fcntl(pbm->mon_fd, F_SETFD, 1); (void)fcntl(pbm->filemon_fd, F_SETFD, 1); } /* * Read the build monitor output file and write records to the target's * metadata file. */ static void filemon_read(FILE *mfp, int fd) { - FILE *fp; char buf[BUFSIZ]; + int n; /* Check if we're not writing to a meta data file.*/ if (mfp == NULL) { if (fd >= 0) close(fd); /* not interested */ return; } /* rewind */ (void)lseek(fd, (off_t)0, SEEK_SET); - if ((fp = fdopen(fd, "r")) == NULL) - err(1, "Could not read build monitor file '%d'", fd); fprintf(mfp, "\n-- filemon acquired metadata --\n"); - while (fgets(buf, sizeof(buf), fp)) { - fprintf(mfp, "%s", buf); + while ((n = read(fd, buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, mfp); } fflush(mfp); - clearerr(fp); - fclose(fp); + close(fd); } #endif /* * when realpath() fails, * we use this, to clean up ./ and ../ */ static void eat_dots(char *buf, size_t bufsz, int dots) { char *cp; char *cp2; const char *eat; size_t eatlen; switch (dots) { case 1: eat = "/./"; eatlen = 2; break; case 2: eat = "/../"; eatlen = 3; break; default: return; } do { cp = strstr(buf, eat); if (cp) { cp2 = cp + eatlen; if (dots == 2 && cp > buf) { do { cp--; } while (cp > buf && *cp != '/'); } if (*cp == '/') { strlcpy(cp, cp2, bufsz - (cp - buf)); } else { return; /* can't happen? */ } } } while (cp); } static char * meta_name(struct GNode *gn, char *mname, size_t mnamelen, const char *dname, const char *tname) { char buf[MAXPATHLEN]; char cwd[MAXPATHLEN]; char *rp; char *cp; char *tp; char *p[4]; /* >= number of possible uses */ int i; i = 0; if (!dname) dname = Var_Value(".OBJDIR", gn, &p[i++]); if (!tname) tname = Var_Value(TARGET, gn, &p[i++]); if (realpath(dname, cwd)) dname = cwd; /* * Weed out relative paths from the target file name. * We have to be careful though since if target is a * symlink, the result will be unstable. * So we use realpath() just to get the dirname, and leave the * basename as given to us. */ if ((cp = strrchr(tname, '/'))) { if (realpath(tname, buf)) { if ((rp = strrchr(buf, '/'))) { rp++; cp++; if (strcmp(cp, rp) != 0) strlcpy(rp, cp, sizeof(buf) - (rp - buf)); } tname = buf; } else { /* * We likely have a directory which is about to be made. * We pretend realpath() succeeded, to have a chance * of generating the same meta file name that we will * next time through. */ if (tname[0] == '/') { strlcpy(buf, tname, sizeof(buf)); } else { snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); } eat_dots(buf, sizeof(buf), 1); /* ./ */ eat_dots(buf, sizeof(buf), 2); /* ../ */ tname = buf; } } /* on some systems dirname may modify its arg */ tp = bmake_strdup(tname); if (strcmp(dname, dirname(tp)) == 0) snprintf(mname, mnamelen, "%s.meta", tname); else { snprintf(mname, mnamelen, "%s/%s.meta", dname, tname); /* * Replace path separators in the file name after the * current object directory path. */ cp = mname + strlen(dname) + 1; while (*cp != '\0') { if (*cp == '/') *cp = '_'; cp++; } } free(tp); for (i--; i >= 0; i--) { if (p[i]) free(p[i]); } return (mname); } /* * Return true if running ${.MAKE} * Bypassed if target is flagged .MAKE */ static int is_submake(void *cmdp, void *gnp) { static char *p_make = NULL; static int p_len; char *cmd = cmdp; GNode *gn = gnp; char *mp = NULL; char *cp; char *cp2; int rc = 0; /* keep looking */ if (!p_make) { p_make = Var_Value(".MAKE", gn, &cp); p_len = strlen(p_make); } cp = strchr(cmd, '$'); if ((cp)) { mp = Var_Subst(NULL, cmd, gn, FALSE); cmd = mp; } cp2 = strstr(cmd, p_make); if ((cp2)) { switch (cp2[p_len]) { case '\0': case ' ': case '\t': case '\n': rc = 1; break; } if (cp2 > cmd && rc > 0) { switch (cp2[-1]) { case ' ': case '\t': case '\n': break; default: rc = 0; /* no match */ break; } } } if (mp) free(mp); return (rc); } typedef struct meta_file_s { FILE *fp; GNode *gn; } meta_file_t; static int printCMD(void *cmdp, void *mfpp) { meta_file_t *mfp = mfpp; char *cmd = cmdp; char *cp = NULL; if (strchr(cmd, '$')) { cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE); } fprintf(mfp->fp, "CMD %s\n", cmd); if (cp) free(cp); return 0; } /* * Certain node types never get a .meta file */ #define SKIP_META_TYPE(_type) do { \ if ((gn->type & __CONCAT(OP_, _type))) { \ if (DEBUG(META)) { \ fprintf(debug_file, "Skipping meta for %s: .%s\n", \ gn->name, __STRING(_type)); \ } \ return (NULL); \ } \ } while (0) static FILE * meta_create(BuildMon *pbm, GNode *gn) { meta_file_t mf; char buf[MAXPATHLEN]; char objdir[MAXPATHLEN]; char **ptr; const char *dname; const char *tname; char *fname; const char *cp; char *p[4]; /* >= possible uses */ int i; struct stat fs; /* This may be a phony node which we don't want meta data for... */ /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */ /* Or it may be explicitly flagged as .NOMETA */ SKIP_META_TYPE(NOMETA); /* Unless it is explicitly flagged as .META */ if (!(gn->type & OP_META)) { SKIP_META_TYPE(PHONY); SKIP_META_TYPE(SPECIAL); SKIP_META_TYPE(MAKE); } mf.fp = NULL; i = 0; dname = Var_Value(".OBJDIR", gn, &p[i++]); tname = Var_Value(TARGET, gn, &p[i++]); /* The object directory may not exist. Check it.. */ if (stat(dname, &fs) != 0) { if (DEBUG(META)) fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n", gn->name); goto out; } /* Check if there are no commands to execute. */ if (Lst_IsEmpty(gn->commands)) { if (DEBUG(META)) fprintf(debug_file, "Skipping meta for %s: no commands\n", gn->name); goto out; } /* make sure these are canonical */ if (realpath(dname, objdir)) dname = objdir; /* If we aren't in the object directory, don't create a meta file. */ if (!metaCurdirOk && strcmp(curdir, dname) == 0) { if (DEBUG(META)) fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n", gn->name); goto out; } if (!(gn->type & OP_META)) { /* We do not generate .meta files for sub-makes */ if (Lst_ForEach(gn->commands, is_submake, gn)) { if (DEBUG(META)) fprintf(debug_file, "Skipping meta for %s: .MAKE\n", gn->name); goto out; } } if (metaVerbose) { char *mp; /* Describe the target we are building */ mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0); if (*mp) fprintf(stdout, "%s\n", mp); free(mp); } /* Get the basename of the target */ if ((cp = strrchr(tname, '/')) == NULL) { cp = tname; } else { cp++; } fflush(stdout); if (strcmp(cp, makeDependfile) == 0) goto out; if (!writeMeta) /* Don't create meta data. */ goto out; fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname), dname, tname); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_create: %s\n", fname); #endif if ((mf.fp = fopen(fname, "w")) == NULL) err(1, "Could not open meta file '%s'", fname); fprintf(mf.fp, "# Meta data file %s\n", fname); mf.gn = gn; Lst_ForEach(gn->commands, printCMD, &mf); fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); fprintf(mf.fp, "TARGET %s\n", tname); if (metaEnv) { for (ptr = environ; *ptr != NULL; ptr++) fprintf(mf.fp, "ENV %s\n", *ptr); } fprintf(mf.fp, "-- command output --\n"); fflush(mf.fp); Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL); gn->type |= OP_META; /* in case anyone wants to know */ if (metaSilent) { gn->type |= OP_SILENT; } out: for (i--; i >= 0; i--) { if (p[i]) free(p[i]); } return (mf.fp); } static Boolean boolValue(char *s) { switch(*s) { case '0': case 'N': case 'n': case 'F': case 'f': return FALSE; } return TRUE; } /* * Initialization we need before reading makefiles. */ void meta_init(void) { #ifdef USE_FILEMON /* this allows makefiles to test if we have filemon support */ Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0); #endif } /* * Initialization we need after reading makefiles. */ void meta_mode_init(const char *make_mode) { static int once = 0; char *cp; useMeta = TRUE; useFilemon = TRUE; writeMeta = TRUE; if (make_mode) { if (strstr(make_mode, "env")) metaEnv = TRUE; if (strstr(make_mode, "verb")) metaVerbose = TRUE; if (strstr(make_mode, "read")) writeMeta = FALSE; if (strstr(make_mode, "nofilemon")) useFilemon = FALSE; if ((cp = strstr(make_mode, "curdirok="))) { metaCurdirOk = boolValue(&cp[9]); } if ((cp = strstr(make_mode, "silent="))) { metaSilent = boolValue(&cp[7]); } if (strstr(make_mode, "ignore-cmd")) metaIgnoreCMDs = TRUE; /* for backwards compatability */ Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0); Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0); } if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) { /* * The default value for MAKE_META_PREFIX * prints the absolute path of the target. * This works be cause :H will generate '.' if there is no / * and :tA will resolve that to cwd. */ Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0); } if (once) return; once = 1; memset(&Mybm, 0, sizeof(Mybm)); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ metaBailiwick = Lst_Init(FALSE); cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, 0); if (cp) { str2Lst_Append(metaBailiwick, cp, NULL); } /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ metaIgnorePaths = Lst_Init(FALSE); Var_Append(MAKE_META_IGNORE_PATHS, "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); cp = Var_Subst(NULL, "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 0); if (cp) { str2Lst_Append(metaIgnorePaths, cp, NULL); } } /* * In each case below we allow for job==NULL */ void meta_job_start(Job *job, GNode *gn) { BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } pbm->mfp = meta_create(pbm, gn); #ifdef USE_FILEMON_ONCE /* compat mode we open the filemon dev once per command */ if (job == NULL) return; #endif #ifdef USE_FILEMON if (pbm->mfp != NULL && useFilemon) { filemon_open(pbm); } else { pbm->mon_fd = pbm->filemon_fd = -1; } #endif } /* * The child calls this before doing anything. * It does not disturb our state. */ void meta_job_child(Job *job) { #ifdef USE_FILEMON BuildMon *pbm; - pid_t pid; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } - pid = getpid(); - if (pbm->mfp != NULL && useFilemon) { - if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) { - err(1, "Could not set filemon pid!"); + if (pbm->mfp != NULL) { + close(fileno(pbm->mfp)); + if (useFilemon) { + pid_t pid; + + pid = getpid(); + if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) { + err(1, "Could not set filemon pid!"); + } } } #endif } void meta_job_error(Job *job, GNode *gn, int flags, int status) { char cwd[MAXPATHLEN]; BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { if (!gn) gn = job->node; pbm = &Mybm; } if (pbm->mfp != NULL) { fprintf(pbm->mfp, "*** Error code %d%s\n", status, (flags & JOB_IGNERR) ? "(ignored)" : ""); } if (gn) { Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0); } getcwd(cwd, sizeof(cwd)); Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0); if (pbm && pbm->meta_fname[0]) { Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0); } meta_job_finish(job); } void meta_job_output(Job *job, char *cp, const char *nl) { BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { if (metaVerbose) { static char *meta_prefix = NULL; static int meta_prefix_len; if (!meta_prefix) { char *cp2; meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0); if ((cp2 = strchr(meta_prefix, '$'))) meta_prefix_len = cp2 - meta_prefix; else meta_prefix_len = strlen(meta_prefix); } if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) { cp = strchr(cp+1, '\n'); if (!cp++) return; } } fprintf(pbm->mfp, "%s%s", cp, nl); } } void meta_cmd_finish(void *pbmp) { #ifdef USE_FILEMON BuildMon *pbm = pbmp; if (!pbm) pbm = &Mybm; if (pbm->filemon_fd >= 0) { close(pbm->filemon_fd); filemon_read(pbm->mfp, pbm->mon_fd); pbm->filemon_fd = pbm->mon_fd = -1; } #endif } void meta_job_finish(Job *job) { BuildMon *pbm; if (job != NULL) { pbm = &job->bm; } else { pbm = &Mybm; } if (pbm->mfp != NULL) { meta_cmd_finish(pbm); fclose(pbm->mfp); pbm->mfp = NULL; pbm->meta_fname[0] = '\0'; } } /* * Fetch a full line from fp - growing bufp if needed * Return length in bufp. */ static int fgetLine(char **bufp, size_t *szp, int o, FILE *fp) { char *buf = *bufp; size_t bufsz = *szp; struct stat fs; int x; if (fgets(&buf[o], bufsz - o, fp) != NULL) { check_newline: x = o + strlen(&buf[o]); if (buf[x - 1] == '\n') return x; /* * We need to grow the buffer. * The meta file can give us a clue. */ if (fstat(fileno(fp), &fs) == 0) { size_t newsz; char *p; newsz = ROUNDUP((fs.st_size / 2), BUFSIZ); if (newsz <= bufsz) newsz = ROUNDUP(fs.st_size, BUFSIZ); if (DEBUG(META)) fprintf(debug_file, "growing buffer %u -> %u\n", (unsigned)bufsz, (unsigned)newsz); p = bmake_realloc(buf, newsz); if (p) { *bufp = buf = p; *szp = bufsz = newsz; /* fetch the rest */ if (!fgets(&buf[x], bufsz - x, fp)) return x; /* truncated! */ goto check_newline; } } } return 0; } static int prefix_match(void *p, void *q) { const char *prefix = p; const char *path = q; size_t n = strlen(prefix); return (0 == strncmp(path, prefix, n)); } static int string_match(const void *p, const void *q) { const char *p1 = p; const char *p2 = q; return strcmp(p1, p2); } /* * When running with 'meta' functionality, a target can be out-of-date - * if any of the references in it's meta data file is more recent. + * if any of the references in its meta data file is more recent. * We have to track the latestdir on a per-process basis. */ +#define LCWD_VNAME_FMT ".meta.%d.lcwd" #define LDIR_VNAME_FMT ".meta.%d.ldir" /* * It is possible that a .meta file is corrupted, * if we detect this we want to reproduce it. * Setting oodate TRUE will have that effect. */ #define CHECK_VALID_META(p) if (!(p && *p)) { \ warnx("%s: %d: malformed", fname, lineno); \ oodate = TRUE; \ continue; \ } #define DEQUOTE(p) if (*p == '\'') { \ char *ep; \ p++; \ if ((ep = strchr(p, '\''))) \ *ep = '\0'; \ } Boolean meta_oodate(GNode *gn, Boolean oodate) { static char *tmpdir = NULL; static char cwd[MAXPATHLEN]; + char lcwd_vname[64]; char ldir_vname[64]; + char lcwd[MAXPATHLEN]; char latestdir[MAXPATHLEN]; char fname[MAXPATHLEN]; char fname1[MAXPATHLEN]; char fname2[MAXPATHLEN]; + char fname3[MAXPATHLEN]; char *p; char *cp; char *link_src; char *move_target; static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; Boolean needOODATE = FALSE; Lst missingFiles; if (oodate) return oodate; /* we're done */ missingFiles = Lst_Init(FALSE); /* * We need to check if the target is out-of-date. This includes * checking if the expanded command has changed. This in turn * requires that all variables are set in the same way that they * would be if the target needs to be re-built. */ Make_DoAllVar(gn); meta_name(gn, fname, sizeof(fname), NULL, NULL); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: %s\n", fname); #endif if ((fp = fopen(fname, "r")) != NULL) { static char *buf = NULL; static size_t bufsz; int lineno = 0; int lastpid = 0; int pid; int f = 0; int x; LstNode ln; struct stat fs; if (!buf) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } if (!cwdlen) { if (getcwd(cwd, sizeof(cwd)) == NULL) err(1, "Could not get current working directory"); cwdlen = strlen(cwd); } + strlcpy(lcwd, cwd, sizeof(lcwd)); + strlcpy(latestdir, cwd, sizeof(latestdir)); if (!tmpdir) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); ln = Lst_First(gn->commands); while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') buf[x - 1] = '\0'; else { warnx("%s: %d: line truncated at %u", fname, lineno, x); oodate = TRUE; break; } link_src = NULL; move_target = NULL; /* Find the start of the build monitor section. */ if (!f) { if (strncmp(buf, "-- filemon", 10) == 0) { f = 1; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { f = 1; continue; } } /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); if (f) { /* * We are in the 'filemon' output section. * Each record from filemon follows the general form: * * * * Where: * is a single letter, denoting the syscall. * is the process that made the syscall. * is the arguments (of interest). */ switch(buf[0]) { case '#': /* comment */ case 'V': /* version */ break; default: /* * We need to track pathnames per-process. * * Each process run by make, starts off in the 'CWD' * recorded in the .meta file, if it chdirs ('C') * elsewhere we need to track that - but only for * that process. If it forks ('F'), we initialize * the child to have the same cwd as its parent. * * We also need to track the 'latestdir' of * interest. This is usually the same as cwd, but * not if a process is reading directories. * * Each time we spot a different process ('pid') * we save the current value of 'latestdir' in a * variable qualified by 'lastpid', and * re-initialize 'latestdir' to any pre-saved * value for the current 'pid' and 'CWD' if none. */ CHECK_VALID_META(p); pid = atoi(p); if (pid > 0 && pid != lastpid) { char *ldir; char *tp; if (lastpid > 0) { - /* We need to remember this. */ + /* We need to remember these. */ + Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); } + snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); lastpid = pid; ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); if (ldir) { strlcpy(latestdir, ldir, sizeof(latestdir)); if (tp) free(tp); - } else - strlcpy(latestdir, cwd, sizeof(latestdir)); + } + ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); + if (ldir) { + strlcpy(lcwd, ldir, sizeof(lcwd)); + if (tp) + free(tp); + } } /* Skip past the pid. */ if (strsep(&p, " ") == NULL) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) - fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, latestdir); + fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + pid, buf[0], cwd, lcwd, latestdir); #endif break; } CHECK_VALID_META(p); /* Process according to record type. */ switch (buf[0]) { case 'X': /* eXit */ + Var_Delete(lcwd_vname, VAR_GLOBAL); Var_Delete(ldir_vname, VAR_GLOBAL); lastpid = 0; /* no need to save ldir_vname */ break; case 'F': /* [v]Fork */ { char cldir[64]; int child; child = atoi(p); if (child > 0) { + snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); + Var_Set(cldir, lcwd, VAR_GLOBAL, 0); snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); Var_Set(cldir, latestdir, VAR_GLOBAL, 0); +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + child, cwd, lcwd, latestdir); +#endif } } break; case 'C': /* Chdir */ - /* Update the latest directory. */ - strlcpy(latestdir, p, sizeof(latestdir)); + /* Update lcwd and latest directory. */ + strlcpy(latestdir, p, sizeof(latestdir)); + strlcpy(lcwd, p, sizeof(lcwd)); + Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); + Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0); +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); +#endif break; case 'M': /* renaMe */ /* * For 'M'oves we want to check * the src as for 'R'ead * and the target as for 'W'rite. */ cp = p; /* save this for a second */ /* now get target */ if (strsep(&p, " ") == NULL) continue; CHECK_VALID_META(p); move_target = p; p = cp; /* 'L' and 'M' put single quotes around the args */ DEQUOTE(p); DEQUOTE(move_target); /* FALLTHROUGH */ case 'D': /* unlink */ if (*p == '/' && !Lst_IsEmpty(missingFiles)) { /* remove p from the missingFiles list if present */ if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) { char *tp = Lst_Datum(ln); Lst_Remove(missingFiles, ln); free(tp); ln = NULL; /* we're done with it */ } } if (buf[0] == 'M') { /* the target of the mv is a file 'W'ritten */ #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: M %s -> %s\n", p, move_target); #endif p = move_target; goto check_write; } break; case 'L': /* Link */ /* * For 'L'inks check * the src as for 'R'ead * and the target as for 'W'rite. */ link_src = p; /* now get target */ if (strsep(&p, " ") == NULL) continue; CHECK_VALID_META(p); /* 'L' and 'M' put single quotes around the args */ DEQUOTE(p); DEQUOTE(link_src); #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: L %s -> %s\n", link_src, p); #endif /* FALLTHROUGH */ case 'W': /* Write */ check_write: /* * If a file we generated within our bailiwick * but outside of .OBJDIR is missing, * we need to do it again. */ /* ignore non-absolute paths */ if (*p != '/') break; if (Lst_IsEmpty(metaBailiwick)) break; /* ignore cwd - normal dependencies handle those */ if (strncmp(p, cwd, cwdlen) == 0) break; if (!Lst_ForEach(metaBailiwick, prefix_match, p)) break; /* tmpdir might be within */ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) break; /* ignore anything containing the string "tmp" */ if ((strstr("tmp", p))) break; if (stat(p, &fs) < 0) { Lst_AtEnd(missingFiles, bmake_strdup(p)); } break; check_link_src: p = link_src; link_src = NULL; #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: L src %s\n", p); #endif /* FALLTHROUGH */ case 'R': /* Read */ case 'E': /* Exec */ /* * Check for runtime files that can't * be part of the dependencies because * they are _expected_ to change. */ if (*p == '/' && Lst_ForEach(metaIgnorePaths, prefix_match, p)) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "meta_oodate: ignoring: %s\n", p); #endif break; } if ((cp = strrchr(p, '/'))) { cp++; /* * We don't normally expect to see this, * but we do expect it to change. */ if (strcmp(cp, makeDependfile) == 0) break; } /* * The rest of the record is the file name. * Check if it's not an absolute path. */ { char *sdirs[4]; char **sdp; int sdx = 0; int found = 0; if (*p == '/') { sdirs[sdx++] = p; /* done */ } else { if (strcmp(".", p) == 0) continue; /* no point */ /* Check vs latestdir */ snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); sdirs[sdx++] = fname1; - if (strcmp(latestdir, cwd) != 0) { - /* Check vs cwd */ - snprintf(fname2, sizeof(fname2), "%s/%s", cwd, p); + if (strcmp(latestdir, lcwd) != 0) { + /* Check vs lcwd */ + snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); sdirs[sdx++] = fname2; } + if (strcmp(lcwd, cwd) != 0) { + /* Check vs cwd */ + snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); + sdirs[sdx++] = fname3; + } } sdirs[sdx++] = NULL; for (sdp = sdirs; *sdp && !found; sdp++) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif if (stat(*sdp, &fs) == 0) { found = 1; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE if (DEBUG(META)) fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); #endif if (!S_ISDIR(fs.st_mode) && fs.st_mtime > gn->mtime) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); oodate = TRUE; } else if (S_ISDIR(fs.st_mode)) { /* Update the latest directory. */ realpath(p, latestdir); } } else if (errno == ENOENT && *p == '/' && strncmp(p, cwd, cwdlen) != 0) { /* * A referenced file outside of CWD is missing. * We cannot catch every eventuality here... */ if (DEBUG(META)) fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p); oodate = TRUE; } + } + if (buf[0] == 'E') { + /* previous latestdir is no longer relevant */ + strlcpy(latestdir, lcwd, sizeof(latestdir)); } break; default: break; } if (!oodate && buf[0] == 'L' && link_src != NULL) goto check_link_src; } else if (strcmp(buf, "CMD") == 0) { /* * Compare the current command with the one in the * meta data file. */ if (ln == NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); oodate = TRUE; } else { char *cmd = (char *)Lst_Datum(ln); Boolean hasOODATE = FALSE; if (strstr(cmd, "$?")) hasOODATE = TRUE; else if ((cp = strstr(cmd, ".OODATE"))) { /* check for $[{(].OODATE[:)}] */ if (cp > cmd + 2 && cp[-2] == '$') hasOODATE = TRUE; } if (hasOODATE) { needOODATE = TRUE; if (DEBUG(META)) fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); } cmd = Var_Subst(NULL, cmd, gn, TRUE); if ((cp = strchr(cmd, '\n'))) { int n; /* * This command contains newlines, we need to * fetch more from the .meta file before we * attempt a comparison. */ /* first put the newline back at buf[x - 1] */ buf[x - 1] = '\n'; do { /* now fetch the next line */ if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0) break; x = n; lineno++; if (buf[x - 1] != '\n') { warnx("%s: %d: line truncated at %u", fname, lineno, x); break; } cp = strchr(++cp, '\n'); } while (cp); if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } if (!hasOODATE && !(gn->type & OP_NOMETA_CMP) && strcmp(p, cmd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); ln = Lst_Succ(ln); } } else if (strcmp(buf, "CWD") == 0) { /* * Check if there are extra commands now * that weren't in the meta data file. */ if (!oodate && ln != NULL) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); oodate = TRUE; } if (strcmp(p, cwd) != 0) { if (DEBUG(META)) fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); oodate = TRUE; } } } fclose(fp); if (!Lst_IsEmpty(missingFiles)) { if (DEBUG(META)) fprintf(debug_file, "%s: missing files: %s...\n", fname, (char *)Lst_Datum(Lst_First(missingFiles))); oodate = TRUE; Lst_Destroy(missingFiles, (FreeProc *)free); } } else { if ((gn->type & OP_META)) { if (DEBUG(META)) fprintf(debug_file, "%s: required but missing\n", fname); oodate = TRUE; } } if (oodate && needOODATE) { /* * Target uses .OODATE which is empty; or we wouldn't be here. * We have decided it is oodate, so .OODATE needs to be set. * All we can sanely do is set it to .ALLSRC. */ Var_Delete(OODATE, gn); Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0); if (cp) free(cp); } return oodate; } /* support for compat mode */ static int childPipe[2]; void meta_compat_start(void) { #ifdef USE_FILEMON_ONCE /* * We need to re-open filemon for each cmd. */ BuildMon *pbm = &Mybm; if (pbm->mfp != NULL && useFilemon) { filemon_open(pbm); } else { pbm->mon_fd = pbm->filemon_fd = -1; } #endif if (pipe(childPipe) < 0) Punt("Cannot create pipe: %s", strerror(errno)); /* Set close-on-exec flag for both */ (void)fcntl(childPipe[0], F_SETFD, 1); (void)fcntl(childPipe[1], F_SETFD, 1); } void meta_compat_child(void) { meta_job_child(NULL); if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0) { execError("dup2", "pipe"); _exit(1); } } void meta_compat_parent(void) { FILE *fp; char buf[BUFSIZ]; close(childPipe[1]); /* child side */ fp = fdopen(childPipe[0], "r"); while (fgets(buf, sizeof(buf), fp)) { meta_job_output(NULL, buf, ""); printf("%s", buf); } fclose(fp); } #endif /* USE_META */ Index: head/contrib/bmake/mk/ChangeLog =================================================================== --- head/contrib/bmake/mk/ChangeLog (revision 281811) +++ head/contrib/bmake/mk/ChangeLog (revision 281812) @@ -1,834 +1,942 @@ +2015-04-16 Simon J. Gerraty + + * install-mk (MK_VERSION): 20150411 + bump version + + * own.mk: put AUTO_OBJ in OPTIONS_DEFAULT_NO rather than YES. + it is here mainly for documentation purposes, since + if using auto.obj.mk it is better done via sys.mk + +2015-04-01 Simon J. Gerraty + + * install-mk (MK_VERSION): 20150401 + + * meta2deps.sh: support @list + + * meta2deps.py: updates from Juniper + o add EXCLUDES + o skip bogus input files. + o treat 'M' and 'L' as both an 'R' and a 'W' + +2015-03-03 Simon J. Gerraty + + * install-mk (MK_VERSION): 20150303 + + * dirdeps.mk: if MK_DIRDEPS_CACHE is yes, use dirdeps-cache + which is built via sub-make so we have a .meta file to tell if + it is out-of-date. + The dirdeps-cache contains the same dependency rules that we + normaly construct on the fly. + This adds a few seconds overhead when the cache is out of date, + but for a large target, the savings can be significant (10-20min). + +2014-11-18 Simon J. Gerraty + + * install-mk (MK_VERSION): 20141118 + + * meta.stage.mk: add stale_staged + + * dirdeps.mk (_DIRDEP_USE_LEVEL): allow this to be tweaked + only useful under very rare conditions such as + FreeBSD's make universe. + + * auto.obj.mk: Allow MK_AUTO_OBJ to set MKOBJDIRS=auto + +2014-11-11 Simon J. Gerraty + + * install-mk (MK_VERSION): 20141111 + + * mkopt.sh: use consistent semantics for _mk_opt and _mk_opts + +2014-11-09 Simon J. Gerraty + + * FILES: include mkopt.sh which allows handling options in shell + scripts in a manner compatible with options.mk + +2014-10-12 Simon J. Gerraty + + * meta.stage.mk: ensure only _STAGED_DIRS under objroot are used + for GENDIRDEPS_FILTER to avoid surprises. + +2014-10-10 Simon J. Gerraty + + * dirdeps.mk (NSkipHostDir): this needs SRCTOP prepended since by + the time it is applied to __depdirs they have. + + * dirdeps.mk fix filtering of _machines since M_dep_qual_fixes + expects patterns like *.${MACHINE} + + * cython.mk (pyprefix?): use pyprefix to find python bits + since prefix might be something else (where we install our + stuff) + +2014-09-11 Simon J. Gerraty + + * install-mk (MK_VERSION): 20140911 + + * dirdeps.mk: add bootstrap target to simplify adding support for + new MACHINE. + +2014-09-01 Simon J. Gerraty + + * gendirdeps.mk: Add handling of GENDIRDEPS_FILTER_DIR_VARS and + GENDIRDEPS_FILTER_VARS to make it easier to produce sharable + Makefile.depend files. + +2014-08-28 Simon J. Gerraty + + * install-mk (MK_VERSION): 20140828 + + * cython.mk: capture logic for building python extension modules + with Cython. + +2014-08-08 Simon J. Gerraty + + * meta.stage.mk (_STAGE_AS_BASENAME_USE): Add StageAs variant + +2014-08-02 Simon J. Gerraty + + * install-mk (MK_VERSION): 20140801 + + * dep.mk: use explicit MKDEP_MK rather than overload MKDEP to + identify the autodep.mk variant. + + * sys.dependfile.mk: delete .MAKE.DEPENDFILE if its + initial value does not match .MAKE.DEPENDFILE_PREFIX + + * meta.autodep.mk: if _bootstrap_dirdeps add RELDIR to DIRDEPS + 2014-05-22 Simon J. Gerraty * install-mk (MK_VERSION): 20140522 * lib.mk: use CC to link shlib for linux too patch from Brendan MacDonell 2014-05-05 Simon J. Gerraty * meta.autodep.mk: add _reldir_{finish,failed} for gathering stats if WITH_META_STATS is defined. 2014-05-02 Simon J. Gerraty * dirdeps.mk: accept -DWITHOUT_DIRDEPS (same a as -DNO_DIRDEPS) to supress dirdeps outside of .CURDIR. 2014-04-05 Simon J. Gerraty * Fix spelling errors - patch from Pedro Giffuni 2014-03-14 Simon J. Gerraty * install-mk (MK_VERSION): 20140314 * dirdeps.mk (beforedirdeps): a handy hook * dirdeps.mk (DIRDEP_MAKE): allow the actual command we run to visit leaf dirs to be intercepted (eg. for distributed build). * dirdeps.mk (__depdirs): ensure // don't sneak in * gendirdeps.mk (DIRDEPS): ensure // don't sneak in 2014-02-21 Simon J. Gerraty * rst2htm.mk (RST2PDF): add support for rst2pdf 2014-02-14 Simon J. Gerraty * install-mk (MK_VERSION): bump version * dirdeps.mk (_last_dependfile): use .INCLUDEDFROMFILE if available. 2014-02-10 Simon J. Gerraty * options.mk: avoid :U so this isn't bmake dependent 2014-02-09 Simon J. Gerraty * options.mk: cleanup and simplify semanitcs NO_* dominates all, if both WITH_* and WITHOUT_* are defined then result is DOMINATE_* which defaults to "no". Ie. WITHOUT_ normally wins. 2013-12-12 Simon J. Gerraty * install-mk (MK_VERSION): bump version * meta2deps.py: convert to print function for python3 compat. we also need to open files with mode 'r' rather than 'rb' otherwise we get bytes instead of strings. 2013-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version * dirdeps.mk: when TARGET_SPEC_VARS is more than just MACHINE apply the same filtering (M_dep_qual_fixes) when setting _machines as _build_dirs. Also fix the filtering of Makefile.depend files - for reporting what we are looking for (M_dep_qual_fixes can get confused by Makefile.depend) Add some more debug info. 2013-09-04 Simon J. Gerraty * gendirdeps.mk (_objtops): fix typo also while processing M2D_OBJROOTS to gather qualdir_list qualify $ql with loop iterator to ensure correct results. 2013-08-01 Simon J. Gerraty * install-mk (MK_VERSION): 20130801 * libs.mk: update to match progs.mk 2013-07-26 Simon J. Gerraty * install-mk (MK_VERSION): 20130726 some updates from Juniper and FreeBSD o meta2deps.py: indicate file and line number when we hit parse errors also allow @file to provide huge list of .meta files. * meta2deps.py: add try_parse() to cleanup the above. 2013-07-16 Simon J. Gerraty * install-mk (MK_VERSION): 20130716 * own.mk: add GPROG as an option * prog.mk: honor MK_GPROF==yes 2013-05-10 Simon J. Gerraty * install-mk (MK_VERSION): 20130505 * gendirdeps.mk, meta2deps.py, meta2deps.sh: handle $TARGET_SPEC for when $MACHINE isn't enough for objdir distinction. Bring meta2deps.sh closer to par with meta2deps.py. 2013-04-18 Simon J. Gerraty * meta.stage.mk: set INSTALL to STAGE_INSTALL when making 'all' also if the target 'beforeinstall' exists, make it depend on .dirdep (incase it uses STAGE_INSTALL). 2013-04-17 Simon J. Gerraty * install-mk (MK_VERSION): 20130401 ;-) * meta.stage.mk (STAGE_INSTALL_SH): add stage-install.sh as wrapper around install(1). * options.mk (OPTION_PREFIX): Allow a prefix other than MK_ 2013-03-30 Simon J. Gerraty * meta2deps.py (MetaFile.__init__): ensure self.cwd is initialized. * install-mk (MK_VERSION): bump version 2013-03-21 Simon J. Gerraty * install-mk (MK_VERSION): bump version * gendirdeps.mk: do not apply :tA to DPADD entries, since we lose any trailing /., rather apply :tA only when needed. * gendirdeps.mk: better mimic meta2deps handling of .dirdep files. * meta.stage.mk (LN_CP_SCRIPT): Add LnCp to do the ln||cp dance consistently. * dirdeps.mk: better describe the dance in sys.mk for TARGET_SPEC. 2013-03-18 Simon J. Gerraty * gendirdeps.mk: revert the dance around .MAKE.DEPENDFILE_DEFAULT it is simpler to just not update when say building for "host" (where we know we apply filters to DIRDEPS), and using a non-machine qualified dependfile. 2013-03-16 Simon J. Gerraty * dirdeps.mk: improve DIRDEPS filtering by allowing DEP_SKIP_DIR and DEP_DIRDEPS_FILTER to vary by DEP_MACHINE and DEP_TARGET_SPEC * gendirdeps.mk: ensure _objroot has trailing / if it needs it. * meta2deps.py: if machine is "host", then also trim self.host_target from any OBJROOTS. 2013-03-11 Simon J. Gerraty * gendirdeps.mk: if .MAKE.DEPENDFILE_DEFAULT is not machine qualified but _DEPENDFILE is, and .MAKE.DEPENDFILE_DEFAULT exists but _DEPENDFILE does not, compare the new _DEPENDFILE against .MAKE.DEPENDFILE_DEFAULT and discard if the same. 2013-03-08 Simon J. Gerraty * meta.stage.mk: use STAGE_TARGETS to control .ORDER and hook to all: via staging: 2013-03-07 Simon J. Gerraty * sys.dependfile.mk (.MAKE.DEPENDFILE_DEFAULT): use a separate variable for the default .MAKE.DEPENDFILE value so that it can be controlled independently of .MAKE.DEPENDFILE_PREFERENCE * meta.stage.mk: throw error if cp fails etc. Stage*() return early if passed no args. .ORDER stage_* 2013-03-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version * gendirdeps.mk: handle multiple M2D_OBJROOTS better. 2013-02-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20130210 * import latest dirdeps.mk, gendirdeps.mk and meta2deps.py from Juniper. o dirdeps.mk now fully supports TARGET_SPEC consisting of more than just MACHINE. o no longer use DEP_MACHINE from Makefile.depend* so remove it. 2013-01-23 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20130123 * meta.stage.mk: add stage_links (hard links). if doing hard links, we add dest to link as well. Default the stage dir for [sym]links to STAGE_OBJTOP since these are typically specified as absolute paths. Add -m "mode" flag to StageFiles and StageAs. 2012-11-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121111 * autoconf.mk: avoid meta mode seeing changed commands for config.status * meta.autodep.mk: pass resolved MAKESYSPATH to gendirdeps in case we were found via .../mk * sys.clean-env.mk: move it from examples, we and others use it "as is". * FILES: add srctop.mk and options.mk * own.mk: convert to using options.mk which is modeled after FreeBSD's handling of MK_* but more flexible. This allows MK_* for boolean knobs to not be confused with MK* which can be commands. * examples/sys.clean-env.mk: add WITH[OUT]_ to MAKE_ENV_SAVE_PREFIX_LIST. Mention that HOME=/var/empty might be a good idea. 2012-11-08 Simon J. Gerraty * sys.dependfile.mk: if not depend file exists, $MACHINE specific ones are supported but not the default, check if any exist and follow suit. 2012-11-06 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121106 2012-11-05 Simon J. Gerraty * import latest dirdeps.mk and meta2deps.py from Juniper. * progs.mk: add MAN and CXXFLAGS to PROG_VARS also add PROGS_TARGETS and pass on PROG_CXX if it seems appropriate. 2012-11-04 Simon J. Gerraty * meta.stage.mk: update CLEANFILES remove redundant cp of .dirdep from STAGE_AS_SCRIPT. * progs.mk: Add LDADD to PROG_VARS 2012-10-12 Simon J. Gerraty * meta.stage.mk (STAGE_DIR_FILTER): track dirs we stage to in _STAGED_DIRS so that these can be turned into filters for GENDIRDEPS_FILTER. 2012-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121010 * meta.stage.mk (STAGE_DIRDEP_SCRIPT): check that an existing target.dirdep matches .dirdep 2012-08-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120808 * import latest meta2deps.py from Juniper. 2012-07-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120711 * dep.mk: add explicit dependencies on SRCS after applying SRCS_DEP_FILTER * meta.autodep.mk: add explicit dependencies on SRCS after applying SRCS_DEP_FILTER * meta.autodep.mk: ensure GENDIRDEPS_FILTER is exported if needed. 2012-06-26 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120626 * meta.sys.mk: ignore PYTHON if it does not exist compare ${.MAKE.DEPENDFILE:E} against ${MACHINE} is more reliable. * meta.stage.mk: examine .MAKE.DEPENDFILE_PREFERENCE for any entries ending in .${MACHINE} to decide if qualified _dirdep is needed. * gendirdeps.mk: only produce unqualified deps if no .MAKE.DEPENDFILE_PREFERENCE ends in .${MACHINE} * meta.subdir.mk: apply SUBDIREPS_FILTER 2012-04-20 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120420 * add sys.dependfile.mk so we can experiment with .MAKE.DEPENDFILE_PREFERENCE * meta.autodep.mk: _DEPENDFILE is precious! 2012-03-15 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120315 * install-new.mk: avoid being interrupted 2012-02-26 Simon J. Gerraty * man.mk: MAN might have multiple values so be careful with exists(). 2012-01-19 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120112 * fix examples/sys.clean-env.mk so that MAKEOBJDIR is handled as: MAKEOBJDIR='${.CURDIR:S,${SRCTOP},${OBJTOP},}' 2011-12-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111201 * import dirdeps.mk from Juniper sjg@ o more consistent handling of DEP_MACHINE, especially when dealing with an odd Makefile.depend, when normally using Makefile.depend.${MACHINE} 2011-11-22 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111122 * meta.autodep.mk: add some debug output, be more crisp about updating. Use ${.ALLTARGETS:M*.o} as a clue for .depend 2011-11-13 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111111 it's too cool to miss * import meta* updates from Juniper sjg@ o dirdeps.mk set DEP_MACHINE for Makefile.depend (when we are normally using Makefile.depend.${MACHINE}), handy for read-only manually maintained dependencies. o meta2deps.py add a clear 'ERROR:' token if an exception is raised. o gendirdeps.mk if ERROR: from meta2deps.py do not update anything. 2011-10-30 Simon J. Gerraty * install-new.mk separate the cmp and copy logic to its own function. 2011-10-28 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111028 * sys.mk: include auto.obj.mk if MKOBJDIRS is set to auto * subdir.mk: ensure _SUBDIRUSE is provided * meta.autodep.mk: remove dependency of gendirdeps.mk on auto.obj.mk * meta.subdir.mk: always allow for Makefile.depend 2011-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111010 o minor tweak to *dirdeps.mk from Juniper sjg@ 2011-10-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111001 o add meta2deps.py from Juniper sjg@ o tweak gendirdeps.mk to work with meta2deps.py when not cross-building * autoconf.mk: add autoconf-input as a hook for regenerating AUTOCONF_INPUTS (configure). 2011-08-24 Simon J. Gerraty * meta.autodep.mk: if we do not have OBJS, .depend isn't a useful trigger for updating Makefile.depend* 2011-08-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110808 * obj.mk: minor cleanup * auto.obj.mk: improve description of Mkdirs and honor NO_OBJ too. 2011-08-01 Simon J. Gerraty * auto.obj.mk (.OBJDIR): throw an error if we cannot use the specified dir. 2011-06-28 Simon J. Gerraty * meta.autodep.mk: if XMAKE_META_FILE is set the makefile uses a foreign make, and so dependencies can only be gathered from a clean tree build. 2011-06-24 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110622 * meta.autodep.mk: improve bootstraping 2011-06-10 Simon J. Gerraty * yacc.mk: handle the corner case of .c being removed while .h remains. 2011-06-08 Simon J. Gerraty * yacc.mk: do .y.h and .y.c separately 2011-06-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110606 * don't store SRC_DIRDEPS in Makefile.depend* by default not everyone needs it. 2011-05-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110505 first release including meta mode makefiles 2011-05-02 Simon J. Gerraty * meta.stage.mk: add STAGE_AS_SETS and stage_as for things that need to be staged with different names. 2011-05-01 Simon J. Gerraty * meta.stage.mk: add notion of STAGE_SETS so a makefile can stage to multiple dirs 2011-04-03 Simon J. Gerraty * rst2htm.mk: convert rst to s5 (slides) or plain html depending on target name. 2011-03-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110330 2011-03-29 Simon J. Gerraty * sys.mk (_DEBUG_MAKE_FLAGS): use indirection so that DEBUG_MAKE_FLAGS0 can be used to debug level 0 only and DEBUG_MAKE_FLAGS for the rest. * sys.mk: re-define M_whence in terms of M_type. M_type is useful for checking if something is a builtin. 2011-03-16 Simon J. Gerraty * meta.stage.mk: add stage_symlinks and leverage StageLinks for stage_libs 2011-03-10 Simon J. Gerraty * dirdeps.mk: correct value for _depdir_files depends on .MAKE.DEPENDFILE Add our copyright - just to make it clear we have frobbed this quite a bit. DEP_MACHINE needs to be set to MACHINE each time, if using only Makefile.depend (cf. Makefile.depend.${MACHINE}) * meta.stage.mk: meta mode version of staging * init.mk, final.mk: include local.*.mk to simplify customization 2011-03-03 Simon J. Gerraty * auto.obj.mk: just because we are doing mk destroy, we should still set .OBJDIR correctly if it exists. * install-mk (mksrc): do not exclude meta.sys.mk 2011-03-01 Simon J. Gerraty * host-target.mk: set/export _HOST_ARCH etc separately, catch junk resulting from uname -p, so we can find sys/Linux.mk correctly. 2011-02-18 Simon J. Gerraty * meta.sys.mk: throw an error if /dev/filemon is missing and we expected to be updating Makefile.depend* 2011-02-14 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110214 * meta.subdir.mk: add support for -DBOOTSTRAP_DEPENDFILES 2010-09-25 Simon J. Gerraty * meta.sys.mk: not valid for older bmake 2010-09-24 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100919 include dirdeps.mk et al from Juniper Networks, for meta mode - requires filemon(9). * sys.mk, subdir.mk: Add hooks for meta mode. we do this as meta.sys.mk, meta.autodep.mk and meta.subdir.mk to make turning it on/off simple. 2010-06-16 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100616 * fix typo in sys.mk 2010-06-12 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100612 * lib.mk: remove duplicate addition to SOBJS 2010-06-10 Simon J. Gerraty * sys.mk: Add a means of selectively turning on debug flags. Eg. DEBUG_MAKE_FLAGS=-dv DEBUG_MAKE_DIRS="*lib/sjg" will act as if we did make -dv if .CURDIR ends in lib/sjg DEBUG_MAKE_SYS_DIRS does the same thing, but we set the flags at the start of sys.mk rather than the end. This only makes sense for leaf dirs, so we check that .MAKE.LEVEL > 0 2010-06-09 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100608 * sys.mk: include sys.env.mk later so it can use M_ListToSkip et al. * examples/sys.clean-env.mk: require MAKE_VERIONS >= 20100606 also make it easier for folk to tweak 2010-06-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100606 do not install examples/* * FILES: add examples/sys.clean-env.mk * examples/sys.clean-env.mk: use .export-env to handle MAKEOBJDIR this requires bmake-20100606 or later to work. 2010-05-13 Simon J. Gerraty * sys.mk (M_tA): better simulate the result of :tA if not available. 2010-05-04 Simon J. Gerraty * sys.mk: canonicalize MAKE_VERSION old versions reported bmake- build- whereas we only care about 2010-04-25 Simon J. Gerraty * install-mk: just warn about FORCE_{BSD,SYS}_MK being ignored * lib.mk: we only build the shared lib if SHLIB_FULLVERSION is !empty 2010-04-22 Simon J. Gerraty * dpadd.mk: use LDADD_* if defined. 2010-04-21 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100420 * sys/NetBSD.mk: add MACHINE_CPU to keep netbsd makefiles happy * autoconf.mk allow AUTO_AUTOCONF 2010-04-19 Simon J. Gerraty * obj.mk: add objwarn to keep freebsd makefiles happy * auto.obj.mk: ensure Mkdirs is available. * FILES: add auto.dep.mk - a simpler version of autodep.mk * dep.mk: auto.dep.mk does not do 'make depend' so ignore it if asked to do that. fix/simplify the tests for when to run mkdep. * auto.dep.mk: add some explanation of how/what we do. * autodep.mk: skip the .OPTIONAL frobbing of .depend bmake's FROM_DEPEND flag makes it redundant. 2010-04-13 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100404 * subdir.mk: protect from multiple inclusion using _SUBDIRUSE. * obj.mk: protect from multiple inclusion even as bsd.obj.mk Also create a target _SUBDIRUSE so that we can be used without subdir.mk 2010-04-12 Simon J. Gerraty * dep.mk: use <> when .including so can override. 2010-01-11 Simon J. Gerraty * lib.mk (SHLIB_LINKS): ensure a string comparison. 2010-01-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100102 * own.mk: ensure PRINTOBJDIR works * autoconf.mk: pass on CONFIGURE_ARGS * init.mk: handle COPTS.${.IMPSRC:T} etc. * lib.mk: allow sys.mk to control SHLIB_FULLVERSION fix handling of symlinks for darwin * libnames.mk: add DSHLIBEXT for libs which only exist as shared. * man.mk: suppress chown when not root. * rst2htm.mk: allow srcs from multiple locations. * sys.mk: M_whence, stop after 1st line of output. * sys/Darwin.mk: Use .dylib for DSHLIBEXT and HOST_LIBEXT * sys/SunOS.mk: we need to export PATH 2009-12-23 Simon J. Gerraty * install-mk (MK_VERSION): bump version include rst2htm.mk 2009-12-17 Simon J. Gerraty * sys.mk,libnames.mk add .-include this allows local customization without the need to edit the distributed files. 2009-12-14 Simon J. Gerraty * dpadd.mk (__dpadd_libdirs): order -L's to avoid picking up older versions already installed. 2009-12-13 Simon J. Gerraty * stage.mk (.stage-install): generalize lib.mk's .libinstall * rules.mk rules for generic Makefile. * inc.mk install for includes. 2009-12-11 Simon J. Gerraty * sys/NetBSD.mk (MAKE_VERSION): some of our *.mk want to check this, so provide it if using native make. 2009-12-10 Simon J. Gerraty * FILES: move all the platform *.sys.mk files to sys/*.mk * Rename Generic.sys.mk to sys.mk - we always want it. 2009-11-17 Simon J. Gerraty * install-mk (MK_VERSION): bump version * host-target.mk: only export the expensive stuff * Generic.sys.mk (sys_mk): for SunOS we need to look for ${HOST_OS}.${HOST_OSMAJOR} too! 2009-11-07 Simon J. Gerraty * install-mk (MK_VERSION): bump version * lib.mk: if sys.mk doesn't give us an lorder, don't use it. based on patch from Greg Olszewski. * Generic.sys.mk: if we have nothing to work with set LORDER etc only if we can find it. 2009-09-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version * man.mk: cleanman: remove CLEANMAN if defined. 2009-09-04 Simon J. Gerraty * SunOS.5.sys.mk (CC): Use ?= like the other *sys.mk 2009-07-17 Simon J. Gerraty * install-mk (MK_VERSION): bump version include auto.obj.mk 2009-03-26 Simon J. Gerraty * prog.mk,lib.mk: ensure test of USE_DPADD_MK doesn't fail. 2008-11-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version man.mk: ensure we generate *.cat1 etc in . 2008-07-16 Simon J. Gerraty * install-mk (MK_VERSION): bump version add prlist.mk 2007-11-25 Simon J. Gerraty * Generic.sys.mk: Allow os specific sys.mk to be in a subdir of ${.PARSEDIR} 2007-11-22 Simon J. Gerraty * install-mk (MK_VERSION): bump version * general cleanup * dpadd.mk introduce DPMAGIC_LIBS_* 2007-04-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version * libs.mk, progs.mk, autodep.mk: allow for per lib/prog depend files and ensure clean is called for each lib/prog. 2007-03-27 Simon J. Gerraty * autodep.mk (.depend): delete lines that do not start with space and do not contain ':' 2007-02-16 Simon J. Gerraty * autodep.mk (.depend): gcc may wrap lines if pathnames are long so make sure the transform for .OPTIONAL copes. 2007-02-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version * own.mk: make sure RM and LN are defined. * obj.mk: fix a typo, and objlink target. 2006-12-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version * added libs.mk - analogous to progs.mk make both of them always inlcude {lib,prog}.mk 2006-12-28 Simon J. Gerraty * progs.mk: add a means of building multiple apps in one dir. 2006-11-26 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20061126 * warnings.mk: detect invalid WARNINGS_SET * warnings.mk: use ${.TARGET:T:R}.o when looking for target specific warnings. * For .cc sources, turn off warnings that g++ vomits on. 2006-11-08 Simon J. Gerraty * own.mk: if __initialized__ target doesn't exist and we are FreeBSD we got here directly from sys.mk 2006-11-06 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20061106 add scripts.mk 2006-03-18 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060318 * autodep.mk: avoid := when modifying OBJS into __dependsrcs 2006-03-02 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060302 * autodep.mk: use -MF et al to help gcc+ccache DTRT. 2006-03-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060301 * autodep.mk (.depend): if MAKE_VERSION is newer than 20050530 we can make .END depend on .depend and make .depend depend on __depsrcs that exist. * dpadd.mk: add SRC_PATHADD 2005-11-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20051104 * prog.mk: remove all the LIBC?= junk, use .-include libnames.mk instead (none by default). also if USE_DPADD_MK is set, include that. 2005-10-09 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20051001 Add UnixWare.sys.mk from Klaus Heinz. 2005-04-05 Simon J. Gerraty * install-mk: always install *.sys.mk and if need be symlink one to sys.mk 2005-03-22 Simon J. Gerraty * subdir.mk, own.mk: use .MAKE rather than MAKE 2004-02-15 Simon J. Gerraty * own.mk: don't use NetBSD's _SRC_TOP_ it can cause confusion. Also don't take just 'mk' as a srctop indicator. 2004-02-14 Simon J. Gerraty * warnings.mk: overhauled, now very powerful. 2004-02-03 Simon J. Gerraty * Generic.sys.mk: need to use ${.PARSEDIR} with exists(). 2004-02-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20040201 * extract HOST_TARGET stuff to host-target.mk so own.mk and Generic.sys.mk can share. * fix typo in autodep.mk _SUBDIRUSE not _SUBDIR. 2003-09-30 Simon J. Gerraty * install-mk (MK_VERSION): 20030930 * rename generic.sys.mk to Generic.sys.mk so that it does not get installed (unless being used as sys.mk) * set OS and ROOT_GROUP for those that we know the value. for others (eg. Generic.sys.mk) wrap the != in an .ifndef so we don't do it again for each sub-make. 2003-09-28 Simon J. Gerraty * install-mk (MK_VERSION): 20030928 Add some extra *.sys.mk from bootstrap-pkgsrc some of these likely still need work. Make everything default to root:wheel ownership, sys.mk can set ROOT_GROUP accordingly. 2003-08-07 Simon J. Gerraty * install-mk: if FORCE_BSD_MK={cp,ln} use the ones in SYS_MK_DIR not the portable ones. 2003-07-31 Simon J. Gerraty * install-mk: add ability to use cp -f when updating destination .mk files. Also now possible to play games with FORCE_SYS_MK=ln etc on *BSD machines to link /usr/share/mk/sys.mk into dest - not recommended unless you seriously want to. 2003-07-28 Simon J. Gerraty * own.mk (IMPFLAGS): add support for COPTS.${IMPSRC:T} etc for semi-compatability with NetBSD. 2003-07-23 Simon J. Gerraty * install-mk: add a version indicator 2003-07-22 Simon J. Gerraty * prog.mk: don't try and use ${LIBCRT0} if its /dev/null * install-mk: Allow FORCE_SYS_MK to come from env Index: head/contrib/bmake/mk/FILES =================================================================== --- head/contrib/bmake/mk/FILES (revision 281811) +++ head/contrib/bmake/mk/FILES (revision 281812) @@ -1,61 +1,65 @@ ChangeLog FILES README auto.obj.mk autoconf.mk autodep.mk auto.dep.mk +cython.mk dep.mk doc.mk dpadd.mk final.mk host-target.mk host.libnames.mk inc.mk init.mk install-mk java.mk lib.mk libnames.mk libs.mk links.mk man.mk +manifest.mk mk-files.txt +mkopt.sh nls.mk obj.mk options.mk own.mk prlist.mk prog.mk progs.mk rst2htm.mk scripts.mk srctop.mk stage-install.sh subdir.mk sys.mk sys.clean-env.mk sys.dependfile.mk sys/AIX.mk sys/Darwin.mk sys/Generic.mk sys/HP-UX.mk sys/IRIX.mk sys/Linux.mk sys/NetBSD.mk sys/OSF1.mk sys/OpenBSD.mk sys/SunOS.mk sys/UnixWare.mk target-flags.mk warnings.mk +whats.mk yacc.mk dirdeps.mk gendirdeps.mk install-new.mk meta2deps.py meta2deps.sh meta.sys.mk meta.autodep.mk meta.stage.mk meta.subdir.mk Index: head/contrib/bmake/mk/auto.dep.mk =================================================================== --- head/contrib/bmake/mk/auto.dep.mk (revision 281811) +++ head/contrib/bmake/mk/auto.dep.mk (revision 281812) @@ -1,64 +1,64 @@ # # RCSid: -# $Id: auto.dep.mk,v 1.2 2010/04/19 17:37:19 sjg Exp $ +# $Id: auto.dep.mk,v 1.3 2014/08/04 05:19:10 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # This module provides automagic dependency generation along the # lines suggested in the GNU make.info -# set MKDEP=auto.dep and dep.mk will include us +# set MKDEP_MK=auto.dep.mk and dep.mk will include us # This version differs from autodep.mk, in that # we use ${.TARGET:T}.d rather than ${.TARGET:T:R}.d # this makes it simpler to get the args to -MF and -MT right # and ensure we can simply include all the .d files. # # However suffix rules do not work with something like .o.d so we # don't even try to handle 'make depend' gracefully. # dep.mk will handle that itself. # .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: # this what bmake > 20100401 will look for .MAKE.DEPENDFILE ?= .depend # set this to -MMD to ignore /usr/include # actually it ignores <> so may not be a great idea CFLAGS_MD ?= -MD # -MF etc not available on all gcc versions. CFLAGS_MF ?= -MF ${.TARGET:T}.d -MT ${.TARGET:T} CFLAGS += ${CFLAGS_MD} ${CFLAGS_MF} CXXFLAGS += ${CFLAGS_MD} ${CFLAGS_MF} CLEANFILES += .depend ${.MAKE.DEPENDFILE} *.d # skip generating dependfile for misc targets .if ${.TARGETS:Uall:M*all} != "" .END: ${.MAKE.DEPENDFILE} .endif # doing 'make depend' isn't a big win with this model .if !target(depend) depend: ${.MAKE.DEPENDFILE} .endif # this is trivial ${.MAKE.DEPENDFILE}: ${OBJS} ${POBJS} ${SOBJS} -@for f in ${.ALLSRC:M*o:T:O:u:%=%.d}; do \ echo ".-include \"$$f\""; \ done > $@ .endif Index: head/contrib/bmake/mk/auto.obj.mk =================================================================== --- head/contrib/bmake/mk/auto.obj.mk (revision 281811) +++ head/contrib/bmake/mk/auto.obj.mk (revision 281812) @@ -1,56 +1,61 @@ -# $Id: auto.obj.mk,v 1.8 2011/08/08 17:35:20 sjg Exp $ +# $Id: auto.obj.mk,v 1.10 2015/04/16 16:59:00 sjg Exp $ # # @(#) Copyright (c) 2004, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # ECHO_TRACE ?= echo .ifndef Mkdirs # A race condition in some versions of mkdir, means that it can bail # if another process made a dir that mkdir expected to. # We repeat the mkdir -p a number of times to try and work around this. # We stop looping as soon as the dir exists. # If we get to the end of the loop, a plain mkdir will issue an error. Mkdirs= Mkdirs() { \ for d in $$*; do \ for i in 1 2 3 4 5 6; do \ mkdir -p $$d; \ test -d $$d && return 0; \ done > /dev/null 2>&1; \ mkdir $$d || exit $$?; \ done; } .endif # if MKOBJDIRS is set to auto (and NOOBJ isn't defined) do some magic... # This will automatically create objdirs as needed. # Skip it if we are just doing 'clean'. +.if ${MK_AUTO_OBJ:Uno} == "yes" +MKOBJDIRS= auto +.endif .if !defined(NOOBJ) && !defined(NO_OBJ) && ${MKOBJDIRS:Uno} == auto # Use __objdir here so it is easier to tweak without impacting # the logic. __objdir?= ${MAKEOBJDIR} +__objdir:= ${__objdir:tA} .if ${.OBJDIR} != ${__objdir} # We need to chdir, make the directory if needed .if !exists(${__objdir}/) && \ (${.TARGETS} == "" || ${.TARGETS:Nclean*:N*clean:Ndestroy*} != "") # This will actually make it... __objdir_made != echo ${__objdir}/; umask ${OBJDIR_UMASK:U002}; \ ${ECHO_TRACE} "[Creating objdir ${__objdir}...]" >&2; \ ${Mkdirs}; Mkdirs ${__objdir} +__objdir:= ${__objdir:tA} .endif # This causes make to use the specified directory as .OBJDIR .OBJDIR: ${__objdir} .if ${.OBJDIR} != ${__objdir} && ${__objdir_made:Uno:M${__objdir}/*} != "" -.error could not use ${__objdir} +.error could not use ${__objdir}: .OBJDIR=${.OBJDIR} .endif .endif .endif Index: head/contrib/bmake/mk/autodep.mk =================================================================== --- head/contrib/bmake/mk/autodep.mk (revision 281811) +++ head/contrib/bmake/mk/autodep.mk (revision 281812) @@ -1,216 +1,211 @@ # # RCSid: -# $Id: autodep.mk,v 1.33 2014/04/05 22:56:54 sjg Exp $ +# $Id: autodep.mk,v 1.34 2014/08/04 05:12:27 sjg Exp $ # # @(#) Copyright (c) 1999-2010, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # This module provides automagic dependency generation along the # lines suggested in the GNU make.info # The depend target is mainly for backwards compatibility, # dependencies are normally updated as part of compilation. -# set MKDEP=autodep and dep.mk will include us .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: - -# different versions of bsd.dep.mk use these -MKDEP=autodep -MKDEPCMD=autodep DEPENDFILE?= .depend .for d in ${DEPENDFILE:N.depend} # bmake only groks .depend .-include "$d" .endfor # it does nothing if SRCS is not defined or is empty .if defined(SRCS) && !empty(SRCS) DEPSRCS?=${SRCS} __depsrcs=${DEPSRCS:M*.c} __depsrcs+=${DEPSRCS:M*.y} __depsrcs+=${DEPSRCS:M*.l} __depsrcs+=${DEPSRCS:M*.s} __depsrcs+=${DEPSRCS:M*.S} __depsrcs+=${DEPSRCS:M*.cc} __depsrcs+=${DEPSRCS:M*.cpp} __depsrcs+=${DEPSRCS:M*.C} __depsrcs+=${DEPSRCS:M*.cxx} __depsrcs+=${DEPSRCS:M*.pc} .for s in ${__depsrcs} ${s:T:R}.d: $s .endfor __depsrcs:=${__depsrcs:T:R:S/$/.d/g} # we also need to handle makefiles where the .d's from __depsrcs # don't match those from OBJS # we avoid using := here, since the modifier applied to OBJS # can cause trouble if there are any undefined vars in OBJS. __dependsrcsx?= ${__depsrcs} ${OBJS:S/.o/.d/} __dependsrcs= ${__dependsrcsx:O:u} # clean up any .c files we may have generated #__gensrcs:= ${DEPSRCS:M*.y} ${DEPSRCS:M*.l} #CLEANFILES+= ${__gensrcs:T:R:S/$/.c/g} # set this to -MMD to ignore /usr/include # actually it ignores <> so may not be a great idea CFLAGS_MD?=-MD # -MF etc not available on all gcc versions. # we "fix" the .o later CFLAGS_MF?=-MF ${.TARGET:T:R}.d -MT ${.TARGET:T:R}.o CFLAGS+= ${CFLAGS_MD} ${CFLAGS_MF} RM?= rm # watch out for people who don't use CPPFLAGS CPPFLAGS_MD=${CFLAGS:M-[IUD]*} ${CPPFLAGS} CXXFLAGS_MD=${CXXFLAGS:M-[IUD]*} ${CPPFLAGS} # just in case these need to be different CC_MD?=${CC} CXX_MD?=${CXX} # so we can do an explicit make depend, but not otherwise .if make(depend) .SUFFIXES: .d .if empty(CFLAGS_MD) .y.d: @echo updating dependencies for $< @${YACC} ${YFLAGS} $< @${SHELL} -ec "${CC_MD} -M ${CPPFLAGS_MD} y.tab.c | sed '/:/s/^/$@ /' > $@" || { ${RM} -f y.tab.c $@; false; } @${RM} -f y.tab.c .l.d: @echo updating dependencies for $< ${LEX} ${LFLAGS} $< @${SHELL} -ec "${CC_MD} -M ${CPPFLAGS_MD} lex.yy.c | sed '/:/s/^/$@ /' > $@" || { ${RM} -f lex.yy.c $@; false; } @${RM} -f lex.yy.c .c.d: @echo updating dependencies for $< @${SHELL} -ec "${CC_MD} -M ${CPPFLAGS_MD} $< | sed '/:/s/^/$@ /' > $@" || { ${RM} -f $@; false; } .s.d .S.d: @echo updating dependencies for $< @${SHELL} -ec "${CC_MD} -M ${CPPFLAGS_MD} ${AINC} $< | sed '/:/s/^/$@ /' > $@" || { ${RM} -f $@; false; } .cc.d .cpp.d .C.d .cxx.d: @echo updating dependencies for $< @${SHELL} -ec "${CXX_MD} -M ${CXXFLAGS_MD} $< | sed '/:/s/^/$@ /' > $@" || { ${RM} -f $@; false; } .else .y.d: ${YACC} ${YFLAGS} $< ${CC_MD} ${CFLAGS_MD:S/D//} ${CPPFLAGS_MD} y.tab.c > $@ || { ${RM} -f y.tab.c $@; false; } ${RM} -f y.tab.c .l.d: ${LEX} ${LFLAGS} $< ${CC_MD} ${CFLAGS_MD:S/D//} ${CPPFLAGS_MD} lex.yy.c > $@ || { ${RM} -f lex.yy.c $@; false; } ${RM} -f lex.yy.c .c.d: ${CC_MD} ${CFLAGS_MD:S/D//} ${CPPFLAGS_MD} $< > $@ || { ${RM} -f $@; false; } .s.d .S.d: ${CC_MD} ${CFLAGS_MD:S/D//} ${CPPFLAGS_MD} ${AINC} $< > $@ || { ${RM} -f $@; false; } .cc.d .cpp.d .C.d .cxx.d: ${CXX_MD} ${CFLAGS_MD:S/D//} ${CXXFLAGS_MD} $< > $@ || { ${RM} -f $@; false; } .endif .if !target(depend) depend: beforedepend ${DEPENDFILE} afterdepend _SUBDIRUSE ${DEPENDFILE}: ${DEPSRCS} ${__dependsrcs} .NOPATH: ${__dependsrcs} .OPTIONAL: ${__dependsrcs} .endif .endif # make(depend) .if empty(CFLAGS_MD) # make sure the .d's are generated/updated ${PROG} ${_LIBS}: ${DEPENDFILE} .endif .ORDER: beforedepend ${DEPENDFILE} afterdepend .if ${.OBJDIR} != ${.CURDIR} __depfiles= *.d .else __depfiles= ${__dependsrcs} .endif DEPCLEANFILES= ${DEPENDFILE} ${__depfiles} y.tab.d *.tmp.d cleandir: cleanautodepend cleanautodepend: ${RM} -f ${DEPCLEANFILES} CLEANFILES+= ${DEPCLEANFILES} .if defined(__dependsrcs) && !empty(__dependsrcs) .if make(depend) || !(make(clean*) || make(destroy*) || make(obj) || make(*install) || make(install-*)) # this ensures we do the right thing if only building a shared or # profiled lib OBJ_EXTENSIONS?=.o .po .so .So MDLIB_SED= -e '/:/s,^\([^\.:]*\)\.[psS]*o,${OBJ_EXTENSIONS:S,^,\1,},' .ifdef NOMD_SED .ifdef LIB MD_SED=sed ${MDLIB_SED} .else MD_SED=cat .endif .else # arrange to put some variable names into ${DEPENDFILE} .ifdef LIB MD_SED=sed ${MDLIB_SED} .else MD_SED=sed .endif SUBST_DEPVARS+= SB TOP BACKING SRC SRCDIR BASE BASEDIR .for v in ${SUBST_DEPVARS} .if defined(${v}) && !empty(${v}) MD_SED+= -e 's,${$v},$${$v},' .endif .endfor .endif .if (${MD_SED} == "sed") MD_SED=cat .endif # this will be done whenever make finishes successfully .if ${MAKE_VERSION:U0:[1]:C/.*-//} < 20050530 .END: .else .END: ${DEPENDFILE} # we do not want to trigger building .d's just use them if they exist ${DEPENDFILE}: ${__dependsrcs:@d@${exists($d):?$d:}@} .endif -@${MD_SED} ${__depfiles} > ${DEPENDFILE}.new 2> /dev/null && \ test -s ${DEPENDFILE}.new && mv ${DEPENDFILE}.new ${DEPENDFILE}; \ ${RM} -f ${DEPENDFILE}.new .endif .endif .else depend: beforedepend afterdepend _SUBDIRUSE .endif .if !target(beforedepend) beforedepend: .endif .if !target(afterdepend) afterdepend: .endif .endif Index: head/contrib/bmake/mk/cython.mk =================================================================== --- head/contrib/bmake/mk/cython.mk (nonexistent) +++ head/contrib/bmake/mk/cython.mk (revision 281812) @@ -0,0 +1,96 @@ +# RCSid: +# $Id: cython.mk,v 1.6 2014/10/15 06:23:51 sjg Exp $ +# +# @(#) Copyright (c) 2014, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +# this is what we build +CYTHON_MODULE = ${CYTHON_MODULE_NAME}${CYTHON_PYVERSION}.so + +CYTHON_MODULE_NAME?= it +CYTHON_SRCS?= ${CYTHON_MODULE_NAME}.pyx + +# this is where we save generated src +CYTHON_SAVEGENDIR?= ${.CURDIR}/gen + +# pyprefix is where python bits are +# which may not be where we want to put ours (prefix) +.if exists(/usr/pkg/include) +pyprefix?= /usr/pkg +.endif +pyprefix?= /usr/local + +PYTHON_VERSION?= 2.7 +PYTHON_H?= ${pyprefix}/include/python${PYTHON_VERSION}/Python.h +PYVERSION:= ${PYTHON_VERSION:C,\..*,,} + +# set this empty if you don't want to handle multiple versions +.if !defined(CYTHON_PYVERSION) +CYTHON_PYVERSION:= ${PYVERSION} +.endif + +CFLAGS+= -I${PYTHON_H:H} + +CYTHON_GENSRCS= ${CYTHON_SRCS:R:S,$,${CYTHON_PYVERSION}.c,} +SRCS+= ${CYTHON_GENSRCS} + +.SUFFIXES: .pyx .c .So + +CYTHON?= ${pyprefix}/bin/cython + +# if we don't have cython we can use pre-generated srcs +.if ${type ${CYTHON} 2> /dev/null || echo:L:sh:M/*} == "" +.PATH: ${CYTHON_SAVEGENDIR} +.else + +.if !empty(CYTHON_PYVERSION) +.for c in ${CYTHON_SRCS} +${c:R}${CYTHON_PYVERSION}.${c:E}: $c + ln -sf ${.ALLSRC:M*pyx} ${.TARGET} +.endfor +.endif + +.pyx.c: + ${CYTHON} ${CYTHON_FLAGS} -${PYVERSION} -o ${.TARGET} ${.IMPSRC} + + +save-gen: ${CYTHON_GENSRCS} + mkdir -p ${CYTHON_SAVEGENDIR} + cp -p ${.ALLSRC} ${CYTHON_SAVEGENDIR} + +.endif + +COMPILE.c?= ${CC} -c ${CFLAGS} + +.c.So: + ${COMPILE.c} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET} + +${CYTHON_MODULE}: ${SRCS:S,.c,.So,} + ${CC} ${CC_SHARED:U-shared} -o ${.TARGET} ${.ALLSRC:M*.So} ${LDADD} + +# conf.host_target() is limited to uname -m rather than uname -p +_HOST_MACHINE!= uname -m +.if ${HOST_TARGET:M*${_HOST_MACHINE}} == "" +PY_HOST_TARGET:= ${HOST_TARGET:S,${_HOST_ARCH:U${uname -p:L:sh}}$,${_HOST_MACHINE},} +.endif + +MODULE_BINDIR?= ${.CURDIR:H}/${PY_HOST_TARGET:U${HOST_TARGET}} + +build-cython-module: ${CYTHON_MODULE} + +install-cython-module: ${CYTHON_MODULE} + test -d ${DESTDIR}${MODULE_BINDIR} || \ + ${INSTALL} -d ${DESTDIR}${MODULE_BINDIR} + ${INSTALL} -m 755 ${.ALLSRC} ${DESTDIR}${MODULE_BINDIR} + +CLEANFILES+= *.So ${CYTHON_MODULE} Property changes on: head/contrib/bmake/mk/cython.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/mk/dep.mk =================================================================== --- head/contrib/bmake/mk/dep.mk (revision 281811) +++ head/contrib/bmake/mk/dep.mk (revision 281812) @@ -1,133 +1,127 @@ -# $Id: dep.mk,v 1.16 2012/11/11 22:37:02 sjg Exp $ +# $Id: dep.mk,v 1.17 2014/08/04 05:12:27 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: # handle Proc*C as well... .if defined(SRCS) .if !empty(SRCS:M*.pc) .include .endif # it would be nice to be able to query .SUFFIXES OBJ_EXTENSIONS+= .o .po .lo .So # explicit dependencies help short-circuit .SUFFIX searches SRCS_DEP_FILTER+= N*.[hly] .for s in ${SRCS:${SRCS_DEP_FILTER:O:u:ts:}} .for e in ${OBJ_EXTENSIONS:O:u} .if !target(${s:T:R}$e) ${s:T:R}$e: $s .endif .endfor .endfor .endif .if exists(/usr/bin/mkdep) MKDEP_CMD?= mkdep .elif exists(/usr/local/share/bin/mkdeps.sh) MKDEP_CMD?= /usr/local/share/bin/mkdeps.sh -N .endif MKDEP_CMD?= mkdep MKDEP ?= ${MKDEP_CMD} .NOPATH: .depend -.if ${MKDEP} == "auto.dep" && make(depend) +.if ${MKDEP_MK:Uno} == "auto.dep.mk" && make(depend) # auto.dep.mk does not "do" depend MK_AUTODEP= no .endif .if ${MK_AUTODEP} == yes -.if ${MKDEP:T:S,auto,,} != ${MKDEP:T} -.include <${MKDEP}.mk> +MKDEP_MK ?= autodep.mk +.include <${MKDEP_MK}> .else -.include -.endif -.else -.if ${MKDEP:T:S,auto,,} != ${MKDEP:T} -MKDEP = ${MKDEP_CMD} -.endif MKDEP_ENV_VARS += CC CXX .for v in ${MKDEP_ENV_VARS:O:u} .if !empty($v) MKDEP_ENV += $v='${$v}' .endif .endfor _MKDEP = ${MKDEP_ENV} ${MKDEP} # some of the rules involve .h sources, so remove them from mkdep line .if !target(depend) depend: beforedepend .depend _SUBDIRUSE afterdepend .if defined(SRCS) # libs can have too many SRCS for a single command line # so do them one at a time. .depend: ${SRCS} ${.PARSEDIR}/${.PASEFILE} @rm -f .depend .ifdef LIB @files="${.ALLSRC:M*.[sS]}"; \ set -x; for f in $$files; do ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} ${AINC} $$f; done @files="${.ALLSRC:M*.c} ${.ALLSRC:M*.pc:T:.pc=.c}"; \ set -x; for f in $$files; do ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} $$f; done @files="${.ALLSRC:M*.cc} ${.ALLSRC:M*.C} ${.ALLSRC:M*.cxx}"; \ set -x; for f in $$files; do ${_MKDEP} -a ${MKDEPFLAGS} \ ${CXXFLAGS:M-[ID]*} ${CPPFLAGS} $$f; done .else @files="${.ALLSRC:M*.[Ss]}"; \ case "$$files" in *.[Ss]*) \ echo ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} ${AINC} $$files; \ ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} ${AINC} $$files;; \ esac @files="${.ALLSRC:M*.c} ${.ALLSRC:M*.pc:T:.pc=.c}"; \ case "$$files" in *.c*) \ echo ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} $$files; \ ${_MKDEP} -a ${MKDEPFLAGS} \ ${CFLAGS:M-[ID]*} ${CPPFLAGS} $$files;; \ esac @files="${.ALLSRC:M*.cc} ${.ALLSRC:M*.C} ${.ALLSRC:M*.cxx}"; \ case "$$files" in *.[Cc]*) \ echo ${_MKDEP} -a ${MKDEPFLAGS} \ ${CXXFLAGS:M-[ID]*} ${CPPFLAGS} $$files; \ ${_MKDEP} -a ${MKDEPFLAGS} \ ${CXXFLAGS:M-[ID]*} ${CPPFLAGS} $$files;; \ esac .endif .else .depend: .endif .if !target(beforedepend) beforedepend: .endif .if !target(afterdepend) afterdepend: .endif .endif .endif .if !target(tags) .if defined(SRCS) tags: ${SRCS} _SUBDIRUSE -cd ${.CURDIR}; ctags -f /dev/stdout ${.ALLSRC:N*.h} | \ sed "s;\${.CURDIR}/;;" > tags .else tags: .endif .endif .if defined(SRCS) cleandir: cleandepend .if !target(cleandepend) cleandepend: rm -f .depend ${.CURDIR}/tags .endif .endif .endif Index: head/contrib/bmake/mk/dirdeps.mk =================================================================== --- head/contrib/bmake/mk/dirdeps.mk (revision 281811) +++ head/contrib/bmake/mk/dirdeps.mk (revision 281812) @@ -1,513 +1,642 @@ -# $Id: dirdeps.mk,v 1.35 2014/05/03 06:27:56 sjg Exp $ +# $Id: dirdeps.mk,v 1.49 2015/03/11 21:39:28 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Much of the complexity here is for supporting cross-building. # If a tree does not support that, simply using plain Makefile.depend # should provide sufficient clue. # Otherwise the recommendation is to use Makefile.depend.${MACHINE} # as expected below. # Note: this file gets multiply included. # This is what we do with DIRDEPS # DIRDEPS: # This is a list of directories - relative to SRCTOP, it is # normally only of interest to .MAKE.LEVEL 0. # In some cases the entry may be qualified with a . # or . suffix (see TARGET_SPEC_VARS below), # for example to force building something for the pseudo # machines "host" or "common" regardless of current ${MACHINE}. # # All unqualified entries end up being qualified with .${TARGET_SPEC} # and partially qualified (if TARGET_SPEC_VARS has multiple # entries) are also expanded to a full .. # The _DIRDEP_USE target uses the suffix to set TARGET_SPEC # correctly when visiting each entry. # # The fully qualified directory entries are used to construct a # dependency graph that will drive the build later. # # Also, for each fully qualified directory target, we will search # using ${.MAKE.DEPENDFILE_PREFERENCE} to find additional # dependencies. We use Makefile.depend (default value for # .MAKE.DEPENDFILE_PREFIX) to refer to these makefiles to # distinguish them from others. # # Each Makefile.depend file sets DEP_RELDIR to be the # the RELDIR (path relative to SRCTOP) for its directory, and # since each Makefile.depend file includes dirdeps.mk, this # processing is recursive and results in .MAKE.LEVEL 0 learning the # dependencies of the tree wrt the initial directory (_DEP_RELDIR). # # BUILD_AT_LEVEL0 # Indicates whether .MAKE.LEVEL 0 builds anything: # if "no" sub-makes are used to build everything, # if "yes" sub-makes are only used to build for other machines. # It is best to use "no", but this can require fixing some # makefiles to not do anything at .MAKE.LEVEL 0. # # TARGET_SPEC_VARS # The default value is just MACHINE, and for most environments # this is sufficient. The _DIRDEP_USE target actually sets # both MACHINE and TARGET_SPEC to the suffix of the current # target so that in the general case TARGET_SPEC can be ignored. # # If more than MACHINE is needed then sys.mk needs to decompose # TARGET_SPEC and set the relevant variables accordingly. # It is important that MACHINE be included in and actually be # the first member of TARGET_SPEC_VARS. This allows other # variables to be considered optional, and some of the treatment # below relies on MACHINE being the first entry. # Note: TARGET_SPEC cannot contain any '.'s so the target # triple used by compiler folk won't work (directly anyway). # # For example: # # # Always list MACHINE first, # # other variables might be optional. # TARGET_SPEC_VARS = MACHINE TARGET_OS # .if ${TARGET_SPEC:Uno:M*,*} != "" # _tspec := ${TARGET_SPEC:S/,/ /g} # MACHINE := ${_tspec:[1]} # TARGET_OS := ${_tspec:[2]} # # etc. # # We need to stop that TARGET_SPEC affecting any submakes # # and deal with MACHINE=${TARGET_SPEC} in the environment. # TARGET_SPEC = # # export but do not track # .export-env TARGET_SPEC # .export ${TARGET_SPEC_VARS} # .for v in ${TARGET_SPEC_VARS:O:u} # .if empty($v) # .undef $v # .endif # .endfor # .endif # # make sure we know what TARGET_SPEC is # # as we may need it to find Makefile.depend* # TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # -.if ${.MAKE.LEVEL} == 0 +# touch this at your peril +_DIRDEP_USE_LEVEL?= 0 +.if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL} # only the first instance is interested in all this # First off, we want to know what ${MACHINE} to build for. # This can be complicated if we are using a mixture of ${MACHINE} specific # and non-specific Makefile.depend* .if !target(_DIRDEP_USE) # do some setup we only need once _CURDIR ?= ${.CURDIR} +_OBJDIR ?= ${.OBJDIR} +now_utc = ${%s:L:gmtime} +.if !defined(start_utc) +start_utc := ${now_utc} +.endif + # make sure these are empty to start with _DEP_TARGET_SPEC = _DIRDEP_CHECKED = # If TARGET_SPEC_VARS is other than just MACHINE # it should be set by sys.mk or similar by now. # TARGET_SPEC must not contain any '.'s. TARGET_SPEC_VARS ?= MACHINE # this is what we started with TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # this is what we mostly use below DEP_TARGET_SPEC = ${TARGET_SPEC_VARS:S,^,DEP_,:@v@${$v:U}@:ts,} # make sure we have defaults .for v in ${TARGET_SPEC_VARS} DEP_$v ?= ${$v} .endfor .if ${TARGET_SPEC_VARS:[#]} > 1 # Ok, this gets more complex (putting it mildly). # In order to stay sane, we need to ensure that all the build_dirs # we compute below are fully qualified wrt DEP_TARGET_SPEC. # The makefiles may only partially specify (eg. MACHINE only), # so we need to construct a set of modifiers to fill in the gaps. # jot 10 should output 1 2 3 .. 10 JOT ?= jot _tspec_x := ${${JOT} ${TARGET_SPEC_VARS:[#]}:L:sh} # this handles unqualified entries M_dep_qual_fixes = C;(/[^/.,]+)$$;\1.$${DEP_TARGET_SPEC}; # there needs to be at least one item missing for these to make sense .for i in ${_tspec_x:[2..-1]} _tspec_m$i := ${TARGET_SPEC_VARS:[2..$i]:@w@[^,]+@:ts,} _tspec_a$i := ,${TARGET_SPEC_VARS:[$i..-1]:@v@$$$${DEP_$v}@:ts,} M_dep_qual_fixes += C;(\.${_tspec_m$i})$$;\1${_tspec_a$i}; .endfor .else # A harmless? default. M_dep_qual_fixes = U .endif .if !defined(.MAKE.DEPENDFILE_PREFERENCE) # .MAKE.DEPENDFILE_PREFERENCE makes the logic below neater? # you really want this set by sys.mk or similar .MAKE.DEPENDFILE_PREFERENCE = ${_CURDIR}/${.MAKE.DEPENDFILE:T} .if ${.MAKE.DEPENDFILE:E} == "${TARGET_SPEC}" .if ${TARGET_SPEC} != ${MACHINE} .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R}.$${MACHINE} .endif .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R} .endif .endif _default_dependfile := ${.MAKE.DEPENDFILE_PREFERENCE:[1]:T} _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*} # for machine specific dependfiles we require ${MACHINE} to be at the end # also for the sake of sanity we require a common prefix .if !defined(.MAKE.DEPENDFILE_PREFIX) # knowing .MAKE.DEPENDFILE_PREFIX helps .if !empty(_machine_dependfiles) .MAKE.DEPENDFILE_PREFIX := ${_machine_dependfiles:[1]:T:R} .else .MAKE.DEPENDFILE_PREFIX := ${_default_dependfile:T} .endif .endif # this is how we identify non-machine specific dependfiles N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}} .endif # !target(_DIRDEP_USE) # if we were included recursively _DEP_TARGET_SPEC should be valid. .if empty(_DEP_TARGET_SPEC) # we may or may not have included a dependfile yet .if defined(.INCLUDEDFROMFILE) _last_dependfile := ${.INCLUDEDFROMFILE:M${.MAKE.DEPENDFILE_PREFIX}*} .else _last_dependfile := ${.MAKE.MAKEFILES:M*/${.MAKE.DEPENDFILE_PREFIX}*:[-1]} .endif -.if !empty(_debug_reldir) +.if ${_debug_reldir:U0} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _last_dependfile='${_last_dependfile}' .endif .if empty(_last_dependfile) || ${_last_dependfile:E:${N_notmachine}} == "" # this is all we have to work with DEP_MACHINE = ${TARGET_MACHINE:U${MACHINE}} _DEP_TARGET_SPEC := ${DEP_TARGET_SPEC} .else _DEP_TARGET_SPEC = ${_last_dependfile:${M_dep_qual_fixes:ts:}:E} .endif .if !empty(_last_dependfile) # record that we've read dependfile for this _DIRDEP_CHECKED += ${_CURDIR}.${TARGET_SPEC} .endif .endif # by now _DEP_TARGET_SPEC should be set, parse it. .if ${TARGET_SPEC_VARS:[#]} > 1 # we need to parse DEP_MACHINE may or may not contain more info _tspec := ${_DEP_TARGET_SPEC:S/,/ /g} .for i in ${_tspec_x} DEP_${TARGET_SPEC_VARS:[$i]} := ${_tspec:[$i]} .endfor .for v in ${TARGET_SPEC_VARS:O:u} .if empty(DEP_$v) .undef DEP_$v .endif .endfor .else DEP_MACHINE := ${_DEP_TARGET_SPEC} .endif # pickup customizations # as below you can use !target(_DIRDEP_USE) to protect things # which should only be done once. .-include "local.dirdeps.mk" # the first time we are included the _DIRDEP_USE target will not be defined # we can use this as a clue to do initialization and other one time things. .if !target(_DIRDEP_USE) # make sure this target exists dirdeps: beforedirdeps .WAIT beforedirdeps: # We normally expect to be included by Makefile.depend.* # which sets the DEP_* macros below. DEP_RELDIR ?= ${RELDIR} # this can cause lots of output! # set to a set of glob expressions that might match RELDIR DEBUG_DIRDEPS ?= no # remember the initial value of DEP_RELDIR - we test for it below. _DEP_RELDIR := ${DEP_RELDIR} # things we skip for host tools SKIP_HOSTDIR ?= -NSkipHostDir = ${SKIP_HOSTDIR:N*.host:S,$,.host,:N.host:${M_ListToSkip}} +NSkipHostDir = ${SKIP_HOSTDIR:N*.host*:S,$,.host*,:N.host*:S,^,${SRCTOP}/,:${M_ListToSkip}} # things we always skip # SKIP_DIRDEPS allows for adding entries on command line. SKIP_DIR += .host *.WAIT ${SKIP_DIRDEPS} SKIP_DIR.host += ${SKIP_HOSTDIR} DEP_SKIP_DIR = ${SKIP_DIR} \ ${SKIP_DIR.${DEP_TARGET_SPEC}:U} \ ${SKIP_DIR.${DEP_MACHINE}:U} \ ${SKIP_DIRDEPS.${DEP_MACHINE}:U} NSkipDir = ${DEP_SKIP_DIR:${M_ListToSkip}} .if defined(NO_DIRDEPS) || defined(NODIRDEPS) || defined(WITHOUT_DIRDEPS) # confine ourselves to the original dir DIRDEPS_FILTER += M${_DEP_RELDIR}* .endif # this is what we run below DIRDEP_MAKE?= ${.MAKE} # we suppress SUBDIR when visiting the leaves # we assume sys.mk will set MACHINE_ARCH # you can add extras to DIRDEP_USE_ENV # if there is no makefile in the target directory, we skip it. _DIRDEP_USE: .USE .MAKE @for m in ${.MAKE.MAKEFILE_PREFERENCE}; do \ test -s ${.TARGET:R}/$$m || continue; \ echo "${TRACER}Checking ${.TARGET:R} for ${.TARGET:E} ..."; \ MACHINE_ARCH= NO_SUBDIR=1 ${DIRDEP_USE_ENV} \ TARGET_SPEC=${.TARGET:E} \ MACHINE=${.TARGET:E} \ ${DIRDEP_MAKE} -C ${.TARGET:R} || exit 1; \ break; \ done .ifdef ALL_MACHINES # this is how you limit it to only the machines we have been built for # previously. .if empty(ONLY_MACHINE_LIST) .if !empty(ALL_MACHINE_LIST) # ALL_MACHINE_LIST is the list of all legal machines - ignore anything else _machine_list != cd ${_CURDIR} && 'ls' -1 ${ALL_MACHINE_LIST:O:u:@m@${.MAKE.DEPENDFILE:T:R}.$m@} 2> /dev/null; echo .else _machine_list != 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.* 2> /dev/null; echo .endif _only_machines := ${_machine_list:${NIgnoreFiles:UN*.bak}:E:O:u} .else _only_machines := ${ONLY_MACHINE_LIST} .endif .if empty(_only_machines) # we must be boot-strapping _only_machines := ${TARGET_MACHINE:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}} .endif .else # ! ALL_MACHINES # if ONLY_MACHINE_LIST is set, we are limited to that # if TARGET_MACHINE is set - it is really the same as ONLY_MACHINE_LIST # otherwise DEP_MACHINE is it - so DEP_MACHINE will match. _only_machines := ${ONLY_MACHINE_LIST:U${TARGET_MACHINE:U${DEP_MACHINE}}:M${DEP_MACHINE}} .endif .if !empty(NOT_MACHINE_LIST) _only_machines := ${_only_machines:${NOT_MACHINE_LIST:${M_ListToSkip}}} .endif # make sure we have a starting place? DIRDEPS ?= ${RELDIR} .endif # target -_debug_reldir := ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} -_debug_search := ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} +# if repeatedly building the same target, +# we can avoid the overhead of re-computing the tree dependencies. +MK_DIRDEPS_CACHE ?= no +BUILD_DIRDEPS_CACHE ?= no +BUILD_DIRDEPS ?= yes +.if !defined(NO_DIRDEPS) +.if ${MK_DIRDEPS_CACHE} == "yes" +# this is where we will cache all our work +DIRDEPS_CACHE?= ${_OBJDIR}/dirdeps.cache${.TARGETS:Nall:O:u:ts-:S,^,.,:N.} + +# just ensure this exists +build-dirdeps: + +M_oneperline = @x@\\${.newline} $$x@ + +.if ${BUILD_DIRDEPS_CACHE} == "no" +.if !target(dirdeps-cached) +# we do this via sub-make +BUILD_DIRDEPS = no + +dirdeps: dirdeps-cached +dirdeps-cached: ${DIRDEPS_CACHE} .MAKE + @echo "${TRACER}Using ${DIRDEPS_CACHE}" + @MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \ + dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no + +# these should generally do +BUILD_DIRDEPS_MAKEFILE ?= ${MAKEFILE} +BUILD_DIRDEPS_TARGETS ?= ${.TARGETS} + +# we need the .meta file to ensure we update if +# any of the Makefile.depend* changed. +# We do not want to compare the command line though. +${DIRDEPS_CACHE}: .META .NOMETA_CMP + +@{ echo '# Autogenerated - do NOT edit!'; echo; \ + echo 'BUILD_DIRDEPS=no'; echo; \ + echo '.include '; \ + } > ${.TARGET}.new + +@MAKELEVEL=${.MAKE.LEVEL} DIRDEPS_CACHE=${DIRDEPS_CACHE} \ + DIRDEPS="${DIRDEPS}" \ + MAKEFLAGS= ${.MAKE} -C ${_CURDIR} -f ${BUILD_DIRDEPS_MAKEFILE} \ + ${BUILD_DIRDEPS_TARGETS} BUILD_DIRDEPS_CACHE=yes \ + 3>&1 1>&2 | sed 's,${SRCTOP},$${SRCTOP},g' >> ${.TARGET}.new && \ + mv ${.TARGET}.new ${.TARGET} + +.endif +.elif !target(_count_dirdeps) +# we want to capture the dirdeps count in the cache +.END: _count_dirdeps +_count_dirdeps: .NOMETA + @echo '.info $${.newline}$${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]}' >&3 + +.endif +.endif +.elif !target(_count_dirdeps) +beforedirdeps: _count_dirdeps +_count_dirdeps: .NOMETA + @echo "${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} seconds=`expr ${now_utc} - ${start_utc}`" + +.endif +.if ${BUILD_DIRDEPS} == "yes" +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != "" +_debug_reldir = 1 +.else +_debug_reldir = 0 +.endif +.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != "" +_debug_search = 1 +.else +_debug_search = 0 +.endif + # the rest is done repeatedly for every Makefile.depend we read. # if we are anything but the original dir we care only about the # machine type we were included for.. .if ${DEP_RELDIR} == "." _this_dir := ${SRCTOP} .else _this_dir := ${SRCTOP}/${DEP_RELDIR} .endif # on rare occasions, there can be a need for extra help _dep_hack := ${_this_dir}/${.MAKE.DEPENDFILE_PREFIX}.inc .-include "${_dep_hack}" .if ${DEP_RELDIR} != ${_DEP_RELDIR} || ${DEP_TARGET_SPEC} != ${TARGET_SPEC} # this should be all _machines := ${DEP_MACHINE} .else # this is the machine list we actually use below _machines := ${_only_machines} .if defined(HOSTPROG) || ${DEP_MACHINE} == "host" # we need to build this guy's dependencies for host as well. _machines += host .endif _machines := ${_machines:O:u} .endif .if ${TARGET_SPEC_VARS:[#]} > 1 # we need to tweak _machines _dm := ${DEP_MACHINE} # apply the same filtering that we do when qualifying DIRDEPS. -_machines := ${_machines:@DEP_MACHINE@${DEP_TARGET_SPEC}@:${M_dep_qual_fixes:ts:}:O:u} +# M_dep_qual_fixes expects .${MACHINE}* so add (and remove) '.' +_machines := ${_machines:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:${M_dep_qual_fixes:ts:}:O:u:S,^.,,} DEP_MACHINE := ${_dm} .endif # reset each time through _build_dirs = .if ${DEP_RELDIR} == ${_DEP_RELDIR} # pickup other machines for this dir if necessary .if ${BUILD_AT_LEVEL0:Uyes} == "no" _build_dirs += ${_machines:@m@${_CURDIR}.$m@} .else _build_dirs += ${_machines:N${DEP_TARGET_SPEC}:@m@${_CURDIR}.$m@} .if ${DEP_TARGET_SPEC} == ${TARGET_SPEC} # pickup local dependencies now .-include <.depend> .endif .endif .endif -.if !empty(_debug_reldir) +.if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: DIRDEPS='${DIRDEPS}' .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _machines='${_machines}' .endif .if !empty(DIRDEPS) # these we reset each time through as they can depend on DEP_MACHINE DEP_DIRDEPS_FILTER = \ ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \ ${DIRDEPS_FILTER.${DEP_MACHINE}:U} \ ${DIRDEPS_FILTER:U} .if empty(DEP_DIRDEPS_FILTER) # something harmless DEP_DIRDEPS_FILTER = U .endif # this is what we start with __depdirs := ${DIRDEPS:${NSkipDir}:${DEP_DIRDEPS_FILTER:ts:}:C,//+,/,g:O:u:@d@${SRCTOP}/$d@} # some entries may be qualified with . # the :M*/*/*.* just tries to limit the dirs we check to likely ones. # the ${d:E:M*/*} ensures we don't consider junos/usr.sbin/mgd __qual_depdirs := ${__depdirs:M*/*/*.*:@d@${exists($d):?:${"${d:E:M*/*}":?:${exists(${d:R}):?$d:}}}@} __unqual_depdirs := ${__depdirs:${__qual_depdirs:Uno:${M_ListToSkip}}} .if ${DEP_RELDIR} == ${_DEP_RELDIR} # if it was called out - we likely need it. __hostdpadd := ${DPADD:U.:M${HOST_OBJTOP}/*:S,${HOST_OBJTOP}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host,:N.*:@d@${SRCTOP}/$d@} __qual_depdirs += ${__hostdpadd} .endif -.if !empty(_debug_reldir) +.if ${_debug_reldir} .info depdirs=${__depdirs} .info qualified=${__qual_depdirs} .info unqualified=${__unqual_depdirs} .endif # _build_dirs is what we will feed to _DIRDEP_USE _build_dirs += \ ${__qual_depdirs:M*.host:${NSkipHostDir}:N.host} \ ${__qual_depdirs:N*.host} \ - ${_machines:@m@${__unqual_depdirs:@d@$d.$m@}@} + ${_machines:Mhost*:@m@${__unqual_depdirs:@d@$d.$m@}@:${NSkipHostDir}:N.host} \ + ${_machines:Nhost*:@m@${__unqual_depdirs:@d@$d.$m@}@} # qualify everything now _build_dirs := ${_build_dirs:${M_dep_qual_fixes:ts:}:O:u} .endif # empty DIRDEPS # Normally if doing make -V something, # we do not want to waste time chasing DIRDEPS # but if we want to count the number of Makefile.depend* read, we do. .if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS}} == "" .if !empty(_build_dirs) +.if ${BUILD_DIRDEPS_CACHE} == "yes" +x!= { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; \ + echo 'dirdeps: ${_build_dirs:${M_oneperline}}'; echo; } >&3; echo +x!= { ${_build_dirs:@x@${target($x):?:echo '$x: _DIRDEP_USE';}@} echo; } >&3; echo +.else # this makes it all happen dirdeps: ${_build_dirs} +.endif ${_build_dirs}: _DIRDEP_USE -.if !empty(_debug_reldir) +.if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs} .endif # this builds the dependency graph .for m in ${_machines} # it would be nice to do :N${.TARGET} .if !empty(__qual_depdirs) .for q in ${__qual_depdirs:${M_dep_qual_fixes:ts:}:E:O:u:N$m} -.if !empty(_debug_reldir) || ${DEBUG_DIRDEPS:@x@${${DEP_RELDIR}.$m:L:M$x}${${DEP_RELDIR}.$q:L:M$x}@} != "" +.if ${_debug_reldir} || ${DEBUG_DIRDEPS:@x@${${DEP_RELDIR}.$m:L:M$x}${${DEP_RELDIR}.$q:L:M$x}@} != "" .info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$q} .endif +.if ${BUILD_DIRDEPS_CACHE} == "yes" +x!= { echo; echo '${_this_dir}.$m: ${_build_dirs:M*.$q:${M_oneperline}}'; echo; } >&3; echo +.else ${_this_dir}.$m: ${_build_dirs:M*.$q} +.endif .endfor .endif -.if !empty(_debug_reldir) +.if ${_debug_reldir} .info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$m:N${_this_dir}.$m} .endif +.if ${BUILD_DIRDEPS_CACHE} == "yes" +x!= { echo; echo '${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m:${M_oneperline}}'; echo; } >&3; echo +.else ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m} +.endif .endfor .endif # Now find more dependencies - and recurse. .for d in ${_build_dirs} .if ${_DIRDEP_CHECKED:M$d} == "" # once only _DIRDEP_CHECKED += $d -.if !empty(_debug_search) +.if ${_debug_search} .info checking $d .endif # Note: _build_dirs is fully qualifed so d:R is always the directory .if exists(${d:R}) # Warning: there is an assumption here that MACHINE is always # the first entry in TARGET_SPEC_VARS. # If TARGET_SPEC and MACHINE are insufficient, you have a problem. _m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:S;${MACHINE};${d:E:C/,.*//};:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} .if !empty(_m) # M_dep_qual_fixes isn't geared to Makefile.depend _qm := ${_m:C;(\.depend)$;\1.${d:E};:${M_dep_qual_fixes:ts:}} -.if !empty(_debug_search) +.if ${_debug_search} .info Looking for ${_qm} .endif # we pass _DEP_TARGET_SPEC to tell the next step what we want _DEP_TARGET_SPEC := ${d:E} # some makefiles may still look at this _DEP_MACHINE := ${d:E:C/,.*//} -.if !empty(_debug_reldir) && ${_qm} != ${_m} +.if ${_debug_reldir} && ${_qm} != ${_m} .info loading ${_m} for ${d:E} .endif .include <${_m}> .endif .endif .endif .endfor .endif # -V +.endif # BUILD_DIRDEPS .elif ${.MAKE.LEVEL} > 42 .error You should have stopped recursing by now. .else _DEP_RELDIR := ${DEP_RELDIR} # pickup local dependencies .-include <.depend> .endif +# bootstrapping new dependencies made easy? +.if make(bootstrap*) && !target(bootstrap) + +.if exists(${.CURDIR}/${.MAKE.DEPENDFILE:T}) +# stop here +${.TARGETS:Mboot*}: +.else +# find a Makefile.depend to use as _src +_src != cd ${.CURDIR} && for m in ${.MAKE.DEPENDFILE_PREFERENCE:T:S,${MACHINE},*,}; do test -s $$m || continue; echo $$m; break; done; echo +.if empty(_src) +.error cannot find any of ${.MAKE.DEPENDFILE_PREFERENCE:T} +.endif + +_src?= ${.MAKE.DEPENDFILE:T} + +bootstrap-this: .NOTMAIN + @echo Bootstrapping ${RELDIR}/${.MAKE.DEPENDFILE:T} from ${_src:T} + (cd ${.CURDIR} && sed 's,${_src:E},${MACHINE},g' ${_src} > ${.MAKE.DEPENDFILE:T}) + +bootstrap: bootstrap-recurse +bootstrap-recurse: bootstrap-this + +_mf := ${.PARSEFILE} +bootstrap-recurse: .NOTMAIN .MAKE + @cd ${SRCTOP} && \ + for d in `cd ${RELDIR} && ${.MAKE} -B -f ${"${.MAKEFLAGS:M-n}":?${_src}:${.MAKE.DEPENDFILE:T}} -V DIRDEPS`; do \ + test -d $$d || d=$${d%.*}; \ + test -d $$d || continue; \ + echo "Checking $$d for bootstrap ..."; \ + (cd $$d && ${.MAKE} -f ${_mf} bootstrap-recurse); \ + done + +.endif +.endif Index: head/contrib/bmake/mk/gendirdeps.mk =================================================================== --- head/contrib/bmake/mk/gendirdeps.mk (revision 281811) +++ head/contrib/bmake/mk/gendirdeps.mk (revision 281812) @@ -1,337 +1,348 @@ -# $Id: gendirdeps.mk,v 1.25 2014/03/14 21:28:37 sjg Exp $ +# $Id: gendirdeps.mk,v 1.26 2014/09/05 04:40:52 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # This makefile [re]generates ${.MAKE.DEPENDFILE} # .include # Assumptions: # RELDIR is the relative path from ${SRCTOP} to ${_CURDIR} # (SRCTOP is ${SB}/src) # _CURDIR is the absolute version of ${.CURDIR} # _OBJDIR is the absolute version of ${.OBJDIR} # _objroot is realpath of ${_OBJTOP} without ${MACHINE} # this may be different from _OBJROOT if $SB/obj is a # symlink to another filesystem. # _objroot must be a prefix match for _objtop .MAIN: all # keep this simple .MAKE.MODE = compat all: _CURDIR ?= ${.CURDIR} _OBJDIR ?= ${.OBJDIR} _OBJTOP ?= ${OBJTOP} _OBJROOT ?= ${OBJROOT:U${_OBJTOP}} .if ${_OBJROOT:M*/} _slash=/ .else _slash= .endif _objroot ?= ${_OBJROOT:tA}${_slash} _this = ${.PARSEDIR}/${.PARSEFILE} # remember what to make _DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T} # We do _not_ want to read our own output! .MAKE.DEPENDFILE = /dev/null # caller should have set this META_FILES ?= ${.MAKE.META.FILES} .if !empty(META_FILES) .if ${.MAKE.LEVEL} > 0 && !empty(GENDIRDEPS_FILTER) # so we can compare below .-include <${_DEPENDFILE}> # yes, I mean :U with no value _DIRDEPS := ${DIRDEPS:U:O:u} .endif META_FILES := ${META_FILES:T:O:u} .export META_FILES # pickup customizations .-include "local.gendirdeps.mk" # these are actually prefixes that we'll skip # they should all be absolute paths SKIP_GENDIRDEPS ?= .if !empty(SKIP_GENDIRDEPS) _skip_gendirdeps = egrep -v '^(${SKIP_GENDIRDEPS:O:u:ts|})' | .else _skip_gendirdeps = +.endif + +# Below we will turn _{VAR} into ${VAR} which keeps this simple +# GENDIRDEPS_FILTER_DIR_VARS is a list of dirs to be substiuted for. +# GENDIRDEPS_FILTER_VARS is more general. +# In each case order matters. +.if !empty(GENDIRDEPS_FILTER_DIR_VARS) +GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_DIR_VARS:@v@S,${$v},_{${v}},@} +.endif +.if !empty(GENDIRDEPS_FILTER_VARS) +GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_VARS:@v@S,/${$v}/,/_{${v}}/,@:NS,//,*:u} .endif # this (*should* be set in meta.sys.mk) # is the script that extracts what we want. META2DEPS ?= ${.PARSEDIR}/meta2deps.sh META2DEPS := ${META2DEPS} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" && ${DEBUG_GENDIRDEPS:Uno:Mmeta2d*} != "" _time = time _sh_x = sh -x _py_d = -ddd .else _time = _sh_x = _py_d = .endif .if ${META2DEPS:E} == "py" # we can afford to do this all the time. DPDEPS ?= no META2DEPS_CMD = ${_time} ${PYTHON} ${META2DEPS} ${_py_d} .if ${DPDEPS:tl} != "no" META2DEPS_CMD += -D ${DPDEPS} .endif META2DEPS_FILTER = sed 's,^src:,${SRCTOP}/,;s,^\([^/]\),${OBJTOP}/\1,' | .elif ${META2DEPS:E} == "sh" META2DEPS_CMD = ${_time} ${_sh_x} ${META2DEPS} OBJTOP=${_OBJTOP} .else META2DEPS_CMD ?= ${META2DEPS} .endif .if ${TARGET_OBJ_SPEC:U${MACHINE}} != ${MACHINE} META2DEPS_CMD += -T ${TARGET_OBJ_SPEC} .endif META2DEPS_CMD += \ -R ${RELDIR} -H ${HOST_TARGET} \ ${M2D_OBJROOTS:O:u:@o@-O $o@} M2D_OBJROOTS += ${OBJTOP} ${_OBJROOT} ${_objroot} .if defined(SB_OBJROOT) M2D_OBJROOTS += ${SB_OBJROOT} .endif .if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} == "" # meta2deps.py only groks objroot # so we need to give it what it expects # and tell it not to add machine qualifiers META2DEPS_ARGS += MACHINE=none .endif .if defined(SB_BACKING_SB) META2DEPS_CMD += -S ${SB_BACKING_SB}/src M2D_OBJROOTS += ${SB_BACKING_SB}/${SB_OBJPREFIX} .endif # we are only interested in the dirs # sepecifically those we read something from. # we canonicalize them to keep things simple # if we are using a split-fs sandbox, it gets a little messier. _objtop := ${_OBJTOP:tA} dir_list != cd ${_OBJDIR} && \ ${META2DEPS_CMD} MACHINE=${MACHINE} \ SRCTOP=${SRCTOP} RELDIR=${RELDIR} CURDIR=${_CURDIR} \ ${META2DEPS_ARGS} \ ${META_FILES:O:u} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \ sed 's,//*$$,,;s,\.${HOST_TARGET}$$,.host,' .if ${dir_list:M*ERROR\:*} != "" .warning ${dir_list:tW:C,.*(ERROR),\1,} .warning Skipping ${_DEPENDFILE:S,${SRCTOP}/,,} # we are not going to update anything .else dpadd_dir_list= .if !empty(DPADD) _nonlibs := ${DPADD:T:Nlib*:N*include} .if !empty(_nonlibs) ddep_list = .for f in ${_nonlibs:@x@${DPADD:M*/$x}@} .if exists($f.dirdep) ddep_list += $f.dirdep .elif exists(${f:H}.dirdep) ddep_list += ${f:H}.dirdep .else dir_list += ${f:H:tA} dpadd_dir_list += ${f:H:tA} .endif .endfor .if !empty(ddep_list) ddeps != cat ${ddep_list:O:u} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \ sed 's,//*$$,,;s,\.${HOST_TARGET}$$,.host,;s,\.${MACHINE}$$,,' .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: raw_dir_list='${dir_list}' .info ${RELDIR}: ddeps='${ddeps}' .endif dir_list += ${ddeps} .endif .endif .endif # DIRDEPS represent things that had to have been built first # so they should all be undir OBJTOP. # Note that ${_OBJTOP}/bsd/include/machine will get reported # to us as $SRCTOP/bsd/sys/$MACHINE_ARCH/include meaning we # will want to visit bsd/include # so we add # ${"${dir_list:M*bsd/sys/${MACHINE_ARCH}/include}":?bsd/include:} # to GENDIRDEPS_DIR_LIST_XTRAS _objtops = ${OBJTOP} ${_OBJTOP} ${_objtop} _objtops := ${_objtops:O:u} dirdep_list = \ ${_objtops:@o@${dir_list:M$o*/*:C,$o[^/]*/,,}@} \ ${GENDIRDEPS_DIR_LIST_XTRAS} # sort longest first M2D_OBJROOTS := ${M2D_OBJROOTS:O:u:[-1..1]} # anything we use from an object dir other than ours # needs to be qualified with its . suffix # (we used the pseudo machine "host" for the HOST_TARGET). skip_ql= ${SRCTOP}* ${_objtops:@o@$o*@} .for o in ${M2D_OBJROOTS:${skip_ql:${M_ListToSkip}}} # we need := so only skip_ql to this point applies ql.$o := ${dir_list:${skip_ql:${M_ListToSkip}}:M$o*/*/*:C,$o([^/]+)/(.*),\2.\1,:S,.${HOST_TARGET},.host,} qualdir_list += ${ql.$o} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: o=$o ${ql.$o qualdir_list:L:@v@$v=${$v}@} .endif skip_ql+= $o* .endfor dirdep_list := ${dirdep_list:O:u} qualdir_list := ${qualdir_list:N*.${MACHINE}:O:u} DIRDEPS = \ ${dirdep_list:N${RELDIR}:N${RELDIR}/*} \ ${qualdir_list:N${RELDIR}.*:N${RELDIR}/*} # We only consider things below $RELDIR/ if they have a makefile. # This is the same test that _DIRDEP_USE applies. # We have do a double test with dirdep_list as it _may_ contain # qualified dirs - if we got anything from a stage dir. # qualdir_list we know are all qualified. # It would be nice do peform this check for all of DIRDEPS, # but we cannot assume that all of the tree is present, # in fact we can only assume that RELDIR is. DIRDEPS += \ ${dirdep_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/$d/$m):?$d:${exists(${SRCTOP}/${d:R}/$m):?$d:}}@}@} \ ${qualdir_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/${d:R}/$m):?$d:}@}@} DIRDEPS := ${DIRDEPS:${GENDIRDEPS_FILTER:UNno:ts:}:C,//+,/,g:O:u} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: M2D_OBJROOTS=${M2D_OBJROOTS} .info ${RELDIR}: dir_list='${dir_list}' .info ${RELDIR}: dpadd_dir_list='${dpadd_dir_list}' .info ${RELDIR}: dirdep_list='${dirdep_list}' .info ${RELDIR}: qualdir_list='${qualdir_list}' .info ${RELDIR}: SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS}' .info ${RELDIR}: GENDIRDEPS_FILTER='${GENDIRDEPS_FILTER}' .info ${RELDIR}: FORCE_DPADD='${DPADD}' .info ${RELDIR}: DIRDEPS='${DIRDEPS}' .endif # SRC_DIRDEPS is for checkout logic src_dirdep_list = \ ${dir_list:M${SRCTOP}/*:S,${SRCTOP}/,,} SRC_DIRDEPS = \ ${src_dirdep_list:N${RELDIR}:N${RELDIR}/*:C,(/h)/.*,,} SRC_DIRDEPS := ${SRC_DIRDEPS:${GENDIRDEPS_SRC_FILTER:UN/*:ts:}:C,//+,/,g:O:u} # if you want to capture SRC_DIRDEPS in .MAKE.DEPENDFILE put # SRC_DIRDEPS_FILE = ${_DEPENDFILE} # in local.gendirdeps.mk .if ${SRC_DIRDEPS_FILE:Uno:tl} != "no" ECHO_SRC_DIRDEPS = echo 'SRC_DIRDEPS = \'; echo '${SRC_DIRDEPS:@d@ $d \\${.newline}@}'; echo; .if ${SRC_DIRDEPS_FILE:T} == ${_DEPENDFILE:T} _include_src_dirdeps = ${ECHO_SRC_DIRDEPS} .else all: ${SRC_DIRDEPS_FILE} .if !target(${SRC_DIRDEPS_FILE}) ${SRC_DIRDEPS_FILE}: ${META_FILES} ${_this} ${META2DEPS} @(${ECHO_SRC_DIRDEPS}) > $@ .endif .endif .endif _include_src_dirdeps ?= all: ${_DEPENDFILE} # if this is going to exist it would be there by now .if !exists(.depend) CAT_DEPEND = /dev/null .endif CAT_DEPEND ?= .depend .if !empty(_DIRDEPS) && ${DIRDEPS} != ${_DIRDEPS} # we may have changed a filter .PHONY: ${_DEPENDFILE} .endif # 'cat .depend' should suffice, but if we are mixing build modes # .depend may contain things we don't want. # The sed command at the end of the stream, allows for the filters # to output _{VAR} tokens which we will turn into proper ${VAR} references. ${_DEPENDFILE}: ${CAT_DEPEND:M.depend} ${META_FILES:O:u:@m@${exists($m):?$m:}@} ${_this} ${META2DEPS} @(echo '# Autogenerated - do NOT edit!'; echo; \ echo 'DEP_RELDIR := $${_PARSEDIR:S,$${SRCTOP}/,,}'; echo; \ echo 'DIRDEPS = \'; \ echo '${DIRDEPS:@d@ $d \\${.newline}@}'; echo; \ ${_include_src_dirdeps} \ echo '.include '; \ echo; \ echo '.if $${DEP_RELDIR} == $${_DEP_RELDIR}'; \ echo '# local dependencies - needed for -jN in clean tree'; \ [ -s ${CAT_DEPEND} ] && { grep : ${CAT_DEPEND} | grep -v '[/\\]'; }; \ echo '.endif' ) | sed 's,_\([{(]\),$$\1,g' > $@.new${.MAKE.PID} @${InstallNew}; InstallNew -s $@.new${.MAKE.PID} .endif # meta2deps failed .elif !empty(SUBDIR) DIRDEPS := ${SUBDIR:S,^,${RELDIR}/,:O:u} all: ${_DEPENDFILE} ${_DEPENDFILE}: ${MAKEFILE} ${_this} @(echo '# Autogenerated - do NOT edit!'; echo; \ echo 'DEP_RELDIR := $${_PARSEDIR:S,$${SRCTOP}/,,}'; echo; \ echo 'DIRDEPS = \'; \ echo '${DIRDEPS:@d@ $d \\${.newline}@}'; echo; \ echo '.include '; \ echo ) | sed 's,_\([{(]\),$$\1,g' > $@.new @${InstallNew}; InstallNew $@.new .else # nothing to do all ${_DEPENDFILE}: .endif ${_DEPENDFILE}: .PRECIOUS Index: head/contrib/bmake/mk/install-mk =================================================================== --- head/contrib/bmake/mk/install-mk (revision 281811) +++ head/contrib/bmake/mk/install-mk (revision 281812) @@ -1,185 +1,185 @@ : # NAME: # install-mk - install mk files # # SYNOPSIS: # install-mk [options] [var=val] [dest] # # DESCRIPTION: # This tool installs mk files in a semi-intelligent manner into # "dest". # # Options: # # -n just say what we want to do, but don't touch anything. # # -f use -f when copying sys,mk. # # -v be verbose # # -q be quiet # # -m "mode" # Use "mode" for installed files (444). # # -o "owner" # Use "owner" for installed files. # # -g "group" # Use "group" for installed files. # # var=val # Set "var" to "val". See below. # # All our *.mk files are copied to "dest" with appropriate # ownership and permissions. # # By default if a sys.mk can be found in a standard location # (that bmake will find) then no sys.mk will be put in "dest". # # SKIP_SYS_MK: # If set, we will avoid installing our 'sys.mk' # This is probably a bad idea. # # SKIP_BSD_MK: # If set, we will skip making bsd.*.mk links to *.mk # # sys.mk: # # By default (and provided we are not installing to the system # mk dir - '/usr/share/mk') we install our own 'sys.mk' which # includes a sys specific file, or a generic one. # # # AUTHOR: # Simon J. Gerraty # RCSid: -# $Id: install-mk,v 1.100 2014/05/23 01:30:36 sjg Exp $ +# $Id: install-mk,v 1.109 2015/04/16 16:59:00 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # -MK_VERSION=20140522 +MK_VERSION=20150411 OWNER= GROUP= MODE=444 BINMODE=555 ECHO=: SKIP= cp_f=-f while : do case "$1" in *=*) eval "$1"; shift;; +f) cp_f=; shift;; -f) cp_f=-f; shift;; -m) MODE=$2; shift 2;; -o) OWNER=$2; shift 2;; -g) GROUP=$2; shift 2;; -v) ECHO=echo; shift;; -q) ECHO=:; shift;; -n) ECHO=echo SKIP=:; shift;; --) shift; break;; *) break;; esac done case $# in 0) echo "$0 [options] []" echo "eg." echo "$0 -o bin -g bin -m 444 /usr/local/share/mk" exit 1 ;; esac dest=$1 os=${2:-`uname`} osrel=${3:-`uname -r`} Do() { $ECHO "$@" $SKIP "$@" } Error() { echo "ERROR: $@" >&2 exit 1 } Warning() { echo "WARNING: $@" >&2 } [ "$FORCE_SYS_MK" ] && Warning "ignoring: FORCE_{BSD,SYS}_MK (no longer supported)" SYS_MK_DIR=${SYS_MK_DIR:-/usr/share/mk} SYS_MK=${SYS_MK:-$SYS_MK_DIR/sys.mk} realpath() { [ -d $1 ] && cd $1 && 'pwd' && return echo $1 } if [ -s $SYS_MK -a -d $dest ]; then # if this is a BSD system we don't want to touch $SYS_MK dest=`realpath $dest` sys_mk_dir=`realpath $SYS_MK_DIR` if [ $dest = $sys_mk_dir ]; then case "$os" in *BSD*) SKIP_SYS_MK=: SKIP_BSD_MK=: ;; *) # could be fake? if [ ! -d $dest/sys -a ! -s $dest/Generic.sys.mk ]; then SKIP_SYS_MK=: # play safe SKIP_BSD_MK=: fi ;; esac fi fi [ -d $dest/sys ] || Do mkdir -p $dest/sys [ -d $dest/sys ] || Do mkdir $dest/sys || exit 1 [ -z "$SKIP" ] && dest=`realpath $dest` cd `dirname $0` mksrc=`'pwd'` if [ $mksrc = $dest ]; then SKIP_MKFILES=: else # we do not install the examples mk_files=`grep '^[a-z].*\.mk' FILES | egrep -v '(examples/|^sys\.mk|sys/)'` mk_scripts=`egrep '^[a-z].*\.(sh|py)' FILES | egrep -v '/'` sys_mk_files=`grep 'sys/.*\.mk' FILES` SKIP_MKFILES= [ -z "$SKIP_SYS_MK" ] && mk_files="sys.mk $mk_files" fi $SKIP_MKFILES Do cp $cp_f $mk_files $dest $SKIP_MKFILES Do cp $cp_f $sys_mk_files $dest/sys $SKIP_MKFILES Do cp $cp_f $mk_scripts $dest $SKIP cd $dest $SKIP_MKFILES Do chmod $MODE $mk_files $sys_mk_files $SKIP_MKFILES Do chmod $BINMODE $mk_scripts [ "$GROUP" ] && $SKIP_MKFILES Do chgrp $GROUP $mk_files $sys_mk_files [ "$OWNER" ] && $SKIP_MKFILES Do chown $OWNER $mk_files $sys_mk_files # if this is a BSD system the bsd.*.mk should exist and be used. if [ -z "$SKIP_BSD_MK" ]; then for f in dep doc init lib links man nls obj own prog subdir do b=bsd.$f.mk [ -s $b ] || Do ln -s $f.mk $b done fi exit 0 Index: head/contrib/bmake/mk/links.mk =================================================================== --- head/contrib/bmake/mk/links.mk (revision 281811) +++ head/contrib/bmake/mk/links.mk (revision 281812) @@ -1,75 +1,80 @@ -# $Id: links.mk,v 1.5 2005/07/11 18:01:05 sjg Exp $ +# $Id: links.mk,v 1.6 2014/09/29 17:14:40 sjg Exp $ # # @(#) Copyright (c) 2005, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # some platforms need something special LN?= ln ECHO?= echo LINKS?= SYMLINKS?= __SYMLINK_SCRIPT= \ ${ECHO} "$$t -> $$l"; \ - mkdir -p `dirname $$t`; \ - rm -f $$t; \ - ${LN} -s $$l $$t + case `'ls' -l $$t 2> /dev/null` in \ + *"> $$l") ;; \ + *) \ + mkdir -p `dirname $$t`; \ + rm -f $$t; \ + ${LN} -s $$l $$t;; \ + esac + __LINK_SCRIPT= \ ${ECHO} "$$t -> $$l"; \ mkdir -p `dirname $$t`; \ rm -f $$t; \ ${LN} $$l $$t _SYMLINKS_SCRIPT= \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=${DESTDIR}$$1; shift; \ ${__SYMLINK_SCRIPT}; \ done; :; _LINKS_SCRIPT= \ while test $$\# -ge 2; do \ l=${DESTDIR}$$1; shift; \ t=${DESTDIR}$$1; shift; \ ${__LINK_SCRIPT}; \ done; :; _SYMLINKS_USE: .USE @set ${$@_SYMLINKS:U${SYMLINKS}}; ${_SYMLINKS_SCRIPT} _LINKS_USE: .USE @set ${$@_LINKS:U${LINKS}}; ${_LINKS_SCRIPT} # sometimes we want to ensure DESTDIR is ignored _BUILD_SYMLINKS_SCRIPT= \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=$$1; shift; \ ${__SYMLINK_SCRIPT}; \ done; :; _BUILD_LINKS_SCRIPT= \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=$$1; shift; \ ${__LINK_SCRIPT}; \ done; :; _BUILD_SYMLINKS_USE: .USE @set ${$@_SYMLINKS:U${SYMLINKS}}; ${_BUILD_SYMLINKS_SCRIPT} _BUILD_LINKS_USE: .USE @set ${$@_LINKS:U${LINKS}}; ${_BUILD_LINKS_SCRIPT} Index: head/contrib/bmake/mk/manifest.mk =================================================================== --- head/contrib/bmake/mk/manifest.mk (nonexistent) +++ head/contrib/bmake/mk/manifest.mk (revision 281812) @@ -0,0 +1,66 @@ +# $Id: manifest.mk,v 1.2 2014/10/31 18:06:17 sjg Exp $ +# +# @(#) Copyright (c) 2014, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +# generate mtree style manifest supported by makefs in FreeBSD + +# input looks like +# MANIFEST= my.mtree +# for each MANIFEST we have a list of dirs +# ${MANIFEST}.DIRS += bin sbin usr/bin ... +# for each dir we have a ${MANIFEST}.SRCS.$dir +# that provides the absolute path to the contents +# ${MANIFEST}.SRCS.bin += ${OBJTOP}/bin/sh/sh +# ${MANIFEST}.SYMLINKS is a list of src target pairs +# for each file/dir there are a number of attributes +# UID GID MODE FLAGS +# which can be set per dir, per file or we use defaults +# eg. +# MODE.sbin = 550 +# MODE.usr/sbin = 550 +# MODE.dirs = 555 +# means that sbin and usr/sbin get 550 all other dirs get 555 +# MODE.usr/bin/passwd = 4555 +# MODE.usr/bin.files = 555 +# MODE.usr/sbin.files = 500 +# means passwd gets 4555 other files in usr/bin get 555 and +# files in usr/sbin get 500 +# STORE defaults to basename of src and target directory +# but we can use +# ${MANIFEST}.SRCS.sbin += ${OBJTOP}/bin/sh-static/sh-static +# STORE.sbin/sh-static = sbin/sh +# +# the above is a little overkill but means we can easily adapt to +# different formats + +UID.dirs ?= 0 +GID.dirs ?= 0 +MODE.dirs ?= 775 +FLAGS.dirs ?= + +UID.files ?= 0 +GID.files ?= 0 +MODE.files ?= 555 + +# a is attribute name d is dirname +M_DIR_ATTR = L:@a@$${$$a.$$d:U$${$$a.dirs}}@ +# as above and s is set to the name we store f as +M_FILE_ATTR = L:@a@$${$$a.$$s:U$${$$a.$$d.files:U$${$$a.files}}}@ + +# this produces the body of the manifest +# there should typically be a header prefixed +_GEN_MTREE_MANIFEST_USE: .USE + @(${${.TARGET}.DIRS:O:u:@d@echo '$d type=dir uid=${UID:${M_DIR_ATTR}} gid=${GID:${M_DIR_ATTR}} mode=${MODE:${M_DIR_ATTR}} ${FLAGS:${M_DIR_ATTR}}';@} \ + ${${.TARGET}.DIRS:O:u:@d@${${.TARGET}.SRCS.$d:O:u:@f@echo '${s::=${STORE.$d/${f:T}:U$d/${f:T}}}$s contents="$f" type=file uid=${UID:${M_FILE_ATTR}} gid=${GID:${M_FILE_ATTR}} mode=${MODE:${M_FILE_ATTR}} ${FLAGS:${M_FILE_ATTR}}';@}@} \ + set ${${.TARGET}.SYMLINKS}; while test $$# -ge 2; do echo "$$2 type=link link=$$1"; shift 2; done) > ${.TARGET} Property changes on: head/contrib/bmake/mk/manifest.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/mk/meta.autodep.mk =================================================================== --- head/contrib/bmake/mk/meta.autodep.mk (revision 281811) +++ head/contrib/bmake/mk/meta.autodep.mk (revision 281812) @@ -1,288 +1,291 @@ -# $Id: meta.autodep.mk,v 1.35 2014/05/09 00:05:46 sjg Exp $ +# $Id: meta.autodep.mk,v 1.36 2014/08/02 23:10:29 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # _this ?= ${.PARSEFILE} .if !target(__${_this}__) __${_this}__: .NOTMAIN .-include "local.autodep.mk" .if defined(SRCS) # it would be nice to be able to query .SUFFIXES OBJ_EXTENSIONS+= .o .po .lo .So # explicit dependencies help short-circuit .SUFFIX searches SRCS_DEP_FILTER+= N*.[hly] .for s in ${SRCS:${SRCS_DEP_FILTER:O:u:ts:}} .for e in ${OBJ_EXTENSIONS:O:u} .if !target(${s:T:R}$e) ${s:T:R}$e: $s .endif .endfor .endfor .endif .if make(gendirdeps) # you are supposed to know what you are doing! UPDATE_DEPENDFILE = yes .elif !empty(.TARGETS) && !make(all) # do not update the *depend* files # unless we are building the entire directory or the default target. # NO means don't update .depend - or Makefile.depend* # no means update .depend but not Makefile.depend* UPDATE_DEPENDFILE = NO .elif ${.MAKEFLAGS:M-k} != "" # it is a bad idea to update anything UPDATE_DEPENDFILE = NO .endif _CURDIR ?= ${.CURDIR} _DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T} .if ${.MAKE.LEVEL} == 0 .if ${BUILD_AT_LEVEL0:Uyes:tl} == "no" UPDATE_DEPENDFILE = NO .endif .endif .if !exists(${_DEPENDFILE}) _bootstrap_dirdeps = yes .endif _bootstrap_dirdeps ?= no UPDATE_DEPENDFILE ?= yes .if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != "" .info ${_DEPENDFILE:S,${SRCTOP}/,,} update=${UPDATE_DEPENDFILE} .endif .if !empty(XMAKE_META_FILE) .if exists(${.OBJDIR}/${XMAKE_META_FILE}) # we cannot get accurate dependencies from an update build UPDATE_DEPENDFILE = NO .else META_XTRAS += ${XMAKE_META_FILE} .endif .endif .if ${_bootstrap_dirdeps} == "yes" || exists(${_DEPENDFILE}) # if it isn't supposed to be touched by us the Makefile should have # UPDATE_DEPENDFILE = no WANT_UPDATE_DEPENDFILE ?= yes .endif .if ${WANT_UPDATE_DEPENDFILE:Uno:tl} != "no" .if ${.MAKE.MODE:Mmeta*} == "" || ${.MAKE.MODE:M*read*} != "" UPDATE_DEPENDFILE = no .endif .if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != "" .info ${_DEPENDFILE:S,${SRCTOP}/,,} update=${UPDATE_DEPENDFILE} .endif .if ${UPDATE_DEPENDFILE:tl} == "yes" # sometimes we want .meta files generated to aid debugging/error detection # but do not want to consider them for dependencies # for example the result of running configure # just make sure this is not empty META_FILE_FILTER ?= N.meta .if !empty(DPADD) # if we have any non-libs in DPADD, # they probably need to be paid attention to .if !empty(DPLIBS) FORCE_DPADD = ${DPADD:${DPLIBS:${M_ListToSkip}}:${DPADD_LAST:${M_ListToSkip}}} .else _nonlibs := ${DPADD:T:Nlib*:N*include} .if !empty(_nonlibs) FORCE_DPADD += ${_nonlibs:@x@${DPADD:M*/$x}@} .endif .endif .endif .if !make(gendirdeps) .END: gendirdeps .endif # if we don't have OBJS, then .depend isn't useful .if !target(.depend) && (!empty(OBJS) || ${.ALLTARGETS:M*.o} != "") # some makefiles and/or targets contain # circular dependencies if you dig too deep # (as meta mode is apt to do) # so we provide a means of suppressing them. # the input to the loop below is target: dependency # with just one dependency per line. # Also some targets are not really local, or use random names. # Use local.autodep.mk to provide local additions! SUPPRESS_DEPEND += \ ${SB:S,/,_,g}* \ *:y.tab.c \ *.c:*.c \ *.h:*.h .NOPATH: .depend # we use ${.MAKE.META.CREATED} to trigger an update but # we process using ${.MAKE.META.FILES} # the double $$ defers initial evaluation # if necessary, we fake .po dependencies, just so the result # in Makefile.depend* is stable # The current objdir may be referred to in various ways OBJDIR_REFS += ${.OBJDIR} ${.OBJDIR:tA} ${_OBJDIR} ${RELOBJTOP}/${RELDIR} _depend = .depend # it would be nice to be able to get .SUFFIXES as ${.SUFFIXES} # we actually only care about the .SUFFIXES of files that might be # generated by tools like yacc. DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh .depend: .NOMETA $${.MAKE.META.CREATED} ${_this} @echo "Updating $@: ${.OODATE:T:[1..8]}" @egrep -i '^R .*\.(${DEPEND_SUFFIXES:tl:O:u:S,^.,,:ts|})$$' /dev/null ${.MAKE.META.FILES:T:O:u:${META_FILE_FILTER:ts:}:M*o.meta} | \ sed -e 's, \./, ,${OBJDIR_REFS:O:u:@d@;s, $d/, ,@};/\//d' \ -e 's,^\([^/][^/]*\).meta...[0-9]* ,\1: ,' | \ sort -u | \ while read t d; do \ case "$$d:" in $$t) continue;; esac; \ case "$$t$$d" in ${SUPPRESS_DEPEND:U.:O:u:ts|}) continue;; esac; \ echo $$t $$d; \ done > $@.${.MAKE.PID} @case "${.MAKE.META.FILES:T:M*.po.*}" in \ *.po.*) mv $@.${.MAKE.PID} $@;; \ *) { cat $@.${.MAKE.PID}; \ sed 's,\.So:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \ rm -f $@.${.MAKE.PID};; \ esac .else # make sure this exists .depend: # do _not_ assume that .depend is in any fit state for us to use CAT_DEPEND = /dev/null .if ${.MAKE.LEVEL} > 0 .export CAT_DEPEND .endif _depend = .endif .if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != "" .info ${_DEPENDFILE:S,${SRCTOP}/,,} _depend=${_depend} .endif .if ${UPDATE_DEPENDFILE} == "yes" gendirdeps: ${_DEPENDFILE} .endif .if !target(${_DEPENDFILE}) .if ${_bootstrap_dirdeps} == "yes" # We are boot-strapping a new directory # Use DPADD to seed DIRDEPS .if !empty(DPADD) # anything which matches ${_OBJROOT}* but not ${_OBJTOP}* # needs to be qualified in DIRDEPS # The pseudo machine "host" is used for HOST_TARGET DIRDEPS = \ ${DPADD:M${_OBJTOP}*:H:C,${_OBJTOP}[^/]*/,,:N.:O:u} \ ${DPADD:M${_OBJROOT}*:N${_OBJTOP}*:H:S,${_OBJROOT},,:C,^([^/]+)/(.*),\2.\1,:S,${HOST_TARGET}$,host,:N.*:O:u} .endif .endif _gendirdeps_mutex = .if defined(NEED_GENDIRDEPS_MUTEX) # If a src dir gets built with multiple object dirs, # we need a mutex. Obviously, this is best avoided. # Note if .MAKE.DEPENDFILE is common for all ${MACHINE} # you either need to mutex, or ensure only one machine builds at a time! # lockf is an example of a suitable tool LOCKF ?= /usr/bin/lockf .if exists(${LOCKF}) GENDIRDEPS_MUTEXER ?= ${LOCKF} -k .endif .if empty(GENDIRDEPS_MUTEXER) .error NEED_GENDIRDEPS_MUTEX defined, but GENDIRDEPS_MUTEXER not set .else _gendirdeps_mutex = ${GENDIRDEPS_MUTEXER} ${GENDIRDEPS_MUTEX:U${_CURDIR}/Makefile} .endif .endif # If we have META_XTRAS we most likely did not create them # but we need to behave as if we did. # Avoid adding glob patterns to .MAKE.META.CREATED though. .MAKE.META.CREATED += ${META_XTRAS:N*\**:O:u} .if make(gendirdeps) META_FILES = *.meta .elif ${OPTIMIZE_OBJECT_META_FILES:Uno:tl} == "no" META_FILES = ${.MAKE.META.FILES:T:N.depend*:O:u} .else # if we have 1000's of .o.meta, .So.meta etc we need only look at one set # it is left as an exercise for the reader to work out what this does META_FILES = ${.MAKE.META.FILES:T:N.depend*:N*o.meta:O:u} \ ${.MAKE.META.FILES:T:M*.${.MAKE.META.FILES:M*o.meta:R:E:O:u:[1]}.meta:O:u} .endif .if ${DEBUG_AUTODEP:Uno:@m@${RELDIR:M$m}@} != "" .info ${_DEPENDFILE:S,${SRCTOP}/,,}: ${_depend} ${.PARSEDIR}/gendirdeps.mk ${META2DEPS} xtras=${META_XTRAS} .endif .if ${.MAKE.LEVEL} > 0 && !empty(GENDIRDEPS_FILTER) .export GENDIRDEPS_FILTER .endif # we might have .../ in MAKESYSPATH _makesyspath:= ${_PARSEDIR} ${_DEPENDFILE}: ${_depend} ${.PARSEDIR}/gendirdeps.mk ${META2DEPS} $${.MAKE.META.CREATED} @echo Checking $@: ${.OODATE:T:[1..8]} @(cd . && \ SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS:O:u}' \ DPADD='${FORCE_DPADD:O:u}' ${_gendirdeps_mutex} \ MAKESYSPATH=${_makesyspath} \ ${.MAKE} -f gendirdeps.mk RELDIR=${RELDIR} _DEPENDFILE=${_DEPENDFILE} \ META_FILES='${META_XTRAS:T:O:u} ${META_FILES:T:O:u:${META_FILE_FILTER:ts:}}') @test -s $@ && touch $@; : .endif .endif .endif .if ${_bootstrap_dirdeps} == "yes" +.if ${BUILD_AT_LEVEL0:Uno} == "no" +DIRDEPS+= ${RELDIR}.${TARGET_SPEC:U${MACHINE}} +.endif # make sure this is included at least once .include .else ${_DEPENDFILE}: .PRECIOUS .endif CLEANFILES += *.meta filemon.* *.db # these make it easy to gather some stats now_utc = ${%s:L:gmtime} start_utc := ${now_utc} meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \ created=${empty(.MAKE.META.CREATED):?0:${.MAKE.META.CREATED:[#]}} #.END: _reldir_finish .if target(gendirdeps) _reldir_finish: gendirdeps .endif _reldir_finish: .NOMETA @echo "${TIME_STAMP} Finished ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" #.ERROR: _reldir_failed _reldir_failed: .NOMETA @echo "${TIME_STAMP} Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" .if defined(WITH_META_STATS) && ${.MAKE.LEVEL} > 0 .END: _reldir_finish .ERROR: _reldir_failed .endif .endif Index: head/contrib/bmake/mk/meta.stage.mk =================================================================== --- head/contrib/bmake/mk/meta.stage.mk (revision 281811) +++ head/contrib/bmake/mk/meta.stage.mk (revision 281812) @@ -1,244 +1,277 @@ -# $Id: meta.stage.mk,v 1.30 2013/04/19 16:32:57 sjg Exp $ +# $Id: meta.stage.mk,v 1.34 2014/11/20 22:40:08 sjg Exp $ # # @(#) Copyright (c) 2011, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} != "" # this is generally safer anyway _dirdep = ${RELDIR}.${MACHINE} .else _dirdep = ${RELDIR} .endif # this allows us to trace dependencies back to their src dir .dirdep: @echo '${_dirdep}' > $@ .if defined(NO_POSIX_SHELL) || ${type printf:L:sh:Mbuiltin} == "" _stage_file_basename = `basename $$f` _stage_target_dirname = `dirname $$t` .else _stage_file_basename = $${f\#\#*/} _stage_target_dirname = $${t%/*} .endif +_OBJROOT ?= ${OBJROOT:U${OBJTOP:H}} +.if ${_OBJROOT:M*/} != "" +_objroot ?= ${_OBJROOT:tA}/ +.else _objroot ?= ${_OBJROOT:tA} +.endif + # make sure this is global _STAGED_DIRS ?= .export _STAGED_DIRS # add each dir we stage to to _STAGED_DIRS # and make sure we have absolute paths so that bmake # will match against .MAKE.META.BAILIWICK STAGE_DIR_FILTER = tA:@d@$${_STAGED_DIRS::+=$$d}$$d@ # convert _STAGED_DIRS into suitable filters GENDIRDEPS_FILTER += Nnot-empty-is-important \ ${_STAGED_DIRS:O:u:M${OBJTOP}*:S,${OBJTOP}/,N,} \ - ${_STAGED_DIRS:O:u:N${OBJTOP}*:S,${_objroot},,:C,^([^/]+)/(.*),N\2.\1,:S,${HOST_TARGET},.host,} + ${_STAGED_DIRS:O:u:M${_objroot}*:N${OBJTOP}*:S,${_objroot},,:C,^([^/]+)/(.*),N\2.\1,:S,${HOST_TARGET},.host,} LN_CP_SCRIPT = LnCp() { \ rm -f $$2 2> /dev/null; \ ln $$1 $$2 2> /dev/null || \ cp -p $$1 $$2; } # it is an error for more than one src dir to try and stage # the same file STAGE_DIRDEP_SCRIPT = ${LN_CP_SCRIPT}; StageDirdep() { \ t=$$1; \ if [ -s $$t.dirdep ]; then \ cmp -s .dirdep $$t.dirdep && return; \ echo "ERROR: $$t installed by `cat $$t.dirdep` not ${_dirdep}" >&2; \ exit 1; \ fi; \ LnCp .dirdep $$t.dirdep || exit 1; } # common logic for staging files # this all relies on RELDIR being set to a subdir of SRCTOP # we use ln(1) if we can, else cp(1) STAGE_FILE_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageFiles() { \ case "$$1" in "") return;; -m) mode=$$2; shift 2;; *) mode=;; esac; \ dest=$$1; shift; \ mkdir -p $$dest; \ [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \ for f in "$$@"; do \ case "$$f" in */*) t=$$dest/${_stage_file_basename};; *) t=$$dest/$$f;; esac; \ StageDirdep $$t; \ LnCp $$f $$t || exit 1; \ [ -z "$$mode" ] || chmod $$mode $$t; \ done; :; } STAGE_LINKS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageLinks() { \ case "$$1" in "") return;; --) shift;; -*) ldest= lnf=$$1; shift;; /*) ldest=$$1/;; esac; \ dest=$$1; shift; \ mkdir -p $$dest; \ [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \ while test $$\# -ge 2; do \ l=$$ldest$$1; shift; \ t=$$dest/$$1; \ case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \ shift; \ StageDirdep $$t; \ rm -f $$t 2>/dev/null; \ ln $$lnf $$l $$t || exit 1; \ done; :; } STAGE_AS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageAs() { \ case "$$1" in "") return;; -m) mode=$$2; shift 2;; *) mode=;; esac; \ dest=$$1; shift; \ mkdir -p $$dest; \ [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \ while test $$\# -ge 2; do \ s=$$1; shift; \ t=$$dest/$$1; \ case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \ shift; \ StageDirdep $$t; \ LnCp $$s $$t || exit 1; \ [ -z "$$mode" ] || chmod $$mode $$t; \ done; :; } # this is simple, a list of the "staged" files depends on this, _STAGE_BASENAME_USE: .USE ${.TARGET:T} @${STAGE_FILE_SCRIPT}; StageFiles ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T} +_STAGE_AS_BASENAME_USE: .USE ${.TARGET:T} + @${STAGE_AS_SCRIPT}; StageAs ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T} ${STAGE_AS_${.TARGET:T}:U${.TARGET:T}} + .if !empty(STAGE_INCSDIR) STAGE_TARGETS += stage_incs STAGE_INCS ?= ${.ALLSRC:N.dirdep} +stage_includes: stage_incs stage_incs: .dirdep @${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_INCSDIR:${STAGE_DIR_FILTER}} ${STAGE_INCS} @touch $@ .endif .if !empty(STAGE_LIBDIR) STAGE_TARGETS += stage_libs STAGE_LIBS ?= ${.ALLSRC:N.dirdep} stage_libs: .dirdep @${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${STAGE_LIBS} +.if !defined(NO_SHLIB_LINKS) .if !empty(SHLIB_LINKS) @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} \ ${SHLIB_LINKS:@t@${STAGE_LIBS:T:M$t.*} $t@} .elif !empty(SHLIB_LINK) && !empty(SHLIB_NAME) @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${SHLIB_NAME} ${SHLIB_LINK} ${SYMLINKS:T} .endif +.endif @touch $@ .endif .if !empty(STAGE_DIR) STAGE_SETS += _default STAGE_DIR._default = ${STAGE_DIR} STAGE_LINKS_DIR._default = ${STAGE_LINKS_DIR:U${STAGE_OBJTOP}} STAGE_SYMLINKS_DIR._default = ${STAGE_SYMLINKS_DIR:U${STAGE_OBJTOP}} STAGE_FILES._default = ${STAGE_FILES} STAGE_LINKS._default = ${STAGE_LINKS} STAGE_SYMLINKS._default = ${STAGE_SYMLINKS} STAGE_FILES ?= ${.ALLSRC:N.dirdep:Nstage_*} STAGE_SYMLINKS ?= ${.ALLSRC:T:N.dirdep:Nstage_*} .endif .if !empty(STAGE_SETS) CLEANFILES += ${STAGE_SETS:@s@stage*$s@} # some makefiles need to populate multiple directories .for s in ${STAGE_SETS:O:u} STAGE_FILES.$s ?= ${.ALLSRC:N.dirdep} STAGE_SYMLINKS.$s ?= ${.ALLSRC:N.dirdep} STAGE_LINKS_DIR.$s ?= ${STAGE_OBJTOP} STAGE_SYMLINKS_DIR.$s ?= ${STAGE_OBJTOP} STAGE_TARGETS += stage_files .if $s != "_default" stage_files: stage_files.$s stage_files.$s: .dirdep .else stage_files: .dirdep .endif @${STAGE_FILE_SCRIPT}; StageFiles ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_FILES.$s} @touch $@ STAGE_TARGETS += stage_links .if $s != "_default" stage_links: stage_links.$s stage_links.$s: .dirdep .else stage_links: .dirdep .endif @${STAGE_LINKS_SCRIPT}; StageLinks ${STAGE_LINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_LINKS.$s} @touch $@ STAGE_TARGETS += stage_symlinks .if $s != "_default" stage_symlinks: stage_symlinks.$s stage_symlinks.$s: .dirdep .else stage_symlinks: .dirdep .endif @${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_SYMLINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_SYMLINKS.$s} @touch $@ .endfor .endif .if !empty(STAGE_AS_SETS) CLEANFILES += ${STAGE_AS_SETS:@s@stage*$s@} STAGE_TARGETS += stage_as # sometimes things need to be renamed as they are staged # each ${file} will be staged as ${STAGE_AS_${file:T}} # one could achieve the same with SYMLINKS .for s in ${STAGE_AS_SETS:O:u} STAGE_AS.$s ?= ${.ALLSRC:N.dirdep} stage_as: stage_as.$s stage_as.$s: .dirdep @${STAGE_AS_SCRIPT}; StageAs ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_AS.$s:@f@$f ${STAGE_AS_${f:T}:U${f:T}}@} @touch $@ .endfor .endif -CLEANFILES += ${STAGE_TARGETS} +CLEANFILES += ${STAGE_TARGETS} stage_incs stage_includes # stage_*links usually needs to follow any others. .for t in ${STAGE_TARGETS:N*links:O:u} .ORDER: $t stage_links .ORDER: $t stage_symlinks .endfor # make sure this exists staging: # generally we want staging to wait until everything else is done STAGING_WAIT ?= .WAIT all: ${STAGING_WAIT} staging .if exists(${.PARSEDIR}/stage-install.sh) && !defined(STAGE_INSTALL) # this will run install(1) and then followup with .dirdep files. STAGE_INSTALL := sh ${.PARSEDIR:tA}/stage-install.sh INSTALL="${INSTALL}" OBJDIR=${.OBJDIR:tA} .endif # if ${INSTALL} gets run during 'all' assume it is for staging? .if ${.TARGETS:Nall} == "" && defined(STAGE_INSTALL) INSTALL := ${STAGE_INSTALL} .if target(beforeinstall) beforeinstall: .dirdep .endif .endif +.NOPATH: ${STAGE_FILES} +.if !empty(STAGE_TARGETS) +MK_STALE_STAGED?= no +.if ${MK_STALE_STAGED} == "yes" +all: stale_staged +# get a list of paths that we have just staged +# get a list of paths that we have previously staged to those same dirs +# anything in the 2nd list but not the first is stale - remove it. +stale_staged: staging .NOMETA + @egrep '^[WL] .*${STAGE_OBJTOP}' /dev/null ${.MAKE.META.FILES:M*stage_*} | \ + sed "/\.dirdep/d;s,.* '*\(${STAGE_OBJTOP}/[^ '][^ ']*\).*,\1," | \ + sort > ${.TARGET}.staged1 + @grep -l '${_dirdep}' /dev/null ${_STAGED_DIRS:M${STAGE_OBJTOP}*:O:u:@d@$d/*.dirdep@} | \ + sed 's,\.dirdep,,' | sort > ${.TARGET}.staged2 + @comm -13 ${.TARGET}.staged1 ${.TARGET}.staged2 > ${.TARGET}.stale + @test ! -s ${.TARGET}.stale || { \ + echo "Removing stale staged files..."; \ + sed 's,.*,& &.dirdep,' ${.TARGET}.stale | xargs rm -f; } + +.endif +.endif .endif Index: head/contrib/bmake/mk/meta.sys.mk =================================================================== --- head/contrib/bmake/mk/meta.sys.mk (revision 281811) +++ head/contrib/bmake/mk/meta.sys.mk (revision 281812) @@ -1,139 +1,137 @@ -# $Id: meta.sys.mk,v 1.16 2012/07/03 05:26:00 sjg Exp $ +# $Id: meta.sys.mk,v 1.20 2014/08/04 05:12:27 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # include this if you want to enable meta mode # for maximum benefit, requires filemon(4) driver. .if ${MAKE_VERSION:U0} > 20100901 .if !target(.ERROR) META_MODE += meta verbose .MAKE.MODE ?= ${META_MODE} .if ${.MAKE.LEVEL} == 0 _make_mode := ${.MAKE.MODE} ${META_MODE} .if ${_make_mode:M*read*} != "" || ${_make_mode:M*nofilemon*} != "" # tell everyone we are not updating Makefile.depend* UPDATE_DEPENDFILE = NO .export UPDATE_DEPENDFILE .endif .if ${UPDATE_DEPENDFILE:Uyes:tl} == "no" && !exists(/dev/filemon) # we should not get upset META_MODE += nofilemon .export META_MODE .endif .endif .if !defined(NO_SILENT) .if ${MAKE_VERSION} > 20110818 # only be silent when we have a .meta file META_MODE += silent=yes .else .SILENT: .endif .endif # make defaults .MAKE.DEPENDFILE to .depend # that won't work for us. .if ${.MAKE.DEPENDFILE} == ".depend" .undef .MAKE.DEPENDFILE .endif # if you don't cross build for multiple MACHINEs concurrently, then # .MAKE.DEPENDFILE = Makefile.depend # probably makes sense - you can set that in local.sys.mk .MAKE.DEPENDFILE ?= Makefile.depend.${MACHINE} # we use the pseudo machine "host" for the build host. # this should be taken care of before we get here .if ${OBJTOP:Ua} == ${HOST_OBJTOP:Ub} MACHINE = host .endif .if ${.MAKE.LEVEL} == 0 # it can be handy to know which MACHINE kicked off the build # for example, if using Makefild.depend for multiple machines, # allowing only MACHINE0 to update can keep things simple. MACHINE0 := ${MACHINE} .if defined(PYTHON) && exists(${PYTHON}) # we prefer the python version of this - it is much faster META2DEPS ?= ${.PARSEDIR}/meta2deps.py .else META2DEPS ?= ${.PARSEDIR}/meta2deps.sh .endif META2DEPS := ${META2DEPS} .export META2DEPS .endif MAKE_PRINT_VAR_ON_ERROR += \ .ERROR_TARGET \ .ERROR_META_FILE \ .MAKE.LEVEL \ MAKEFILE \ .MAKE.MODE .if !defined(SB) && defined(SRCTOP) SB = ${SRCTOP:H} .endif ERROR_LOGDIR ?= ${SB}/error meta_error_log = ${ERROR_LOGDIR}/meta-${.MAKE.PID}.log # we are not interested in make telling us a failure happened elsewhere .ERROR: _metaError _metaError: .NOMETA .NOTMAIN -@[ "${.ERROR_META_FILE}" ] && { \ grep -q 'failure has been detected in another branch' ${.ERROR_META_FILE} && exit 0; \ mkdir -p ${meta_error_log:H}; \ cp ${.ERROR_META_FILE} ${meta_error_log}; \ echo "ERROR: log ${meta_error_log}" >&2; }; : .endif # Are we, after all, in meta mode? .if ${.MAKE.MODE:Mmeta*} != "" -MKDEP = meta.autodep +MKDEP_MK = meta.autodep.mk +# if we think we are updating dependencies, +# then filemon had better be present +.if ${UPDATE_DEPENDFILE:Uyes:tl} != "no" && !exists(/dev/filemon) +.error ${.newline}ERROR: The filemon module (/dev/filemon) is not loaded. +.endif + .if ${.MAKE.LEVEL} == 0 # make sure dirdeps target exists and do it first all: dirdeps .WAIT dirdeps: .NOPATH: dirdeps .if defined(ALL_MACHINES) # the first .MAIN: is what counts # by default dirdeps is all we want at level0 .MAIN: dirdeps # tell dirdeps.mk what we want BUILD_AT_LEVEL0 = no .endif - -.if ${.MAKE.DEPENDFILE:E} == ${MACHINE} +.if ${.TARGETS:Nall} == "" # it works best if we do everything via sub-makes BUILD_AT_LEVEL0 ?= no .endif -BUILD_AT_LEVEL0 ?= yes -.endif -# if we think we are updating dependencies, -# then filemon had better be present -.if ${UPDATE_DEPENDFILE:Uyes:tl} != "no" && !exists(/dev/filemon) -.error ${.newline}ERROR: The filemon module (/dev/filemon) is not loaded. .endif - .endif .endif Index: head/contrib/bmake/mk/meta2deps.py =================================================================== --- head/contrib/bmake/mk/meta2deps.py (revision 281811) +++ head/contrib/bmake/mk/meta2deps.py (revision 281812) @@ -1,679 +1,713 @@ #!/usr/bin/env python from __future__ import print_function """ This script parses each "meta" file and extracts the information needed to deduce build and src dependencies. It works much the same as the original shell script, but is *much* more efficient. The parsing work is handled by the class MetaFile. We only pay attention to a subset of the information in the "meta" files. Specifically: 'CWD' to initialize our notion. 'C' to track chdir(2) on a per process basis 'R' files read are what we really care about. directories read, provide a clue to resolving subsequent relative paths. That is if we cannot find them relative to 'cwd', we check relative to the last dir read. 'W' files opened for write or read-write, for filemon V3 and earlier. 'E' files executed. 'L' files linked 'V' the filemon version, this record is used as a clue that we have reached the interesting bit. """ """ RCSid: - $Id: meta2deps.py,v 1.17 2014/04/05 22:56:54 sjg Exp $ + $Id: meta2deps.py,v 1.18 2015/04/03 18:23:25 sjg Exp $ Copyright (c) 2011-2013, Juniper Networks, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import os, re, sys def getv(dict, key, d=None): """Lookup key in dict and return value or the supplied default.""" if key in dict: return dict[key] return d def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. """ if path.endswith('/.'): path = path[0:-2] if len(path) > 0 and path[0] == '/': return path if path == '.': return cwd if path.startswith('./'): return cwd + path[1:] if last_dir == cwd: last_dir = None for d in [last_dir, cwd]: if not d: continue p = '/'.join([d,path]) if debug > 2: print("looking for:", p, end=' ', file=debug_out) if not os.path.exists(p): if debug > 2: print("nope", file=debug_out) p = None continue if debug > 2: print("found:", p, file=debug_out) return p return None def abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. this gets called a lot, so we try to avoid calling realpath until we know we have something. """ rpath = resolve(path, cwd, last_dir, debug, debug_out) if rpath: path = rpath - if (path.find('./') > 0 or + if (path.find('/') < 0 or + path.find('./') > 0 or path.endswith('/..') or os.path.islink(path)): return os.path.realpath(path) return path def sort_unique(list, cmp=None, key=None, reverse=False): list.sort(cmp, key, reverse) nl = [] le = None for e in list: if e == le: continue nl.append(e) return nl def add_trims(x): return ['/' + x + '/', '/' + x, x + '/', x] class MetaFile: """class to parse meta files generated by bmake.""" conf = None dirdep_re = None host_target = None srctops = [] objroots = [] - + excludes = [] seen = {} obj_deps = [] src_deps = [] file_deps = [] def __init__(self, name, conf={}): """if name is set we will parse it now. conf can have the follwing keys: SRCTOPS list of tops of the src tree(s). CURDIR the src directory 'bmake' was run from. RELDIR the relative path from SRCTOP to CURDIR MACHINE the machine we built for. set to 'none' if we are not cross-building. More specifically if machine cannot be deduced from objdirs. TARGET_SPEC Sometimes MACHINE isn't enough. HOST_TARGET when we build for the pseudo machine 'host' the object tree uses HOST_TARGET rather than MACHINE. OBJROOTS a list of the common prefix for all obj dirs it might end in '/' or '-'. DPDEPS names an optional file to which per file dependencies will be appended. For example if 'some/path/foo.h' is read from SRCTOP then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output. This can allow 'bmake' to learn all the dirs within the tree that depend on 'foo.h' + EXCLUDES + A list of paths to ignore. + ccache(1) can otherwise be trouble. + debug desired debug level debug_out open file to send debug output to (sys.stderr) """ self.name = name self.debug = getv(conf, 'debug', 0) self.debug_out = getv(conf, 'debug_out', sys.stderr) self.machine = getv(conf, 'MACHINE', '') self.machine_arch = getv(conf, 'MACHINE_ARCH', '') self.target_spec = getv(conf, 'TARGET_SPEC', '') self.curdir = getv(conf, 'CURDIR') self.reldir = getv(conf, 'RELDIR') self.dpdeps = getv(conf, 'DPDEPS') self.line = 0 if not self.conf: # some of the steps below we want to do only once self.conf = conf self.host_target = getv(conf, 'HOST_TARGET') for srctop in getv(conf, 'SRCTOPS', []): if srctop[-1] != '/': srctop += '/' if not srctop in self.srctops: self.srctops.append(srctop) _srctop = os.path.realpath(srctop) if _srctop[-1] != '/': _srctop += '/' if not _srctop in self.srctops: self.srctops.append(_srctop) trim_list = add_trims(self.machine) if self.machine == 'host': trim_list += add_trims(self.host_target) if self.target_spec: trim_list += add_trims(self.target_spec) for objroot in getv(conf, 'OBJROOTS', []): for e in trim_list: if objroot.endswith(e): # this is not what we want - fix it objroot = objroot[0:-len(e)] if e.endswith('/'): objroot += '/' if not objroot in self.objroots: self.objroots.append(objroot) _objroot = os.path.realpath(objroot) if objroot[-1] == '/': _objroot += '/' if not _objroot in self.objroots: self.objroots.append(_objroot) # we want the longest match self.srctops.sort(reverse=True) self.objroots.sort(reverse=True) - + + self.excludes = getv(conf, 'EXCLUDES', []) + if self.debug: print("host_target=", self.host_target, file=self.debug_out) print("srctops=", self.srctops, file=self.debug_out) print("objroots=", self.objroots, file=self.debug_out) + print("excludes=", self.excludes, file=self.debug_out) self.dirdep_re = re.compile(r'([^/]+)/(.+)') if self.dpdeps and not self.reldir: if self.debug: print("need reldir:", end=' ', file=self.debug_out) if self.curdir: srctop = self.find_top(self.curdir, self.srctops) if srctop: self.reldir = self.curdir.replace(srctop,'') if self.debug: print(self.reldir, file=self.debug_out) if not self.reldir: self.dpdeps = None # we cannot do it? self.cwd = os.getcwd() # make sure this is initialized + self.last_dir = self.cwd if name: self.try_parse() def reset(self): """reset state if we are being passed meta files from multiple directories.""" self.seen = {} self.obj_deps = [] self.src_deps = [] self.file_deps = [] def dirdeps(self, sep='\n'): """return DIRDEPS""" return sep.strip() + sep.join(self.obj_deps) def src_dirdeps(self, sep='\n'): """return SRC_DIRDEPS""" return sep.strip() + sep.join(self.src_deps) def file_depends(self, out=None): """Append DPDEPS_${file} += ${RELDIR} for each file we saw, to the output file.""" if not self.reldir: return None for f in sort_unique(self.file_deps): print('DPDEPS_%s += %s' % (f, self.reldir), file=out) def seenit(self, dir): """rememer that we have seen dir.""" self.seen[dir] = 1 def add(self, list, data, clue=''): """add data to list if it isn't already there.""" if data not in list: list.append(data) if self.debug: print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out) def find_top(self, path, list): """the logical tree may be split across multiple trees""" for top in list: if path.startswith(top): if self.debug > 2: print("found in", top, file=self.debug_out) return top return None def find_obj(self, objroot, dir, path, input): """return path within objroot, taking care of .dirdep files""" ddep = None for ddepf in [path + '.dirdep', dir + '/.dirdep']: if not ddep and os.path.exists(ddepf): ddep = open(ddepf, 'r').readline().strip('# \n') if self.debug > 1: print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out) if ddep.endswith(self.machine): ddep = ddep[0:-(1+len(self.machine))] elif self.target_spec and ddep.endswith(self.target_spec): ddep = ddep[0:-(1+len(self.target_spec))] if not ddep: # no .dirdeps, so remember that we've seen the raw input self.seenit(input) self.seenit(dir) if self.machine == 'none': if dir.startswith(objroot): return dir.replace(objroot,'') return None m = self.dirdep_re.match(dir.replace(objroot,'')) if m: ddep = m.group(2) dmachine = m.group(1) if dmachine != self.machine: if not (self.machine == 'host' and dmachine == self.host_target): if self.debug > 2: print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out) ddep += '.' + dmachine return ddep def try_parse(self, name=None, file=None): """give file and line number causing exception""" try: self.parse(name, file) except: # give a useful clue print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr) raise def parse(self, name=None, file=None): """A meta file looks like: # Meta data file "path" CMD "command-line" CWD "cwd" TARGET "target" -- command output -- -- filemon acquired metadata -- # buildmon version 3 V 3 C "pid" "cwd" E "pid" "path" - F "pid" "child" + F "pid" "child" R "pid" "path" W "pid" "path" X "pid" "status" - D "pid" "path" - L "pid" "src" "target" - M "pid" "old" "new" - S "pid" "path" - # Bye bye + D "pid" "path" + L "pid" "src" "target" + M "pid" "old" "new" + S "pid" "path" + # Bye bye - We go to some effort to avoid processing a dependency more than once. - Of the above record types only C,E,F,L,R,V and W are of interest. + We go to some effort to avoid processing a dependency more than once. + Of the above record types only C,E,F,L,R,V and W are of interest. """ version = 0 # unknown if name: self.name = name; if file: f = file - cwd = last_dir = self.cwd + cwd = self.last_dir = self.cwd else: f = open(self.name, 'r') skip = True pid_cwd = {} pid_last_dir = {} last_pid = 0 self.line = 0 if self.curdir: self.seenit(self.curdir) # we ignore this interesting = 'CEFLRV' for line in f: self.line += 1 # ignore anything we don't care about if not line[0] in interesting: continue if self.debug > 2: print("input:", line, end=' ', file=self.debug_out) w = line.split() if skip: if w[0] == 'V': skip = False version = int(w[1]) """ if version < 4: # we cannot ignore 'W' records # as they may be 'rw' interesting += 'W' """ elif w[0] == 'CWD': - self.cwd = cwd = last_dir = w[1] + self.cwd = cwd = self.last_dir = w[1] self.seenit(cwd) # ignore this if self.debug: print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) continue pid = int(w[1]) if pid != last_pid: if last_pid: pid_cwd[last_pid] = cwd - pid_last_dir[last_pid] = last_dir + pid_last_dir[last_pid] = self.last_dir cwd = getv(pid_cwd, pid, self.cwd) - last_dir = getv(pid_last_dir, pid, self.cwd) + self.last_dir = getv(pid_last_dir, pid, self.cwd) last_pid = pid # process operations if w[0] == 'F': npid = int(w[2]) pid_cwd[npid] = cwd pid_last_dir[npid] = cwd last_pid = npid continue elif w[0] == 'C': cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) if cwd.endswith('/.'): cwd = cwd[0:-2] - last_dir = cwd + self.last_dir = cwd if self.debug > 1: print("cwd=", cwd, file=self.debug_out) continue if w[2] in self.seen: if self.debug > 2: print("seen:", w[2], file=self.debug_out) continue # file operations if w[0] in 'ML': - path = w[2].strip("'") - else: - path = w[2] - # we are never interested in .dirdep files as dependencies - if path.endswith('.dirdep'): + # these are special, tread src as read and + # target as write + self.parse_path(w[1].strip("'"), cwd, 'R', w) + self.parse_path(w[2].strip("'"), cwd, 'W', w) continue - # we don't want to resolve the last component if it is - # a symlink - path = resolve(path, cwd, last_dir, self.debug, self.debug_out) - if not path: - continue - dir,base = os.path.split(path) - if dir in self.seen: + elif w[0] in 'ERWS': + path = w[2] + self.parse_path(path, cwd, w[0], w) + + if not file: + f.close() + + def parse_path(self, path, cwd, op=None, w=[]): + """look at a path for the op specified""" + + if not op: + op = w[0] + + # we are never interested in .dirdep files as dependencies + if path.endswith('.dirdep'): + return + for p in self.excludes: + if p and path.startswith(p): if self.debug > 2: - print("seen:", dir, file=self.debug_out) - continue - # we can have a path in an objdir which is a link - # to the src dir, we may need to add dependencies for each - rdir = dir - dir = abspath(dir, cwd, last_dir, self.debug, self.debug_out) - if rdir == dir or rdir.find('./') > 0: - rdir = None - # now put path back together - path = '/'.join([dir,base]) - if self.debug > 1: - print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) - if w[0] in 'SRWL': - if w[0] == 'W' and path.endswith('.dirdep'): - continue - if path in [last_dir, cwd, self.cwd, self.curdir]: - if self.debug > 1: - print("skipping:", path, file=self.debug_out) - continue - if os.path.isdir(path): - if w[0] in 'RW': - last_dir = path; - if self.debug > 1: - print("ldir=", last_dir, file=self.debug_out) - continue + print >> self.debug_out, "exclude:", p, path + return + # we don't want to resolve the last component if it is + # a symlink + path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) + if not path: + return + dir,base = os.path.split(path) + if dir in self.seen: + if self.debug > 2: + print("seen:", dir, file=self.debug_out) + return + # we can have a path in an objdir which is a link + # to the src dir, we may need to add dependencies for each + rdir = dir + dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) + if rdir == dir or rdir.find('./') > 0: + rdir = None + # now put path back together + path = '/'.join([dir,base]) + if self.debug > 1: + print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) + if op in 'RWS': + if path in [self.last_dir, cwd, self.cwd, self.curdir]: + if self.debug > 1: + print("skipping:", path, file=self.debug_out) + return + if os.path.isdir(path): + if op in 'RW': + self.last_dir = path; + if self.debug > 1: + print("ldir=", self.last_dir, file=self.debug_out) + return - if w[0] in 'REWML': - # finally, we get down to it - if dir == self.cwd or dir == self.curdir: - continue - srctop = self.find_top(path, self.srctops) - if srctop: - if self.dpdeps: - self.add(self.file_deps, path.replace(srctop,''), 'file') - self.add(self.src_deps, dir.replace(srctop,''), 'src') - self.seenit(w[2]) - self.seenit(dir) - if rdir and not rdir.startswith(srctop): - dir = rdir # for below - rdir = None - else: - continue + if op in 'ERW': + # finally, we get down to it + if dir == self.cwd or dir == self.curdir: + return + srctop = self.find_top(path, self.srctops) + if srctop: + if self.dpdeps: + self.add(self.file_deps, path.replace(srctop,''), 'file') + self.add(self.src_deps, dir.replace(srctop,''), 'src') + self.seenit(w[2]) + self.seenit(dir) + if rdir and not rdir.startswith(srctop): + dir = rdir # for below + rdir = None + else: + return - objroot = None - for dir in [dir,rdir]: - if not dir: - continue - objroot = self.find_top(dir, self.objroots) - if objroot: - break + objroot = None + for dir in [dir,rdir]: + if not dir: + continue + objroot = self.find_top(dir, self.objroots) if objroot: - ddep = self.find_obj(objroot, dir, path, w[2]) - if ddep: - self.add(self.obj_deps, ddep, 'obj') - else: - # don't waste time looking again - self.seenit(w[2]) - self.seenit(dir) - if not file: - f.close() + break + if objroot: + ddep = self.find_obj(objroot, dir, path, w[2]) + if ddep: + self.add(self.obj_deps, ddep, 'obj') + else: + # don't waste time looking again + self.seenit(w[2]) + self.seenit(dir) def main(argv, klass=MetaFile, xopts='', xoptf=None): """Simple driver for class MetaFile. Usage: - script [options] [key=value ...] "meta" ... + script [options] [key=value ...] "meta" ... Options and key=value pairs contribute to the dictionary passed to MetaFile. -S "SRCTOP" - add "SRCTOP" to the "SRCTOPS" list. + add "SRCTOP" to the "SRCTOPS" list. -C "CURDIR" -O "OBJROOT" - add "OBJROOT" to the "OBJROOTS" list. + add "OBJROOT" to the "OBJROOTS" list. -m "MACHINE" -a "MACHINE_ARCH" -H "HOST_TARGET" -D "DPDEPS" - -d bumps debug level + -d bumps debug level """ import getopt # import Psyco if we can # it can speed things up quite a bit have_psyco = 0 try: import psyco psyco.full() have_psyco = 1 except: pass conf = { 'SRCTOPS': [], 'OBJROOTS': [], + 'EXCLUDES': [], } try: machine = os.environ['MACHINE'] if machine: conf['MACHINE'] = machine machine_arch = os.environ['MACHINE_ARCH'] if machine_arch: conf['MACHINE_ARCH'] = machine_arch srctop = os.environ['SB_SRC'] if srctop: conf['SRCTOPS'].append(srctop) objroot = os.environ['SB_OBJROOT'] if objroot: conf['OBJROOTS'].append(objroot) except: pass debug = 0 output = True - opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:' + xopts) + opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) for o, a in opts: if o == '-a': conf['MACHINE_ARCH'] = a elif o == '-d': debug += 1 elif o == '-q': output = False elif o == '-H': conf['HOST_TARGET'] = a elif o == '-S': if a not in conf['SRCTOPS']: conf['SRCTOPS'].append(a) elif o == '-C': conf['CURDIR'] = a elif o == '-O': if a not in conf['OBJROOTS']: conf['OBJROOTS'].append(a) elif o == '-R': conf['RELDIR'] = a elif o == '-D': conf['DPDEPS'] = a elif o == '-m': conf['MACHINE'] = a elif o == '-T': conf['TARGET_SPEC'] = a + elif o == '-X': + if a not in conf['EXCLUDES']: + conf['EXCLUDES'].append(a) elif xoptf: xoptf(o, a, conf) conf['debug'] = debug # get any var=val assignments eaten = [] for a in args: if a.find('=') > 0: k,v = a.split('=') if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: if k == 'SRCTOP': k = 'SRCTOPS' elif k == 'OBJROOT': k = 'OBJROOTS' if v not in conf[k]: conf[k].append(v) else: conf[k] = v eaten.append(a) continue break for a in eaten: args.remove(a) debug_out = getv(conf, 'debug_out', sys.stderr) if debug: print("config:", file=debug_out) print("psyco=", have_psyco, file=debug_out) for k,v in list(conf.items()): print("%s=%s" % (k,v), file=debug_out) + m = None for a in args: if a.endswith('.meta'): + if not os.path.exists(a): + continue m = klass(a, conf) elif a.startswith('@'): # there can actually multiple files per line for line in open(a[1:]): for f in line.strip().split(): + if not os.path.exists(f): + continue m = klass(f, conf) - if output: + if output and m: print(m.dirdeps()) print(m.src_dirdeps('\nsrc:')) dpdeps = getv(conf, 'DPDEPS') if dpdeps: m.file_depends(open(dpdeps, 'wb')) return m if __name__ == '__main__': try: main(sys.argv) except: # yes, this goes to stdout print("ERROR: ", sys.exc_info()[1]) raise Index: head/contrib/bmake/mk/meta2deps.sh =================================================================== --- head/contrib/bmake/mk/meta2deps.sh (revision 281811) +++ head/contrib/bmake/mk/meta2deps.sh (revision 281812) @@ -1,379 +1,403 @@ #!/bin/sh # NAME: # meta2deps.sh - extract useful info from .meta files # # SYNOPSIS: # meta2deps.sh SB="SB" "meta" ... # # DESCRIPTION: # This script looks each "meta" file and extracts the # information needed to deduce build and src dependencies. # # To do this, we extract the 'CWD' record as well as all the # syscall traces which describe 'R'ead, 'C'hdir and 'E'xec # syscalls. # # The typical meta file looks like:: #.nf # # # Meta data file "path" # CMD "command-line" # CWD "cwd" # TARGET "target" # -- command output -- # -- filemon acquired metadata -- # # buildmon version 2 # V 2 # E "pid" "path" # R "pid" "path" # C "pid" "cwd" # R "pid" "path" # X "pid" "status" #.fi # # The fact that all the syscall entry lines start with a single # character make these files quite easy to process using sed(1). # # To simplify the logic the 'CWD' line is made to look like a # normal 'C'hdir entry, and "cwd" is remembered so that it can # be prefixed to any "path" which is not absolute. # # If the "path" being read ends in '.srcrel' it is the content # of (actually the first line of) that file that we are # interested in. # # Any "path" which lies outside of the sandbox "SB" is generally # not of interest and is ignored. # # The output, is a set of absolute paths with "SB" like: #.nf # # $SB/obj-i386/bsd/gnu/lib/csu # $SB/obj-i386/bsd/gnu/lib/libgcc # $SB/obj-i386/bsd/include # $SB/obj-i386/bsd/lib/csu/i386-elf # $SB/obj-i386/bsd/lib/libc # $SB/src/bsd/include # $SB/src/bsd/sys/i386/include # $SB/src/bsd/sys/sys # $SB/src/pan-release/rtsock # $SB/src/pfe-shared/include/jnx #.fi # # Which can then be further processed by 'gendirdeps.mk' # # If we are passed 'DPDEPS='"dpdeps", then for each src file # outside of "CURDIR" we read, we output a line like: #.nf # # DPDEPS_$path += $RELDIR #.fi # # with "$path" geting turned into reldir's, so that we can end # up with a list of all the directories which depend on each src # file in another directory. This can allow for efficient yet # complete testing of changes. # RCSid: -# $Id: meta2deps.sh,v 1.7 2014/04/05 22:56:54 sjg Exp $ +# $Id: meta2deps.sh,v 1.9 2015/04/03 18:23:25 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. meta2src() { cat /dev/null "$@" | sed -n '/^R .*\.[chyl]$/s,^..[0-9]* ,,p' | sort -u } meta2dirs() { cat /dev/null "$@" | sed -n '/^R .*\/.*\.[a-z0-9][^\/]*$/s,^..[0-9]* \(.*\)/[^/]*$,\1,p' | sort -u } add_list() { sep=' ' suffix= while : do case "$1" in "|") sep="$1"; shift;; -s) suffix="$2"; shift 2;; *) break;; esac done name=$1 shift eval list="\$$name" for top in "$@" do case "$sep$list$sep" in *"$sep$top$suffix$sep"*) continue;; esac list="${list:+$list$sep}$top$suffix" done eval "$name=\"$list\"" } +_excludes_f() { + egrep -v "$EXCLUDES" +} + meta2deps() { DPDEPS= SRCTOPS=$SRCTOP OBJROOTS= + EXCLUDES= while : do case "$1" in *=*) eval export "$1"; shift;; -a) MACHINE_ARCH=$2; shift 2;; -m) MACHINE=$2; shift 2;; -C) CURDIR=$2; shift 2;; -H) HOST_TARGET=$2; shift 2;; -S) add_list SRCTOPS $2; shift 2;; -O) add_list OBJROOTS $2; shift 2;; + -X) add_list EXCLUDES '|' $2; shift 2;; -R) RELDIR=$2; shift 2;; -T) TARGET_SPEC=$2; shift 2;; *) break;; esac done _th= _o= case "$MACHINE" in host) _ht=$HOST_TARGET;; esac for o in $OBJROOTS do case "$MACHINE,/$o/" in host,*$HOST_TARGET*) ;; *$MACHINE*|*${TARGET_SPEC:-$MACHINE}*) ;; *) add_list _o $o; continue;; esac for x in $_ht $TARGET_SPEC $MACHINE do case "$o" in "") continue;; */$x/) add_list _o ${o%$x/}; o=;; */$x) add_list _o ${o%$x}; o=;; *$x/) add_list _o ${o%$x/}; o=;; *$x) add_list _o ${o%$x}; o=;; esac done done OBJROOTS="$_o" case "$OBJTOP" in "") for o in $OBJROOTS do OBJTOP=$o${TARGET_SPEC:-$MACHINE} break done ;; esac src_re= obj_re= add_list '|' -s '/*' src_re $SRCTOPS add_list '|' -s '*' obj_re $OBJROOTS [ -z "$RELDIR" ] && unset DPDEPS tf=/tmp/m2d$$-$USER rm -f $tf.* trap 'rm -f $tf.*; trap 0' 0 > $tf.dirdep > $tf.qual > $tf.srcdep > $tf.srcrel > $tf.dpdeps seenit= seensrc= lpid= - cat /dev/null "$@" | - sed -e 's,^CWD,C C,;/^[CREFL] /!d' -e "s,',,g" | + case "$EXCLUDES" in + "") _excludes=cat;; + *) _excludes=_excludes_f;; + esac + # handle @list files + case "$@" in + *@[!.]*) + for f in "$@" + do + case "$f" in + *.meta) cat $f;; + @*) xargs cat < ${f#@};; + *) cat $f;; + esac + done + ;; + *) cat /dev/null "$@";; + esac 2> /dev/null | + sed -e 's,^CWD,C C,;/^[CREFLM] /!d' -e "s,',,g" | + $_excludes | while read op pid path junk do : op=$op pid=$pid path=$path # we track cwd and ldir (of interest) per pid # CWD is bmake's cwd case "$lpid,$pid" in ,C) CWD=$path cwd=$path ldir=$path if [ -z "$SB" ]; then SB=`echo $CWD | sed 's,/obj.*,,'` fi SRCTOP=${SRCTOP:-$SB/src} continue ;; $pid,$pid) ;; *) case "$lpid" in "") ;; *) eval ldir_$lpid=$ldir cwd_$lpid=$cwd;; esac eval ldir=\${ldir_$pid:-$CWD} cwd=\${cwd_$pid:-$CWD} lpid=$pid ;; esac case "$op,$path" in W,*srcrel|*.dirdep) continue;; C,*) case "$path" in /*) cwd=$path;; *) cwd=`cd $cwd/$path 2> /dev/null && /bin/pwd`;; esac # watch out for temp dirs that no longer exist test -d ${cwd:-/dev/null/no/such} || cwd=$CWD continue ;; F,*) eval cwd_$path=$cwd ldir_$path=$ldir continue ;; *) dir=${path%/*} case "$path" in $src_re|$obj_re) ;; /*/stage/*) ;; /*) continue;; *) for path in $ldir/$path $cwd/$path do test -e $path && break done dir=${path%/*} ;; esac ;; esac # avoid repeating ourselves... case "$DPDEPS,$seensrc," in ,*) case ",$seenit," in *,$dir,*) continue;; esac ;; *,$path,*) continue;; esac # canonicalize if needed case "/$dir/" in */../*|*/./*) rdir=$dir dir=`cd $dir 2> /dev/null && /bin/pwd` seen="$rdir,$dir" ;; *) seen=$dir;; esac case "$dir" in ${CURDIR:-.}|${CURDIR:-.}/*|"") continue;; $src_re) # avoid repeating ourselves... case "$DPDEPS,$seensrc," in ,*) case ",$seenit," in *,$dir,*) continue;; esac ;; esac ;; *) case ",$seenit," in *,$dir,*) continue;; esac ;; esac if [ -d $path ]; then case "$path" in */..) ldir=${dir%/*};; *) ldir=$path;; esac continue fi [ -f $path ] || continue case "$dir" in $CWD) continue;; # ignore $src_re) seenit="$seenit,$seen" echo $dir >> $tf.srcdep case "$DPDEPS,$reldir,$seensrc," in ,*) ;; *) seensrc="$seensrc,$path" echo "DPDEPS_$dir/${path##*/} += $RELDIR" >> $tf.dpdeps ;; esac continue ;; esac # if there is a .dirdep we cannot skip # just because we've seen the dir before. if [ -s $path.dirdep ]; then # this file contains: # '# ${RELDIR}.' echo $path.dirdep >> $tf.qual continue elif [ -s $dir.dirdep ]; then echo $dir.dirdep >> $tf.qual seenit="$seenit,$seen" continue fi seenit="$seenit,$seen" case "$dir" in $obj_re) echo $dir;; esac done > $tf.dirdep _nl=echo for f in $tf.dirdep $tf.qual $tf.srcdep do [ -s $f ] || continue case $f in *qual) # a list of .dirdep files # we can prefix everything with $OBJTOP to # tell gendirdeps.mk that these are # DIRDEP entries, since they are already # qualified with . as needed. # We strip .$MACHINE though xargs cat < $f | sort -u | sed "s,^# ,,;s,^,$OBJTOP/,;s,\.${TARGET_SPEC:-$MACHINE}\$,,;s,\.$MACHINE\$,," ;; *) sort -u $f;; esac _nl=: done if [ -s $tf.dpdeps ]; then case "$DPDEPS" in */*) ;; *) echo > $DPDEPS;; # the echo is needed! esac sort -u $tf.dpdeps | sed "s,${SRCTOP}/,,;s,${SB_BACKING_SB:-$SB}/src/,," >> $DPDEPS fi # ensure we produce _something_ else egrep -v gets upset $_nl } case /$0 in */meta2dep*) meta2deps "$@";; */meta2dirs*) meta2dirs "$@";; */meta2src*) meta2src "$@";; esac Index: head/contrib/bmake/mk/mk-files.txt =================================================================== --- head/contrib/bmake/mk/mk-files.txt (revision 281811) +++ head/contrib/bmake/mk/mk-files.txt (revision 281812) @@ -1,467 +1,481 @@ mk-files ******** The term ``mk-files`` refers to a collection of ``*.mk`` files. You need bmake_ or a *recent* NetBSD_ make. If in doubt use bmake_. Introduction ============ Many years ago, when building large software projects, I used GNU make (or my own patched version of it), and had developed a set of macros to simplify developing complex build trees. Since the early 90's my main development machines, run BSD (NetBSD_ to be precise), and the BSD source tree is good example of a large software project. It quickly became clear that ``/usr/share/mk/*.mk`` were a great model, but were quite tightly linked to building the BSD tree. Much as I liked using NetBSD, my customers were more likely to be using SunOS, HP-UX etc, so I started on bmake_ and a portable collection of mk-files (mk.tar.gz_). NetBSD provided much of the original structure. Since then I've added a lot of features to NetBSD's make and hence to bmake which is kept closely in sync. The mk-files however have diverged quite a bit, though ideas are still picked up from NetBSD. Basics ------ The BSD build model is very simple. A directory produces one component, which is generally either a library or a program. Library makefiles include ``lib.mk`` and programs include ``prog.mk`` and they *do the right thing*. A simple library makefile might look like:: LIB = sig SRCS = \ sigaction.c \ sigcompat.c \ sighdl.c .include a simple program makefile:: PROG = cat SRCS = cat.c .include in such cases even the ``SRCS`` line is unnecessary as ``prog.mk`` will default it to ``${PROG}.c``. It is the sensible use of defaults and the plethora of macro modifiers provided by bmake_ that allow simple makefiles such as the above *just work* on many different systems. mk-files ======== This section provides a brief description of some of the ``*.mk`` files. sys.mk ------ When bmake starts, it looks for ``sys.mk`` and reads it before doing anything else. Thus, this is the place to setup the environment for everyone else. In this distribution, sys.mk avoids doing anything platform dependent. It is quite short, and includes a number of other files (which may or may not exists) sys.env.mk If it exists, is expected to do things like conditioning the environment. Since it will only be included by the initial instance of bmake, it should ``.export`` anything that sub-makes might need. examples/sys.clean-env.mk An example of how to clean the environment. See the file for all the details:: .if ${MAKE_VERSION} >= 20100606 && ${.MAKE.LEVEL} == 0 # we save any env var that starts with these MAKE_SAVE_ENV_PREFIX += SB MK MAKE MACHINE NEED_ CCACHE DISTCC USE_ SSH MAKE_SAVE_ENV_VARS += \ PATH HOME USER LOGNAME \ SRCTOP OBJTOP OBJROOT \ ${_env_vars} _env_vars != env | egrep '^(${MAKE_SAVE_ENV_PREFIX:ts|})' | sed 's,=.*,,'; echo _export_list = .for v in ${MAKE_SAVE_ENV_VARS:O:u} .if !empty($v) _export_list += $v $v := ${$v} .endif .endfor # now clobber the environment .unexport-env # list of vars that we handle specially below _tricky_env_vars = MAKEOBJDIR # export our selection - sans tricky ones .export ${_export_list:${_tricky_env_vars:${M_ListToSkip}}} # this next bit may need tweaking .if defined(MAKEOBJDIR) srctop := ${SRCTOP:U${SB_SRC:U${SB}/src}} objroot := ${OBJROOT:U${SB_OBJROOT:U${SB}/${SB_OBJPREFIX}}} # we'll take care of MACHINE below objtop := ${OBJTOP:U${objroot}${MACHINE}} .if !empty(objtop) # we would normally want something like (/bin/sh): # MAKEOBJDIR="\${.CURDIR:S,${SRCTOP},${OBJROOT}\${MACHINE},}" # the $$ below is how we achieve the same result here. # since everything saved from the environment above # has run through := we need to compensate for ${MACHINE} MAKEOBJDIR = $${.CURDIR:S,${srctop},${objtop:S,${MACHINE},\${MACHINE},},} # export these as-is, and do not track... .export-env ${_tricky_env_vars} # now evaluate for ourselves .for v in ${_tricky_env_vars} $v := ${$v} .endfor .endif .endif .endif host-target.mk Is used to set macros like ``HOST_TARGET``, ``HOST_OS`` and ``host_os`` which are used to find the next step. sys/\*.mk Platform specific additions, such as ``Darwin.mk`` or ``SunOS.mk`` set things like ``HOST_LIBEXT = .dylib`` for Darwin or ``SHLIB_FULLVERSION = ${SHLIB_MAJOR}`` for SunOS 5. If there is no OS specific file, ``sys/Generic.mk`` is used. local.sys.mk Any ``local.*.mk`` file is not part of the distribution. This provides a hook for sites to do extra setup without having to edit the distributed files. The above arrangement makes it easy for the mk files to be part of a src tree on an NFS volume and to allow building on multiple platforms. lib.mk ------ This file is used to build a number of different libraries from the same SRCS. lib${LIB}.a An archive lib of ``.o`` files, this is the default lib${LIB}_p.a A profiled lib of ``.po`` files. Still an archive lib, but all the objects are built with profiling in mind - hence the different extension. It is skipped if ``MKPROFILE`` is "no". lib${LIB}_pic.a An archive of ``.so`` objects compiled for relocation. On NetBSD this is the input to ``lib${LIB}.${LD_so}``, it is skipped if ``MKPICLIB`` is "no". lib${LIB}.${LD_so} A shared library. The value of ``LD_so`` is very platform specific. For example:: # SunOS 5 and most other ELF systems libsslfd.so.1 # Darwin libsslfd.1.dylib This library will only be built if ``SHLIB_MAJOR`` has a value, and ``MKPIC`` is not set to "no". There is a lot of platform specific tweaking in ``lib.mk``, largely the result of the original distributions trying to avoid interfering with the system's ``sys.mk``. libnames.mk ----------- This is included by both ``prog.mk`` and ``lib.mk`` and tries to include ``*.libnames.mk`` of which: local.libnames.mk does not exist unless you create it. It is a handy way for you to customize without touching the distributed files. For example, on a test machine I needed to build openssl but not install it, so put the following in ``local.libnames.mk``:: .if ${host_os} == "sunos" LIBCRYPTO = ${OBJTOP}/openssl/lib/crypto/libcrypto${DLIBEXT} LIBSSL = ${OBJTOP}/openssl/lib/ssl/libssl${DLIBEXT} INCLUDES_libcrypto = -I${OBJ_libcrypto} .endif The makefile created an openssl dir in ``${OBJ_libcrypto}`` to gather all the headers. dpadd.mk_ did the rest. sjg.libnames.mk not part of the mk-files distribution. host.libnames.mk contains logic to find any libs named in ``HOST_LIBS`` in ``HOST_LIBDIRS``. Each file above gets an opportunity to define things like:: LIBSSLFD ?= ${OBJTOP}/ssl/lib/sslfd/libsslfd${DLIBEXT} INCLUDES_libsslfd = -I${SRC_libsslfd}/h -I${OBJ_libslfd} these are used by dpadd.mk_ and will be explained below. dpadd.mk -------- This file looks like line noise, and is best considered read-only. However it provides some very useful functionality, which simplifies the build. Makefiles can use the LIB* macros defined via libnames.mk_ or anywhere else in various ways:: # indicate that we need to include headers from LIBCRYPTO # this would result in ${INCLUDES_libcrypto} being added to CFLAGS. SRC_LIBS += ${LIBCRYPTO} # indicate that libsslfd must be built already. # it also has the same effect as SRC_LIBS DPADD += ${LIBSSLFD} # indicate that not only must libsslfd be built, # but that we need to link with it. # this is almost exactly equivalent to # DPADD += ${LIBSSLFD} # LDADD += -L${LIBSSLFD:H} -lsslfd # and mostly serves to ensure that DPADD and LDADD are in sync. DPLIBS += ${LIBSSLFD} Any library (referenced by its full path) in any of the above, is added to ``DPMAGIC_LIBS`` with the following results, for each lib *foo*. SRC_libfoo Is set to indicate where the src for libfoo is. By default it is derived from ``LIBFOO`` by replacing ``${OBJTOP}`` with ``${SRCTOP}``. OBJ_libfoo Not very exciting, is just the dir where libfoo lives. INCLUDES_libfoo What to add to ``CFLAGS`` to find the public headers. The default varies. If ``${SRC_libfoo}/h`` exists, it is assumed to be the home of all public headers and thus the default is ``-I${SRC_libfoo}/h`` Otherwise we make no assumptions and the default is ``-I${SRC_libfoo} -I${OBJ_libfoo}`` LDADD_libfoo This only applies to libs reference via ``DPLIBS``. The default is ``-lfoo``, ``LDADD_*`` provides a hook to instantiate other linker flags at the appropriate point without losing the benfits of ``DPLIBS``. prog.mk ------- Compiles the specified SRCS and links them and the nominated libraries into a program. Prog makefiles usually need to list the libraries that need to be linked. We prefer use of ``DPLIBS`` but the more traditional ``DPADD`` and ``LDADD`` work just as well. That is:: DPLIBS += ${LIBCRYPTO} is equivalent to:: DPADD += ${LIBCRYPTO} LDADD += -lcrypto obj.mk ------ One of the cool aspects of BSD make, is its support for separating object files from the src tree. This is also the source of much confusion to some. Traditionally one had to do a separate ``make obj`` pass through the tree. If ``MKOBJDIRS`` is "auto", we include auto.obj.mk_. auto.obj.mk ----------- This leverages the ``.OBJDIR`` target introduced some years ago to NetBSD make, to automatically create the desired object dir. subdir.mk --------- This is the traditional means of walking the tree. A makefile sets ``SUBDIR`` to the list of sub-dirs to visit. If ``SUBDIR_MUST_EXIST`` is set, missing directories cause an error, otherwise a warning is issued. If you don't even want the warning, set ``MISSING_DIR=continue``. Traditionally, ``subdir.mk`` prints clue as it visits each subdir:: ===> ssl ===> ssl/lib ===> ssl/lib/sslfd you can suppress that - or enhance it by setting ``ECHO_DIR``:: # suppress subdir noise ECHO_DIR=: # print time stamps ECHO_DIR=echo @ `date "+%s [%Y-%m-%d %T] "` links.mk -------- Provides rules for processing lists of ``LINKS`` and ``SYMLINKS``. Each is expected to be a list of ``link`` and ``target`` pairs (``link`` -> ``target``). The logic is generally in a ``_*_SCRIPT`` which is referenced in a ``_*_USE`` (``.USE``) target. The ``_BUILD_*`` forms are identical, but do not use ``${DESTDIR}`` and so are useful for creating symlinks during the build phase. For example:: SYMLINKS += ${.CURDIR}/${MACHINE_ARCH}/include machine header_links: _BUILD_SYMLINKS_USE md.o: header_links would create a symlink called ``machine`` in ``${.OBJDIR}`` pointing to ``${.CURDIR}/${MACHINE_ARCH}/include`` before compiling ``md.o`` autoconf.mk ----------- Deals with running (or generating) GNU autoconf ``configure`` scripts. dep.mk ------ Deals with collecting dependencies. Another useful feature of BSD make is the separation of this sort of information into a ``.depend`` file. ``MKDEP`` needs to point to a suitable tool (like mkdeps.sh_) If ``USE_AUTODEP_MK`` is "yes" includes autodep.mk_ autodep.mk ---------- Leverages the ``-MD`` feature of recent GCC to collect dependency information as a side effect of compilation. With this GCC puts dependency info into a ``.d`` file. Unfortunately GCC bases the name of the ``.d`` file on the name of the input rather than the output file, which causes problems when the same source is compiled different ways. The latest GCC supports ``-MF`` to name the ``.d`` file and ``-MT`` to control the name to put as the dependent. Recent bmake allows dependencies for the ``.END`` target (run at the end if everything was successful), and ``autodep.mk`` uses this to post process the ``.d`` files into ``.depend``. auto.dep.mk ----------- A much simpler implementation than autodep.mk_ it uses ``-MF ${.TARGET:T}.d`` to avoid possible conflicts during parallel builds. This precludes the use of suffix rules to drive ``make depend``, so dep.mk_ handles that if specifically requested. own.mk ------ Normally included by ``init.mk`` (included by ``lib.mk`` and ``prog.mk`` etc), sets macros for default ownership etc. It includes ``${MAKECONF}`` if it is defined and exists. man.mk ------ Deals with man pages. warnings.mk ----------- This provides a means of fine grained control over warnings on a per ``${MACHINE}`` or even file basis. A makefile sets ``WARNINGS_SET`` to name a list of warnings and individual ``W_*`` macros can be used to tweak them. For example:: WARNINGS_SET = HIGH W_unused_sparc = -Wno-unused would add all the warnings in ``${HIGH_WARNINGS}`` to CFLAGS, but on sparc, ``-Wno-unused`` would replace ``-Wunused``. You should never need to edit ``warnings.mk``, it will include ``warnings-sets.mk`` if it exists and you use that to make any local customizations. +rst2htm.mk +---------- + +Logic to simplify generating HTML (and PDF) documents from ReStructuredText. + +cython.mk +--------- + +Logic to build Python C interface modules using Cython_ + +.. _Cython: http://www.cython.org/ + Meta mode ========= The 20110505 and later versions of ``mk-files`` include a number of -makefile contributed by Juniper Networks, Inc. -These allow the latest version of bmake_ to run in `meta mode`_. +makefiles contributed by Juniper Networks, Inc. +These allow the latest version of bmake_ to run in `meta mode`_ +see `dirdeps.mk`_ +.. _`dirdeps.mk`: /help/sjg/dirdeps.htm .. _`meta mode`: bmake-meta-mode.htm Install ======= You can use the content of mk.tar.gz_ without installing at all. The script ``install-mk`` takes care of copying ``*.mk`` into a destination directory, and unless told not to, create ``bsd.*.mk`` links for ``lib.mk`` etc. If you just want to create the ``bsd.*.mk`` links in the directory where you unpacked the tar file, you can:: ./mk/install-mk ./mk ------ .. _bmake: bmake.htm .. _NetBSD: http://www.netbsd.org/ .. _mkdeps.sh: http://www.crufty.net/ftp/pub/sjg/mkdeps.sh .. _mk.tar.gz: http://www.crufty.net/ftp/pub/sjg/mk.tar.gz :Author: sjg@crufty.net -:Revision: $Id: mk-files.txt,v 1.15 2011/06/08 07:06:18 sjg Exp $ +:Revision: $Id: mk-files.txt,v 1.16 2014/09/05 04:41:16 sjg Exp $ :Copyright: Crufty.NET Index: head/contrib/bmake/mk/mkopt.sh =================================================================== --- head/contrib/bmake/mk/mkopt.sh (nonexistent) +++ head/contrib/bmake/mk/mkopt.sh (revision 281812) @@ -0,0 +1,94 @@ +: +# $Id: mkopt.sh,v 1.8 2014/11/15 07:07:18 sjg Exp $ +# +# @(#) Copyright (c) 2014, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +# handle WITH[OUT]_* options in a manner compatible with +# options.mk and bsd.mkopt.mk in recent FreeBSD + +# no need to be included more than once +_MKOPT_SH=: + +# +# _mk_opt OPT default +# +# Set MK_$OPT +# +# The semantics are simple, if MK_$OPT has no value +# WITHOUT_$OPT results in MK_$OPT=no +# otherwise WITH_$OPT results in MK_$OPT=yes. +# Note WITHOUT_$OPT overrides WITH_$OPT. +# +# For backwards compatability reasons we treat WITH_$OPT=no +# the same as WITHOUT_$OPT. +# +_mk_opt() { + _d=$1 + _mo=MK_$2 _wo=WITHOUT_$2 _wi=WITH_$2 + eval "_mov=\$$_mo _wov=\$$_wo _wiv=\$$_wi" + + case "$_wiv" in + no) _wov=no;; + esac + _v=${_mov:-${_wov:+no}} + _v=${_v:-${_wiv:+yes}} + _v=${_v:-$_d} + _opt_list="$_opt_list $_mo" + case "$_v" in + yes|no) ;; # sane + 0|[NnFf]*) _v=no;; # they mean no + 1|[YyTt]*) _v=yes;; # they mean yes + *) _v=$_d;; # ignore bogus value + esac + eval "$_mo=$_v" +} + +# +# _mk_opts default opt ... [default [opt] ...] +# +# see _mk_opts_defaults for example +# +_mk_opts() { + _d=no + for _o in "$@" + do + case "$_o" in + yes|no) _d=$_o; continue;; + esac + _mk_opt $_d $_o + done +} + +_mk_opts_defaults() { + _mk_opts no $__DEFAULT_NO_OPTIONS yes $__DEFAULT_YES_OPTIONS +} + +case "/$0" in +*/mkopt*) + _list=no + while : + do + case "$1" in + *=*) eval "$1"; shift;; + --no|no) _list="$_list no"; shift;; + --yes|yes) _list="$_list yes"; shift;; + -DWITH*) eval "${1#-D}=1"; shift;; + [A-Z]*) _list="$_list $1"; shift;; + *) break;; + esac + done + _mk_opts $_list + ;; +esac + Property changes on: head/contrib/bmake/mk/mkopt.sh ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/mk/own.mk =================================================================== --- head/contrib/bmake/mk/own.mk (revision 281811) +++ head/contrib/bmake/mk/own.mk (revision 281812) @@ -1,245 +1,245 @@ -# $Id: own.mk,v 1.27 2013/07/18 05:46:24 sjg Exp $ +# $Id: own.mk,v 1.28 2015/04/16 16:59:00 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .if !target(__init.mk__) .include "init.mk" .endif .ifndef NOMAKECONF MAKECONF?= /etc/mk.conf .-include "${MAKECONF}" .endif .include TARGET_OSNAME?= ${_HOST_OSNAME} TARGET_OSREL?= ${_HOST_OSREL} TARGET_OSTYPE?= ${HOST_OSTYPE} TARGET_HOST?= ${HOST_TARGET} # these may or may not exist .-include "${TARGET_HOST}.mk" .-include "config.mk" RM?= rm LN?= ln INSTALL?= install prefix?= /usr .if exists(${prefix}/lib) libprefix?= ${prefix} .else libprefix?= /usr .endif # FreeBSD at least does not set this MACHINE_ARCH?=${MACHINE} # we need to make sure these are defined too in case sys.mk fails to. COMPILE.s?= ${CC} ${AFLAGS} -c LINK.s?= ${CC} ${AFLAGS} ${LDFLAGS} COMPILE.S?= ${CC} ${AFLAGS} ${CPPFLAGS} -c -traditional-cpp LINK.S?= ${CC} ${AFLAGS} ${CPPFLAGS} ${LDFLAGS} COMPILE.c?= ${CC} ${CFLAGS} ${CPPFLAGS} -c LINK.c?= ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} CXXFLAGS?= ${CFLAGS} COMPILE.cc?= ${CXX} ${CXXFLAGS} ${CPPFLAGS} -c LINK.cc?= ${CXX} ${CXXFLAGS} ${CPPFLAGS} ${LDFLAGS} COMPILE.m?= ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} -c LINK.m?= ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} ${LDFLAGS} COMPILE.f?= ${FC} ${FFLAGS} -c LINK.f?= ${FC} ${FFLAGS} ${LDFLAGS} COMPILE.F?= ${FC} ${FFLAGS} ${CPPFLAGS} -c LINK.F?= ${FC} ${FFLAGS} ${CPPFLAGS} ${LDFLAGS} COMPILE.r?= ${FC} ${FFLAGS} ${RFLAGS} -c LINK.r?= ${FC} ${FFLAGS} ${RFLAGS} ${LDFLAGS} LEX.l?= ${LEX} ${LFLAGS} COMPILE.p?= ${PC} ${PFLAGS} ${CPPFLAGS} -c LINK.p?= ${PC} ${PFLAGS} ${CPPFLAGS} ${LDFLAGS} YACC.y?= ${YACC} ${YFLAGS} # for suffix rules IMPFLAGS?= ${COPTS.${.IMPSRC:T}} ${CPUFLAGS.${.IMPSRC:T}} ${CPPFLAGS.${.IMPSRC:T}} .for s in .c .cc COMPILE.$s += ${IMPFLAGS} LINK.$s += ${IMPFLAGS} .endfor PRINT.VAR.MAKE = MAKESYSPATH=${MAKESYSPATH:U${.PARSEDIR}} ${.MAKE} .if empty(.MAKEFLAGS:M-V*) .if defined(MAKEOBJDIRPREFIX) || defined(MAKEOBJDIR) PRINTOBJDIR= ${PRINT.VAR.MAKE} -r -V .OBJDIR -f /dev/null xxx .else PRINTOBJDIR= ${PRINT.VAR.MAKE} -V .OBJDIR .endif .else PRINTOBJDIR= echo # prevent infinite recursion .endif # we really like to have SRCTOP and OBJTOP defined... .if !defined(SRCTOP) || !defined(OBJTOP) .-include "srctop.mk" .endif .if !defined(SRCTOP) || !defined(OBJTOP) # dpadd.mk is rather pointless without these OPTIONS_DEFAULT_NO+= DPADD_MK .endif # process options OPTIONS_DEFAULT_NO+= \ + AUTO_OBJ \ INSTALL_AS_USER \ GPROF \ LIBTOOL \ LINT \ META_MODE \ OPTIONS_DEFAULT_YES+= \ ARCHIVE \ AUTODEP \ - AUTO_OBJ \ CRYPTO \ DOC \ DPADD_MK \ GDB \ KERBEROS \ LINKLIB \ MAN \ NLS \ OBJ \ PIC \ SHARE \ SKEY \ YP \ OPTIONS_DEFAULT_DEPENDENT+= \ CATPAGES/MAN \ OBJDIRS/OBJ \ PICINSTALL/LINKLIB \ PICLIB/PIC \ PROFILE/LINKLIB \ .include .if ${MK_INSTALL_AS_USER} == "yes" # We ignore this if user is root. _uid!= id -u .if ${_uid} != 0 .if !defined(USERGRP) USERGRP!= id -g .export USERGRP .endif .for x in BIN CONF DOC INFO KMOD LIB MAN NLS SHARE $xOWN= ${USER} $xGRP= ${USERGRP} .endfor .endif .endif # override this in sys.mk ROOT_GROUP?= wheel BINGRP?= ${ROOT_GROUP} BINOWN?= root BINMODE?= 555 NONBINMODE?= 444 # Define MANZ to have the man pages compressed (gzip) #MANZ= 1 MANTARGET?= cat MANDIR?= ${prefix}/share/man/${MANTARGET} MANGRP?= ${BINGRP} MANOWN?= ${BINOWN} MANMODE?= ${NONBINMODE} LIBDIR?= ${libprefix}/lib SHLIBDIR?= ${libprefix}/lib .if ${USE_SHLIBDIR:Uno} == "yes" _LIBSODIR?= ${SHLIBDIR} .else _LIBSODIR?= ${LIBDIR} .endif # this is where ld.*so lives SHLINKDIR?= /usr/libexec LINTLIBDIR?= ${libprefix}/libdata/lint LIBGRP?= ${BINGRP} LIBOWN?= ${BINOWN} LIBMODE?= ${NONBINMODE} DOCDIR?= ${prefix}/share/doc DOCGRP?= ${BINGRP} DOCOWN?= ${BINOWN} DOCMODE?= ${NONBINMODE} NLSDIR?= ${prefix}/share/nls NLSGRP?= ${BINGRP} NLSOWN?= ${BINOWN} NLSMODE?= ${NONBINMODE} KMODDIR?= ${prefix}/lkm KMODGRP?= ${BINGRP} KMODOWN?= ${BINOWN} KMODMODE?= ${NONBINMODE} COPY?= -c STRIP_FLAG?= -s .if ${TARGET_OSNAME} == "NetBSD" .if exists(/usr/libexec/ld.elf_so) OBJECT_FMT=ELF .endif OBJECT_FMT?=a.out .endif # sys.mk should set something appropriate if need be. OBJECT_FMT?=ELF .if (${_HOST_OSNAME} == "FreeBSD") CFLAGS+= ${CPPFLAGS} .endif # allow for per target flags # apply the :T:R first, so the more specific :T can override if needed CPPFLAGS += ${CPPFLAGS_${.TARGET:T:R}} ${CPPFLAGS_${.TARGET:T}} CFLAGS += ${CFLAGS_${.TARGET:T:R}} ${CFLAGS_${.TARGET:T}} # Define SYS_INCLUDE to indicate whether you want symbolic links to the system # source (``symlinks''), or a separate copy (``copies''); (latter useful # in environments where it's not possible to keep /sys publicly readable) #SYS_INCLUDE= symlinks # don't try to generate PIC versions of libraries on machines # which don't support PIC. .if (${MACHINE_ARCH} == "vax") || \ ((${MACHINE_ARCH} == "mips") && defined(STATIC_TOOLCHAIN)) || \ ((${MACHINE_ARCH} == "alpha") && defined(ECOFF_TOOLCHAIN)) MK_PIC=no .endif # No lint, for now. NOLINT= .if ${MK_LINKLIB} == "no" MK_PICINSTALL= no MK_PROFILE= no .endif .if ${MK_MAN} == "no" MK_CATPAGES= no .endif .if ${MK_OBJ} == "no" MK_OBJDIRS= no MK_AUTO_OBJ= no .endif .if ${MK_SHARE} == "no" MK_CATPAGES= no MK_DOC= no MK_INFO= no MK_MAN= no MK_NLS= no .endif .endif Index: head/contrib/bmake/mk/sys.dependfile.mk =================================================================== --- head/contrib/bmake/mk/sys.dependfile.mk (revision 281811) +++ head/contrib/bmake/mk/sys.dependfile.mk (revision 281812) @@ -1,51 +1,57 @@ -# $Id: sys.dependfile.mk,v 1.5 2013/03/08 00:59:21 sjg Exp $ +# $Id: sys.dependfile.mk,v 1.6 2014/08/02 18:02:06 sjg Exp $ # # @(#) Copyright (c) 2012, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # This only makes sense in meta mode. # This allows a mixture of auto generated as well as manually edited # dependency files, which can be differentiated by their names. # As per dirdeps.mk we only require: # 1. a common prefix # 2. that machine specific files end in .${MACHINE} # # The .MAKE.DEPENDFILE_PREFERENCE below is an example. # All depend file names should start with this .MAKE.DEPENDFILE_PREFIX ?= Makefile.depend + +.if !empty(.MAKE.DEPENDFILE) && \ + ${.MAKE.DEPENDFILE:M${.MAKE.DEPENDFILE_PREFIX}*} == "" +# let us do our thing below... +.undef .MAKE.DEPENDFILE +.endif # The order of preference: we will use the first one of these we find. # It usually makes sense to order from most specific to least. .MAKE.DEPENDFILE_PREFERENCE ?= \ ${.CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.${MACHINE} \ ${.CURDIR}/${.MAKE.DEPENDFILE_PREFIX} # Normally the 1st entry is our default choice # Another useful default is ${.MAKE.DEPENDFILE_PREFIX} .MAKE.DEPENDFILE_DEFAULT ?= ${.MAKE.DEPENDFILE_PREFERENCE:[1]} _e := ${.MAKE.DEPENDFILE_PREFERENCE:@m@${exists($m):?$m:}@} .if !empty(_e) .MAKE.DEPENDFILE := ${_e:[1]} .elif ${.MAKE.DEPENDFILE_PREFERENCE:M*${MACHINE}} != "" && ${.MAKE.DEPENDFILE_DEFAULT:E} != ${MACHINE} # MACHINE specific depend files are supported, but *not* default. # If any already exist, we should follow suit. _aml = ${ALL_MACHINE_LIST:Uarm amd64 i386 powerpc:N${MACHINE}} ${MACHINE} # MACHINE must be the last entry in _aml ;-) _e := ${_aml:@MACHINE@${.MAKE.DEPENDFILE_PREFERENCE:@m@${exists($m):?$m:}@}@} .if !empty(_e) .MAKE.DEPENDFILE ?= ${.MAKE.DEPENDFILE_PREFERENCE:M*${MACHINE}:[1]} .endif .endif .MAKE.DEPENDFILE ?= ${.MAKE.DEPENDFILE_DEFAULT} Index: head/contrib/bmake/mk/whats.mk =================================================================== --- head/contrib/bmake/mk/whats.mk (nonexistent) +++ head/contrib/bmake/mk/whats.mk (revision 281812) @@ -0,0 +1,63 @@ +# $Id: whats.mk,v 1.1 2014/08/30 22:40:47 sjg Exp $ +# +# @(#) Copyright (c) 2014, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +.if ${MK_WHATSTRING:Uno} != "no" +what_build_exts?= o +# it can be useful to embed a what(1) string in binaries +# so that the build location can be seen from a core file. +.if defined(PROG) && ${.MAKE.MAKEFILES:M*prog.mk} != "" +what_thing?= ${PROGNAME:U${PROG}} +what_build_thing?= ${PROG} +.elif defined(LIB) && ${.MAKE.MAKEFILES:M*lib.mk} != "" +# probably only makes sense for shared libs +# and the plumbing needed varies depending on *lib.mk +what_thing?= lib${LIB} +.if !empty(SOBJS) +_soe:= ${SOBJS:E:[1]} +what_build_exts= ${_soe} +SOBJS+= ${what_uuid}.${_soe} +.endif +.elif defined(KMOD) && ${.MAKE.MAKEFILES:M*kmod.mk} != "" +what_thing?= ${KMOD} +what_build_thing?= ${KMOD}.ko +.endif + +.if !empty(what_thing) +# a unique name that won't conflict with anything +what_uuid = what_${.CURDIR:T:hash} + +.if !empty(what_build_thing) +${what_build_thing}: ${what_build_exts:@e@${what_uuid}.$e@} +.endif +OBJS+= ${what_uuid}.o +CLEANFILES+= ${what_uuid}.c + +# we do not need to capture this +SUPPRESS_DEPEND+= *${what_uuid}.c + +SB?= ${SRCTOP:H} +SB_LOCATION?= ${HOST}:${SB} +what_location:= ${.OBJDIR:S,${SB},${SB_LOCATION},} + +# this works with clang and gcc +_what_t= const char __attribute__ ((section(".data"))) +_what1:= @(\#)${what_thing:tu} built ${%Y%m%d:L:localtime} by ${USER} +_what2:= @(\#)${what_location} + +${what_uuid}.c: + echo '${_what_t} ${what_uuid}1[] = "${_what1}";' > $@ ${.OODATE:MNO_META_CMP} + echo '${_what_t} ${what_uuid}2[] = "${_what2}";' >> $@ +.endif +.endif Property changes on: head/contrib/bmake/mk/whats.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/nonints.h =================================================================== --- head/contrib/bmake/nonints.h (revision 281811) +++ head/contrib/bmake/nonints.h (revision 281812) @@ -1,199 +1,199 @@ -/* $NetBSD: nonints.h,v 1.65 2012/08/30 21:17:05 sjg Exp $ */ +/* $NetBSD: nonints.h,v 1.67 2014/09/07 20:55:34 joerg Exp $ */ /*- * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 */ /*- * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 */ /* arch.c */ ReturnStatus Arch_ParseArchive(char **, Lst, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); time_t Arch_MTime(GNode *); time_t Arch_MemMTime(GNode *); void Arch_FindLib(GNode *, Lst); Boolean Arch_LibOODate(GNode *); void Arch_Init(void); void Arch_End(void); int Arch_IsLib(GNode *); /* compat.c */ int CompatRunCommand(void *, void *); void Compat_Run(Lst); int Compat_Make(void *, void *); /* cond.c */ struct If; int Cond_EvalExpression(const struct If *, char *, Boolean *, int); int Cond_Eval(char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); /* for.c */ int For_Eval(char *); int For_Accum(char *); void For_Run(int); /* job.c */ #ifdef WAIT_T void JobReapChild(pid_t, WAIT_T, Boolean); #endif /* main.c */ void Main_ParseArgLine(const char *); void MakeMode(const char *); int main(int, char **); char *Cmd_Exec(const char *, const char **); void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void DieHorribly(void) MAKE_ATTR_DEAD; int PrintAddr(void *, void *); void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); void execError(const char *, const char *); char *getTmpdir(void); Boolean getBoolean(const char *, Boolean); /* parse.c */ void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); Boolean Parse_AnyExport(void); Boolean Parse_IsVar(char *); void Parse_DoVar(char *, GNode *); void Parse_AddIncludeDir(char *); void Parse_File(const char *, int); void Parse_Init(void); void Parse_End(void); void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *); Lst Parse_MainName(void); /* str.c */ char *str_concat(const char *, const char *, int); char **brk_string(const char *, int *, Boolean, char **); char *Str_FindSubstring(const char *, const char *); int Str_Match(const char *, const char *); char *Str_SYSVMatch(const char *, const char *, int *len); void Str_SYSVSubst(Buffer *, char *, char *, int); /* suff.c */ void Suff_ClearSuffixes(void); Boolean Suff_IsTransform(char *); GNode *Suff_AddTransform(char *); int Suff_EndTransform(void *, void *); void Suff_AddSuffix(char *, GNode **); Lst Suff_GetPath(char *); void Suff_DoPaths(void); void Suff_AddInclude(char *); void Suff_AddLib(char *); void Suff_FindDeps(GNode *); Lst Suff_FindPath(GNode *); void Suff_SetNull(char *); void Suff_Init(void); void Suff_End(void); void Suff_PrintAll(void); /* targ.c */ void Targ_Init(void); void Targ_End(void); Lst Targ_List(void); GNode *Targ_NewGN(const char *); GNode *Targ_FindNode(const char *, int); Lst Targ_FindList(Lst, int); Boolean Targ_Ignore(GNode *); Boolean Targ_Silent(GNode *); Boolean Targ_Precious(GNode *); void Targ_SetMain(GNode *); int Targ_PrintCmd(void *, void *); int Targ_PrintNode(void *, void *); char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); void Targ_Propagate(void); void Targ_Propagate_Wait(void); /* var.c */ void Var_Delete(const char *, GNode *); void Var_Set(const char *, const char *, GNode *, int); void Var_Append(const char *, const char *, GNode *); Boolean Var_Exists(const char *, GNode *); char *Var_Value(const char *, GNode *, char **); char *Var_Parse(const char *, GNode *, Boolean, int *, void **); char *Var_Subst(const char *, const char *, GNode *, Boolean); char *Var_GetTail(const char *); char *Var_GetHead(const char *); void Var_Init(void); void Var_End(void); void Var_Dump(GNode *); void Var_ExportVars(void); void Var_Export(char *, int); void Var_UnExport(char *); /* util.c */ void (*bmake_signal(int, void (*)(int)))(int); Index: head/contrib/bmake/parse.c =================================================================== --- head/contrib/bmake/parse.c (revision 281811) +++ head/contrib/bmake/parse.c (revision 281812) @@ -1,3197 +1,3268 @@ -/* $NetBSD: parse.c,v 1.194 2014/02/15 00:17:17 christos Exp $ */ +/* $NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: parse.c,v 1.194 2014/02/15 00:17:17 christos Exp $"; +static char rcsid[] = "$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: parse.c,v 1.194 2014/02/15 00:17:17 christos Exp $"); +__RCSID("$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $"); #endif #endif /* not lint */ #endif /*- * parse.c -- * Functions to parse a makefile. * * One function, Parse_Init, must be called before any functions * in this module are used. After that, the function Parse_File is the * main entry point and controls most of the other functions in this * module. * * Most important structures are kept in Lsts. Directories for * the .include "..." function are kept in the 'parseIncPath' Lst, while * those for the .include <...> are kept in the 'sysIncPath' Lst. The * targets currently being defined are kept in the 'targets' Lst. * * The variables 'fname' and 'lineno' are used to track the name * of the current file and the line number in that file so that error * messages can be more meaningful. * * Interface: * Parse_Init Initialization function which must be * called before anything else in this module * is used. * * Parse_End Cleanup the module * * Parse_File Function used to parse a makefile. It must * be given the name of the file, which should * already have been opened, and a function * to call to read a character from the file. * * Parse_IsVar Returns TRUE if the given line is a * variable assignment. Used by MainParseArgs * to determine if an argument is a target * or a variable assignment. Used internally * for pretty much the same thing... * * Parse_Error Function called when an error occurs in * parsing. Used by the variable and * conditional modules. * Parse_MainName Returns a Lst of the main target to create. */ #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "buf.h" #include "pathnames.h" #ifdef HAVE_MMAP #include #ifndef MAP_COPY #define MAP_COPY MAP_PRIVATE #endif #ifndef MAP_FILE #define MAP_FILE 0 #endif #endif //////////////////////////////////////////////////////////// // types and constants /* * Structure for a file being read ("included file") */ typedef struct IFile { char *fname; /* name of file */ int lineno; /* current line number in file */ int first_lineno; /* line number of start of text */ int cond_depth; /* 'if' nesting when file opened */ char *P_str; /* point to base of string buffer */ char *P_ptr; /* point to next char of string buffer */ char *P_end; /* point to the end of string buffer */ char *(*nextbuf)(void *, size_t *); /* Function to get more data */ void *nextbuf_arg; /* Opaque arg for nextbuf() */ struct loadedfile *lf; /* loadedfile object, if any */ } IFile; /* * These values are returned by ParseEOF to tell Parse_File whether to * CONTINUE parsing, i.e. it had only reached the end of an include file, * or if it's DONE. */ #define CONTINUE 1 #define DONE 0 /* * Tokens for target attributes */ typedef enum { Begin, /* .BEGIN */ Default, /* .DEFAULT */ End, /* .END */ dotError, /* .ERROR */ Ignore, /* .IGNORE */ Includes, /* .INCLUDES */ Interrupt, /* .INTERRUPT */ Libs, /* .LIBS */ Meta, /* .META */ MFlags, /* .MFLAGS or .MAKEFLAGS */ Main, /* .MAIN and we don't have anything user-specified to * make */ NoExport, /* .NOEXPORT */ NoMeta, /* .NOMETA */ NoMetaCmp, /* .NOMETA_CMP */ NoPath, /* .NOPATH */ Not, /* Not special */ NotParallel, /* .NOTPARALLEL */ Null, /* .NULL */ ExObjdir, /* .OBJDIR */ Order, /* .ORDER */ Parallel, /* .PARALLEL */ ExPath, /* .PATH */ Phony, /* .PHONY */ #ifdef POSIX Posix, /* .POSIX */ #endif Precious, /* .PRECIOUS */ ExShell, /* .SHELL */ Silent, /* .SILENT */ SingleShell, /* .SINGLESHELL */ Stale, /* .STALE */ Suffixes, /* .SUFFIXES */ Wait, /* .WAIT */ Attribute /* Generic attribute */ } ParseSpecial; /* * Other tokens */ #define LPAREN '(' #define RPAREN ')' //////////////////////////////////////////////////////////// // result data /* * The main target to create. This is the first target on the first * dependency line in the first makefile. */ static GNode *mainNode; //////////////////////////////////////////////////////////// // eval state /* targets we're working on */ static Lst targets; #ifdef CLEANUP /* command lines for targets */ static Lst targCmds; #endif /* * specType contains the SPECial TYPE of the current target. It is * Not if the target is unspecial. If it *is* special, however, the children * are linked as children of the parent but not vice versa. This variable is * set in ParseDoDependency */ static ParseSpecial specType; /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ static GNode *predecessor; //////////////////////////////////////////////////////////// // parser state /* true if currently in a dependency line or its commands */ static Boolean inLine; /* number of fatal errors */ static int fatals = 0; /* * Variables for doing includes */ /* current file being read */ static IFile *curFile; /* stack of IFiles generated by .includes */ static Lst includes; /* include paths (lists of directories) */ Lst parseIncPath; /* dirs for "..." includes */ Lst sysIncPath; /* dirs for <...> includes */ Lst defIncPath; /* default for sysIncPath */ //////////////////////////////////////////////////////////// // parser tables /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial * type of the keyword ("Not" if the keyword isn't special as a target) while * the 'op' field is the operator to apply to the list of targets if the * keyword is used as a source ("0" if the keyword isn't special as a source) */ static const struct { const char *name; /* Name of keyword */ ParseSpecial spec; /* Type when used as a target */ int op; /* Operator when used as a source */ } parseKeywords[] = { { ".BEGIN", Begin, 0 }, { ".DEFAULT", Default, 0 }, { ".END", End, 0 }, { ".ERROR", dotError, 0 }, { ".EXEC", Attribute, OP_EXEC }, { ".IGNORE", Ignore, OP_IGNORE }, { ".INCLUDES", Includes, 0 }, { ".INTERRUPT", Interrupt, 0 }, { ".INVISIBLE", Attribute, OP_INVISIBLE }, { ".JOIN", Attribute, OP_JOIN }, { ".LIBS", Libs, 0 }, { ".MADE", Attribute, OP_MADE }, { ".MAIN", Main, 0 }, { ".MAKE", Attribute, OP_MAKE }, { ".MAKEFLAGS", MFlags, 0 }, { ".META", Meta, OP_META }, { ".MFLAGS", MFlags, 0 }, { ".NOMETA", NoMeta, OP_NOMETA }, { ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP }, { ".NOPATH", NoPath, OP_NOPATH }, { ".NOTMAIN", Attribute, OP_NOTMAIN }, { ".NOTPARALLEL", NotParallel, 0 }, { ".NO_PARALLEL", NotParallel, 0 }, { ".NULL", Null, 0 }, { ".OBJDIR", ExObjdir, 0 }, { ".OPTIONAL", Attribute, OP_OPTIONAL }, { ".ORDER", Order, 0 }, { ".PARALLEL", Parallel, 0 }, { ".PATH", ExPath, 0 }, { ".PHONY", Phony, OP_PHONY }, #ifdef POSIX { ".POSIX", Posix, 0 }, #endif { ".PRECIOUS", Precious, OP_PRECIOUS }, { ".RECURSIVE", Attribute, OP_MAKE }, { ".SHELL", ExShell, 0 }, { ".SILENT", Silent, OP_SILENT }, { ".SINGLESHELL", SingleShell, 0 }, { ".STALE", Stale, 0 }, { ".SUFFIXES", Suffixes, 0 }, { ".USE", Attribute, OP_USE }, { ".USEBEFORE", Attribute, OP_USEBEFORE }, { ".WAIT", Wait, 0 }, }; //////////////////////////////////////////////////////////// // local functions static int ParseIsEscaped(const char *, const char *); static void ParseErrorInternal(const char *, size_t, int, const char *, ...) MAKE_ATTR_PRINTFLIKE(4,5); static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list) MAKE_ATTR_PRINTFLIKE(5, 0); static int ParseFindKeyword(const char *); static int ParseLinkSrc(void *, void *); static int ParseDoOp(void *, void *); static void ParseDoSrc(int, const char *); static int ParseFindMain(void *, void *); static int ParseAddDir(void *, void *); static int ParseClearPath(void *, void *); static void ParseDoDependency(char *); static int ParseAddCmd(void *, void *); static void ParseHasCommands(void *); static void ParseDoInclude(char *); static void ParseSetParseFile(const char *); static void ParseSetIncludedFile(void); #ifdef SYSVINCLUDE static void ParseTraditionalInclude(char *); #endif #ifdef GMAKEEXPORT static void ParseGmakeExport(char *); #endif static int ParseEOF(void); static char *ParseReadLine(void); static void ParseFinishLine(void); static void ParseMark(GNode *); //////////////////////////////////////////////////////////// // file loader struct loadedfile { const char *path; /* name, for error reports */ char *buf; /* contents buffer */ size_t len; /* length of contents */ size_t maplen; /* length of mmap area, or 0 */ Boolean used; /* XXX: have we used the data yet */ }; /* * Constructor/destructor for loadedfile */ static struct loadedfile * loadedfile_create(const char *path) { struct loadedfile *lf; lf = bmake_malloc(sizeof(*lf)); lf->path = (path == NULL ? "(stdin)" : path); lf->buf = NULL; lf->len = 0; lf->maplen = 0; lf->used = FALSE; return lf; } static void loadedfile_destroy(struct loadedfile *lf) { if (lf->buf != NULL) { if (lf->maplen > 0) { #ifdef HAVE_MMAP munmap(lf->buf, lf->maplen); #endif } else { free(lf->buf); } } free(lf); } /* * nextbuf() operation for loadedfile, as needed by the weird and twisted * logic below. Once that's cleaned up, we can get rid of lf->used... */ static char * loadedfile_nextbuf(void *x, size_t *len) { struct loadedfile *lf = x; if (lf->used) { return NULL; } lf->used = TRUE; *len = lf->len; return lf->buf; } /* * Try to get the size of a file. */ static ReturnStatus load_getsize(int fd, size_t *ret) { struct stat st; if (fstat(fd, &st) < 0) { return FAILURE; } if (!S_ISREG(st.st_mode)) { return FAILURE; } /* * st_size is an off_t, which is 64 bits signed; *ret is * size_t, which might be 32 bits unsigned or 64 bits * unsigned. Rather than being elaborate, just punt on * files that are more than 2^31 bytes. We should never * see a makefile that size in practice... * * While we're at it reject negative sizes too, just in case. */ if (st.st_size < 0 || st.st_size > 0x7fffffff) { return FAILURE; } *ret = (size_t) st.st_size; return SUCCESS; } /* * Read in a file. * * Until the path search logic can be moved under here instead of * being in the caller in another source file, we need to have the fd * passed in already open. Bleh. * * If the path is NULL use stdin and (to insure against fd leaks) * assert that the caller passed in -1. */ static struct loadedfile * loadfile(const char *path, int fd) { struct loadedfile *lf; #ifdef HAVE_MMAP long pagesize; #endif ssize_t result; size_t bufpos; lf = loadedfile_create(path); if (path == NULL) { assert(fd == -1); fd = STDIN_FILENO; } else { #if 0 /* notyet */ fd = open(path, O_RDONLY); if (fd < 0) { ... Error("%s: %s", path, strerror(errno)); exit(1); } #endif } #ifdef HAVE_MMAP if (load_getsize(fd, &lf->len) == SUCCESS) { /* found a size, try mmap */ pagesize = sysconf(_SC_PAGESIZE); if (pagesize <= 0) { pagesize = 0x1000; } /* round size up to a page */ lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize); /* * XXX hack for dealing with empty files; remove when * we're no longer limited by interfacing to the old * logic elsewhere in this file. */ if (lf->maplen == 0) { lf->maplen = pagesize; } /* * FUTURE: remove PROT_WRITE when the parser no longer * needs to scribble on the input. */ lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE, MAP_FILE|MAP_COPY, fd, 0); if (lf->buf != MAP_FAILED) { /* succeeded */ if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { char *b = malloc(lf->len + 1); b[lf->len] = '\n'; memcpy(b, lf->buf, lf->len++); munmap(lf->buf, lf->maplen); lf->maplen = 0; lf->buf = b; } goto done; } } #endif /* cannot mmap; load the traditional way */ lf->maplen = 0; lf->len = 1024; lf->buf = bmake_malloc(lf->len); bufpos = 0; while (1) { assert(bufpos <= lf->len); if (bufpos == lf->len) { lf->len *= 2; lf->buf = bmake_realloc(lf->buf, lf->len); } result = read(fd, lf->buf + bufpos, lf->len - bufpos); if (result < 0) { Error("%s: read error: %s", path, strerror(errno)); exit(1); } if (result == 0) { break; } bufpos += result; } assert(bufpos <= lf->len); lf->len = bufpos; /* truncate malloc region to actual length (maybe not useful) */ if (lf->len > 0) { lf->buf = bmake_realloc(lf->buf, lf->len); } #ifdef HAVE_MMAP done: #endif if (path != NULL) { close(fd); } return lf; } //////////////////////////////////////////////////////////// // old code /*- *---------------------------------------------------------------------- * ParseIsEscaped -- * Check if the current character is escaped on the current line * * Results: * 0 if the character is not backslash escaped, 1 otherwise * * Side Effects: * None *---------------------------------------------------------------------- */ static int ParseIsEscaped(const char *line, const char *c) { int active = 0; for (;;) { if (line == c) return active; if (*--c != '\\') return active; active = !active; } } /*- *---------------------------------------------------------------------- * ParseFindKeyword -- * Look in the table of keywords for one matching the given string. * * Input: * str String to find * * Results: * The index of the keyword, or -1 if it isn't there. * * Side Effects: * None *---------------------------------------------------------------------- */ static int ParseFindKeyword(const char *str) { int start, end, cur; int diff; start = 0; end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; do { cur = start + ((end - start) / 2); diff = strcmp(str, parseKeywords[cur].name); if (diff == 0) { return (cur); } else if (diff < 0) { end = cur - 1; } else { start = cur + 1; } } while (start <= end); return (-1); } /*- * ParseVErrorInternal -- * Error message abort function for parsing. Prints out the context * of the error (line number and file) as well as the message with * two optional arguments. * * Results: * None * * Side Effects: * "fatals" is incremented if the level is PARSE_FATAL. */ /* VARARGS */ static void ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type, const char *fmt, va_list ap) { static Boolean fatal_warning_error_printed = FALSE; (void)fprintf(f, "%s: ", progname); if (cfname != NULL) { (void)fprintf(f, "\""); if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) { char *cp; const char *dir; /* * Nothing is more annoying than not knowing * which Makefile is the culprit. */ dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp); if (dir == NULL || *dir == '\0' || (*dir == '.' && dir[1] == '\0')) dir = Var_Value(".CURDIR", VAR_GLOBAL, &cp); if (dir == NULL) dir = "."; (void)fprintf(f, "%s/%s", dir, cfname); } else (void)fprintf(f, "%s", cfname); (void)fprintf(f, "\" line %d: ", (int)clineno); } if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); if (type == PARSE_FATAL || parseWarnFatal) fatals += 1; if (parseWarnFatal && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = TRUE; } } /*- * ParseErrorInternal -- * Error function * * Results: * None * * Side Effects: * None */ /* VARARGS */ static void ParseErrorInternal(const char *cfname, size_t clineno, int type, const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void)fflush(stdout); ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); va_end(ap); if (debug_file != stderr && debug_file != stdout) { va_start(ap, fmt); ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap); va_end(ap); } } /*- * Parse_Error -- * External interface to ParseErrorInternal; uses the default filename * Line number. * * Results: * None * * Side Effects: * None */ /* VARARGS */ void Parse_Error(int type, const char *fmt, ...) { va_list ap; const char *fname; size_t lineno; if (curFile == NULL) { fname = NULL; lineno = 0; } else { fname = curFile->fname; lineno = curFile->lineno; } va_start(ap, fmt); (void)fflush(stdout); ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); if (debug_file != stderr && debug_file != stdout) { va_start(ap, fmt); ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap); va_end(ap); } } /* * ParseMessage * Parse a .info .warning or .error directive * * The input is the line minus the ".". We substitute * variables, print the message and exit(1) (for .error) or just print * a warning if the directive is malformed. */ static Boolean ParseMessage(char *line) { int mtype; switch(*line) { case 'i': mtype = 0; break; case 'w': mtype = PARSE_WARNING; break; case 'e': mtype = PARSE_FATAL; break; default: Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line); return FALSE; } while (isalpha((u_char)*line)) line++; if (!isspace((u_char)*line)) return FALSE; /* not for us */ while (isspace((u_char)*line)) line++; line = Var_Subst(NULL, line, VAR_CMD, 0); Parse_Error(mtype, "%s", line); free(line); if (mtype == PARSE_FATAL) { /* Terminate immediately. */ exit(1); } return TRUE; } /*- *--------------------------------------------------------------------- * ParseLinkSrc -- * Link the parent node to its new child. Used in a Lst_ForEach by * ParseDoDependency. If the specType isn't 'Not', the parent * isn't linked as a parent of the child. * * Input: * pgnp The parent node * cgpn The child node * * Results: * Always = 0 * * Side Effects: * New elements are added to the parents list of cgn and the * children list of cgn. the unmade field of pgn is updated * to reflect the additional child. *--------------------------------------------------------------------- */ static int ParseLinkSrc(void *pgnp, void *cgnp) { GNode *pgn = (GNode *)pgnp; GNode *cgn = (GNode *)cgnp; if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts)); (void)Lst_AtEnd(pgn->children, cgn); if (specType == Not) (void)Lst_AtEnd(cgn->parents, pgn); pgn->unmade += 1; if (DEBUG(PARSE)) { fprintf(debug_file, "# %s: added child %s - %s\n", __func__, pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } return (0); } /*- *--------------------------------------------------------------------- * ParseDoOp -- * Apply the parsed operator to the given target node. Used in a * Lst_ForEach call by ParseDoDependency once all targets have * been found and their operator parsed. If the previous and new * operators are incompatible, a major error is taken. * * Input: * gnp The node to which the operator is to be applied * opp The operator to apply * * Results: * Always 0 * * Side Effects: * The type field of the node is altered to reflect any new bits in * the op. *--------------------------------------------------------------------- */ static int ParseDoOp(void *gnp, void *opp) { GNode *gn = (GNode *)gnp; int op = *(int *)opp; /* * If the dependency mask of the operator and the node don't match and * the node has actually had an operator applied to it before, and * the operator actually has some dependency information in it, complain. */ if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && !OP_NOP(gn->type) && !OP_NOP(op)) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); return (1); } if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { /* * If the node was the object of a :: operator, we need to create a * new instance of it for the children and commands on this dependency * line. The new instance is placed on the 'cohorts' list of the * initial one (note the initial one is not on its own cohorts list) * and the new instance is linked to all parents of the initial * instance. */ GNode *cohort; /* * Propagate copied bits to the initial node. They'll be propagated * back to the rest of the cohorts later. */ gn->type |= op & ~OP_OPMASK; cohort = Targ_FindNode(gn->name, TARG_NOHASH); if (doing_depend) ParseMark(cohort); /* * Make the cohort invisible as well to avoid duplicating it into * other variables. True, parents of this target won't tend to do * anything with their local variables, but better safe than * sorry. (I think this is pointless now, since the relevant list * traversals will no longer see this node anyway. -mycroft) */ cohort->type = op | OP_INVISIBLE; (void)Lst_AtEnd(gn->cohorts, cohort); cohort->centurion = gn; gn->unmade_cohorts += 1; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", gn->unmade_cohorts); } else { /* * We don't want to nuke any previous flags (whatever they were) so we * just OR the new operator into the old */ gn->type |= op; } return (0); } /*- *--------------------------------------------------------------------- * ParseDoSrc -- * Given the name of a source, figure out if it is an attribute * and apply it to the targets if it is. Else decide if there is * some attribute which should be applied *to* the source because * of some special target and apply it if so. Otherwise, make the * source be a child of the targets in the list 'targets' * * Input: * tOp operator (if any) from special targets * src name of the source to handle * * Results: * None * * Side Effects: * Operator bits may be added to the list of targets or to the source. * The targets may have a new source added to their lists of children. *--------------------------------------------------------------------- */ static void ParseDoSrc(int tOp, const char *src) { GNode *gn = NULL; static int wait_number = 0; char wait_src[16]; if (*src == '.' && isupper ((unsigned char)src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { int op = parseKeywords[keywd].op; if (op != 0) { Lst_ForEach(targets, ParseDoOp, &op); return; } if (parseKeywords[keywd].spec == Wait) { /* * We add a .WAIT node in the dependency list. * After any dynamic dependencies (and filename globbing) * have happened, it is given a dependency on the each * previous child back to and previous .WAIT node. * The next child won't be scheduled until the .WAIT node * is built. * We give each .WAIT node a unique name (mainly for diag). */ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); gn = Targ_FindNode(wait_src, TARG_NOHASH); if (doing_depend) ParseMark(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; Lst_ForEach(targets, ParseLinkSrc, gn); return; } } } switch (specType) { case Main: /* * If we have noted the existence of a .MAIN, it means we need * to add the sources of said target to the list of things * to create. The string 'src' is likely to be free, so we * must make a new copy of it. Note that this will only be * invoked if the user didn't specify a target on the command * line. This is to allow #ifmake's to succeed, or something... */ (void)Lst_AtEnd(create, bmake_strdup(src)); /* * Add the name to the .TARGETS variable as well, so the user can * employ that, if desired. */ Var_Append(".TARGETS", src, VAR_GLOBAL); return; case Order: /* * Create proper predecessor/successor links between the previous * source and the current one. */ gn = Targ_FindNode(src, TARG_CREATE); if (doing_depend) ParseMark(gn); if (predecessor != NULL) { (void)Lst_AtEnd(predecessor->order_succ, gn); (void)Lst_AtEnd(gn->order_pred, predecessor); if (DEBUG(PARSE)) { fprintf(debug_file, "# %s: added Order dependency %s - %s\n", __func__, predecessor->name, gn->name); Targ_PrintNode(predecessor, 0); Targ_PrintNode(gn, 0); } } /* * The current source now becomes the predecessor for the next one. */ predecessor = gn; break; default: /* * If the source is not an attribute, we need to find/create * a node for it. After that we can apply any operator to it * from a special target or link it to its parents, as * appropriate. * * In the case of a source that was the object of a :: operator, * the attribute is applied to all of its instances (as kept in * the 'cohorts' list of the node) or all the cohorts are linked * to all the targets. */ /* Find/create the 'src' node and attach to all targets */ gn = Targ_FindNode(src, TARG_CREATE); if (doing_depend) ParseMark(gn); if (tOp) { gn->type |= tOp; } else { Lst_ForEach(targets, ParseLinkSrc, gn); } break; } } /*- *----------------------------------------------------------------------- * ParseFindMain -- * Find a real target in the list and set it to be the main one. * Called by ParseDoDependency when a main target hasn't been found * yet. * * Input: * gnp Node to examine * * Results: * 0 if main not found yet, 1 if it is. * * Side Effects: * mainNode is changed and Targ_SetMain is called. * *----------------------------------------------------------------------- */ static int ParseFindMain(void *gnp, void *dummy) { GNode *gn = (GNode *)gnp; if ((gn->type & OP_NOTARGET) == 0) { mainNode = gn; Targ_SetMain(gn); return (dummy ? 1 : 1); } else { return (dummy ? 0 : 0); } } /*- *----------------------------------------------------------------------- * ParseAddDir -- * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_AddDir. * *----------------------------------------------------------------------- */ static int ParseAddDir(void *path, void *name) { (void)Dir_AddDir((Lst) path, (char *)name); return(0); } /*- *----------------------------------------------------------------------- * ParseClearPath -- * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_ClearPath * *----------------------------------------------------------------------- */ static int ParseClearPath(void *path, void *dummy) { Dir_ClearPath((Lst) path); return(dummy ? 0 : 0); } /*- *--------------------------------------------------------------------- * ParseDoDependency -- * Parse the dependency line in line. * * Input: * line the line to parse * * Results: * None * * Side Effects: * The nodes of the sources are linked as children to the nodes of the * targets. Some nodes may be created. * * We parse a dependency line by first extracting words from the line and * finding nodes in the list of all targets with that name. This is done * until a character is encountered which is an operator character. Currently * these are only ! and :. At this point the operator is parsed and the * pointer into the line advanced until the first source is encountered. * The parsed operator is applied to each node in the 'targets' list, * which is where the nodes found for the targets are kept, by means of * the ParseDoOp function. * The sources are read in much the same way as the targets were except * that now they are expanded using the wildcarding scheme of the C-Shell * and all instances of the resulting words in the list of all targets * are found. Each of the resulting nodes is then linked to each of the * targets as one of its children. * Certain targets are handled specially. These are the ones detailed * by the specType variable. * The storing of transformation rules is also taken care of here. * A target is recognized as a transformation rule by calling * Suff_IsTransform. If it is a transformation rule, its node is gotten * from the suffix module via Suff_AddTransform rather than the standard * Targ_FindNode in the target module. *--------------------------------------------------------------------- */ static void ParseDoDependency(char *line) { char *cp; /* our current position */ GNode *gn = NULL; /* a general purpose temporary node */ int op; /* the operator on the line */ char savec; /* a place to save a character */ Lst paths; /* List of search paths to alter when parsing * a list of .PATH targets */ int tOp; /* operator from special target */ Lst sources; /* list of archive source names after * expansion */ Lst curTargs; /* list of target names to be found and added * to the targets list */ char *lstart = line; if (DEBUG(PARSE)) fprintf(debug_file, "ParseDoDependency(%s)\n", line); tOp = 0; specType = Not; paths = NULL; curTargs = Lst_Init(FALSE); + /* + * First, grind through the targets. + */ + do { + /* + * Here LINE points to the beginning of the next word, and + * LSTART points to the actual beginning of the line. + */ + + /* Find the end of the next word. */ for (cp = line; *cp && (ParseIsEscaped(lstart, cp) || !(isspace((unsigned char)*cp) || *cp == '!' || *cp == ':' || *cp == LPAREN)); cp++) { if (*cp == '$') { /* * Must be a dynamic source (would have been expanded * otherwise), so call the Var module to parse the puppy * so we can safely advance beyond it...There should be * no errors in this, as they would have been discovered * in the initial Var_Subst and we wouldn't be here. */ int length; void *freeIt; (void)Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); if (freeIt) free(freeIt); cp += length-1; } } + /* + * If the word is followed by a left parenthesis, it's the + * name of an object file inside an archive (ar file). + */ if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) { /* * Archives must be handled specially to make sure the OP_ARCHV * flag is set in their 'type' field, for one thing, and because * things like "archive(file1.o file2.o file3.o)" are permissible. * Arch_ParseArchive will set 'line' to be the first non-blank * after the archive-spec. It creates/finds nodes for the members * and places them on the given list, returning SUCCESS if all * went well and FAILURE if there was an error in the * specification. On error, line should remain untouched. */ if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", line); goto out; } else { + /* Done with this word; on to the next. */ continue; } } - savec = *cp; if (!*cp) { /* + * We got to the end of the line while we were still + * looking at targets. + * * Ending a dependency line without an operator is a Bozo * no-no. As a heuristic, this is also often triggered by * undetected conflicts from cvs/rcs merges. */ if ((strncmp(line, "<<<<<<", 6) == 0) || (strncmp(line, "======", 6) == 0) || (strncmp(line, ">>>>>>", 6) == 0)) Parse_Error(PARSE_FATAL, "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); else Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" : "Need an operator"); goto out; } + + /* Insert a null terminator. */ + savec = *cp; *cp = '\0'; /* - * Have a word in line. See if it's a special target and set + * Got the word. See if it's a special target and if so set * specType to match it. */ if (*line == '.' && isupper ((unsigned char)line[1])) { /* * See if the target is a special target that must have it * or its sources handled specially. */ int keywd = ParseFindKeyword(line); if (keywd != -1) { if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); goto out; } specType = parseKeywords[keywd].spec; tOp = parseKeywords[keywd].op; /* * Certain special targets have special semantics: * .PATH Have to set the dirSearchPath * variable too * .MAIN Its sources are only used if * nothing has been specified to * create. * .DEFAULT Need to create a node to hang * commands on, but we don't want * it in the graph, nor do we want * it to be the Main Target, so we * create it, set OP_NOTMAIN and * add it to the list, setting * DEFAULT to the new node for * later use. We claim the node is * A transformation rule to make * life easier later, when we'll * use Make_HandleUse to actually * apply the .DEFAULT commands. * .PHONY The list of targets * .NOPATH Don't search for file in the path * .STALE * .BEGIN * .END * .ERROR * .INTERRUPT Are not to be considered the * main target. * .NOTPARALLEL Make only one target at a time. * .SINGLESHELL Create a shell for each command. * .ORDER Must set initial predecessor to NULL */ switch (specType) { case ExPath: if (paths == NULL) { paths = Lst_Init(FALSE); } (void)Lst_AtEnd(paths, dirSearchPath); break; case Main: if (!Lst_IsEmpty(create)) { specType = Not; } break; case Begin: case End: case Stale: case dotError: case Interrupt: gn = Targ_FindNode(line, TARG_CREATE); if (doing_depend) ParseMark(gn); gn->type |= OP_NOTMAIN|OP_SPECIAL; (void)Lst_AtEnd(targets, gn); break; case Default: gn = Targ_NewGN(".DEFAULT"); gn->type |= (OP_NOTMAIN|OP_TRANSFORM); (void)Lst_AtEnd(targets, gn); DEFAULT = gn; break; case NotParallel: maxJobs = 1; break; case SingleShell: compatMake = TRUE; break; case Order: predecessor = NULL; break; default: break; } } else if (strncmp(line, ".PATH", 5) == 0) { /* * .PATH has to be handled specially. * Call on the suffix module to give us a path to * modify. */ Lst path; specType = ExPath; path = Suff_GetPath(&line[5]); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", &line[5]); goto out; } else { if (paths == NULL) { paths = Lst_Init(FALSE); } (void)Lst_AtEnd(paths, path); } } } /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ if ((specType == Not) && (*line != '\0')) { if (Dir_HasWildcards(line)) { /* * Targets are to be sought only in the current directory, * so create an empty path for the thing. Note we need to * use Dir_Destroy in the destruction of the path as the * Dir module could have added a directory to the path... */ Lst emptyPath = Lst_Init(FALSE); Dir_Expand(line, emptyPath, curTargs); Lst_Destroy(emptyPath, Dir_Destroy); } else { /* * No wildcards, but we want to avoid code duplication, * so create a list with the word on it. */ (void)Lst_AtEnd(curTargs, line); } + /* Apply the targets. */ + while(!Lst_IsEmpty(curTargs)) { char *targName = (char *)Lst_DeQueue(curTargs); if (!Suff_IsTransform (targName)) { gn = Targ_FindNode(targName, TARG_CREATE); } else { gn = Suff_AddTransform(targName); } if (doing_depend) ParseMark(gn); (void)Lst_AtEnd(targets, gn); } } else if (specType == ExPath && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } + /* Don't need the inserted null terminator any more. */ *cp = savec; + /* * If it is a special type and not .PATH, it's the only target we * allow on this line... */ if (specType != Not && specType != ExPath) { Boolean warning = FALSE; while (*cp && (ParseIsEscaped(lstart, cp) || ((*cp != '!') && (*cp != ':')))) { if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) { warning = TRUE; } cp++; } if (warning) { Parse_Error(PARSE_WARNING, "Extra target ignored"); } } else { while (*cp && isspace ((unsigned char)*cp)) { cp++; } } line = cp; } while (*line && (ParseIsEscaped(lstart, line) || ((*line != '!') && (*line != ':')))); /* * Don't need the list of target names anymore... */ Lst_Destroy(curTargs, NULL); curTargs = NULL; if (!Lst_IsEmpty(targets)) { switch(specType) { default: Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); break; case Default: case Stale: case Begin: case End: case dotError: case Interrupt: /* * These four create nodes on which to hang commands, so * targets shouldn't be empty... */ case Not: /* * Nothing special here -- targets can be empty if it wants. */ break; } } /* * Have now parsed all the target names. Must parse the operator next. The * result is left in op . */ if (*cp == '!') { op = OP_FORCE; } else if (*cp == ':') { if (cp[1] == ':') { op = OP_DOUBLEDEP; cp++; } else { op = OP_DEPENDS; } } else { Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" : "Missing dependency operator"); goto out; } - cp++; /* Advance beyond operator */ + /* Advance beyond the operator */ + cp++; + /* + * Apply the operator to the target. This is how we remember which + * operator a target was defined with. It fails if the operator + * used isn't consistent across all references. + */ Lst_ForEach(targets, ParseDoOp, &op); /* - * Get to the first source + * Onward to the sources. + * + * LINE will now point to the first source word, if any, or the + * end of the string if not. */ while (*cp && isspace ((unsigned char)*cp)) { cp++; } line = cp; /* * Several special targets take different actions if present with no * sources: * a .SUFFIXES line with no sources clears out all old suffixes * a .PRECIOUS line makes all targets precious * a .IGNORE line ignores errors for all targets * a .SILENT line creates silence when making all targets * a .PATH removes all directories from the search path(s). */ if (!*line) { switch (specType) { case Suffixes: Suff_ClearSuffixes(); break; case Precious: allPrecious = TRUE; break; case Ignore: ignoreErrors = TRUE; break; case Silent: beSilent = TRUE; break; case ExPath: Lst_ForEach(paths, ParseClearPath, NULL); Dir_SetPATH(); break; #ifdef POSIX case Posix: Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0); break; #endif default: break; } } else if (specType == MFlags) { /* * Call on functions in main.c to deal with these arguments and * set the initial character to a null-character so the loop to * get sources won't get anything */ Main_ParseArgLine(line); *line = '\0'; } else if (specType == ExShell) { if (Job_ParseShell(line) != SUCCESS) { Parse_Error(PARSE_FATAL, "improper shell specification"); goto out; } *line = '\0'; } else if ((specType == NotParallel) || (specType == SingleShell)) { *line = '\0'; } /* * NOW GO FOR THE SOURCES */ if ((specType == Suffixes) || (specType == ExPath) || (specType == Includes) || (specType == Libs) || (specType == Null) || (specType == ExObjdir)) { while (*line) { /* * If the target was one that doesn't take files as its sources * but takes something like suffixes, we take each * space-separated word on the line as a something and deal * with it accordingly. * * If the target was .SUFFIXES, we take each source as a * suffix and add it to the list of suffixes maintained by the * Suff module. * * If the target was a .PATH, we add the source as a directory * to search on the search path. * * If it was .INCLUDES, the source is taken to be the suffix of * files which will be #included and whose search path should * be present in the .INCLUDES variable. * * If it was .LIBS, the source is taken to be the suffix of * files which are considered libraries and whose search path * should be present in the .LIBS variable. * * If it was .NULL, the source is the suffix to use when a file * has no valid suffix. * * If it was .OBJDIR, the source is a new definition for .OBJDIR, * and will cause make to do a new chdir to that path. */ while (*cp && !isspace ((unsigned char)*cp)) { cp++; } savec = *cp; *cp = '\0'; switch (specType) { case Suffixes: Suff_AddSuffix(line, &mainNode); break; case ExPath: Lst_ForEach(paths, ParseAddDir, line); break; case Includes: Suff_AddInclude(line); break; case Libs: Suff_AddLib(line); break; case Null: Suff_SetNull(line); break; case ExObjdir: Main_SetObjdir(line); break; default: break; } *cp = savec; if (savec != '\0') { cp++; } while (*cp && isspace ((unsigned char)*cp)) { cp++; } line = cp; } if (paths) { Lst_Destroy(paths, NULL); } if (specType == ExPath) Dir_SetPATH(); } else { while (*line) { /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) * and handle them accordingly. */ for (; *cp && !isspace ((unsigned char)*cp); cp++) { if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) { /* * Only stop for a left parenthesis if it isn't at the * start of a word (that'll be for variable changes * later) and isn't preceded by a dollar sign (a dynamic * source). */ break; } } if (*cp == LPAREN) { sources = Lst_Init(FALSE); if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", line); goto out; } while (!Lst_IsEmpty (sources)) { gn = (GNode *)Lst_DeQueue(sources); ParseDoSrc(tOp, gn->name); } Lst_Destroy(sources, NULL); cp = line; } else { if (*cp) { *cp = '\0'; cp += 1; } ParseDoSrc(tOp, line); } while (*cp && isspace ((unsigned char)*cp)) { cp++; } line = cp; } } if (mainNode == NULL) { /* * If we have yet to decide on a main target to make, in the * absence of any user input, we want the first target on * the first dependency line that is actually a real target * (i.e. isn't a .USE or .EXEC rule) to be made. */ Lst_ForEach(targets, ParseFindMain, NULL); } out: if (curTargs) Lst_Destroy(curTargs, NULL); } /*- *--------------------------------------------------------------------- * Parse_IsVar -- * Return TRUE if the passed line is a variable assignment. A variable * assignment consists of a single word followed by optional whitespace * followed by either a += or an = operator. * This function is used both by the Parse_File function and main when * parsing the command-line arguments. * * Input: * line the line to check * * Results: * TRUE if it is. FALSE if it ain't * * Side Effects: * none *--------------------------------------------------------------------- */ Boolean Parse_IsVar(char *line) { Boolean wasSpace = FALSE; /* set TRUE if found a space */ char ch; int level = 0; #define ISEQOPERATOR(c) \ (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) /* * Skip to variable name */ for (;(*line == ' ') || (*line == '\t'); line++) continue; /* Scan for one of the assignment operators outside a variable expansion */ while ((ch = *line++) != 0) { if (ch == '(' || ch == '{') { level++; continue; } if (ch == ')' || ch == '}') { level--; continue; } if (level != 0) continue; while (ch == ' ' || ch == '\t') { ch = *line++; wasSpace = TRUE; } #ifdef SUNSHCMD if (ch == ':' && strncmp(line, "sh", 2) == 0) { line += 2; continue; } #endif if (ch == '=') return TRUE; if (*line == '=' && ISEQOPERATOR(ch)) return TRUE; if (wasSpace) return FALSE; } return FALSE; } /*- *--------------------------------------------------------------------- * Parse_DoVar -- * Take the variable assignment in the passed line and do it in the * global context. * * Note: There is a lexical ambiguity with assignment modifier characters * in variable names. This routine interprets the character before the = * as a modifier. Therefore, an assignment like * C++=/usr/bin/CC * is interpreted as "C+ +=" instead of "C++ =". * * Input: * line a line guaranteed to be a variable assignment. * This reduces error checks * ctxt Context in which to do the assignment * * Results: * none * * Side Effects: * the variable structure of the given variable name is altered in the * global context. *--------------------------------------------------------------------- */ void Parse_DoVar(char *line, GNode *ctxt) { char *cp; /* pointer into line */ enum { VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL } type; /* Type of assignment */ char *opc; /* ptr to operator character to * null-terminate the variable name */ Boolean freeCp = FALSE; /* TRUE if cp needs to be freed, * i.e. if any variable expansion was * performed */ int depth; /* * Skip to variable name */ while ((*line == ' ') || (*line == '\t')) { line++; } /* * Skip to operator character, nulling out whitespace as we go * XXX Rather than counting () and {} we should look for $ and * then expand the variable. */ for (depth = 0, cp = line + 1; depth != 0 || *cp != '='; cp++) { if (*cp == '(' || *cp == '{') { depth++; continue; } if (*cp == ')' || *cp == '}') { depth--; continue; } if (depth == 0 && isspace ((unsigned char)*cp)) { *cp = '\0'; } } opc = cp-1; /* operator is the previous character */ *cp++ = '\0'; /* nuke the = */ /* * Check operator type */ switch (*opc) { case '+': type = VAR_APPEND; *opc = '\0'; break; case '?': /* * If the variable already has a value, we don't do anything. */ *opc = '\0'; if (Var_Exists(line, ctxt)) { return; } else { type = VAR_NORMAL; } break; case ':': type = VAR_SUBST; *opc = '\0'; break; case '!': type = VAR_SHELL; *opc = '\0'; break; default: #ifdef SUNSHCMD while (opc > line && *opc != ':') opc--; if (strncmp(opc, ":sh", 3) == 0) { type = VAR_SHELL; *opc = '\0'; break; } #endif type = VAR_NORMAL; break; } while (isspace ((unsigned char)*cp)) { cp++; } if (type == VAR_APPEND) { Var_Append(line, cp, ctxt); } else if (type == VAR_SUBST) { /* * Allow variables in the old value to be undefined, but leave their * invocation alone -- this is done by forcing oldVars to be false. * XXX: This can cause recursive variables, but that's not hard to do, * and this allows someone to do something like * * CFLAGS = $(.INCLUDES) * CFLAGS := -I.. $(CFLAGS) * * And not get an error. */ Boolean oldOldVars = oldVars; oldVars = FALSE; /* * make sure that we set the variable the first time to nothing * so that it gets substituted! */ if (!Var_Exists(line, ctxt)) Var_Set(line, "", ctxt, 0); cp = Var_Subst(NULL, cp, ctxt, FALSE); oldVars = oldOldVars; freeCp = TRUE; Var_Set(line, cp, ctxt, 0); } else if (type == VAR_SHELL) { char *res; const char *error; if (strchr(cp, '$') != NULL) { /* * There's a dollar sign in the command, so perform variable * expansion on the whole thing. The resulting string will need * freeing when we're done, so set freeCmd to TRUE. */ cp = Var_Subst(NULL, cp, VAR_CMD, TRUE); freeCp = TRUE; } res = Cmd_Exec(cp, &error); Var_Set(line, res, ctxt, 0); free(res); if (error) Parse_Error(PARSE_WARNING, error, cp); } else { /* * Normal assignment -- just do it. */ Var_Set(line, cp, ctxt, 0); } if (strcmp(line, MAKEOVERRIDES) == 0) Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ else if (strcmp(line, ".CURDIR") == 0) { /* * Somone is being (too?) clever... * Let's pretend they know what they are doing and * re-initialize the 'cur' Path. */ Dir_InitCur(cp); Dir_SetPATH(); } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) { Job_SetPrefix(); } else if (strcmp(line, MAKE_EXPORTED) == 0) { Var_Export(cp, 0); } if (freeCp) free(cp); } +/* + * ParseMaybeSubMake -- + * Scan the command string to see if it a possible submake node + * Input: + * cmd the command to scan + * Results: + * TRUE if the command is possibly a submake, FALSE if not. + */ +static Boolean +ParseMaybeSubMake(const char *cmd) +{ + size_t i; + static struct { + const char *name; + size_t len; + } vals[] = { +#define MKV(A) { A, sizeof(A) - 1 } + MKV("${MAKE}"), + MKV("${.MAKE}"), + MKV("$(MAKE)"), + MKV("$(.MAKE)"), + MKV("make"), + }; + for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { + char *ptr; + if ((ptr = strstr(cmd, vals[i].name)) == NULL) + continue; + if ((ptr == cmd || !isalnum((unsigned char)ptr[-1])) + && !isalnum((unsigned char)ptr[vals[i].len])) + return TRUE; + } + return FALSE; +} + /*- * ParseAddCmd -- * Lst_ForEach function to add a command line to all targets * * Input: * gnp the node to which the command is to be added * cmd the command to add * * Results: * Always 0 * * Side Effects: - * A new element is added to the commands list of the node. + * A new element is added to the commands list of the node, + * and the node can be marked as a submake node if the command is + * determined to be that. */ static int ParseAddCmd(void *gnp, void *cmd) { GNode *gn = (GNode *)gnp; /* Add to last (ie current) cohort for :: targets */ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) { (void)Lst_AtEnd(gn->commands, cmd); + if (ParseMaybeSubMake(cmd)) + gn->type |= OP_SUBMAKE; ParseMark(gn); } else { #ifdef notyet /* XXX: We cannot do this until we fix the tree */ (void)Lst_AtEnd(gn->commands, cmd); Parse_Error(PARSE_WARNING, "overriding commands for target \"%s\"; " "previous commands defined at %s: %d ignored", gn->name, gn->fname, gn->lineno); #else Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, "using previous script for \"%s\" defined here", gn->name); #endif } return(0); } /*- *----------------------------------------------------------------------- * ParseHasCommands -- * Callback procedure for Parse_File when destroying the list of * targets on the last dependency line. Marks a target as already * having commands if it does, to keep from having shell commands * on multiple dependency lines. * * Input: * gnp Node to examine * * Results: * None * * Side Effects: * OP_HAS_COMMANDS may be set for the target. * *----------------------------------------------------------------------- */ static void ParseHasCommands(void *gnp) { GNode *gn = (GNode *)gnp; if (!Lst_IsEmpty(gn->commands)) { gn->type |= OP_HAS_COMMANDS; } } /*- *----------------------------------------------------------------------- * Parse_AddIncludeDir -- * Add a directory to the path searched for included makefiles * bracketed by double-quotes. Used by functions in main.c * * Input: * dir The name of the directory to add * * Results: * None. * * Side Effects: * The directory is appended to the list. * *----------------------------------------------------------------------- */ void Parse_AddIncludeDir(char *dir) { (void)Dir_AddDir(parseIncPath, dir); } /*- *--------------------------------------------------------------------- * ParseDoInclude -- * Push to another file. * * The input is the line minus the `.'. A file spec is a string * enclosed in <> or "". The former is looked for only in sysIncPath. * The latter in . and the directories specified by -I command line * options * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, lineno, * fname and curFILE are altered for the new file *--------------------------------------------------------------------- */ static void Parse_include_file(char *file, Boolean isSystem, int silent) { struct loadedfile *lf; char *fullname; /* full pathname of file */ char *newName; char *prefEnd, *incdir; int fd; int i; /* * Now we know the file's name and its search path, we attempt to * find the durn thing. A return of NULL indicates the file don't * exist. */ fullname = file[0] == '/' ? bmake_strdup(file) : NULL; if (fullname == NULL && !isSystem) { /* * Include files contained in double-quotes are first searched for * relative to the including file's location. We don't want to * cd there, of course, so we just tack on the old file's * leading path components and call Dir_FindFile to see if * we can locate the beast. */ incdir = bmake_strdup(curFile->fname); prefEnd = strrchr(incdir, '/'); if (prefEnd != NULL) { *prefEnd = '\0'; /* Now do lexical processing of leading "../" on the filename */ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { prefEnd = strrchr(incdir + 1, '/'); if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0) break; *prefEnd = '\0'; } newName = str_concat(incdir, file + i, STR_ADDSLASH); fullname = Dir_FindFile(newName, parseIncPath); if (fullname == NULL) fullname = Dir_FindFile(newName, dirSearchPath); free(newName); } free(incdir); if (fullname == NULL) { /* * Makefile wasn't found in same directory as included makefile. * Search for it first on the -I search path, * then on the .PATH search path, if not found in a -I directory. * If we have a suffix specific path we should use that. */ char *suff; Lst suffPath = NULL; if ((suff = strrchr(file, '.'))) { suffPath = Suff_GetPath(suff); if (suffPath != NULL) { fullname = Dir_FindFile(file, suffPath); } } if (fullname == NULL) { fullname = Dir_FindFile(file, parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(file, dirSearchPath); } } } } /* Looking for a system file or file still not found */ if (fullname == NULL) { /* * Look for it on the system path */ fullname = Dir_FindFile(file, Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); } if (fullname == NULL) { if (!silent) Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } /* Actually open the file... */ fd = open(fullname, O_RDONLY); if (fd == -1) { if (!silent) Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); free(fullname); return; } /* load it */ lf = loadfile(fullname, fd); ParseSetIncludedFile(); /* Start reading from this file next */ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); curFile->lf = lf; } static void ParseDoInclude(char *line) { char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ int silent = (*line != 'i') ? 1 : 0; char *file = &line[7 + silent]; /* Skip to delimiter character so we know where to look */ while (*file == ' ' || *file == '\t') file++; if (*file != '"' && *file != '<') { Parse_Error(PARSE_FATAL, ".include filename must be delimited by '\"' or '<'"); return; } /* * Set the search path on which to find the include file based on the * characters which bracket its name. Angle-brackets imply it's * a system Makefile while double-quotes imply it's a user makefile */ if (*file == '<') { endc = '>'; } else { endc = '"'; } /* Skip to matching delimiter */ for (cp = ++file; *cp && *cp != endc; cp++) continue; if (*cp != endc) { Parse_Error(PARSE_FATAL, "Unclosed %cinclude filename. '%c' expected", '.', endc); return; } *cp = '\0'; /* * Substitute for any variables in the file name before trying to * find the thing. */ file = Var_Subst(NULL, file, VAR_CMD, FALSE); Parse_include_file(file, endc == '>', silent); free(file); } /*- *--------------------------------------------------------------------- * ParseSetIncludedFile -- * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR * * Results: * None * * Side Effects: * The .INCLUDEDFROMFILE variable is overwritten by the contents * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten * by the contents of .PARSEDIR *--------------------------------------------------------------------- */ static void ParseSetIncludedFile(void) { char *pf, *fp = NULL; char *pd, *dp = NULL; pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp); Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0); pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp); Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0); if (DEBUG(PARSE)) fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' " "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf); if (fp) free(fp); if (dp) free(dp); } /*- *--------------------------------------------------------------------- * ParseSetParseFile -- * Set the .PARSEDIR and .PARSEFILE variables to the dirname and * basename of the given filename * * Results: * None * * Side Effects: * The .PARSEDIR and .PARSEFILE variables are overwritten by the * dirname and basename of the given filename. *--------------------------------------------------------------------- */ static void ParseSetParseFile(const char *filename) { char *slash, *dirname; const char *pd, *pf; int len; slash = strrchr(filename, '/'); if (slash == NULL) { Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0); Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0); dirname= NULL; } else { len = slash - filename; dirname = bmake_malloc(len + 1); memcpy(dirname, filename, len); dirname[len] = '\0'; Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0); Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0); } if (DEBUG(PARSE)) fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n", __func__, pd, pf); free(dirname); } /* * Track the makefiles we read - so makefiles can * set dependencies on them. * Avoid adding anything more than once. */ static void ParseTrackInput(const char *name) { char *old; char *fp = NULL; size_t name_len = strlen(name); old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp); if (old) { /* does it contain name? */ for (; old != NULL; old = strchr(old, ' ')) { if (*old == ' ') old++; if (memcmp(old, name, name_len) == 0 && (old[name_len] == 0 || old[name_len] == ' ')) goto cleanup; } } Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL); cleanup: if (fp) { free(fp); } } /*- *--------------------------------------------------------------------- * Parse_setInput -- * Start Parsing from the given source * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, lineno, * fname and curFile are altered for the new file *--------------------------------------------------------------------- */ void Parse_SetInput(const char *name, int line, int fd, char *(*nextbuf)(void *, size_t *), void *arg) { char *buf; size_t len; if (name == NULL) name = curFile->fname; else ParseTrackInput(name); if (DEBUG(PARSE)) fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n", __func__, name, line, fd, nextbuf, arg); if (fd == -1 && nextbuf == NULL) /* sanity */ return; if (curFile != NULL) /* Save exiting file info */ Lst_AtFront(includes, curFile); /* Allocate and fill in new structure */ curFile = bmake_malloc(sizeof *curFile); /* * Once the previous state has been saved, we can get down to reading * the new file. We set up the name of the file to be the absolute * name of the include file so error messages refer to the right * place. */ curFile->fname = bmake_strdup(name); curFile->lineno = line; curFile->first_lineno = line; curFile->nextbuf = nextbuf; curFile->nextbuf_arg = arg; curFile->lf = NULL; assert(nextbuf != NULL); /* Get first block of input data */ buf = curFile->nextbuf(curFile->nextbuf_arg, &len); if (buf == NULL) { /* Was all a waste of time ... */ if (curFile->fname) free(curFile->fname); free(curFile); return; } curFile->P_str = buf; curFile->P_ptr = buf; curFile->P_end = buf+len; curFile->cond_depth = Cond_save_depth(); ParseSetParseFile(name); } #ifdef SYSVINCLUDE /*- *--------------------------------------------------------------------- * ParseTraditionalInclude -- * Push to another file. * * The input is the current line. The file name(s) are * following the "include". * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, lineno, * fname and curFILE are altered for the new file *--------------------------------------------------------------------- */ static void ParseTraditionalInclude(char *line) { char *cp; /* current position in file spec */ int done = 0; int silent = (line[0] != 'i') ? 1 : 0; char *file = &line[silent + 7]; char *all_files; if (DEBUG(PARSE)) { fprintf(debug_file, "%s: %s\n", __func__, file); } /* * Skip over whitespace */ while (isspace((unsigned char)*file)) file++; /* * Substitute for any variables in the file name before trying to * find the thing. */ all_files = Var_Subst(NULL, file, VAR_CMD, FALSE); if (*file == '\0') { Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); return; } for (file = all_files; !done; file = cp + 1) { /* Skip to end of line or next whitespace */ for (cp = file; *cp && !isspace((unsigned char) *cp); cp++) continue; if (*cp) *cp = '\0'; else done = 1; Parse_include_file(file, FALSE, silent); } free(all_files); } #endif #ifdef GMAKEEXPORT /*- *--------------------------------------------------------------------- * ParseGmakeExport -- * Parse export = * * And set the environment with it. * * Results: * None * * Side Effects: * None *--------------------------------------------------------------------- */ static void ParseGmakeExport(char *line) { char *variable = &line[6]; char *value; if (DEBUG(PARSE)) { fprintf(debug_file, "%s: %s\n", __func__, variable); } /* * Skip over whitespace */ while (isspace((unsigned char)*variable)) variable++; for (value = variable; *value && *value != '='; value++) continue; if (*value != '=') { Parse_Error(PARSE_FATAL, "Variable/Value missing from \"export\""); return; } *value++ = '\0'; /* terminate variable */ /* * Expand the value before putting it in the environment. */ value = Var_Subst(NULL, value, VAR_CMD, FALSE); setenv(variable, value, 1); } #endif /*- *--------------------------------------------------------------------- * ParseEOF -- * Called when EOF is reached in the current file. If we were reading * an include file, the includes stack is popped and things set up * to go back to reading the previous file at the previous location. * * Results: * CONTINUE if there's more to do. DONE if not. * * Side Effects: * The old curFILE, is closed. The includes list is shortened. * lineno, curFILE, and fname are changed if CONTINUE is returned. *--------------------------------------------------------------------- */ static int ParseEOF(void) { char *ptr; size_t len; assert(curFile->nextbuf != NULL); /* get next input buffer, if any */ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); curFile->P_ptr = ptr; curFile->P_str = ptr; curFile->P_end = ptr + len; curFile->lineno = curFile->first_lineno; if (ptr != NULL) { /* Iterate again */ return CONTINUE; } /* Ensure the makefile (or loop) didn't have mismatched conditionals */ Cond_restore_depth(curFile->cond_depth); if (curFile->lf != NULL) { loadedfile_destroy(curFile->lf); curFile->lf = NULL; } /* Dispose of curFile info */ /* Leak curFile->fname because all the gnodes have pointers to it */ free(curFile->P_str); free(curFile); curFile = Lst_DeQueue(includes); if (curFile == NULL) { /* We've run out of input */ Var_Delete(".PARSEDIR", VAR_GLOBAL); Var_Delete(".PARSEFILE", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); return DONE; } if (DEBUG(PARSE)) fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n", curFile->fname, curFile->lineno); /* Restore the PARSEDIR/PARSEFILE variables */ ParseSetParseFile(curFile->fname); return (CONTINUE); } #define PARSE_RAW 1 #define PARSE_SKIP 2 static char * ParseGetLine(int flags, int *length) { IFile *cf = curFile; char *ptr; char ch; char *line; char *line_end; char *escaped; char *comment; char *tp; /* Loop through blank lines and comment lines */ for (;;) { cf->lineno++; line = cf->P_ptr; ptr = line; line_end = line; escaped = NULL; comment = NULL; for (;;) { if (cf->P_end != NULL && ptr == cf->P_end) { /* end of buffer */ ch = 0; break; } ch = *ptr; if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { if (cf->P_end == NULL) /* End of string (aka for loop) data */ break; /* see if there is more we can parse */ while (ptr++ < cf->P_end) { if ((ch = *ptr) == '\n') { if (ptr > line && ptr[-1] == '\\') continue; Parse_Error(PARSE_WARNING, "Zero byte read from file, skipping rest of line."); break; } } if (cf->nextbuf != NULL) { /* * End of this buffer; return EOF and outer logic * will get the next one. (eww) */ break; } Parse_Error(PARSE_FATAL, "Zero byte read from file"); return NULL; } if (ch == '\\') { /* Don't treat next character as special, remember first one */ if (escaped == NULL) escaped = ptr; if (ptr[1] == '\n') cf->lineno++; ptr += 2; line_end = ptr; continue; } if (ch == '#' && comment == NULL) { /* Remember first '#' for comment stripping */ /* Unless previous char was '[', as in modifier :[#] */ if (!(ptr > line && ptr[-1] == '[')) comment = line_end; } ptr++; if (ch == '\n') break; if (!isspace((unsigned char)ch)) /* We are not interested in trailing whitespace */ line_end = ptr; } /* Save next 'to be processed' location */ cf->P_ptr = ptr; /* Check we have a non-comment, non-blank line */ if (line_end == line || comment == line) { if (ch == 0) /* At end of file */ return NULL; /* Parse another line */ continue; } /* We now have a line of data */ *line_end = 0; if (flags & PARSE_RAW) { /* Leave '\' (etc) in line buffer (eg 'for' lines) */ *length = line_end - line; return line; } if (flags & PARSE_SKIP) { /* Completely ignore non-directives */ if (line[0] != '.') continue; /* We could do more of the .else/.elif/.endif checks here */ } break; } /* Brutally ignore anything after a non-escaped '#' in non-commands */ if (comment != NULL && line[0] != '\t') { line_end = comment; *line_end = 0; } /* If we didn't see a '\\' then the in-situ data is fine */ if (escaped == NULL) { *length = line_end - line; return line; } /* Remove escapes from '\n' and '#' */ tp = ptr = escaped; escaped = line; for (; ; *tp++ = ch) { ch = *ptr++; if (ch != '\\') { if (ch == 0) break; continue; } ch = *ptr++; if (ch == 0) { /* Delete '\\' at end of buffer */ tp--; break; } if (ch == '#' && line[0] != '\t') /* Delete '\\' from before '#' on non-command lines */ continue; if (ch != '\n') { /* Leave '\\' in buffer for later */ *tp++ = '\\'; /* Make sure we don't delete an escaped ' ' from the line end */ escaped = tp + 1; continue; } /* Escaped '\n' replace following whitespace with a single ' ' */ while (ptr[0] == ' ' || ptr[0] == '\t') ptr++; ch = ' '; } /* Delete any trailing spaces - eg from empty continuations */ while (tp > escaped && isspace((unsigned char)tp[-1])) tp--; *tp = 0; *length = tp - line; return line; } /*- *--------------------------------------------------------------------- * ParseReadLine -- * Read an entire line from the input file. Called only by Parse_File. * * Results: * A line w/o its newline * * Side Effects: * Only those associated with reading a character *--------------------------------------------------------------------- */ static char * ParseReadLine(void) { char *line; /* Result */ int lineLength; /* Length of result */ int lineno; /* Saved line # */ int rval; for (;;) { line = ParseGetLine(0, &lineLength); if (line == NULL) return NULL; if (line[0] != '.') return line; /* * The line might be a conditional. Ask the conditional module * about it and act accordingly */ switch (Cond_Eval(line)) { case COND_SKIP: /* Skip to next conditional that evaluates to COND_PARSE. */ do { line = ParseGetLine(PARSE_SKIP, &lineLength); } while (line && Cond_Eval(line) != COND_PARSE); if (line == NULL) break; continue; case COND_PARSE: continue; case COND_INVALID: /* Not a conditional line */ /* Check for .for loops */ rval = For_Eval(line); if (rval == 0) /* Not a .for line */ break; if (rval < 0) /* Syntax error - error printed, ignore line */ continue; /* Start of a .for loop */ lineno = curFile->lineno; /* Accumulate loop lines until matching .endfor */ do { line = ParseGetLine(PARSE_RAW, &lineLength); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in for loop."); break; } } while (For_Accum(line)); /* Stash each iteration as a new 'input file' */ For_Run(lineno); /* Read next line from for-loop buffer */ continue; } return (line); } } /*- *----------------------------------------------------------------------- * ParseFinishLine -- * Handle the end of a dependency group. * * Results: * Nothing. * * Side Effects: * inLine set FALSE. 'targets' list destroyed. * *----------------------------------------------------------------------- */ static void ParseFinishLine(void) { if (inLine) { Lst_ForEach(targets, Suff_EndTransform, NULL); Lst_Destroy(targets, ParseHasCommands); targets = NULL; inLine = FALSE; } } /*- *--------------------------------------------------------------------- * Parse_File -- * Parse a file into its component parts, incorporating it into the * current dependency graph. This is the main function and controls * almost every other function in this module * * Input: * name the name of the file being read * fd Open file to makefile to parse * * Results: * None * * Side Effects: * closes fd. * Loads. Nodes are added to the list of all targets, nodes and links * are added to the dependency graph. etc. etc. etc. *--------------------------------------------------------------------- */ void Parse_File(const char *name, int fd) { char *cp; /* pointer into the line */ char *line; /* the line we're working on */ struct loadedfile *lf; lf = loadfile(name, fd); inLine = FALSE; fatals = 0; if (name == NULL) { name = "(stdin)"; } Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); curFile->lf = lf; do { for (; (line = ParseReadLine()) != NULL; ) { if (DEBUG(PARSE)) fprintf(debug_file, "ParseReadLine (%d): '%s'\n", curFile->lineno, line); if (*line == '.') { /* * Lines that begin with the special character may be * include or undef directives. * On the other hand they can be suffix rules (.c.o: ...) * or just dependencies for filenames that start '.'. */ for (cp = line + 1; isspace((unsigned char)*cp); cp++) { continue; } if (strncmp(cp, "include", 7) == 0 || ((cp[0] == 's' || cp[0] == '-') && strncmp(&cp[1], "include", 7) == 0)) { ParseDoInclude(cp); continue; } if (strncmp(cp, "undef", 5) == 0) { char *cp2; for (cp += 5; isspace((unsigned char) *cp); cp++) continue; for (cp2 = cp; !isspace((unsigned char) *cp2) && (*cp2 != '\0'); cp2++) continue; *cp2 = '\0'; Var_Delete(cp, VAR_GLOBAL); continue; } else if (strncmp(cp, "export", 6) == 0) { for (cp += 6; isspace((unsigned char) *cp); cp++) continue; Var_Export(cp, 1); continue; } else if (strncmp(cp, "unexport", 8) == 0) { Var_UnExport(cp); continue; } else if (strncmp(cp, "info", 4) == 0 || strncmp(cp, "error", 5) == 0 || strncmp(cp, "warning", 7) == 0) { if (ParseMessage(cp)) continue; } } if (*line == '\t') { /* * If a line starts with a tab, it can only hope to be * a creation command. */ cp = line + 1; shellCommand: for (; isspace ((unsigned char)*cp); cp++) { continue; } if (*cp) { if (!inLine) Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", cp); /* * So long as it's not a blank line and we're actually * in a dependency spec, add the command to the list of * commands of all targets in the dependency spec */ if (targets) { cp = bmake_strdup(cp); Lst_ForEach(targets, ParseAddCmd, cp); #ifdef CLEANUP Lst_AtEnd(targCmds, cp); #endif } } continue; } #ifdef SYSVINCLUDE if (((strncmp(line, "include", 7) == 0 && isspace((unsigned char) line[7])) || ((line[0] == 's' || line[0] == '-') && strncmp(&line[1], "include", 7) == 0 && isspace((unsigned char) line[8]))) && strchr(line, ':') == NULL) { /* * It's an S3/S5-style "include". */ ParseTraditionalInclude(line); continue; } #endif #ifdef GMAKEEXPORT if (strncmp(line, "export", 6) == 0 && isspace((unsigned char) line[6]) && strchr(line, ':') == NULL) { /* * It's a Gmake "export". */ ParseGmakeExport(line); continue; } #endif if (Parse_IsVar(line)) { ParseFinishLine(); Parse_DoVar(line, VAR_GLOBAL); continue; } #ifndef POSIX /* * To make life easier on novices, if the line is indented we * first make sure the line has a dependency operator in it. * If it doesn't have an operator and we're in a dependency * line's script, we assume it's actually a shell command * and add it to the current list of targets. */ cp = line; if (isspace((unsigned char) line[0])) { while ((*cp != '\0') && isspace((unsigned char) *cp)) cp++; while (*cp && (ParseIsEscaped(line, cp) || (*cp != ':') && (*cp != '!'))) { cp++; } if (*cp == '\0') { if (inLine) { Parse_Error(PARSE_WARNING, "Shell command needs a leading tab"); goto shellCommand; } } } #endif ParseFinishLine(); /* * For some reason - probably to make the parser impossible - * a ';' can be used to separate commands from dependencies. * Attempt to avoid ';' inside substitution patterns. */ { int level = 0; for (cp = line; *cp != 0; cp++) { if (*cp == '\\' && cp[1] != 0) { cp++; continue; } if (*cp == '$' && (cp[1] == '(' || cp[1] == '{')) { level++; continue; } if (level > 0) { if (*cp == ')' || *cp == '}') { level--; continue; } } else if (*cp == ';') { break; } } } if (*cp != 0) /* Terminate the dependency list at the ';' */ *cp++ = 0; else cp = NULL; /* * We now know it's a dependency line so it needs to have all * variables expanded before being parsed. Tell the variable * module to complain if some variable is undefined... */ line = Var_Subst(NULL, line, VAR_CMD, TRUE); /* * Need a non-circular list for the target nodes */ if (targets) Lst_Destroy(targets, NULL); targets = Lst_Init(FALSE); inLine = TRUE; ParseDoDependency(line); free(line); /* If there were commands after a ';', add them now */ if (cp != NULL) { goto shellCommand; } } /* * Reached EOF, but it may be just EOF of an include file... */ } while (ParseEOF() == CONTINUE); if (fatals) { (void)fflush(stdout); (void)fprintf(stderr, "%s: Fatal errors encountered -- cannot continue", progname); PrintOnError(NULL, NULL); exit(1); } } /*- *--------------------------------------------------------------------- * Parse_Init -- * initialize the parsing module * * Results: * none * * Side Effects: * the parseIncPath list is initialized... *--------------------------------------------------------------------- */ void Parse_Init(void) { mainNode = NULL; parseIncPath = Lst_Init(FALSE); sysIncPath = Lst_Init(FALSE); defIncPath = Lst_Init(FALSE); includes = Lst_Init(FALSE); #ifdef CLEANUP targCmds = Lst_Init(FALSE); #endif } void Parse_End(void) { #ifdef CLEANUP Lst_Destroy(targCmds, (FreeProc *)free); if (targets) Lst_Destroy(targets, NULL); Lst_Destroy(defIncPath, Dir_Destroy); Lst_Destroy(sysIncPath, Dir_Destroy); Lst_Destroy(parseIncPath, Dir_Destroy); Lst_Destroy(includes, NULL); /* Should be empty now */ #endif } /*- *----------------------------------------------------------------------- * Parse_MainName -- * Return a Lst of the main target to create for main()'s sake. If * no such target exists, we Punt with an obnoxious error message. * * Results: * A Lst of the single node to create. * * Side Effects: * None. * *----------------------------------------------------------------------- */ Lst Parse_MainName(void) { Lst mainList; /* result list */ mainList = Lst_Init(FALSE); if (mainNode == NULL) { Punt("no target to make."); /*NOTREACHED*/ } else if (mainNode->type & OP_DOUBLEDEP) { (void)Lst_AtEnd(mainList, mainNode); Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW); } else (void)Lst_AtEnd(mainList, mainNode); Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); return (mainList); } /*- *----------------------------------------------------------------------- * ParseMark -- * Add the filename and lineno to the GNode so that we remember * where it was first defined. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static void ParseMark(GNode *gn) { gn->fname = curFile->fname; gn->lineno = curFile->lineno; } Index: head/contrib/bmake/suff.c =================================================================== --- head/contrib/bmake/suff.c (revision 281811) +++ head/contrib/bmake/suff.c (revision 281812) @@ -1,2654 +1,2654 @@ -/* $NetBSD: suff.c,v 1.70 2013/05/18 13:13:34 sjg Exp $ */ +/* $NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: suff.c,v 1.70 2013/05/18 13:13:34 sjg Exp $"; +static char rcsid[] = "$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94"; #else -__RCSID("$NetBSD: suff.c,v 1.70 2013/05/18 13:13:34 sjg Exp $"); +__RCSID("$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $"); #endif #endif /* not lint */ #endif /*- * suff.c -- * Functions to maintain suffix lists and find implicit dependents * using suffix transformation rules * * Interface: * Suff_Init Initialize all things to do with suffixes. * * Suff_End Cleanup the module * * Suff_DoPaths This function is used to make life easier * when searching for a file according to its * suffix. It takes the global search path, * as defined using the .PATH: target, and appends * its directories to the path of each of the * defined suffixes, as specified using * .PATH: targets. In addition, all * directories given for suffixes labeled as * include files or libraries, using the .INCLUDES * or .LIBS targets, are played with using * Dir_MakeFlags to create the .INCLUDES and * .LIBS global variables. * * Suff_ClearSuffixes Clear out all the suffixes and defined * transformations. * * Suff_IsTransform Return TRUE if the passed string is the lhs * of a transformation rule. * * Suff_AddSuffix Add the passed string as another known suffix. * * Suff_GetPath Return the search path for the given suffix. * * Suff_AddInclude Mark the given suffix as denoting an include * file. * * Suff_AddLib Mark the given suffix as denoting a library. * * Suff_AddTransform Add another transformation to the suffix * graph. Returns GNode suitable for framing, I * mean, tacking commands, attributes, etc. on. * * Suff_SetNull Define the suffix to consider the suffix of * any file that doesn't have a known one. * * Suff_FindDeps Find implicit sources for and the location of * a target based on its suffix. Returns the * bottom-most node added to the graph or NULL * if the target had no implicit sources. * * Suff_FindPath Return the appropriate path to search in * order to find the node. */ #include #include "make.h" #include "hash.h" #include "dir.h" static Lst sufflist; /* Lst of suffixes */ #ifdef CLEANUP static Lst suffClean; /* Lst of suffixes to be cleaned */ #endif static Lst srclist; /* Lst of sources */ static Lst transforms; /* Lst of transformation rules */ static int sNum = 0; /* Counter for assigning suffix numbers */ /* * Structure describing an individual suffix. */ typedef struct _Suff { char *name; /* The suffix itself */ int nameLen; /* Length of the suffix */ short flags; /* Type of suffix */ #define SUFF_INCLUDE 0x01 /* One which is #include'd */ #define SUFF_LIBRARY 0x02 /* One which contains a library */ #define SUFF_NULL 0x04 /* The empty suffix */ Lst searchPath; /* The path along which files of this suffix * may be found */ int sNum; /* The suffix number */ int refCount; /* Reference count of list membership */ Lst parents; /* Suffixes we have a transformation to */ Lst children; /* Suffixes we have a transformation from */ Lst ref; /* List of lists this suffix is referenced */ } Suff; /* * for SuffSuffIsSuffix */ typedef struct { char *ename; /* The end of the name */ int len; /* Length of the name */ } SuffixCmpData; /* * Structure used in the search for implied sources. */ typedef struct _Src { char *file; /* The file to look for */ char *pref; /* Prefix from which file was formed */ Suff *suff; /* The suffix on the file */ struct _Src *parent; /* The Src for which this is a source */ GNode *node; /* The node describing the file */ int children; /* Count of existing children (so we don't free * this thing too early or never nuke it) */ #ifdef DEBUG_SRC Lst cp; /* Debug; children list */ #endif } Src; /* * A structure for passing more than one argument to the Lst-library-invoked * function... */ typedef struct { Lst l; Src *s; } LstSrc; typedef struct { GNode **gn; Suff *s; Boolean r; } GNodeSuff; static Suff *suffNull; /* The NULL suffix for this run */ static Suff *emptySuff; /* The empty suffix required for POSIX * single-suffix transformation rules */ static const char *SuffStrIsPrefix(const char *, const char *); static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *); static int SuffSuffIsSuffixP(const void *, const void *); static int SuffSuffHasNameP(const void *, const void *); static int SuffSuffIsPrefix(const void *, const void *); static int SuffGNHasNameP(const void *, const void *); static void SuffUnRef(void *, void *); static void SuffFree(void *); static void SuffInsert(Lst, Suff *); static void SuffRemove(Lst, Suff *); static Boolean SuffParseTransform(char *, Suff **, Suff **); static int SuffRebuildGraph(void *, void *); static int SuffScanTargets(void *, void *); static int SuffAddSrc(void *, void *); static int SuffRemoveSrc(Lst); static void SuffAddLevel(Lst, Src *); static Src *SuffFindThem(Lst, Lst); static Src *SuffFindCmds(Src *, Lst); static void SuffExpandChildren(LstNode, GNode *); static void SuffExpandWildcards(LstNode, GNode *); static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); static void SuffFindDeps(GNode *, Lst); static void SuffFindArchiveDeps(GNode *, Lst); static void SuffFindNormalDeps(GNode *, Lst); static int SuffPrintName(void *, void *); static int SuffPrintSuff(void *, void *); static int SuffPrintTrans(void *, void *); /*************** Lst Predicates ****************/ /*- *----------------------------------------------------------------------- * SuffStrIsPrefix -- * See if pref is a prefix of str. * * Input: * pref possible prefix * str string to check * * Results: * NULL if it ain't, pointer to character in str after prefix if so * * Side Effects: * None *----------------------------------------------------------------------- */ static const char * SuffStrIsPrefix(const char *pref, const char *str) { while (*str && *pref == *str) { pref++; str++; } return (*pref ? NULL : str); } /*- *----------------------------------------------------------------------- * SuffSuffIsSuffix -- * See if suff is a suffix of str. sd->ename should point to THE END * of the string to check. (THE END == the null byte) * * Input: * s possible suffix * sd string to examine * * Results: * NULL if it ain't, pointer to character in str before suffix if * it is. * * Side Effects: * None *----------------------------------------------------------------------- */ static char * SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd) { char *p1; /* Pointer into suffix name */ char *p2; /* Pointer into string being examined */ if (sd->len < s->nameLen) return NULL; /* this string is shorter than the suffix */ p1 = s->name + s->nameLen; p2 = sd->ename; while (p1 >= s->name && *p1 == *p2) { p1--; p2--; } return (p1 == s->name - 1 ? p2 : NULL); } /*- *----------------------------------------------------------------------- * SuffSuffIsSuffixP -- * Predicate form of SuffSuffIsSuffix. Passed as the callback function * to Lst_Find. * * Results: * 0 if the suffix is the one desired, non-zero if not. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int SuffSuffIsSuffixP(const void *s, const void *sd) { return(!SuffSuffIsSuffix(s, sd)); } /*- *----------------------------------------------------------------------- * SuffSuffHasNameP -- * Callback procedure for finding a suffix based on its name. Used by * Suff_GetPath. * * Input: * s Suffix to check * sd Desired name * * Results: * 0 if the suffix is of the given name. non-zero otherwise. * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffSuffHasNameP(const void *s, const void *sname) { return (strcmp(sname, ((const Suff *)s)->name)); } /*- *----------------------------------------------------------------------- * SuffSuffIsPrefix -- * See if the suffix described by s is a prefix of the string. Care * must be taken when using this to search for transformations and * what-not, since there could well be two suffixes, one of which * is a prefix of the other... * * Input: * s suffix to compare * str string to examine * * Results: * 0 if s is a prefix of str. non-zero otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffSuffIsPrefix(const void *s, const void *str) { return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL; } /*- *----------------------------------------------------------------------- * SuffGNHasNameP -- * See if the graph node has the desired name * * Input: * gn current node we're looking at * name name we're looking for * * Results: * 0 if it does. non-zero if it doesn't * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffGNHasNameP(const void *gn, const void *name) { return (strcmp(name, ((const GNode *)gn)->name)); } /*********** Maintenance Functions ************/ static void SuffUnRef(void *lp, void *sp) { Lst l = (Lst) lp; LstNode ln = Lst_Member(l, sp); if (ln != NULL) { Lst_Remove(l, ln); ((Suff *)sp)->refCount--; } } /*- *----------------------------------------------------------------------- * SuffFree -- * Free up all memory associated with the given suffix structure. * * Results: * none * * Side Effects: * the suffix entry is detroyed *----------------------------------------------------------------------- */ static void SuffFree(void *sp) { Suff *s = (Suff *)sp; if (s == suffNull) suffNull = NULL; if (s == emptySuff) emptySuff = NULL; #ifdef notdef /* We don't delete suffixes in order, so we cannot use this */ if (s->refCount) Punt("Internal error deleting suffix `%s' with refcount = %d", s->name, s->refCount); #endif Lst_Destroy(s->ref, NULL); Lst_Destroy(s->children, NULL); Lst_Destroy(s->parents, NULL); Lst_Destroy(s->searchPath, Dir_Destroy); free(s->name); free(s); } /*- *----------------------------------------------------------------------- * SuffRemove -- * Remove the suffix into the list * * Results: * None * * Side Effects: * The reference count for the suffix is decremented and the * suffix is possibly freed *----------------------------------------------------------------------- */ static void SuffRemove(Lst l, Suff *s) { SuffUnRef(l, s); if (s->refCount == 0) { SuffUnRef(sufflist, s); SuffFree(s); } } /*- *----------------------------------------------------------------------- * SuffInsert -- * Insert the suffix into the list keeping the list ordered by suffix * numbers. * * Input: * l the list where in s should be inserted * s the suffix to insert * * Results: * None * * Side Effects: * The reference count of the suffix is incremented *----------------------------------------------------------------------- */ static void SuffInsert(Lst l, Suff *s) { LstNode ln; /* current element in l we're examining */ Suff *s2 = NULL; /* the suffix descriptor in this element */ if (Lst_Open(l) == FAILURE) { return; } while ((ln = Lst_Next(l)) != NULL) { s2 = (Suff *)Lst_Datum(ln); if (s2->sNum >= s->sNum) { break; } } Lst_Close(l); if (DEBUG(SUFF)) { fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum); } if (ln == NULL) { if (DEBUG(SUFF)) { fprintf(debug_file, "at end of list\n"); } (void)Lst_AtEnd(l, s); s->refCount++; (void)Lst_AtEnd(s->ref, l); } else if (s2->sNum != s->sNum) { if (DEBUG(SUFF)) { fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum); } (void)Lst_InsertBefore(l, ln, s); s->refCount++; (void)Lst_AtEnd(s->ref, l); } else if (DEBUG(SUFF)) { fprintf(debug_file, "already there\n"); } } /*- *----------------------------------------------------------------------- * Suff_ClearSuffixes -- * This is gross. Nuke the list of suffixes but keep all transformation * rules around. The transformation graph is destroyed in this process, * but we leave the list of rules so when a new graph is formed the rules * will remain. * This function is called from the parse module when a * .SUFFIXES:\n line is encountered. * * Results: * none * * Side Effects: * the sufflist and its graph nodes are destroyed *----------------------------------------------------------------------- */ void Suff_ClearSuffixes(void) { #ifdef CLEANUP Lst_Concat(suffClean, sufflist, LST_CONCLINK); #endif sufflist = Lst_Init(FALSE); sNum = 0; suffNull = emptySuff; } /*- *----------------------------------------------------------------------- * SuffParseTransform -- * Parse a transformation string to find its two component suffixes. * * Input: * str String being parsed * srcPtr Place to store source of trans. * targPtr Place to store target of trans. * * Results: * TRUE if the string is a valid transformation and FALSE otherwise. * * Side Effects: * The passed pointers are overwritten. * *----------------------------------------------------------------------- */ static Boolean SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr) { LstNode srcLn; /* element in suffix list of trans source*/ Suff *src; /* Source of transformation */ LstNode targLn; /* element in suffix list of trans target*/ char *str2; /* Extra pointer (maybe target suffix) */ LstNode singleLn; /* element in suffix list of any suffix * that exactly matches str */ Suff *single = NULL;/* Source of possible transformation to * null suffix */ srcLn = NULL; singleLn = NULL; /* * Loop looking first for a suffix that matches the start of the * string and then for one that exactly matches the rest of it. If * we can find two that meet these criteria, we've successfully * parsed the string. */ for (;;) { if (srcLn == NULL) { srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix); } else { srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str, SuffSuffIsPrefix); } if (srcLn == NULL) { /* * Ran out of source suffixes -- no such rule */ if (singleLn != NULL) { /* * Not so fast Mr. Smith! There was a suffix that encompassed * the entire string, so we assume it was a transformation * to the null suffix (thank you POSIX). We still prefer to * find a double rule over a singleton, hence we leave this * check until the end. * * XXX: Use emptySuff over suffNull? */ *srcPtr = single; *targPtr = suffNull; return(TRUE); } return (FALSE); } src = (Suff *)Lst_Datum(srcLn); str2 = str + src->nameLen; if (*str2 == '\0') { single = src; singleLn = srcLn; } else { targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP); if (targLn != NULL) { *srcPtr = src; *targPtr = (Suff *)Lst_Datum(targLn); return (TRUE); } } } } /*- *----------------------------------------------------------------------- * Suff_IsTransform -- * Return TRUE if the given string is a transformation rule * * * Input: * str string to check * * Results: * TRUE if the string is a concatenation of two known suffixes. * FALSE otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Suff_IsTransform(char *str) { Suff *src, *targ; return (SuffParseTransform(str, &src, &targ)); } /*- *----------------------------------------------------------------------- * Suff_AddTransform -- * Add the transformation rule described by the line to the * list of rules and place the transformation itself in the graph * * Input: * line name of transformation to add * * Results: * The node created for the transformation in the transforms list * * Side Effects: * The node is placed on the end of the transforms Lst and links are * made between the two suffixes mentioned in the target name *----------------------------------------------------------------------- */ GNode * Suff_AddTransform(char *line) { GNode *gn; /* GNode of transformation rule */ Suff *s, /* source suffix */ *t; /* target suffix */ LstNode ln; /* Node for existing transformation */ ln = Lst_Find(transforms, line, SuffGNHasNameP); if (ln == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ gn = Targ_NewGN(line); (void)Lst_AtEnd(transforms, gn); } else { /* * New specification for transformation rule. Just nuke the old list * of commands so they can be filled in again... We don't actually * free the commands themselves, because a given command can be * attached to several different transformations. */ gn = (GNode *)Lst_Datum(ln); Lst_Destroy(gn->commands, NULL); Lst_Destroy(gn->children, NULL); gn->commands = Lst_Init(FALSE); gn->children = Lst_Init(FALSE); } gn->type = OP_TRANSFORM; (void)SuffParseTransform(line, &s, &t); /* * link the two together in the proper relationship and order */ if (DEBUG(SUFF)) { fprintf(debug_file, "defining transformation from `%s' to `%s'\n", s->name, t->name); } SuffInsert(t->children, s); SuffInsert(s->parents, t); return (gn); } /*- *----------------------------------------------------------------------- * Suff_EndTransform -- * Handle the finish of a transformation definition, removing the * transformation from the graph if it has neither commands nor * sources. This is a callback procedure for the Parse module via * Lst_ForEach * * Input: * gnp Node for transformation * dummy Node for transformation * * Results: * === 0 * * Side Effects: * If the node has no commands or children, the children and parents * lists of the affected suffixes are altered. * *----------------------------------------------------------------------- */ int Suff_EndTransform(void *gnp, void *dummy) { GNode *gn = (GNode *)gnp; if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { Suff *s, *t; /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ if (SuffParseTransform(gn->name, &s, &t)) { Lst p; if (DEBUG(SUFF)) { fprintf(debug_file, "deleting transformation from `%s' to `%s'\n", s->name, t->name); } /* * Store s->parents because s could be deleted in SuffRemove */ p = s->parents; /* * Remove the source from the target's children list. We check for a * nil return to handle a beanhead saying something like * .c.o .c.o: * * We'll be called twice when the next target is seen, but .c and .o * are only linked once... */ SuffRemove(t->children, s); /* * Remove the target from the source's parents list */ SuffRemove(p, t); } } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) { fprintf(debug_file, "transformation %s complete\n", gn->name); } return(dummy ? 0 : 0); } /*- *----------------------------------------------------------------------- * SuffRebuildGraph -- * Called from Suff_AddSuffix via Lst_ForEach to search through the * list of existing transformation rules and rebuild the transformation * graph when it has been destroyed by Suff_ClearSuffixes. If the * given rule is a transformation involving this suffix and another, * existing suffix, the proper relationship is established between * the two. * * Input: * transformp Transformation to test * sp Suffix to rebuild * * Results: * Always 0. * * Side Effects: * The appropriate links will be made between this suffix and * others if transformation rules exist for it. * *----------------------------------------------------------------------- */ static int SuffRebuildGraph(void *transformp, void *sp) { GNode *transform = (GNode *)transformp; Suff *s = (Suff *)sp; char *cp; LstNode ln; Suff *s2; SuffixCmpData sd; /* * First see if it is a transformation from this suffix. */ cp = UNCONST(SuffStrIsPrefix(s->name, transform->name)); if (cp != NULL) { ln = Lst_Find(sufflist, cp, SuffSuffHasNameP); if (ln != NULL) { /* * Found target. Link in and return, since it can't be anything * else. */ s2 = (Suff *)Lst_Datum(ln); SuffInsert(s2->children, s); SuffInsert(s->parents, s2); return(0); } } /* * Not from, maybe to? */ sd.len = strlen(transform->name); sd.ename = transform->name + sd.len; cp = SuffSuffIsSuffix(s, &sd); if (cp != NULL) { /* * Null-terminate the source suffix in order to find it. */ cp[1] = '\0'; ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP); /* * Replace the start of the target suffix */ cp[1] = s->name[0]; if (ln != NULL) { /* * Found it -- establish the proper relationship */ s2 = (Suff *)Lst_Datum(ln); SuffInsert(s->children, s2); SuffInsert(s2->parents, s); } } return(0); } /*- *----------------------------------------------------------------------- * SuffScanTargets -- * Called from Suff_AddSuffix via Lst_ForEach to search through the * list of existing targets and find if any of the existing targets * can be turned into a transformation rule. * * Results: * 1 if a new main target has been selected, 0 otherwise. * * Side Effects: * If such a target is found and the target is the current main * target, the main target is set to NULL and the next target * examined (if that exists) becomes the main target. * *----------------------------------------------------------------------- */ static int SuffScanTargets(void *targetp, void *gsp) { GNode *target = (GNode *)targetp; GNodeSuff *gs = (GNodeSuff *)gsp; Suff *s, *t; char *ptr; if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) { *gs->gn = target; Targ_SetMain(target); return 1; } if ((unsigned int)target->type == OP_TRANSFORM) return 0; if ((ptr = strstr(target->name, gs->s->name)) == NULL || ptr == target->name) return 0; if (SuffParseTransform(target->name, &s, &t)) { if (*gs->gn == target) { gs->r = TRUE; *gs->gn = NULL; Targ_SetMain(NULL); } Lst_Destroy(target->children, NULL); target->children = Lst_Init(FALSE); target->type = OP_TRANSFORM; /* * link the two together in the proper relationship and order */ if (DEBUG(SUFF)) { fprintf(debug_file, "defining transformation from `%s' to `%s'\n", s->name, t->name); } SuffInsert(t->children, s); SuffInsert(s->parents, t); } return 0; } /*- *----------------------------------------------------------------------- * Suff_AddSuffix -- * Add the suffix in string to the end of the list of known suffixes. * Should we restructure the suffix graph? Make doesn't... * * Input: * str the name of the suffix to add * * Results: * None * * Side Effects: * A GNode is created for the suffix and a Suff structure is created and * added to the suffixes list unless the suffix was already known. * The mainNode passed can be modified if a target mutated into a * transform and that target happened to be the main target. *----------------------------------------------------------------------- */ void Suff_AddSuffix(char *str, GNode **gn) { Suff *s; /* new suffix descriptor */ LstNode ln; GNodeSuff gs; ln = Lst_Find(sufflist, str, SuffSuffHasNameP); if (ln == NULL) { s = bmake_malloc(sizeof(Suff)); s->name = bmake_strdup(str); s->nameLen = strlen(s->name); s->searchPath = Lst_Init(FALSE); s->children = Lst_Init(FALSE); s->parents = Lst_Init(FALSE); s->ref = Lst_Init(FALSE); s->sNum = sNum++; s->flags = 0; s->refCount = 1; (void)Lst_AtEnd(sufflist, s); /* * We also look at our existing targets list to see if adding * this suffix will make one of our current targets mutate into * a suffix rule. This is ugly, but other makes treat all targets * that start with a . as suffix rules. */ gs.gn = gn; gs.s = s; gs.r = FALSE; Lst_ForEach(Targ_List(), SuffScanTargets, &gs); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ Lst_ForEach(transforms, SuffRebuildGraph, s); } } /*- *----------------------------------------------------------------------- * Suff_GetPath -- * Return the search path for the given suffix, if it's defined. * * Results: * The searchPath for the desired suffix or NULL if the suffix isn't * defined. * * Side Effects: * None *----------------------------------------------------------------------- */ Lst Suff_GetPath(char *sname) { LstNode ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln == NULL) { return NULL; } else { s = (Suff *)Lst_Datum(ln); return (s->searchPath); } } /*- *----------------------------------------------------------------------- * Suff_DoPaths -- * Extend the search paths for all suffixes to include the default * search path. * * Results: * None. * * Side Effects: * The searchPath field of all the suffixes is extended by the * directories in dirSearchPath. If paths were specified for the * ".h" suffix, the directories are stuffed into a global variable * called ".INCLUDES" with each directory preceded by a -I. The same * is done for the ".a" suffix, except the variable is called * ".LIBS" and the flag is -L. *----------------------------------------------------------------------- */ void Suff_DoPaths(void) { Suff *s; LstNode ln; char *ptr; Lst inIncludes; /* Cumulative .INCLUDES path */ Lst inLibs; /* Cumulative .LIBS path */ if (Lst_Open(sufflist) == FAILURE) { return; } inIncludes = Lst_Init(FALSE); inLibs = Lst_Init(FALSE); while ((ln = Lst_Next(sufflist)) != NULL) { s = (Suff *)Lst_Datum(ln); if (!Lst_IsEmpty (s->searchPath)) { #ifdef INCLUDES if (s->flags & SUFF_INCLUDE) { Dir_Concat(inIncludes, s->searchPath); } #endif /* INCLUDES */ #ifdef LIBRARIES if (s->flags & SUFF_LIBRARY) { Dir_Concat(inLibs, s->searchPath); } #endif /* LIBRARIES */ Dir_Concat(s->searchPath, dirSearchPath); } else { Lst_Destroy(s->searchPath, Dir_Destroy); s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); } } Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0); free(ptr); Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0); free(ptr); Lst_Destroy(inIncludes, Dir_Destroy); Lst_Destroy(inLibs, Dir_Destroy); Lst_Close(sufflist); } /*- *----------------------------------------------------------------------- * Suff_AddInclude -- * Add the given suffix as a type of file which gets included. * Called from the parse module when a .INCLUDES line is parsed. * The suffix must have already been defined. * * Input: * sname Name of the suffix to mark * * Results: * None. * * Side Effects: * The SUFF_INCLUDE bit is set in the suffix's flags field * *----------------------------------------------------------------------- */ void Suff_AddInclude(char *sname) { LstNode ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = (Suff *)Lst_Datum(ln); s->flags |= SUFF_INCLUDE; } } /*- *----------------------------------------------------------------------- * Suff_AddLib -- * Add the given suffix as a type of file which is a library. * Called from the parse module when parsing a .LIBS line. The * suffix must have been defined via .SUFFIXES before this is * called. * * Input: * sname Name of the suffix to mark * * Results: * None. * * Side Effects: * The SUFF_LIBRARY bit is set in the suffix's flags field * *----------------------------------------------------------------------- */ void Suff_AddLib(char *sname) { LstNode ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = (Suff *)Lst_Datum(ln); s->flags |= SUFF_LIBRARY; } } /********** Implicit Source Search Functions *********/ /*- *----------------------------------------------------------------------- * SuffAddSrc -- * Add a suffix as a Src structure to the given list with its parent * being the given Src structure. If the suffix is the null suffix, * the prefix is used unaltered as the file name in the Src structure. * * Input: * sp suffix for which to create a Src structure * lsp list and parent for the new Src * * Results: * always returns 0 * * Side Effects: * A Src structure is created and tacked onto the end of the list *----------------------------------------------------------------------- */ static int SuffAddSrc(void *sp, void *lsp) { Suff *s = (Suff *)sp; LstSrc *ls = (LstSrc *)lsp; Src *s2; /* new Src structure */ Src *targ; /* Target structure */ targ = ls->s; if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { /* * If the suffix has been marked as the NULL suffix, also create a Src * structure for a file with no suffix attached. Two birds, and all * that... */ s2 = bmake_malloc(sizeof(Src)); s2->file = bmake_strdup(targ->pref); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; (void)Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(FALSE); Lst_AtEnd(targ->cp, s2); fprintf(debug_file, "1 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, NULL); fprintf(debug_file, "\n"); #endif } s2 = bmake_malloc(sizeof(Src)); s2->file = str_concat(targ->pref, s->name, 0); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; (void)Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(FALSE); Lst_AtEnd(targ->cp, s2); fprintf(debug_file, "2 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, NULL); fprintf(debug_file, "\n"); #endif return(0); } /*- *----------------------------------------------------------------------- * SuffAddLevel -- * Add all the children of targ as Src structures to the given list * * Input: * l list to which to add the new level * targ Src structure to use as the parent * * Results: * None * * Side Effects: * Lots of structures are created and added to the list *----------------------------------------------------------------------- */ static void SuffAddLevel(Lst l, Src *targ) { LstSrc ls; ls.s = targ; ls.l = l; Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); } /*- *---------------------------------------------------------------------- * SuffRemoveSrc -- * Free all src structures in list that don't have a reference count * * Results: * Ture if an src was removed * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ static int SuffRemoveSrc(Lst l) { LstNode ln; Src *s; int t = 0; if (Lst_Open(l) == FAILURE) { return 0; } #ifdef DEBUG_SRC fprintf(debug_file, "cleaning %lx: ", (unsigned long) l); Lst_ForEach(l, PrintAddr, NULL); fprintf(debug_file, "\n"); #endif while ((ln = Lst_Next(l)) != NULL) { s = (Src *)Lst_Datum(ln); if (s->children == 0) { free(s->file); if (!s->parent) free(s->pref); else { #ifdef DEBUG_SRC LstNode ln = Lst_Member(s->parent->cp, s); if (ln != NULL) Lst_Remove(s->parent->cp, ln); #endif --s->parent->children; } #ifdef DEBUG_SRC fprintf(debug_file, "free: [l=%x] p=%x %d\n", l, s, s->children); Lst_Destroy(s->cp, NULL); #endif Lst_Remove(l, ln); free(s); t |= 1; Lst_Close(l); return TRUE; } #ifdef DEBUG_SRC else { fprintf(debug_file, "keep: [l=%x] p=%x %d: ", l, s, s->children); Lst_ForEach(s->cp, PrintAddr, NULL); fprintf(debug_file, "\n"); } #endif } Lst_Close(l); return t; } /*- *----------------------------------------------------------------------- * SuffFindThem -- * Find the first existing file/target in the list srcs * * Input: * srcs list of Src structures to search through * * Results: * The lowest structure in the chain of transformations * * Side Effects: * None *----------------------------------------------------------------------- */ static Src * SuffFindThem(Lst srcs, Lst slst) { Src *s; /* current Src */ Src *rs; /* returned Src */ char *ptr; rs = NULL; while (!Lst_IsEmpty (srcs)) { s = (Src *)Lst_DeQueue(srcs); if (DEBUG(SUFF)) { fprintf(debug_file, "\ttrying %s...", s->file); } /* * A file is considered to exist if either a node exists in the * graph for it or the file actually exists. */ if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) { #ifdef DEBUG_SRC fprintf(debug_file, "remove %x from %x\n", s, srcs); #endif rs = s; break; } if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { rs = s; #ifdef DEBUG_SRC fprintf(debug_file, "remove %x from %x\n", s, srcs); #endif free(ptr); break; } if (DEBUG(SUFF)) { fprintf(debug_file, "not there\n"); } SuffAddLevel(srcs, s); Lst_AtEnd(slst, s); } if (DEBUG(SUFF) && rs) { fprintf(debug_file, "got it\n"); } return (rs); } /*- *----------------------------------------------------------------------- * SuffFindCmds -- * See if any of the children of the target in the Src structure is * one from which the target can be transformed. If there is one, * a Src structure is put together for it and returned. * * Input: * targ Src structure to play with * * Results: * The Src structure of the "winning" child, or NULL if no such beast. * * Side Effects: * A Src structure may be allocated. * *----------------------------------------------------------------------- */ static Src * SuffFindCmds(Src *targ, Lst slst) { LstNode ln; /* General-purpose list node */ GNode *t, /* Target GNode */ *s; /* Source GNode */ int prefLen;/* The length of the defined prefix */ Suff *suff; /* Suffix on matching beastie */ Src *ret; /* Return value */ char *cp; t = targ->node; (void)Lst_Open(t->children); prefLen = strlen(targ->pref); for (;;) { ln = Lst_Next(t->children); if (ln == NULL) { Lst_Close(t->children); return NULL; } s = (GNode *)Lst_Datum(ln); if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) { /* * We haven't looked to see if .OPTIONAL files exist yet, so * don't use one as the implicit source. * This allows us to use .OPTIONAL in .depend files so make won't * complain "don't know how to make xxx.h' when a dependent file * has been moved/deleted. */ continue; } cp = strrchr(s->name, '/'); if (cp == NULL) { cp = s->name; } else { cp++; } if (strncmp(cp, targ->pref, prefLen) != 0) continue; /* * The node matches the prefix ok, see if it has a known * suffix. */ ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP); if (ln == NULL) continue; /* * It even has a known suffix, see if there's a transformation * defined between the node's suffix and the target's suffix. * * XXX: Handle multi-stage transformations here, too. */ suff = (Suff *)Lst_Datum(ln); if (Lst_Member(suff->parents, targ->suff) != NULL) break; } /* * Hot Damn! Create a new Src structure to describe * this transformation (making sure to duplicate the * source node's name so Suff_FindDeps can free it * again (ick)), and return the new structure. */ ret = bmake_malloc(sizeof(Src)); ret->file = bmake_strdup(s->name); ret->pref = targ->pref; ret->suff = suff; suff->refCount++; ret->parent = targ; ret->node = s; ret->children = 0; targ->children += 1; #ifdef DEBUG_SRC ret->cp = Lst_Init(FALSE); fprintf(debug_file, "3 add %x %x\n", targ, ret); Lst_AtEnd(targ->cp, ret); #endif Lst_AtEnd(slst, ret); if (DEBUG(SUFF)) { fprintf(debug_file, "\tusing existing source %s\n", s->name); } return (ret); } /*- *----------------------------------------------------------------------- * SuffExpandChildren -- * Expand the names of any children of a given node that contain * variable invocations or file wildcards into actual targets. * * Input: * cln Child to examine * pgn Parent node being processed * * Results: * === 0 (continue) * * Side Effects: * The expanded node is removed from the parent's list of children, * and the parent's unmade counter is decremented, but other nodes * may be added. * *----------------------------------------------------------------------- */ static void SuffExpandChildren(LstNode cln, GNode *pgn) { GNode *cgn = (GNode *)Lst_Datum(cln); GNode *gn; /* New source 8) */ char *cp; /* Expanded value */ if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ)) /* It is all too hard to process the result of .ORDER */ return; if (cgn->type & OP_WAIT) /* Ignore these (& OP_PHONY ?) */ return; /* * First do variable expansion -- this takes precedence over * wildcard expansion. If the result contains wildcards, they'll be gotten * to later since the resulting words are tacked on to the end of * the children list. */ if (strchr(cgn->name, '$') == NULL) { SuffExpandWildcards(cln, pgn); return; } if (DEBUG(SUFF)) { fprintf(debug_file, "Expanding \"%s\"...", cgn->name); } cp = Var_Subst(NULL, cgn->name, pgn, TRUE); if (cp != NULL) { Lst members = Lst_Init(FALSE); if (cgn->type & OP_ARCHV) { /* * Node was an archive(member) target, so we want to call * on the Arch module to find the nodes for us, expanding * variables in the parent's context. */ char *sacrifice = cp; (void)Arch_ParseArchive(&sacrifice, members, pgn); } else { /* * Break the result into a vector of strings whose nodes * we can find, then add those nodes to the members list. * Unfortunately, we can't use brk_string b/c it * doesn't understand about variable specifications with * spaces in them... */ char *start; char *initcp = cp; /* For freeing... */ for (start = cp; *start == ' ' || *start == '\t'; start++) continue; for (cp = start; *cp != '\0'; cp++) { if (*cp == ' ' || *cp == '\t') { /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *cp++ = '\0'; gn = Targ_FindNode(start, TARG_CREATE); (void)Lst_AtEnd(members, gn); while (*cp == ' ' || *cp == '\t') { cp++; } /* * Adjust cp for increment at start of loop, but * set start to first non-space. */ start = cp--; } else if (*cp == '$') { /* * Start of a variable spec -- contact variable module * to find the end so we can skip over it. */ char *junk; int len; void *freeIt; junk = Var_Parse(cp, pgn, TRUE, &len, &freeIt); if (junk != var_Error) { cp += len - 1; } if (freeIt) free(freeIt); } else if (*cp == '\\' && *cp != '\0') { /* * Escaped something -- skip over it */ cp++; } } if (cp != start) { /* * Stuff left over -- add it to the list too */ gn = Targ_FindNode(start, TARG_CREATE); (void)Lst_AtEnd(members, gn); } /* * Point cp back at the beginning again so the variable value * can be freed. */ cp = initcp; } /* * Add all elements of the members list to the parent node. */ while(!Lst_IsEmpty(members)) { gn = (GNode *)Lst_DeQueue(members); if (DEBUG(SUFF)) { fprintf(debug_file, "%s...", gn->name); } /* Add gn to the parents child list before the original child */ (void)Lst_InsertBefore(pgn->children, cln, gn); (void)Lst_AtEnd(gn->parents, pgn); pgn->unmade++; /* Expand wildcards on new node */ SuffExpandWildcards(Lst_Prev(cln), pgn); } Lst_Destroy(members, NULL); /* * Free the result */ free(cp); } if (DEBUG(SUFF)) { fprintf(debug_file, "\n"); } /* * Now the source is expanded, remove it from the list of children to * keep it from being processed. */ pgn->unmade--; Lst_Remove(pgn->children, cln); Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); } static void SuffExpandWildcards(LstNode cln, GNode *pgn) { GNode *cgn = (GNode *)Lst_Datum(cln); GNode *gn; /* New source 8) */ char *cp; /* Expanded value */ Lst explist; /* List of expansions */ if (!Dir_HasWildcards(cgn->name)) return; /* * Expand the word along the chosen path */ explist = Lst_Init(FALSE); Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); while (!Lst_IsEmpty(explist)) { /* * Fetch next expansion off the list and find its GNode */ cp = (char *)Lst_DeQueue(explist); if (DEBUG(SUFF)) { fprintf(debug_file, "%s...", cp); } gn = Targ_FindNode(cp, TARG_CREATE); /* Add gn to the parents child list before the original child */ (void)Lst_InsertBefore(pgn->children, cln, gn); (void)Lst_AtEnd(gn->parents, pgn); pgn->unmade++; } /* * Nuke what's left of the list */ Lst_Destroy(explist, NULL); if (DEBUG(SUFF)) { fprintf(debug_file, "\n"); } /* * Now the source is expanded, remove it from the list of children to * keep it from being processed. */ pgn->unmade--; Lst_Remove(pgn->children, cln); Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); } /*- *----------------------------------------------------------------------- * Suff_FindPath -- * Find a path along which to expand the node. * * If the word has a known suffix, use that path. * If it has no known suffix, use the default system search path. * * Input: * gn Node being examined * * Results: * The appropriate path to search for the GNode. * * Side Effects: * XXX: We could set the suffix here so that we don't have to scan * again. * *----------------------------------------------------------------------- */ Lst Suff_FindPath(GNode* gn) { Suff *suff = gn->suffix; if (suff == NULL) { SuffixCmpData sd; /* Search string data */ LstNode ln; sd.len = strlen(gn->name); sd.ename = gn->name + sd.len; ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP); if (DEBUG(SUFF)) { fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name); } if (ln != NULL) suff = (Suff *)Lst_Datum(ln); /* XXX: Here we can save the suffix so we don't have to do this again */ } if (suff != NULL) { if (DEBUG(SUFF)) { fprintf(debug_file, "suffix is \"%s\"...", suff->name); } return suff->searchPath; } else { /* * Use default search path */ return dirSearchPath; } } /*- *----------------------------------------------------------------------- * SuffApplyTransform -- * Apply a transformation rule, given the source and target nodes * and suffixes. * * Input: * tGn Target node * sGn Source node * t Target suffix * s Source suffix * * Results: * TRUE if successful, FALSE if not. * * Side Effects: * The source and target are linked and the commands from the * transformation are added to the target node's commands list. * All attributes but OP_DEPMASK and OP_TRANSFORM are applied * to the target. The target also inherits all the sources for * the transformation rule. * *----------------------------------------------------------------------- */ static Boolean SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) { LstNode ln, nln; /* General node */ char *tname; /* Name of transformation rule */ GNode *gn; /* Node for same */ /* * Form the proper links between the target and source. */ (void)Lst_AtEnd(tGn->children, sGn); (void)Lst_AtEnd(sGn->parents, tGn); tGn->unmade += 1; /* * Locate the transformation rule itself */ tname = str_concat(s->name, t->name, 0); ln = Lst_Find(transforms, tname, SuffGNHasNameP); free(tname); if (ln == NULL) { /* * Not really such a transformation rule (can happen when we're * called to link an OP_MEMBER and OP_ARCHV node), so return * FALSE. */ return(FALSE); } gn = (GNode *)Lst_Datum(ln); if (DEBUG(SUFF)) { fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); } /* * Record last child for expansion purposes */ ln = Lst_Last(tGn->children); /* * Pass the buck to Make_HandleUse to apply the rule */ (void)Make_HandleUse(gn, tGn); /* * Deal with wildcards and variables in any acquired sources */ for (ln = Lst_Succ(ln); ln != NULL; ln = nln) { nln = Lst_Succ(ln); SuffExpandChildren(ln, tGn); } /* * Keep track of another parent to which this beast is transformed so * the .IMPSRC variable can be set correctly for the parent. */ (void)Lst_AtEnd(sGn->iParents, tGn); return(TRUE); } /*- *----------------------------------------------------------------------- * SuffFindArchiveDeps -- * Locate dependencies for an OP_ARCHV node. * * Input: * gn Node for which to locate dependencies * * Results: * None * * Side Effects: * Same as Suff_FindDeps * *----------------------------------------------------------------------- */ static void SuffFindArchiveDeps(GNode *gn, Lst slst) { char *eoarch; /* End of archive portion */ char *eoname; /* End of member portion */ GNode *mem; /* Node for member */ static const char *copy[] = { /* Variables to be copied from the member node */ TARGET, /* Must be first */ PREFIX, /* Must be second */ }; int i; /* Index into copy and vals */ Suff *ms; /* Suffix descriptor for member */ char *name; /* Start of member's name */ /* * The node is an archive(member) pair. so we must find a * suffix for both of them. */ eoarch = strchr(gn->name, '('); eoname = strchr(eoarch, ')'); *eoname = '\0'; /* Nuke parentheses during suffix search */ *eoarch = '\0'; /* So a suffix can be found */ name = eoarch + 1; /* * To simplify things, call Suff_FindDeps recursively on the member now, * so we can simply compare the member's .PREFIX and .TARGET variables * to locate its suffix. This allows us to figure out the suffix to * use for the archive without having to do a quadratic search over the * suffix list, backtracking for each one... */ mem = Targ_FindNode(name, TARG_CREATE); SuffFindDeps(mem, slst); /* * Create the link between the two nodes right off */ (void)Lst_AtEnd(gn->children, mem); (void)Lst_AtEnd(mem->parents, gn); gn->unmade += 1; /* * Copy in the variables from the member node to this one. */ for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) { char *p1; Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0); if (p1) free(p1); } ms = mem->suffix; if (ms == NULL) { /* * Didn't know what it was -- use .NULL suffix if not in make mode */ if (DEBUG(SUFF)) { fprintf(debug_file, "using null suffix\n"); } ms = suffNull; } /* * Set the other two local variables required for this target. */ Var_Set(MEMBER, name, gn, 0); Var_Set(ARCHIVE, gn->name, gn, 0); if (ms != NULL) { /* * Member has a known suffix, so look for a transformation rule from * it to a possible suffix of the archive. Rather than searching * through the entire list, we just look at suffixes to which the * member's suffix may be transformed... */ LstNode ln; SuffixCmpData sd; /* Search string data */ /* * Use first matching suffix... */ sd.len = eoarch - gn->name; sd.ename = eoarch; ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP); if (ln != NULL) { /* * Got one -- apply it */ if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) && DEBUG(SUFF)) { fprintf(debug_file, "\tNo transformation from %s -> %s\n", ms->name, ((Suff *)Lst_Datum(ln))->name); } } } /* * Replace the opening and closing parens now we've no need of the separate * pieces. */ *eoarch = '('; *eoname = ')'; /* * Pretend gn appeared to the left of a dependency operator so * the user needn't provide a transformation from the member to the * archive. */ if (OP_NOP(gn->type)) { gn->type |= OP_DEPENDS; } /* * Flag the member as such so we remember to look in the archive for * its modification time. */ mem->type |= OP_MEMBER; } /*- *----------------------------------------------------------------------- * SuffFindNormalDeps -- * Locate implicit dependencies for regular targets. * * Input: * gn Node for which to find sources * * Results: * None. * * Side Effects: * Same as Suff_FindDeps... * *----------------------------------------------------------------------- */ static void SuffFindNormalDeps(GNode *gn, Lst slst) { char *eoname; /* End of name */ char *sopref; /* Start of prefix */ LstNode ln, nln; /* Next suffix node to check */ Lst srcs; /* List of sources at which to look */ Lst targs; /* List of targets to which things can be * transformed. They all have the same file, * but different suff and pref fields */ Src *bottom; /* Start of found transformation path */ Src *src; /* General Src pointer */ char *pref; /* Prefix to use */ Src *targ; /* General Src target pointer */ SuffixCmpData sd; /* Search string data */ sd.len = strlen(gn->name); sd.ename = eoname = gn->name + sd.len; sopref = gn->name; /* * Begin at the beginning... */ ln = Lst_First(sufflist); srcs = Lst_Init(FALSE); targs = Lst_Init(FALSE); /* * We're caught in a catch-22 here. On the one hand, we want to use any * transformation implied by the target's sources, but we can't examine * the sources until we've expanded any variables/wildcards they may hold, * and we can't do that until we've set up the target's local variables * and we can't do that until we know what the proper suffix for the * target is (in case there are two suffixes one of which is a suffix of * the other) and we can't know that until we've found its implied * source, which we may not want to use if there's an existing source * that implies a different transformation. * * In an attempt to get around this, which may not work all the time, * but should work most of the time, we look for implied sources first, * checking transformations to all possible suffixes of the target, * use what we find to set the target's local variables, expand the * children, then look for any overriding transformations they imply. * Should we find one, we discard the one we found before. */ bottom = NULL; targ = NULL; if (!(gn->type & OP_PHONY)) { while (ln != NULL) { /* * Look for next possible suffix... */ ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP); if (ln != NULL) { int prefLen; /* Length of the prefix */ /* * Allocate a Src structure to which things can be transformed */ targ = bmake_malloc(sizeof(Src)); targ->file = bmake_strdup(gn->name); targ->suff = (Suff *)Lst_Datum(ln); targ->suff->refCount++; targ->node = gn; targ->parent = NULL; targ->children = 0; #ifdef DEBUG_SRC targ->cp = Lst_Init(FALSE); #endif /* * Allocate room for the prefix, whose end is found by * subtracting the length of the suffix from * the end of the name. */ prefLen = (eoname - targ->suff->nameLen) - sopref; targ->pref = bmake_malloc(prefLen + 1); memcpy(targ->pref, sopref, prefLen); targ->pref[prefLen] = '\0'; /* * Add nodes from which the target can be made */ SuffAddLevel(srcs, targ); /* * Record the target so we can nuke it */ (void)Lst_AtEnd(targs, targ); /* * Search from this suffix's successor... */ ln = Lst_Succ(ln); } } /* * Handle target of unknown suffix... */ if (Lst_IsEmpty(targs) && suffNull != NULL) { if (DEBUG(SUFF)) { fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); } targ = bmake_malloc(sizeof(Src)); targ->file = bmake_strdup(gn->name); targ->suff = suffNull; targ->suff->refCount++; targ->node = gn; targ->parent = NULL; targ->children = 0; targ->pref = bmake_strdup(sopref); #ifdef DEBUG_SRC targ->cp = Lst_Init(FALSE); #endif /* * Only use the default suffix rules if we don't have commands * defined for this gnode; traditional make programs used to * not define suffix rules if the gnode had children but we * don't do this anymore. */ if (Lst_IsEmpty(gn->commands)) SuffAddLevel(srcs, targ); else { if (DEBUG(SUFF)) fprintf(debug_file, "not "); } if (DEBUG(SUFF)) fprintf(debug_file, "adding suffix rules\n"); (void)Lst_AtEnd(targs, targ); } /* * Using the list of possible sources built up from the target * suffix(es), try and find an existing file/target that matches. */ bottom = SuffFindThem(srcs, slst); if (bottom == NULL) { /* * No known transformations -- use the first suffix found * for setting the local variables. */ if (!Lst_IsEmpty(targs)) { targ = (Src *)Lst_Datum(Lst_First(targs)); } else { targ = NULL; } } else { /* * Work up the transformation path to find the suffix of the * target to which the transformation was made. */ for (targ = bottom; targ->parent != NULL; targ = targ->parent) continue; } } Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); pref = (targ != NULL) ? targ->pref : gn->name; Var_Set(PREFIX, pref, gn, 0); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { nln = Lst_Succ(ln); SuffExpandChildren(ln, gn); } if (targ == NULL) { if (DEBUG(SUFF)) { fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name); } sfnd_abort: /* * Deal with finding the thing on the default search path. We * always do that, not only if the node is only a source (not * on the lhs of a dependency operator or [XXX] it has neither * children or commands) as the old pmake did. */ if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) { free(gn->path); gn->path = Dir_FindFile(gn->name, (targ == NULL ? dirSearchPath : targ->suff->searchPath)); if (gn->path != NULL) { char *ptr; Var_Set(TARGET, gn->path, gn, 0); if (targ != NULL) { /* * Suffix known for the thing -- trim the suffix off * the path to form the proper .PREFIX variable. */ int savep = strlen(gn->path) - targ->suff->nameLen; char savec; if (gn->suffix) gn->suffix->refCount--; gn->suffix = targ->suff; gn->suffix->refCount++; savec = gn->path[savep]; gn->path[savep] = '\0'; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, gn, 0); gn->path[savep] = savec; } else { /* * The .PREFIX gets the full path if the target has * no known suffix. */ if (gn->suffix) gn->suffix->refCount--; gn->suffix = NULL; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, gn, 0); } } } goto sfnd_return; } /* * If the suffix indicates that the target is a library, mark that in * the node's type field. */ if (targ->suff->flags & SUFF_LIBRARY) { gn->type |= OP_LIB; } /* * Check for overriding transformation rule implied by sources */ if (!Lst_IsEmpty(gn->children)) { src = SuffFindCmds(targ, slst); if (src != NULL) { /* * Free up all the Src structures in the transformation path * up to, but not including, the parent node. */ while (bottom && bottom->parent != NULL) { if (Lst_Member(slst, bottom) == NULL) { Lst_AtEnd(slst, bottom); } bottom = bottom->parent; } bottom = src; } } if (bottom == NULL) { /* * No idea from where it can come -- return now. */ goto sfnd_abort; } /* * We now have a list of Src structures headed by 'bottom' and linked via * their 'parent' pointers. What we do next is create links between * source and target nodes (which may or may not have been created) * and set the necessary local variables in each target. The * commands for each target are set from the commands of the * transformation rule used to get from the src suffix to the targ * suffix. Note that this causes the commands list of the original * node, gn, to be replaced by the commands of the final * transformation rule. Also, the unmade field of gn is incremented. * Etc. */ if (bottom->node == NULL) { bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); } for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; if (src->node->suffix) src->node->suffix->refCount--; src->node->suffix = src->suff; src->node->suffix->refCount++; if (targ->node == NULL) { targ->node = Targ_FindNode(targ->file, TARG_CREATE); } SuffApplyTransform(targ->node, src->node, targ->suff, src->suff); if (targ->node != gn) { /* * Finish off the dependency-search process for any nodes * between bottom and gn (no point in questing around the * filesystem for their implicit source when it's already * known). Note that the node can't have any sources that * need expanding, since SuffFindThem will stop on an existing * node, so all we need to do is set the standard and System V * variables. */ targ->node->type |= OP_DEPS_FOUND; Var_Set(PREFIX, targ->pref, targ->node, 0); Var_Set(TARGET, targ->node->name, targ->node, 0); } } if (gn->suffix) gn->suffix->refCount--; gn->suffix = src->suff; gn->suffix->refCount++; /* * Nuke the transformation path and the Src structures left over in the * two lists. */ sfnd_return: if (bottom) if (Lst_Member(slst, bottom) == NULL) Lst_AtEnd(slst, bottom); while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) continue; Lst_Concat(slst, srcs, LST_CONCLINK); Lst_Concat(slst, targs, LST_CONCLINK); } /*- *----------------------------------------------------------------------- * Suff_FindDeps -- * Find implicit sources for the target described by the graph node * gn * * Results: * Nothing. * * Side Effects: * Nodes are added to the graph below the passed-in node. The nodes * are marked to have their IMPSRC variable filled in. The * PREFIX variable is set for the given node and all its * implied children. * * Notes: * The path found by this target is the shortest path in the * transformation graph, which may pass through non-existent targets, * to an existing target. The search continues on all paths from the * root suffix until a file is found. I.e. if there's a path * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but * the .c and .l files don't, the search will branch out in * all directions from .o and again from all the nodes on the * next level until the .l,v node is encountered. * *----------------------------------------------------------------------- */ void Suff_FindDeps(GNode *gn) { SuffFindDeps(gn, srclist); while (SuffRemoveSrc(srclist)) continue; } /* * Input: * gn node we're dealing with * */ static void SuffFindDeps(GNode *gn, Lst slst) { if (gn->type & OP_DEPS_FOUND) { /* * If dependencies already found, no need to do it again... */ return; } else { gn->type |= OP_DEPS_FOUND; } /* * Make sure we have these set, may get revised below. */ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); Var_Set(PREFIX, gn->name, gn, 0); if (DEBUG(SUFF)) { fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name); } if (gn->type & OP_ARCHV) { SuffFindArchiveDeps(gn, slst); } else if (gn->type & OP_LIB) { /* * If the node is a library, it is the arch module's job to find it * and set the TARGET variable accordingly. We merely provide the * search path, assuming all libraries end in ".a" (if the suffix * hasn't been defined, there's nothing we can do for it, so we just * set the TARGET variable to the node's name in order to give it a * value). */ LstNode ln; Suff *s; ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP); if (gn->suffix) gn->suffix->refCount--; if (ln != NULL) { gn->suffix = s = (Suff *)Lst_Datum(ln); gn->suffix->refCount++; Arch_FindLib(gn, s->searchPath); } else { gn->suffix = NULL; Var_Set(TARGET, gn->name, gn, 0); } /* * Because a library (-lfoo) target doesn't follow the standard * filesystem conventions, we don't set the regular variables for * the thing. .PREFIX is simply made empty... */ Var_Set(PREFIX, "", gn, 0); } else { SuffFindNormalDeps(gn, slst); } } /*- *----------------------------------------------------------------------- * Suff_SetNull -- * Define which suffix is the null suffix. * * Input: * name Name of null suffix * * Results: * None. * * Side Effects: * 'suffNull' is altered. * * Notes: * Need to handle the changing of the null suffix gracefully so the * old transformation rules don't just go away. * *----------------------------------------------------------------------- */ void Suff_SetNull(char *name) { Suff *s; LstNode ln; ln = Lst_Find(sufflist, name, SuffSuffHasNameP); if (ln != NULL) { s = (Suff *)Lst_Datum(ln); if (suffNull != NULL) { suffNull->flags &= ~SUFF_NULL; } s->flags |= SUFF_NULL; /* * XXX: Here's where the transformation mangling would take place */ suffNull = s; } else { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", name); } } /*- *----------------------------------------------------------------------- * Suff_Init -- * Initialize suffixes module * * Results: * None * * Side Effects: * Many *----------------------------------------------------------------------- */ void Suff_Init(void) { sufflist = Lst_Init(FALSE); #ifdef CLEANUP suffClean = Lst_Init(FALSE); #endif srclist = Lst_Init(FALSE); transforms = Lst_Init(FALSE); sNum = 0; /* * Create null suffix for single-suffix rules (POSIX). The thing doesn't * actually go on the suffix list or everyone will think that's its * suffix. */ emptySuff = suffNull = bmake_malloc(sizeof(Suff)); suffNull->name = bmake_strdup(""); suffNull->nameLen = 0; suffNull->searchPath = Lst_Init(FALSE); Dir_Concat(suffNull->searchPath, dirSearchPath); suffNull->children = Lst_Init(FALSE); suffNull->parents = Lst_Init(FALSE); suffNull->ref = Lst_Init(FALSE); suffNull->sNum = sNum++; suffNull->flags = SUFF_NULL; suffNull->refCount = 1; } /*- *---------------------------------------------------------------------- * Suff_End -- * Cleanup the this module * * Results: * None * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ void Suff_End(void) { #ifdef CLEANUP Lst_Destroy(sufflist, SuffFree); Lst_Destroy(suffClean, SuffFree); if (suffNull) SuffFree(suffNull); Lst_Destroy(srclist, NULL); Lst_Destroy(transforms, NULL); #endif } /********************* DEBUGGING FUNCTIONS **********************/ static int SuffPrintName(void *s, void *dummy) { fprintf(debug_file, "%s ", ((Suff *)s)->name); return (dummy ? 0 : 0); } static int SuffPrintSuff(void *sp, void *dummy) { Suff *s = (Suff *)sp; int flags; int flag; fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount); flags = s->flags; if (flags) { fputs(" (", debug_file); while (flags) { flag = 1 << (ffs(flags) - 1); flags &= ~flag; switch (flag) { case SUFF_NULL: fprintf(debug_file, "NULL"); break; case SUFF_INCLUDE: fprintf(debug_file, "INCLUDE"); break; case SUFF_LIBRARY: fprintf(debug_file, "LIBRARY"); break; } fputc(flags ? '|' : ')', debug_file); } } fputc('\n', debug_file); fprintf(debug_file, "#\tTo: "); Lst_ForEach(s->parents, SuffPrintName, NULL); fputc('\n', debug_file); fprintf(debug_file, "#\tFrom: "); Lst_ForEach(s->children, SuffPrintName, NULL); fputc('\n', debug_file); fprintf(debug_file, "#\tSearch Path: "); Dir_PrintPath(s->searchPath); fputc('\n', debug_file); return (dummy ? 0 : 0); } static int SuffPrintTrans(void *tp, void *dummy) { GNode *t = (GNode *)tp; fprintf(debug_file, "%-16s: ", t->name); Targ_PrintType(t->type); fputc('\n', debug_file); Lst_ForEach(t->commands, Targ_PrintCmd, NULL); fputc('\n', debug_file); return(dummy ? 0 : 0); } void Suff_PrintAll(void) { fprintf(debug_file, "#*** Suffixes:\n"); Lst_ForEach(sufflist, SuffPrintSuff, NULL); fprintf(debug_file, "#*** Transformations:\n"); Lst_ForEach(transforms, SuffPrintTrans, NULL); } Index: head/contrib/bmake/targ.c =================================================================== --- head/contrib/bmake/targ.c (revision 281811) +++ head/contrib/bmake/targ.c (revision 281812) @@ -1,848 +1,848 @@ -/* $NetBSD: targ.c,v 1.57 2012/06/12 19:21:51 joerg Exp $ */ +/* $NetBSD: targ.c,v 1.59 2014/09/07 20:55:34 joerg 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: targ.c,v 1.57 2012/06/12 19:21:51 joerg Exp $"; +static char rcsid[] = "$NetBSD: targ.c,v 1.59 2014/09/07 20:55:34 joerg Exp $"; #else #include #ifndef lint #if 0 static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94"; #else -__RCSID("$NetBSD: targ.c,v 1.57 2012/06/12 19:21:51 joerg Exp $"); +__RCSID("$NetBSD: targ.c,v 1.59 2014/09/07 20:55:34 joerg Exp $"); #endif #endif /* not lint */ #endif /*- * targ.c -- * Functions for maintaining the Lst allTargets. Target nodes are * kept in two structures: a Lst, maintained by the list library, and a * hash table, maintained by the hash library. * * Interface: * Targ_Init Initialization procedure. * * Targ_End Cleanup the module * * Targ_List Return the list of all targets so far. * * Targ_NewGN Create a new GNode for the passed target * (string). The node is *not* placed in the * hash table, though all its fields are * initialized. * * Targ_FindNode Find the node for a given target, creating * and storing it if it doesn't exist and the * flags are right (TARG_CREATE) * * Targ_FindList Given a list of names, find nodes for all * of them. If a name doesn't exist and the * TARG_NOCREATE flag was given, an error message * is printed. Else, if a name doesn't exist, * its node is created. * * Targ_Ignore Return TRUE if errors should be ignored when * creating the given target. * * Targ_Silent Return TRUE if we should be silent when * creating the given target. * * Targ_Precious Return TRUE if the target is precious and * should not be removed if we are interrupted. * * Targ_Propagate Propagate information between related * nodes. Should be called after the * makefiles are parsed but before any * action is taken. * * Debugging: * Targ_PrintGraph Print out the entire graphm all variables * and statistics for the directory cache. Should * print something for suffixes, too, but... */ #include #include #include "make.h" #include "hash.h" #include "dir.h" static Lst allTargets; /* the list of all targets found so far */ #ifdef CLEANUP static Lst allGNs; /* List of all the GNodes */ #endif static Hash_Table targets; /* a hash table of same */ #define HTSIZE 191 /* initial size of hash table */ static int TargPrintOnlySrc(void *, void *); static int TargPrintName(void *, void *); #ifdef CLEANUP static void TargFreeGN(void *); #endif static int TargPropagateCohort(void *, void *); static int TargPropagateNode(void *, void *); /*- *----------------------------------------------------------------------- * Targ_Init -- * Initialize this module * * Results: * None * * Side Effects: * The allTargets list and the targets hash table are initialized *----------------------------------------------------------------------- */ void Targ_Init(void) { allTargets = Lst_Init(FALSE); Hash_InitTable(&targets, HTSIZE); } /*- *----------------------------------------------------------------------- * Targ_End -- * Finalize this module * * Results: * None * * Side Effects: * All lists and gnodes are cleared *----------------------------------------------------------------------- */ void Targ_End(void) { #ifdef CLEANUP Lst_Destroy(allTargets, NULL); if (allGNs) Lst_Destroy(allGNs, TargFreeGN); Hash_DeleteTable(&targets); #endif } /*- *----------------------------------------------------------------------- * Targ_List -- * Return the list of all targets * * Results: * The list of all targets. * * Side Effects: * None *----------------------------------------------------------------------- */ Lst Targ_List(void) { return allTargets; } /*- *----------------------------------------------------------------------- * Targ_NewGN -- * Create and initialize a new graph node * * Input: * name the name to stick in the new node * * Results: * An initialized graph node with the name field filled with a copy * of the passed name * * Side Effects: * The gnode is added to the list of all gnodes. *----------------------------------------------------------------------- */ GNode * Targ_NewGN(const char *name) { GNode *gn; gn = bmake_malloc(sizeof(GNode)); gn->name = bmake_strdup(name); gn->uname = NULL; gn->path = NULL; if (name[0] == '-' && name[1] == 'l') { gn->type = OP_LIB; } else { gn->type = 0; } gn->unmade = 0; gn->unmade_cohorts = 0; gn->cohort_num[0] = 0; gn->centurion = NULL; gn->made = UNMADE; gn->flags = 0; gn->checked = 0; gn->mtime = 0; gn->cmgn = NULL; gn->iParents = Lst_Init(FALSE); gn->cohorts = Lst_Init(FALSE); gn->parents = Lst_Init(FALSE); gn->children = Lst_Init(FALSE); gn->order_pred = Lst_Init(FALSE); gn->order_succ = Lst_Init(FALSE); Hash_InitTable(&gn->context, 0); gn->commands = Lst_Init(FALSE); gn->suffix = NULL; gn->lineno = 0; gn->fname = NULL; #ifdef CLEANUP if (allGNs == NULL) allGNs = Lst_Init(FALSE); Lst_AtEnd(allGNs, gn); #endif return (gn); } #ifdef CLEANUP /*- *----------------------------------------------------------------------- * TargFreeGN -- * Destroy a GNode * * Results: * None. * * Side Effects: * None. *----------------------------------------------------------------------- */ static void TargFreeGN(void *gnp) { GNode *gn = (GNode *)gnp; free(gn->name); if (gn->uname) free(gn->uname); if (gn->path) free(gn->path); /* gn->fname points to name allocated when file was opened, don't free */ Lst_Destroy(gn->iParents, NULL); Lst_Destroy(gn->cohorts, NULL); Lst_Destroy(gn->parents, NULL); Lst_Destroy(gn->children, NULL); Lst_Destroy(gn->order_succ, NULL); Lst_Destroy(gn->order_pred, NULL); Hash_DeleteTable(&gn->context); Lst_Destroy(gn->commands, NULL); free(gn); } #endif /*- *----------------------------------------------------------------------- * Targ_FindNode -- * Find a node in the list using the given name for matching * * Input: * name the name to find * flags flags governing events when target not * found * * Results: * The node in the list if it was. If it wasn't, return NULL of * flags was TARG_NOCREATE or the newly created and initialized node * if it was TARG_CREATE * * Side Effects: * Sometimes a node is created and added to the list *----------------------------------------------------------------------- */ GNode * Targ_FindNode(const char *name, int flags) { GNode *gn; /* node in that element */ Hash_Entry *he = NULL; /* New or used hash entry for node */ Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ /* an entry for the node */ if (!(flags & (TARG_CREATE | TARG_NOHASH))) { he = Hash_FindEntry(&targets, name); if (he == NULL) return NULL; return (GNode *)Hash_GetValue(he); } if (!(flags & TARG_NOHASH)) { he = Hash_CreateEntry(&targets, name, &isNew); if (!isNew) return (GNode *)Hash_GetValue(he); } gn = Targ_NewGN(name); if (!(flags & TARG_NOHASH)) Hash_SetValue(he, gn); Var_Append(".ALLTARGETS", name, VAR_GLOBAL); (void)Lst_AtEnd(allTargets, gn); if (doing_depend) gn->flags |= FROM_DEPEND; return gn; } /*- *----------------------------------------------------------------------- * Targ_FindList -- * Make a complete list of GNodes from the given list of names * * Input: * name list of names to find * flags flags used if no node is found for a given name * * Results: * A complete list of graph nodes corresponding to all instances of all * the names in names. * * Side Effects: * If flags is TARG_CREATE, nodes will be created for all names in * names which do not yet have graph nodes. If flags is TARG_NOCREATE, * an error message will be printed for each name which can't be found. * ----------------------------------------------------------------------- */ Lst Targ_FindList(Lst names, int flags) { Lst nodes; /* result list */ LstNode ln; /* name list element */ GNode *gn; /* node in tLn */ char *name; nodes = Lst_Init(FALSE); if (Lst_Open(names) == FAILURE) { return (nodes); } while ((ln = Lst_Next(names)) != NULL) { name = (char *)Lst_Datum(ln); gn = Targ_FindNode(name, flags); if (gn != NULL) { /* * Note: Lst_AtEnd must come before the Lst_Concat so the nodes * are added to the list in the order in which they were * encountered in the makefile. */ (void)Lst_AtEnd(nodes, gn); } else if (flags == TARG_NOCREATE) { Error("\"%s\" -- target unknown.", name); } } Lst_Close(names); return (nodes); } /*- *----------------------------------------------------------------------- * Targ_Ignore -- * Return true if should ignore errors when creating gn * * Input: * gn node to check for * * Results: * TRUE if should ignore errors * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Ignore(GNode *gn) { if (ignoreErrors || gn->type & OP_IGNORE) { return (TRUE); } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * Targ_Silent -- * Return true if be silent when creating gn * * Input: * gn node to check for * * Results: * TRUE if should be silent * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Silent(GNode *gn) { if (beSilent || gn->type & OP_SILENT) { return (TRUE); } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * Targ_Precious -- * See if the given target is precious * * Input: * gn the node to check * * Results: * TRUE if it is precious. FALSE otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Precious(GNode *gn) { if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) { return (TRUE); } else { return (FALSE); } } /******************* DEBUG INFO PRINTING ****************/ static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ /*- *----------------------------------------------------------------------- * Targ_SetMain -- * Set our idea of the main target we'll be creating. Used for * debugging output. * * Input: * gn The main target we'll create * * Results: * None. * * Side Effects: * "mainTarg" is set to the main target's node. *----------------------------------------------------------------------- */ void Targ_SetMain(GNode *gn) { mainTarg = gn; } static int TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED) { GNode *gn = (GNode *)gnp; fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num); return 0; } int Targ_PrintCmd(void *cmd, void *dummy) { fprintf(debug_file, "\t%s\n", (char *)cmd); return (dummy ? 0 : 0); } /*- *----------------------------------------------------------------------- * Targ_FmtTime -- * Format a modification time in some reasonable way and return it. * * Results: * The time reformatted. * * Side Effects: * The time is placed in a static area, so it is overwritten * with each call. * *----------------------------------------------------------------------- */ char * Targ_FmtTime(time_t tm) { struct tm *parts; static char buf[128]; parts = localtime(&tm); (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); return(buf); } /*- *----------------------------------------------------------------------- * Targ_PrintType -- * Print out a type field giving only those attributes the user can * set. * * Results: * * Side Effects: * *----------------------------------------------------------------------- */ void Targ_PrintType(int type) { int tbit; #define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break #define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break type &= ~OP_OPMASK; while (type) { tbit = 1 << (ffs(type) - 1); type &= ~tbit; switch(tbit) { PRINTBIT(OPTIONAL); PRINTBIT(USE); PRINTBIT(EXEC); PRINTBIT(IGNORE); PRINTBIT(PRECIOUS); PRINTBIT(SILENT); PRINTBIT(MAKE); PRINTBIT(JOIN); PRINTBIT(INVISIBLE); PRINTBIT(NOTMAIN); PRINTDBIT(LIB); /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break; PRINTDBIT(ARCHV); PRINTDBIT(MADE); PRINTDBIT(PHONY); } } } static const char * made_name(enum enum_made made) { switch (made) { case UNMADE: return "unmade"; case DEFERRED: return "deferred"; case REQUESTED: return "requested"; case BEINGMADE: return "being made"; case MADE: return "made"; case UPTODATE: return "up-to-date"; case ERROR: return "error when made"; case ABORTED: return "aborted"; default: return "unknown enum_made value"; } } /*- *----------------------------------------------------------------------- * TargPrintNode -- * print the contents of a node *----------------------------------------------------------------------- */ int Targ_PrintNode(void *gnp, void *passp) { GNode *gn = (GNode *)gnp; int pass = passp ? *(int *)passp : 0; fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n", gn->name, gn->cohort_num, gn->flags, gn->type, gn->made); if (gn->flags == 0) return 0; if (!OP_NOP(gn->type)) { fprintf(debug_file, "#\n"); if (gn == mainTarg) { fprintf(debug_file, "# *** MAIN TARGET ***\n"); } if (pass >= 2) { if (gn->unmade) { fprintf(debug_file, "# %d unmade children\n", gn->unmade); } else { fprintf(debug_file, "# No unmade children\n"); } if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { if (gn->mtime != 0) { fprintf(debug_file, "# last modified %s: %s\n", Targ_FmtTime(gn->mtime), made_name(gn->made)); } else if (gn->made != UNMADE) { fprintf(debug_file, "# non-existent (maybe): %s\n", made_name(gn->made)); } else { fprintf(debug_file, "# unmade\n"); } } if (!Lst_IsEmpty (gn->iParents)) { fprintf(debug_file, "# implicit parents: "); Lst_ForEach(gn->iParents, TargPrintName, NULL); fprintf(debug_file, "\n"); } } else { if (gn->unmade) fprintf(debug_file, "# %d unmade children\n", gn->unmade); } if (!Lst_IsEmpty (gn->parents)) { fprintf(debug_file, "# parents: "); Lst_ForEach(gn->parents, TargPrintName, NULL); fprintf(debug_file, "\n"); } if (!Lst_IsEmpty (gn->order_pred)) { fprintf(debug_file, "# order_pred: "); Lst_ForEach(gn->order_pred, TargPrintName, NULL); fprintf(debug_file, "\n"); } if (!Lst_IsEmpty (gn->order_succ)) { fprintf(debug_file, "# order_succ: "); Lst_ForEach(gn->order_succ, TargPrintName, NULL); fprintf(debug_file, "\n"); } fprintf(debug_file, "%-16s", gn->name); switch (gn->type & OP_OPMASK) { case OP_DEPENDS: fprintf(debug_file, ": "); break; case OP_FORCE: fprintf(debug_file, "! "); break; case OP_DOUBLEDEP: fprintf(debug_file, ":: "); break; } Targ_PrintType(gn->type); Lst_ForEach(gn->children, TargPrintName, NULL); fprintf(debug_file, "\n"); Lst_ForEach(gn->commands, Targ_PrintCmd, NULL); fprintf(debug_file, "\n\n"); if (gn->type & OP_DOUBLEDEP) { Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass); } } return (0); } /*- *----------------------------------------------------------------------- * TargPrintOnlySrc -- * Print only those targets that are just a source. * * Results: * 0. * * Side Effects: * The name of each file is printed preceded by #\t * *----------------------------------------------------------------------- */ static int TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED) { GNode *gn = (GNode *)gnp; if (!OP_NOP(gn->type)) return 0; fprintf(debug_file, "#\t%s [%s] ", gn->name, gn->path ? gn->path : gn->name); Targ_PrintType(gn->type); fprintf(debug_file, "\n"); return 0; } /*- *----------------------------------------------------------------------- * Targ_PrintGraph -- * print the entire graph. heh heh * * Input: * pass Which pass this is. 1 => no processing * 2 => processing done * * Results: * none * * Side Effects: * lots o' output *----------------------------------------------------------------------- */ void Targ_PrintGraph(int pass) { fprintf(debug_file, "#*** Input graph:\n"); Lst_ForEach(allTargets, Targ_PrintNode, &pass); fprintf(debug_file, "\n\n"); fprintf(debug_file, "#\n# Files that are only sources:\n"); Lst_ForEach(allTargets, TargPrintOnlySrc, NULL); fprintf(debug_file, "#*** Global Variables:\n"); Var_Dump(VAR_GLOBAL); fprintf(debug_file, "#*** Command-line Variables:\n"); Var_Dump(VAR_CMD); fprintf(debug_file, "\n"); Dir_PrintDirectories(); fprintf(debug_file, "\n"); Suff_PrintAll(); } /*- *----------------------------------------------------------------------- * TargPropagateNode -- * Propagate information from a single node to related nodes if * appropriate. * * Input: * gnp The node that we are processing. * * Results: * Always returns 0, for the benefit of Lst_ForEach(). * * Side Effects: * Information is propagated from this node to cohort or child * nodes. * * If the node was defined with "::", then TargPropagateCohort() * will be called for each cohort node. * * If the node has recursive predecessors, then * TargPropagateRecpred() will be called for each recursive * predecessor. *----------------------------------------------------------------------- */ static int TargPropagateNode(void *gnp, void *junk MAKE_ATTR_UNUSED) { GNode *gn = (GNode *)gnp; if (gn->type & OP_DOUBLEDEP) Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp); return (0); } /*- *----------------------------------------------------------------------- * TargPropagateCohort -- * Propagate some bits in the type mask from a node to * a related cohort node. * * Input: * cnp The node that we are processing. * gnp Another node that has cnp as a cohort. * * Results: * Always returns 0, for the benefit of Lst_ForEach(). * * Side Effects: * cnp's type bitmask is modified to incorporate some of the * bits from gnp's type bitmask. (XXX need a better explanation.) *----------------------------------------------------------------------- */ static int TargPropagateCohort(void *cgnp, void *pgnp) { GNode *cgn = (GNode *)cgnp; GNode *pgn = (GNode *)pgnp; cgn->type |= pgn->type & ~OP_OPMASK; return (0); } /*- *----------------------------------------------------------------------- * Targ_Propagate -- * Propagate information between related nodes. Should be called * after the makefiles are parsed but before any action is taken. * * Results: * none * * Side Effects: * Information is propagated between related nodes throughout the * graph. *----------------------------------------------------------------------- */ void Targ_Propagate(void) { Lst_ForEach(allTargets, TargPropagateNode, NULL); } Index: head/contrib/bmake/unit-tests/comment =================================================================== --- head/contrib/bmake/unit-tests/comment (revision 281811) +++ head/contrib/bmake/unit-tests/comment (nonexistent) @@ -1,31 +0,0 @@ -# This is a comment -.if ${MACHINE_ARCH} == something -FOO=bar -.endif - -#\ - Multiline comment - -BAR=# defined -FOOBAR= # defined - -# This is an escaped comment \ -that keeps going until the end of this line - -# Another escaped comment \ -that \ -goes \ -on - -# This is NOT an escaped comment due to the double backslashes \\ -all: hi foo bar - @echo comment testing done - -hi: - @echo comment testing start - -foo: - @echo this is $@ - -bar: - @echo This is how a comment looks: '# comment' Index: head/contrib/bmake/unit-tests/varcmd =================================================================== --- head/contrib/bmake/unit-tests/varcmd (revision 281811) +++ head/contrib/bmake/unit-tests/varcmd (nonexistent) @@ -1,49 +0,0 @@ -# $Id: varcmd,v 1.3 2008/05/15 04:30:47 sjg Exp $ -# -# Test behaviour of recursive make and vars set on command line. - -FU=fu -FOO?=foo -.if !empty(.TARGETS) -TAG=${.TARGETS} -.endif -TAG?=default - -all: one - -show: - @echo "${TAG} FU=${FU} FOO=${FOO} VAR=${VAR}" - -one: show - @${.MAKE} -f ${MAKEFILE} FU=bar FOO=goo two - -two: show - @${.MAKE} -f ${MAKEFILE} three - -three: show - @${.MAKE} -f ${MAKEFILE} four - - -.ifmake four -VAR=Internal -.MAKEOVERRIDES+= VAR -.endif - -four: show - @${.MAKE} -f ${MAKEFILE} five - -M = x -V.y = is y -V.x = is x -V := ${V.$M} -K := ${V} - -show-v: - @echo '${TAG} v=${V} k=${K}' - -five: show show-v - @${.MAKE} -f ${MAKEFILE} M=y six - -six: show-v - @${.MAKE} -f ${MAKEFILE} V=override show-v - Index: head/contrib/bmake/unit-tests/hash =================================================================== --- head/contrib/bmake/unit-tests/hash (revision 281811) +++ head/contrib/bmake/unit-tests/hash (nonexistent) @@ -1,18 +0,0 @@ -STR1= -STR2= a -STR3= ab -STR4= abc -STR5= abcd -STR6= abcde -STR7= abcdef -STR8= abcdefghijklmnopqrstuvwxyz - -all: - @echo ${STR1:hash} - @echo ${STR2:hash} - @echo ${STR3:hash} - @echo ${STR4:hash} - @echo ${STR5:hash} - @echo ${STR6:hash} - @echo ${STR7:hash} - @echo ${STR8:hash} Index: head/contrib/bmake/unit-tests/posix =================================================================== --- head/contrib/bmake/unit-tests/posix (revision 281811) +++ head/contrib/bmake/unit-tests/posix (nonexistent) @@ -1,24 +0,0 @@ -# $Id: posix,v 1.1.1.1 2004/05/08 16:45:39 sjg Exp $ - -all: x plus subs err - -x: - @echo "Posix says we should execute the command as if run by system(3)" - @echo "Expect 'Hello,' and 'World!'" - @echo Hello,; false; echo "World!" - -plus: - @echo a command - +@echo "a command prefixed by '+' executes even with -n" - @echo another command - -subs: - @echo make -n - @${.MAKE} -f ${MAKEFILE} -n plus - @echo make -n -j1 - @${.MAKE} -f ${MAKEFILE} -n -j1 plus - -err: - @(echo Now we expect an error...; exit 1) - @echo "Oops! you shouldn't see this!" - Index: head/contrib/bmake/unit-tests/export =================================================================== --- head/contrib/bmake/unit-tests/export (revision 281811) +++ head/contrib/bmake/unit-tests/export (nonexistent) @@ -1,22 +0,0 @@ -# $Id: export,v 1.1.1.1 2007/10/08 20:30:12 sjg Exp $ - -UT_TEST=export -UT_FOO=foo${BAR} -UT_FU=fubar -UT_ZOO=hoopie -UT_NO=all -# belive it or not, we expect this one to come out with $UT_FU unexpanded. -UT_DOLLAR= This is $$UT_FU - -.export UT_FU UT_FOO -.export UT_DOLLAR -# this one will be ignored -.export .MAKE.PID - -BAR=bar is ${UT_FU} - -.MAKE.EXPORTED+= UT_ZOO UT_TEST - -all: - @env | grep '^UT_' | sort - Index: head/contrib/bmake/unit-tests/modorder =================================================================== --- head/contrib/bmake/unit-tests/modorder (revision 281811) +++ head/contrib/bmake/unit-tests/modorder (nonexistent) @@ -1,22 +0,0 @@ -# $NetBSD: modorder,v 1.2 2007/10/05 15:27:46 sjg Exp $ - -LIST= one two three four five six seven eight nine ten -LISTX= ${LIST:Ox} -LISTSX:= ${LIST:Ox} -TEST_RESULT= && echo Ok || echo Failed - -# unit-tests have to produce the same results on each run -# so we cannot actually include :Ox output. -all: - @echo "LIST = ${LIST}" - @echo "LIST:O = ${LIST:O}" - # Note that 1 in every 10! trials two independently generated - # randomized orderings will be the same. The test framework doesn't - # support checking probabilistic output, so we accept that the test - # will incorrectly fail with probability 2.8E-7. - @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`" - @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`" - @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`" - @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`" - @echo "BADMOD 1 = ${LIST:OX}" - @echo "BADMOD 2 = ${LIST:OxXX}" Index: head/contrib/bmake/unit-tests/modword =================================================================== --- head/contrib/bmake/unit-tests/modword (revision 281811) +++ head/contrib/bmake/unit-tests/modword (nonexistent) @@ -1,151 +0,0 @@ -# $Id: modword,v 1.1.1.1 2003/09/28 17:01:48 sjg Exp $ -# -# Test behaviour of new :[] modifier - -all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw - -LIST= one two three -LIST+= four five six -LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - -EMPTY= # the space should be ignored -ESCAPEDSPACE=\ # escaped space before the '#' -REALLYSPACE:=${EMPTY:C/^/ /W} -HASH= \# -AT= @ -STAR= * -ZERO= 0 -ONE= 1 -MINUSONE= -1 - -mod-squarebrackets: mod-squarebrackets-0-star-at \ - mod-squarebrackets-hash \ - mod-squarebrackets-n \ - mod-squarebrackets-start-end \ - mod-squarebrackets-nested - -mod-squarebrackets-0-star-at: - @echo 'LIST:[]="${LIST:[]}" is an error' - @echo 'LIST:[0]="${LIST:[0]}"' - @echo 'LIST:[0x0]="${LIST:[0x0]}"' - @echo 'LIST:[000]="${LIST:[000]}"' - @echo 'LIST:[*]="${LIST:[*]}"' - @echo 'LIST:[@]="${LIST:[@]}"' - @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' - @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' - @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' - @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' - @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' - @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' - @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' - @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' - @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' - @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' - @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' - @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' - @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' - -mod-squarebrackets-hash: - @echo 'EMPTY="${EMPTY}"' - @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' - @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' - @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' - @echo 'REALLYSPACE="${REALLYSPACE}"' - @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' - @echo 'LIST:[#]="${LIST:[#]}"' - @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' - @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' - @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' - @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' - @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' - -mod-squarebrackets-n: - @echo 'EMPTY:[1]="${EMPTY:[1]}"' - @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' - @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' - @echo 'REALLYSPACE="${REALLYSPACE}"' - @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' - @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' - @echo 'LIST:[1]="${LIST:[1]}"' - @echo 'LIST:[1.]="${LIST:[1.]}" is an error' - @echo 'LIST:[1].="${LIST:[1].}" is an error' - @echo 'LIST:[2]="${LIST:[2]}"' - @echo 'LIST:[6]="${LIST:[6]}"' - @echo 'LIST:[7]="${LIST:[7]}"' - @echo 'LIST:[999]="${LIST:[999]}"' - @echo 'LIST:[-]="${LIST:[-]}" is an error' - @echo 'LIST:[--]="${LIST:[--]}" is an error' - @echo 'LIST:[-1]="${LIST:[-1]}"' - @echo 'LIST:[-2]="${LIST:[-2]}"' - @echo 'LIST:[-6]="${LIST:[-6]}"' - @echo 'LIST:[-7]="${LIST:[-7]}"' - @echo 'LIST:[-999]="${LIST:[-999]}"' - @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' - @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' - @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' - @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' - @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' - @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' - @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' - @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' - @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' - @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' - @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' - @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' - -mod-squarebrackets-start-end: - @echo 'LIST:[1.]="${LIST:[1.]}" is an error' - @echo 'LIST:[1..]="${LIST:[1..]}" is an error' - @echo 'LIST:[1..1]="${LIST:[1..1]}"' - @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' - @echo 'LIST:[1..2]="${LIST:[1..2]}"' - @echo 'LIST:[2..1]="${LIST:[2..1]}"' - @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' - @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' - @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' - @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' - @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' - @echo 'LIST:[0..0]="${LIST:[0..0]}"' - @echo 'LIST:[3..99]="${LIST:[3..99]}"' - @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' - @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' - -mod-squarebrackets-nested: - @echo 'HASH="${HASH}" == "#" ?' - @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' - @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' - @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' - @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' - @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' - @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' - @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' - @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' - @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' - @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' - @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' - -mod-C-W: - @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' - @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' - @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' - @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' - @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' - -mod-S-W: - @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' - @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' - @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' - @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' - @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' - -mod-tW-tw: - @echo 'LIST:tW="${LIST:tW}"' - @echo 'LIST:tw="${LIST:tw}"' - @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' - @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' - @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' - @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' - @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' - @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' - @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' - @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' Index: head/contrib/bmake/unit-tests/misc =================================================================== --- head/contrib/bmake/unit-tests/misc (revision 281811) +++ head/contrib/bmake/unit-tests/misc (nonexistent) @@ -1,16 +0,0 @@ -# $Id: misc,v 1.1.1.1 2011/03/06 00:04:58 sjg Exp $ - -.if !exists(${.CURDIR}/) -.warning ${.CURDIR}/ doesn't exist ? -.endif - -.if !exists(${.CURDIR}/.) -.warning ${.CURDIR}/. doesn't exist ? -.endif - -.if !exists(${.CURDIR}/..) -.warning ${.CURDIR}/.. doesn't exist ? -.endif - -all: - @: all is well Index: head/contrib/bmake/unit-tests/doterror =================================================================== --- head/contrib/bmake/unit-tests/doterror (revision 281811) +++ head/contrib/bmake/unit-tests/doterror (nonexistent) @@ -1,20 +0,0 @@ -# $Id: doterror,v 1.1.1.1 2010/04/08 17:43:00 sjg Exp $ - - -.BEGIN: - @echo At first, I am - -.END: - @echo not reached - -.ERROR: - @echo "$@: Looks like '${.ERROR_TARGET}' is upset." - -all: happy sad - -happy: - @echo $@ - -sad: - @echo and now: $@; exit 1 - Index: head/contrib/bmake/unit-tests/sunshcmd =================================================================== --- head/contrib/bmake/unit-tests/sunshcmd (revision 281811) +++ head/contrib/bmake/unit-tests/sunshcmd (nonexistent) @@ -1,10 +0,0 @@ -BYECMD = echo bye -LATERCMD = echo later -TEST1 :sh = echo hello -TEST2 :sh = ${BYECMD} -TEST3 = ${LATERCMD:sh} - -all: - @echo "TEST1=${TEST1}" - @echo "TEST2=${TEST2}" - @echo "TEST3=${TEST3}" Index: head/contrib/bmake/unit-tests/export-env =================================================================== --- head/contrib/bmake/unit-tests/export-env (revision 281811) +++ head/contrib/bmake/unit-tests/export-env (nonexistent) @@ -1,24 +0,0 @@ -# $Id: export-env,v 1.1.1.1 2013/03/23 02:26:59 sjg Exp $ - -# our normal .export, subsequent changes affect the environment -UT_TEST=this -.export UT_TEST -UT_TEST:= ${.PARSEFILE} - -# not so with .export-env -UT_ENV=exported -.export-env UT_ENV -UT_ENV=not-exported - -# gmake style export goes further; affects nothing but the environment -UT_EXP=before-export -export UT_EXP=exported -UT_EXP=not-exported - -all: - @echo make:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=${$v};@} - @echo env:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=$${$v};@} - - - - Index: head/contrib/bmake/unit-tests/qequals =================================================================== --- head/contrib/bmake/unit-tests/qequals (revision 281811) +++ head/contrib/bmake/unit-tests/qequals (nonexistent) @@ -1,8 +0,0 @@ -# $Id: qequals,v 1.1.1.1 2008/03/31 00:13:05 sjg Exp $ - -M= i386 -V.i386= OK -V.$M ?= bug - -all: - @echo 'V.$M ?= ${V.$M}' Index: head/contrib/bmake/unit-tests/cond1 =================================================================== --- head/contrib/bmake/unit-tests/cond1 (revision 281811) +++ head/contrib/bmake/unit-tests/cond1 (nonexistent) @@ -1,109 +0,0 @@ -# $Id: cond1,v 1.1.1.3 2011/03/06 00:04:58 sjg Exp $ - -# hard code these! -TEST_UNAME_S= NetBSD -TEST_UNAME_M= sparc -TEST_MACHINE= i386 - -.if ${TEST_UNAME_S} -Ok=var, -.endif -.if ("${TEST_UNAME_S}") -Ok+=(\"var\"), -.endif -.if (${TEST_UNAME_M} != ${TEST_MACHINE}) -Ok+=(var != var), -.endif -.if ${TEST_UNAME_M} != ${TEST_MACHINE} -Ok+= var != var, -.endif -.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X)) -Ok+= !((var != var) && defined(name)), -.endif -# from bsd.obj.mk -MKOBJ?=no -.if ${MKOBJ} == "no" -o= no -Ok+= var == "quoted", -.else -.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) -.if defined(notMAKEOBJDIRPREFIX) -o=${MAKEOBJDIRPREFIX}${__curdir} -.else -o= ${MAKEOBJDIR} -.endif -.endif -o= o -.endif - -# repeat the above to check we get the same result -.if ${MKOBJ} == "no" -o2= no -.else -.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) -.if defined(notMAKEOBJDIRPREFIX) -o2=${MAKEOBJDIRPREFIX}${__curdir} -.else -o2= ${MAKEOBJDIR} -.endif -.endif -o2= o -.endif - -PRIMES=2 3 5 7 11 -NUMBERS=1 2 3 4 5 - -n=2 -.if ${PRIMES:M$n} == "" -X=not -.else -X= -.endif - -.if ${MACHINE_ARCH} == no-such -A=one -.else -.if ${MACHINE_ARCH} == not-this -.if ${MACHINE_ARCH} == something-else -A=unlikely -.else -A=no -.endif -.endif -A=other -# We expect an extra else warning - we're not skipping here -.else -A=this should be an error -.endif - -.if $X != "" -.if $X == not -B=one -.else -B=other -# We expect an extra else warning - we are skipping here -.else -B=this should be an error -.endif -.else -B=unknown -.endif - -.if "quoted" == quoted -C=clever -.else -C=dim -.endif - -.if defined(nosuch) && ${nosuch:Mx} != "" -# this should not happen -.info nosuch is x -.endif - -all: - @echo "$n is $X prime" - @echo "A='$A' B='$B' C='$C' o='$o,${o2}'" - @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}" - @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}" - @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}" - @echo "${${NoSuchNumber:U42} > 0:?OK:No}" Index: head/contrib/bmake/unit-tests/sysv =================================================================== --- head/contrib/bmake/unit-tests/sysv (revision 281811) +++ head/contrib/bmake/unit-tests/sysv (nonexistent) @@ -1,26 +0,0 @@ -# $Id: sysv,v 1.1.1.2 2011/06/05 04:23:49 sjg Exp $ - -FOO ?= -FOOBAR = $(FOO:=bar) - -_this := ${.PARSEDIR}/${.PARSEFILE} - -B = /b -S = / -FUN = ${B}${S}fun -SUN = the Sun - -# we expect nothing when FOO is empty -all: foo fun - -foo: - @echo FOOBAR = $(FOOBAR) -.if empty(FOO) - @FOO="foo fu" ${.MAKE} -f ${_this} foo -.endif - -fun: - @echo ${FUN:T} - @echo ${FUN:${B}${S}fun=fun} - @echo ${FUN:${B}${S}%=%} - @echo ${In:L:%=% ${SUN}} Index: head/contrib/bmake/unit-tests/forsubst =================================================================== --- head/contrib/bmake/unit-tests/forsubst (revision 281811) +++ head/contrib/bmake/unit-tests/forsubst (nonexistent) @@ -1,10 +0,0 @@ -# $Id: forsubst,v 1.1.1.1 2009/10/07 18:53:35 sjg Exp $ - -all: for-subst - -here := ${.PARSEDIR} -# this should not run foul of the parser -.for file in ${.PARSEFILE} -for-subst: ${file:S;^;${here}/;g} - @echo ".for with :S;... OK" -.endfor Index: head/contrib/bmake/unit-tests/forloop =================================================================== --- head/contrib/bmake/unit-tests/forloop (revision 281811) +++ head/contrib/bmake/unit-tests/forloop (nonexistent) @@ -1,45 +0,0 @@ -# $Id: forloop,v 1.1.1.1 2012/06/19 23:30:49 sjg Exp $ - -all: for-loop - -LIST = one "two and three" four "five" - -.if make(for-fail) -for-fail: - -XTRA_LIST = xtra -.else - -.for x in ${LIST} -X!= echo 'x=$x' >&2; echo -.endfor - -CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" -cfl= -.for x in ${CFL} -X!= echo 'x=$x' >&2; echo -.if empty(cfl) -cfl= $x -.else -cfl+= $x -.endif -.endfor -X!= echo 'cfl=${cfl}' >&2; echo - -.if ${cfl} != ${CFL} -.error ${.newline}'${cfl}' != ${.newline}'${CFL}' -.endif - -.for a b in ${EMPTY} -X!= echo 'a=$a b=$b' >&2; echo -.endfor -.endif - -.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} -X!= echo 'a=$a b=$b' >&2; echo -.endfor - -for-loop: - @echo We expect an error next: - @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \ - { echo "Oops that should have failed!"; exit 1; } || echo OK Index: head/contrib/bmake/unit-tests/error =================================================================== --- head/contrib/bmake/unit-tests/error (revision 281811) +++ head/contrib/bmake/unit-tests/error (nonexistent) @@ -1,10 +0,0 @@ -# $Id: error,v 1.1.1.2 2010/05/24 23:36:03 sjg Exp $ - -.info just FYI -.warning this could be serious -.error this is fatal - -all: - -.info.html: - @echo this should be ignored Index: head/contrib/bmake/unit-tests/unexport =================================================================== --- head/contrib/bmake/unit-tests/unexport (revision 281811) +++ head/contrib/bmake/unit-tests/unexport (nonexistent) @@ -1,8 +0,0 @@ -# $Id: unexport,v 1.1.1.1 2009/11/19 00:31:11 sjg Exp $ - -# pick up a bunch of exported vars -.include "export" - -.unexport UT_ZOO UT_FOO - -UT_TEST = unexport Index: head/contrib/bmake/unit-tests/modmatch =================================================================== --- head/contrib/bmake/unit-tests/modmatch (revision 281811) +++ head/contrib/bmake/unit-tests/modmatch (nonexistent) @@ -1,25 +0,0 @@ - -X=a b c d e - -.for x in $X -LIB${x:tu}=/tmp/lib$x.a -.endfor - -X_LIBS= ${LIBA} ${LIBD} ${LIBE} - -LIB?=a - -var = head -res = no -.if !empty(var:M${:Uhead\:tail:C/:.*//}) -res = OK -.endif - -all: - @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done - @echo "Mscanner=${res}" - -show: - @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"' - @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"' - @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"' Index: head/contrib/bmake/unit-tests/modts =================================================================== --- head/contrib/bmake/unit-tests/modts (revision 281811) +++ head/contrib/bmake/unit-tests/modts (nonexistent) @@ -1,43 +0,0 @@ - -LIST= one two three -LIST+= four five six - -FU_mod-ts = a / b / cool - -AAA= a a a -B.aaa= Baaa - -all: mod-ts - -# Use print or printf iff they are builtin. -# XXX note that this causes problems, when make decides -# there is no need to use a shell, so avoid where possible. -.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= print -r -- -.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= printf '%s\n' -.else -PRINT= echo -.endif - -mod-ts: - @echo 'LIST="${LIST}"' - @echo 'LIST:ts,="${LIST:ts,}"' - @echo 'LIST:ts/:tu="${LIST:ts/:tu}"' - @echo 'LIST:ts::tu="${LIST:ts::tu}"' - @echo 'LIST:ts:tu="${LIST:ts:tu}"' - @echo 'LIST:tu:ts/="${LIST:tu:ts/}"' - @echo 'LIST:ts:="${LIST:ts:}"' - @echo 'LIST:ts="${LIST:ts}"' - @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"' - @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"' - @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"' - @echo "Pretend the '/' in '/n' etc. below are back-slashes." - @${PRINT} 'LIST:ts/n="${LIST:ts\n}"' - @${PRINT} 'LIST:ts/t="${LIST:ts\t}"' - @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"' - @${PRINT} 'LIST:tx="${LIST:tx}"' - @${PRINT} 'LIST:ts/x:tu="${LIST:ts\x:tu}"' - @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"' - @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?' - @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?' Index: head/contrib/bmake/unit-tests/order =================================================================== --- head/contrib/bmake/unit-tests/order (revision 281811) +++ head/contrib/bmake/unit-tests/order (nonexistent) @@ -1,20 +0,0 @@ -# $NetBSD: order,v 1.1 2012/11/09 19:08:28 sjg Exp $ - -# Test that .ORDER is handled correctly. -# The explicit dependency the.o: the.h will make us examine the.h -# the .ORDER will prevent us building it immediately, -# we should then examine the.c rather than stop. - -all: the.o - -.ORDER: the.c the.h - -the.c the.h: - @echo Making $@ - -.SUFFIXES: .o .c - -.c.o: - @echo Making $@ from $? - -the.o: the.h Index: head/contrib/bmake/unit-tests/unexport-env =================================================================== --- head/contrib/bmake/unit-tests/unexport-env (revision 281811) +++ head/contrib/bmake/unit-tests/unexport-env (nonexistent) @@ -1,14 +0,0 @@ -# $Id: unexport-env,v 1.1.1.1 2009/11/19 00:31:11 sjg Exp $ - -# pick up a bunch of exported vars -.include "export" - -# an example of setting up a minimal environment. -PATH = /bin:/usr/bin:/sbin:/usr/sbin - -# now clobber the environment to just PATH and UT_TEST -UT_TEST = unexport-env - -# this removes everything -.unexport-env -.export PATH UT_TEST Index: head/contrib/bmake/unit-tests/moderrs =================================================================== --- head/contrib/bmake/unit-tests/moderrs (revision 281811) +++ head/contrib/bmake/unit-tests/moderrs (nonexistent) @@ -1,31 +0,0 @@ -# $Id: moderrs,v 1.2 2006/05/11 18:53:39 sjg Exp $ -# -# various modifier error tests - -VAR=TheVariable -# incase we have to change it ;-) -MOD_UNKN=Z -MOD_TERM=S,V,v -MOD_S:= ${MOD_TERM}, - -all: modunkn modunknV varterm vartermV modtermV - -modunkn: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:Z=${VAR:Z}" - -modunknV: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" - -varterm: - @echo "Expect: Unclosed variable specification for VAR" - @echo VAR:S,V,v,=${VAR:S,V,v, - -vartermV: - @echo "Expect: Unclosed variable specification for VAR" - @echo VAR:${MOD_TERM},=${VAR:${MOD_S} - -modtermV: - @echo "Expect: Unclosed substitution for VAR (, missing)" - -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" Index: head/contrib/bmake/unit-tests/phony-end =================================================================== --- head/contrib/bmake/unit-tests/phony-end (revision 281811) +++ head/contrib/bmake/unit-tests/phony-end (nonexistent) @@ -1,9 +0,0 @@ -# $Id: phony-end,v 1.1.1.1 2011/10/01 17:19:39 sjg Exp $ - -all ok also.ok bug phony: - @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}' - -.END: ok also.ok bug - -phony bug: .PHONY -all: phony Index: head/contrib/bmake/unit-tests/test.exp =================================================================== --- head/contrib/bmake/unit-tests/test.exp (revision 281811) +++ head/contrib/bmake/unit-tests/test.exp (nonexistent) @@ -1,383 +0,0 @@ -comment testing start -this is foo -This is how a comment looks: # comment -comment testing done -make: "cond1" line 75: warning: extra else -make: "cond1" line 85: warning: extra else -2 is prime -A='other' B='unknown' C='clever' o='no,no' -Passed: - var - ("var") - (var != var) - var != var - !((var != var) && defined(name)) - var == quoted - -1 is not prime -2 is prime -3 is prime -4 is not prime -5 is prime - -make: warning: String comparison operator should be either == or != -make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No - -OK -make: "error" line 3: just FYI -make: "error" line 4: warning: this could be serious -make: "error" line 5: this is fatal -UT_DOLLAR=This is $UT_FU -UT_FOO=foobar is fubar -UT_FU=fubar -UT_TEST=export -UT_ZOO=hoopie -UT_ALL=even this gets exported -UT_BADDIR=unit-tests -UT_DOLLAR=This is $UT_FU -UT_F=fine -UT_FOO=foobar is fubar -UT_FU=fubar -UT_NO=all -UT_OK=good -UT_OKDIR=unit-tests -UT_TEST=export-all -UT_ZOO=hoopie -make: -UT_TEST=export-env -UT_ENV=not-exported -UT_EXP=not-exported -env: -UT_TEST=export-env -UT_ENV=exported -UT_EXP=exported -At first, I am -happy -and now: sad -.ERROR: Looks like 'sad' is upset. -*** Error code 1 - -Stop. -make: stopped in unit-tests -simple.1 -simple.1 -simple.2 -simple.2 -recursive.1.1.* -recursive.1.1.* -recursive.1.1.* -recursive.1.1.* -recursive.1.99 -recursive.1.99 -recursive.2.1.* -recursive.2.1.* -recursive.2.1.* -recursive.2.1.* -recursive.2.99 -recursive.2.99 -shared.0 -shared.0 -shared.1.99 -shared.1.99 -shared.2.1 -shared.2.1 -shared.2.99 -shared.2.99 -make: Graph cycles through `cycle.2.99' -make: Graph cycles through `cycle.2.98' -make: Graph cycles through `cycle.2.97' -cycle.1.99 -cycle.1.99 -x=one -x="two and three" -x=four -x="five" -x=-I/this -x=-I"This or that" -x=-Ithat -x="-DTHIS=\"this and that\"" -cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" -a=one b="two and three" -a=four b="five" -a=ONE b="TWO AND THREE" -a=FOUR b="FIVE" -We expect an error next: -make: "forloop" line 38: Wrong number of words (9) in .for substitution list with 2 vars -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -OK -.for with :S;... OK -b2af338b -3360ac65 -7747f046 -9ca87054 -880fe816 -208fcbd3 -d5d376eb -de41416c -Expect: Unknown modifier 'Z' -make: Unknown modifier 'Z' -VAR:Z= -Expect: Unknown modifier 'Z' -make: Unknown modifier 'Z' -VAR:Z= -Expect: Unclosed variable specification for VAR -make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S -VAR:S,V,v,=Thevariable -Expect: Unclosed variable specification for VAR -make: Unclosed variable specification after complex modifier (expecting '}') for VAR -VAR:S,V,v,=Thevariable -Expect: Unclosed substitution for VAR (, missing) -make: Unclosed substitution for VAR (, missing) -VAR:S,V,v= -LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a" -LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a" -LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A" -LIB=b X_LIBS:M${LIB${LIB:tu}} is "" -LIB=b X_LIBS:M*/lib${LIB}.a is "" -LIB=b X_LIBS:M*/lib${LIB}.a:tu is "" -LIB=c X_LIBS:M${LIB${LIB:tu}} is "" -LIB=c X_LIBS:M*/lib${LIB}.a is "" -LIB=c X_LIBS:M*/lib${LIB}.a:tu is "" -LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a" -LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a" -LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A" -LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a" -LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a" -LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A" -Mscanner=OK -path=':/bin:/tmp::/:.:/no/such/dir:.' -path='/bin:/tmp:/:/no/such/dir' -path='/bin:/tmp:/:/no/such/dir' -path='/bin':'/tmp':'/':'/no/such/dir' -path='/bin':'/tmp':'/':'/no/such/dir' -path_/usr/xbin=/opt/xbin/ -paths=/bin /tmp / /no/such/dir /opt/xbin -PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN -The answer is 42 -LIST = one two three four five six seven eight nine ten -LIST:O = eight five four nine one seven six ten three two -LIST:Ox = Ok -LIST:O:Ox = Ok -LISTX = Ok -LISTSX = Ok -make: Bad modifier `:OX' for LIST -BADMOD 1 = } -make: Bad modifier `:OxXX' for LIST -BADMOD 2 = XX} -LIST="one two three four five six" -LIST:ts,="one,two,three,four,five,six" -LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX" -LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX" -LIST:ts:tu="ONETWOTHREEFOURFIVESIX" -LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX" -LIST:ts:="one:two:three:four:five:six" -LIST:ts="onetwothreefourfivesix" -LIST:ts:S/two/2/="one2threefourfivesix" -LIST:S/two/2/:ts="one2threefourfivesix" -LIST:ts/:S/two/2/="one/2/three/four/five/six" -Pretend the '/' in '/n' etc. below are back-slashes. -LIST:ts/n="one -two -three -four -five -six" -LIST:ts/t="one two three four five six" -LIST:ts/012:tu="ONE -TWO -THREE -FOUR -FIVE -SIX" -make: Bad modifier `:tx' for LIST -LIST:tx="}" -make: Bad modifier `:ts\x' for LIST -LIST:ts/x:tu="\x:tu}" -FU_mod-ts="a/b/cool" -FU_mod-ts:ts:T="cool" == cool? -B.${AAA:ts}="Baaa" == Baaa? -make: Bad modifier `:[]' for LIST -LIST:[]="" is an error -LIST:[0]="one two three four five six" -LIST:[0x0]="one two three four five six" -LIST:[000]="one two three four five six" -LIST:[*]="one two three four five six" -LIST:[@]="one two three four five six" -LIST:[0]:C/ /,/="one,two three four five six" -LIST:[0]:C/ /,/g="one,two,three,four,five,six" -LIST:[0]:C/ /,/1g="one,two,three,four,five,six" -LIST:[*]:C/ /,/="one,two three four five six" -LIST:[*]:C/ /,/g="one,two,three,four,five,six" -LIST:[*]:C/ /,/1g="one,two,three,four,five,six" -LIST:[@]:C/ /,/="one two three four five six" -LIST:[@]:C/ /,/g="one two three four five six" -LIST:[@]:C/ /,/1g="one two three four five six" -LIST:[@]:[0]:C/ /,/="one,two three four five six" -LIST:[0]:[@]:C/ /,/="one two three four five six" -LIST:[@]:[*]:C/ /,/="one,two three four five six" -LIST:[*]:[@]:C/ /,/="one two three four five six" -EMPTY="" -EMPTY:[#]="1" == 1 ? -ESCAPEDSPACE="\ " -ESCAPEDSPACE:[#]="1" == 1 ? -REALLYSPACE=" " -REALLYSPACE:[#]="1" == 1 ? -LIST:[#]="6" -LIST:[0]:[#]="1" == 1 ? -LIST:[*]:[#]="1" == 1 ? -LIST:[@]:[#]="6" -LIST:[1]:[#]="1" -LIST:[1..3]:[#]="3" -EMPTY:[1]="" -ESCAPEDSPACE="\ " -ESCAPEDSPACE:[1]="\ " -REALLYSPACE=" " -REALLYSPACE:[1]="" == "" ? -REALLYSPACE:[*]:[1]=" " == " " ? -LIST:[1]="one" -make: Bad modifier `:[1.]' for LIST -LIST:[1.]="" is an error -make: Bad modifier `:[1].' for LIST -LIST:[1].="}" is an error -LIST:[2]="two" -LIST:[6]="six" -LIST:[7]="" -LIST:[999]="" -make: Bad modifier `:[-]' for LIST -LIST:[-]="" is an error -make: Bad modifier `:[--]' for LIST -LIST:[--]="" is an error -LIST:[-1]="six" -LIST:[-2]="five" -LIST:[-6]="one" -LIST:[-7]="" -LIST:[-999]="" -LONGLIST:[17]="17" -LONGLIST:[0x11]="17" -LONGLIST:[021]="17" -LIST:[0]:[1]="one two three four five six" -LIST:[*]:[1]="one two three four five six" -LIST:[@]:[1]="one" -LIST:[0]:[2]="" -LIST:[*]:[2]="" -LIST:[@]:[2]="two" -LIST:[*]:C/ /,/:[2]="" -LIST:[*]:C/ /,/:[*]:[2]="" -LIST:[*]:C/ /,/:[@]:[2]="three" -make: Bad modifier `:[1.]' for LIST -LIST:[1.]="" is an error -make: Bad modifier `:[1..]' for LIST -LIST:[1..]="" is an error -LIST:[1..1]="one" -make: Bad modifier `:[1..1.]' for LIST -LIST:[1..1.]="" is an error -LIST:[1..2]="one two" -LIST:[2..1]="two one" -LIST:[3..-2]="three four five" -LIST:[-4..4]="three four" -make: Bad modifier `:[0..1]' for LIST -LIST:[0..1]="" is an error -make: Bad modifier `:[-1..0]' for LIST -LIST:[-1..0]="" is an error -LIST:[-1..1]="six five four three two one" -LIST:[0..0]="one two three four five six" -LIST:[3..99]="three four five six" -LIST:[-3..-99]="four three two one" -LIST:[-99..-3]="one two three four" -HASH="#" == "#" ? -LIST:[${HASH}]="6" -LIST:[${ZERO}]="one two three four five six" -LIST:[${ZERO}x${ONE}]="one" -LIST:[${ONE}]="one" -LIST:[${MINUSONE}]="six" -LIST:[${STAR}]="one two three four five six" -LIST:[${AT}]="one two three four five six" -make: Bad modifier `:[${EMPTY' for LIST -LIST:[${EMPTY}]="" is an error -LIST:[${LONGLIST:[21]:S/2//}]="one" -LIST:[${LIST:[#]}]="six" -LIST:[${LIST:[${HASH}]}]="six" -LIST:S/ /,/="one two three four five six" -LIST:S/ /,/W="one,two three four five six" -LIST:S/ /,/gW="one,two,three,four,five,six" -EMPTY:S/^/,/="," -EMPTY:S/^/,/W="," -LIST:C/ /,/="one two three four five six" -LIST:C/ /,/W="one,two three four five six" -LIST:C/ /,/gW="one,two,three,four,five,six" -EMPTY:C/^/,/="," -EMPTY:C/^/,/W="," -LIST:tW="one two three four five six" -LIST:tw="one two three four five six" -LIST:tW:C/ /,/="one,two three four five six" -LIST:tW:C/ /,/g="one,two,three,four,five,six" -LIST:tW:C/ /,/1g="one,two,three,four,five,six" -LIST:tw:C/ /,/="one two three four five six" -LIST:tw:C/ /,/g="one two three four five six" -LIST:tw:C/ /,/1g="one two three four five six" -LIST:tw:tW:C/ /,/="one,two three four five six" -LIST:tW:tw:C/ /,/="one two three four five six" -Making the.c -Making the.h -Making the.o from the.h the.c -.TARGET="phony" .PREFIX="phony" .IMPSRC="" -.TARGET="all" .PREFIX="all" .IMPSRC="" -.TARGET="ok" .PREFIX="ok" .IMPSRC="" -.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC="" -.TARGET="bug" .PREFIX="bug" .IMPSRC="" -Posix says we should execute the command as if run by system(3) -Expect 'Hello,' and 'World!' -Hello, -World! -a command -a command prefixed by '+' executes even with -n -another command -make -n -echo a command -echo "a command prefixed by '+' executes even with -n" -a command prefixed by '+' executes even with -n -echo another command -make -n -j1 -{ echo a command -} || exit $? -echo "a command prefixed by '+' executes even with -n" -a command prefixed by '+' executes even with -n -{ echo another command -} || exit $? -Now we expect an error... -*** Error code 1 (continuing) -`all' not remade because of errors. -V.i386 ?= OK -TEST1=hello -TEST2=bye -TEST3=later -FOOBAR = -FOOBAR = foobar fubar -fun -fun -fun -In the Sun -The answer is unknown -The answer is unknown -The answer is empty -The answer is known -The answer is -The answer is empty -The answer is known -The answer is 42 -The answer is 42 -UT_DOLLAR=This is $UT_FU -UT_FU=fubar -UT_TEST=unexport -UT_TEST=unexport-env -default FU=fu FOO=foo VAR= -two FU=bar FOO=goo VAR= -three FU=bar FOO=goo VAR= -four FU=bar FOO=goo VAR=Internal -five FU=bar FOO=goo VAR=Internal -five v=is x k=is x -six v=is y k=is y -show-v v=override k=override -*** Error code 1 (ignored) -*** Error code 1 (ignored) Index: head/contrib/bmake/unit-tests/ternary =================================================================== --- head/contrib/bmake/unit-tests/ternary (revision 281811) +++ head/contrib/bmake/unit-tests/ternary (nonexistent) @@ -1,8 +0,0 @@ - -all: - @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done - -show: - @echo "The answer is ${A:?known:unknown}" - @echo "The answer is ${A:?$A:unknown}" - @echo "The answer is ${empty(A):?empty:$A}" Index: head/contrib/bmake/unit-tests/dotwait =================================================================== --- head/contrib/bmake/unit-tests/dotwait (revision 281811) +++ head/contrib/bmake/unit-tests/dotwait (nonexistent) @@ -1,61 +0,0 @@ -# $NetBSD: dotwait,v 1.1 2006/02/26 22:45:46 apb Exp $ - -THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} - -TESTS= simple recursive shared cycle -PAUSE= sleep 1 - -# Use a .for loop rather than dependencies here, to ensure -# that the tests are run one by one, with parallelism -# only within tests. -# Ignore "--- target ---" lines printed by parallel make. -all: -.for t in ${TESTS} - @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- " -.endfor - -# -# Within each test, the names of the sub-targets follow these -# conventions: -# * If it's expected that two or more targets may be made in parallel, -# then the target names will differ only in an alphabetic component -# such as ".a" or ".b". -# * If it's expected that two or more targets should be made in sequence -# then the target names will differ in numeric components, such that -# lexical ordering of the target names matches the expected order -# in which the targets should be made. -# -# Targets may echo ${PARALLEL_TARG} to print a modified version -# of their own name, in which alphabetic components like ".a" or ".b" -# are converted to ".*". Two targets that are expected to -# be made in parallel will thus print the same strings, so that the -# output is independent of the order in which these targets are made. -# -PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q} -.DEFAULT: - @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} -_ECHOUSE: .USE - @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} - -# simple: no recursion, no cycles -simple: simple.1 .WAIT simple.2 - -# recursive: all children of the left hand side of the .WAIT -# must be made before any child of the right hand side. -recursive: recursive.1.99 .WAIT recursive.2.99 -recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE -recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE - -# shared: both shared.1.99 and shared.2.99 depend on shared.0. -# shared.0 must be made first, even though it is a child of -# the right hand side of the .WAIT. -shared: shared.1.99 .WAIT shared.2.99 -shared.1.99: shared.0 _ECHOUSE -shared.2.99: shared.2.1 shared.0 _ECHOUSE - -# cycle: the cyclic dependency must not cause infinite recursion -# leading to stack overflow and a crash. -cycle: cycle.1.99 .WAIT cycle.2.99 -cycle.2.99: cycle.2.98 _ECHOUSE -cycle.2.98: cycle.2.97 _ECHOUSE -cycle.2.97: cycle.2.99 _ECHOUSE Index: head/contrib/bmake/unit-tests/export-all =================================================================== --- head/contrib/bmake/unit-tests/export-all (revision 281811) +++ head/contrib/bmake/unit-tests/export-all (nonexistent) @@ -1,23 +0,0 @@ -# $Id: export-all,v 1.1.1.2 2010/04/21 04:26:14 sjg Exp $ - -UT_OK=good -UT_F=fine - -# the old way to do :tA -M_tAbad = C,.*,cd & \&\& 'pwd',:sh -# the new -M_tA = tA - -here := ${.PARSEDIR} - -# this will cause trouble (recursing if we let it) -UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T} -# this will be ok -UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T} - -.export - -.include "export" - -UT_TEST=export-all -UT_ALL=even this gets exported Index: head/contrib/bmake/unit-tests/modmisc =================================================================== --- head/contrib/bmake/unit-tests/modmisc (revision 281811) +++ head/contrib/bmake/unit-tests/modmisc (nonexistent) @@ -1,38 +0,0 @@ -# $Id: modmisc,v 1.1.1.5 2011/04/11 15:10:32 sjg Exp $ -# -# miscellaneous modifier tests - -# do not put any dirs in this list which exist on some -# but not all target systems - an exists() check is below. -path=:/bin:/tmp::/:.:/no/such/dir:. -# strip cwd from path. -MOD_NODOT=S/:/ /g:N.:ts: -# and decorate, note that $'s need to be doubled. Also note that -# the modifier_variable can be used with other modifiers. -MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ -# another mod - pretend it is more interesting -MOD_HOMES=S,/home/,/homes/, -MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ -MOD_SEP=S,:, ,g - -all: modvar modvarloop modsysv - -modsysv: - @echo "The answer is ${libfoo.a:L:libfoo.a=42}" - -modvar: - @echo "path='${path}'" - @echo "path='${path:${MOD_NODOT}}'" - @echo "path='${path:S,home,homes,:${MOD_NODOT}}'" - @echo "path=${path:${MOD_NODOTX}:ts:}" - @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" - -.for d in ${path:${MOD_SEP}:N.} /usr/xbin -path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ -paths+= ${d:${MOD_OPT}:${MOD_HOMES}} -.endfor - -modvarloop: - @echo "path_/usr/xbin=${path_/usr/xbin}" - @echo "paths=${paths}" - @echo "PATHS=${paths:tu}" Index: head/contrib/bmake/unit-tests/Makefile.in =================================================================== --- head/contrib/bmake/unit-tests/Makefile.in (revision 281811) +++ head/contrib/bmake/unit-tests/Makefile.in (revision 281812) @@ -1,102 +1,150 @@ -# $Id: Makefile.in,v 1.44 2013/08/28 22:09:29 sjg Exp $ +# $Id: Makefile.in,v 1.46 2014/11/06 01:47:57 sjg Exp $ # -# $NetBSD: Makefile,v 1.38 2013/08/28 21:56:50 sjg Exp $ +# $NetBSD: Makefile,v 1.51 2014/10/20 23:21:11 sjg Exp $ # # Unit tests for make(1) # The main targets are: # # all: run all the tests -# test: run 'all', capture output and compare to expected results +# test: run 'all', and compare to expected results # accept: move generated output to expected results # # Adding a test case. # Each feature should get its own set of tests in its own suitably -# named makefile which should be added to SUBFILES to hook it in. +# named makefile (*.mk), with its own set of expected results (*.exp), +# and it should be added to the TESTNAMES list. # srcdir= @srcdir@ .MAIN: all UNIT_TESTS:= ${srcdir} +.PATH: ${UNIT_TESTS} -# Simple sub-makefiles - we run them as a black box -# keep the list sorted. -SUBFILES= \ +# Each test is in a sub-makefile. +# Keep the list sorted. +TESTNAMES= \ comment \ cond1 \ error \ export \ export-all \ export-env \ doterror \ dotwait \ forloop \ forsubst \ hash \ misc \ moderrs \ modmatch \ modmisc \ modorder \ modts \ modword \ order \ - phony-end \ posix \ qequals \ sunshcmd \ sysv \ ternary \ unexport \ unexport-env \ - varcmd + varcmd \ + varmisc \ + varshell -all: ${SUBFILES} +# these tests were broken by referting POSIX chanegs +STRICT_POSIX_TESTS = \ + escape \ + impsrc \ + phony-end \ + posix1 \ + suffixes +# Override make flags for certain tests flags.doterror= flags.order=-j1 -# the tests are actually done with sub-makes. -.PHONY: ${SUBFILES} -.PRECIOUS: ${SUBFILES} -${SUBFILES}: - -@${.MAKE} ${flags.$@:U-k} -f ${UNIT_TESTS}/$@ +OUTFILES= ${TESTNAMES:S/$/.out/} -clean: - rm -f *.out *.fail *.core +all: ${OUTFILES} -.-include +CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp +CLEANFILES += obj*.[och] lib*.a # posix1.mk +CLEANFILES += issue* .[ab]* # suffixes.mk +CLEANRECURSIVE += dir dummy # posix1.mk +clean: + rm -f ${CLEANFILES} +.if !empty(CLEANRECURSIVE) + rm -rf ${CLEANRECURSIVE} +.endif + TEST_MAKE?= ${.MAKE} TOOL_SED?= sed TOOL_TR?= tr TOOL_DIFF?= diff DIFF_FLAGS?= @diff_u@ .if defined(.PARSEDIR) # ensure consistent results from sort(1) LC_ALL= C LANG= C .export LANG LC_ALL .endif -# The driver. +# some tests need extra post-processing +SED_CMDS.varshell = -e 's,^[a-z]*sh: ,,' \ + -e '/command/s,No such.*,not found,' + +# the tests are actually done with sub-makes. +.SUFFIXES: .mk .rawout .out +.mk.rawout: + @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} + -@cd ${.OBJDIR} && \ + { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \ + 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} + # We always pretend .MAKE was called 'make' # and strip ${.CURDIR}/ from the output # and replace anything after 'stopped in' with unit-tests # so the results can be compared. -test: - @echo "${TEST_MAKE} -f ${MAKEFILE} > ${.TARGET}.out 2>&1" - @cd ${.OBJDIR} && ${TEST_MAKE} -f ${MAKEFILE} 2>&1 | \ - ${TOOL_TR} -d '\015' | \ - ${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[^:]*:,make:,' \ - -e '/stopped/s, /.*, unit-tests,' \ - -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ - -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' > ${.TARGET}.out || { \ - tail ${.TARGET}.out; mv ${.TARGET}.out ${.TARGET}.fail; exit 1; } - ${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/${.TARGET}.exp ${.TARGET}.out +.rawout.out: + @echo postprocess ${.TARGET} + @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \ + -e 's,${TEST_MAKE:C/\./\\\./g},make,' \ + -e '/stopped/s, /.*, unit-tests,' \ + -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ + -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' ${SED_CMDS.${.TARGET:T:R}} \ + < ${.IMPSRC} > ${.TARGET}.tmp + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} +# Compare all output files +test: ${OUTFILES} .PHONY + @failed= ; \ + for test in ${TESTNAMES}; do \ + ${TOOL_DIFF} -u ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || failed="$${failed}$${failed:+ }$${test}" ; \ + done ; \ + if [ -n "$${failed}" ]; then \ + echo "Failed tests: $${failed}" ; false ; \ + else \ + echo "All tests passed" ; \ + fi + accept: - mv test.out ${srcdir}/test.exp + @for test in ${TESTNAMES}; do \ + cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || { echo "Replacing $${test}.exp" ; \ + cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ + done +.if exists(${TEST_MAKE}) +${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE} +.endif + +.-include Index: head/contrib/bmake/unit-tests/comment.exp =================================================================== --- head/contrib/bmake/unit-tests/comment.exp (nonexistent) +++ head/contrib/bmake/unit-tests/comment.exp (revision 281812) @@ -0,0 +1,5 @@ +comment testing start +this is foo +This is how a comment looks: # comment +comment testing done +exit status 0 Index: head/contrib/bmake/unit-tests/comment.mk =================================================================== --- head/contrib/bmake/unit-tests/comment.mk (nonexistent) +++ head/contrib/bmake/unit-tests/comment.mk (revision 281812) @@ -0,0 +1,31 @@ +# This is a comment +.if ${MACHINE_ARCH} == something +FOO=bar +.endif + +#\ + Multiline comment + +BAR=# defined +FOOBAR= # defined + +# This is an escaped comment \ +that keeps going until the end of this line + +# Another escaped comment \ +that \ +goes \ +on + +# This is NOT an escaped comment due to the double backslashes \\ +all: hi foo bar + @echo comment testing done + +hi: + @echo comment testing start + +foo: + @echo this is $@ + +bar: + @echo This is how a comment looks: '# comment' Property changes on: head/contrib/bmake/unit-tests/comment.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/cond1.exp =================================================================== --- head/contrib/bmake/unit-tests/cond1.exp (nonexistent) +++ head/contrib/bmake/unit-tests/cond1.exp (revision 281812) @@ -0,0 +1,23 @@ +make: "cond1.mk" line 75: warning: extra else +make: "cond1.mk" line 85: warning: extra else +2 is prime +A='other' B='unknown' C='clever' o='no,no' +Passed: + var + ("var") + (var != var) + var != var + !((var != var) && defined(name)) + var == quoted + +1 is not prime +2 is prime +3 is prime +4 is not prime +5 is prime + +make: warning: String comparison operator should be either == or != +make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No + +OK +exit status 0 Index: head/contrib/bmake/unit-tests/cond1.mk =================================================================== --- head/contrib/bmake/unit-tests/cond1.mk (nonexistent) +++ head/contrib/bmake/unit-tests/cond1.mk (revision 281812) @@ -0,0 +1,109 @@ +# $Id: cond1.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +# hard code these! +TEST_UNAME_S= NetBSD +TEST_UNAME_M= sparc +TEST_MACHINE= i386 + +.if ${TEST_UNAME_S} +Ok=var, +.endif +.if ("${TEST_UNAME_S}") +Ok+=(\"var\"), +.endif +.if (${TEST_UNAME_M} != ${TEST_MACHINE}) +Ok+=(var != var), +.endif +.if ${TEST_UNAME_M} != ${TEST_MACHINE} +Ok+= var != var, +.endif +.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X)) +Ok+= !((var != var) && defined(name)), +.endif +# from bsd.obj.mk +MKOBJ?=no +.if ${MKOBJ} == "no" +o= no +Ok+= var == "quoted", +.else +.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) +.if defined(notMAKEOBJDIRPREFIX) +o=${MAKEOBJDIRPREFIX}${__curdir} +.else +o= ${MAKEOBJDIR} +.endif +.endif +o= o +.endif + +# repeat the above to check we get the same result +.if ${MKOBJ} == "no" +o2= no +.else +.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) +.if defined(notMAKEOBJDIRPREFIX) +o2=${MAKEOBJDIRPREFIX}${__curdir} +.else +o2= ${MAKEOBJDIR} +.endif +.endif +o2= o +.endif + +PRIMES=2 3 5 7 11 +NUMBERS=1 2 3 4 5 + +n=2 +.if ${PRIMES:M$n} == "" +X=not +.else +X= +.endif + +.if ${MACHINE_ARCH} == no-such +A=one +.else +.if ${MACHINE_ARCH} == not-this +.if ${MACHINE_ARCH} == something-else +A=unlikely +.else +A=no +.endif +.endif +A=other +# We expect an extra else warning - we're not skipping here +.else +A=this should be an error +.endif + +.if $X != "" +.if $X == not +B=one +.else +B=other +# We expect an extra else warning - we are skipping here +.else +B=this should be an error +.endif +.else +B=unknown +.endif + +.if "quoted" == quoted +C=clever +.else +C=dim +.endif + +.if defined(nosuch) && ${nosuch:Mx} != "" +# this should not happen +.info nosuch is x +.endif + +all: + @echo "$n is $X prime" + @echo "A='$A' B='$B' C='$C' o='$o,${o2}'" + @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}" + @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}" + @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}" + @echo "${${NoSuchNumber:U42} > 0:?OK:No}" Property changes on: head/contrib/bmake/unit-tests/cond1.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/doterror.exp =================================================================== --- head/contrib/bmake/unit-tests/doterror.exp (nonexistent) +++ head/contrib/bmake/unit-tests/doterror.exp (revision 281812) @@ -0,0 +1,9 @@ +At first, I am +happy +and now: sad +.ERROR: Looks like 'sad' is upset. +*** Error code 1 + +Stop. +make: stopped in unit-tests +exit status 1 Index: head/contrib/bmake/unit-tests/doterror.mk =================================================================== --- head/contrib/bmake/unit-tests/doterror.mk (nonexistent) +++ head/contrib/bmake/unit-tests/doterror.mk (revision 281812) @@ -0,0 +1,20 @@ +# $Id: doterror.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + + +.BEGIN: + @echo At first, I am + +.END: + @echo not reached + +.ERROR: + @echo "$@: Looks like '${.ERROR_TARGET}' is upset." + +all: happy sad + +happy: + @echo $@ + +sad: + @echo and now: $@; exit 1 + Property changes on: head/contrib/bmake/unit-tests/doterror.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/dotwait.exp =================================================================== --- head/contrib/bmake/unit-tests/dotwait.exp (nonexistent) +++ head/contrib/bmake/unit-tests/dotwait.exp (revision 281812) @@ -0,0 +1,30 @@ +simple.1 +simple.1 +simple.2 +simple.2 +recursive.1.1.* +recursive.1.1.* +recursive.1.1.* +recursive.1.1.* +recursive.1.99 +recursive.1.99 +recursive.2.1.* +recursive.2.1.* +recursive.2.1.* +recursive.2.1.* +recursive.2.99 +recursive.2.99 +shared.0 +shared.0 +shared.1.99 +shared.1.99 +shared.2.1 +shared.2.1 +shared.2.99 +shared.2.99 +make: Graph cycles through `cycle.2.99' +make: Graph cycles through `cycle.2.98' +make: Graph cycles through `cycle.2.97' +cycle.1.99 +cycle.1.99 +exit status 0 Index: head/contrib/bmake/unit-tests/dotwait.mk =================================================================== --- head/contrib/bmake/unit-tests/dotwait.mk (nonexistent) +++ head/contrib/bmake/unit-tests/dotwait.mk (revision 281812) @@ -0,0 +1,61 @@ +# $NetBSD: dotwait.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} + +TESTS= simple recursive shared cycle +PAUSE= sleep 1 + +# Use a .for loop rather than dependencies here, to ensure +# that the tests are run one by one, with parallelism +# only within tests. +# Ignore "--- target ---" lines printed by parallel make. +all: +.for t in ${TESTS} + @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- " +.endfor + +# +# Within each test, the names of the sub-targets follow these +# conventions: +# * If it's expected that two or more targets may be made in parallel, +# then the target names will differ only in an alphabetic component +# such as ".a" or ".b". +# * If it's expected that two or more targets should be made in sequence +# then the target names will differ in numeric components, such that +# lexical ordering of the target names matches the expected order +# in which the targets should be made. +# +# Targets may echo ${PARALLEL_TARG} to print a modified version +# of their own name, in which alphabetic components like ".a" or ".b" +# are converted to ".*". Two targets that are expected to +# be made in parallel will thus print the same strings, so that the +# output is independent of the order in which these targets are made. +# +PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q} +.DEFAULT: + @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} +_ECHOUSE: .USE + @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} + +# simple: no recursion, no cycles +simple: simple.1 .WAIT simple.2 + +# recursive: all children of the left hand side of the .WAIT +# must be made before any child of the right hand side. +recursive: recursive.1.99 .WAIT recursive.2.99 +recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE +recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE + +# shared: both shared.1.99 and shared.2.99 depend on shared.0. +# shared.0 must be made first, even though it is a child of +# the right hand side of the .WAIT. +shared: shared.1.99 .WAIT shared.2.99 +shared.1.99: shared.0 _ECHOUSE +shared.2.99: shared.2.1 shared.0 _ECHOUSE + +# cycle: the cyclic dependency must not cause infinite recursion +# leading to stack overflow and a crash. +cycle: cycle.1.99 .WAIT cycle.2.99 +cycle.2.99: cycle.2.98 _ECHOUSE +cycle.2.98: cycle.2.97 _ECHOUSE +cycle.2.97: cycle.2.99 _ECHOUSE Property changes on: head/contrib/bmake/unit-tests/dotwait.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/error.exp =================================================================== --- head/contrib/bmake/unit-tests/error.exp (nonexistent) +++ head/contrib/bmake/unit-tests/error.exp (revision 281812) @@ -0,0 +1,4 @@ +make: "error.mk" line 3: just FYI +make: "error.mk" line 4: warning: this could be serious +make: "error.mk" line 5: this is fatal +exit status 1 Index: head/contrib/bmake/unit-tests/error.mk =================================================================== --- head/contrib/bmake/unit-tests/error.mk (nonexistent) +++ head/contrib/bmake/unit-tests/error.mk (revision 281812) @@ -0,0 +1,10 @@ +# $Id: error.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +.info just FYI +.warning this could be serious +.error this is fatal + +all: + +.info.html: + @echo this should be ignored Property changes on: head/contrib/bmake/unit-tests/error.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/escape.exp =================================================================== --- head/contrib/bmake/unit-tests/escape.exp (nonexistent) +++ head/contrib/bmake/unit-tests/escape.exp (revision 281812) @@ -0,0 +1,104 @@ +var-1bs +printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ; +VAR1BS=:111\111: +VAR1BSa=:111\aaa: +VAR1BSA=:111\aaa: +VAR1BSda=:111\${a}: +VAR1BSdA=:111\${A}: +VAR1BSc=:111# backslash escapes comment char, so this is part of the value: +VAR1BSsc=:111\ : +var-2bs +printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\; +VAR2BS=:222\\222: +VAR2BSa=:222\\aaa: +VAR2BSA=:222\\aaa: +VAR2BSda=:222\\${a}: +VAR2BSdA=:222\\${A}: +VAR2BSc=:222\\: +VAR2BSsc=:222\\: +var-1bsnl +printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111; +VAR1BSNL=:111 111: +VAR1BSNLa=:111 aaa: +VAR1BSNLA=:111 aaa: +VAR1BSNLda=:111 ${a}: +VAR1BSNLdA=:111 ${A}: +VAR1BSNLc=:111: +VAR1BSNLsc=:111: +var-2bsnl +printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\; +VAR2BSNL=:222\\: +VAR2BSNLa=:222\\: +VAR2BSNLA=:222\\: +VAR2BSNLda=:222\\: +VAR2BSNLdA=:222\\: +VAR2BSNLc=:222\\: +VAR2BSNLsc=:222\\: +var-3bsnl +printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\; +VAR3BSNL=:333\\ 333=: +VAR3BSNLa=:333\\ aaa=: +VAR3BSNLA=:333\\ aaa=: +VAR3BSNLda=:333\\ ${a}=: +VAR3BSNLdA=:333\\ ${A}=: +VAR3BSNLc=:333\\: +VAR3BSNLsc=:333\\: +var-1bsnl-space +printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line; +VAR1BSNL00=:first line: +VAR1BSNL0=:first line no space on second line: +VAR1BSNLs=:first line one space on second line: +VAR1BSNLss=:first line two spaces on second line: +VAR1BSNLt=:first line one tab on second line: +VAR1BSNLtt=:first line two tabs on second line: +VAR1BSNLxx=:first line many spaces and tabs [ ] on second line: +cmd-1bsnl +echo :'first line\ +#second line without space\ +third line': +:first line\ +#second line without space\ +third line: +echo :'first line\ + second line spaces should be retained': +:first line\ + second line spaces should be retained: +echo :'first line\ +second line tab should be elided': +:first line\ +second line tab should be elided: +echo :'first line\ + only one tab should be elided, second tab remains' +:first line\ + only one tab should be elided, second tab remains +cmd-1bsnl-eof +echo :'command ending with backslash-newline'; \ + +:command ending with backslash-newline +cmd-2bsnl +echo take one\\ +take one\ +echo take two\\ +take two\ +echo take three\\ +take three\ +cmd-3bsnl +echo :'first line\\\ +#second line without space\\\ +third line': +:first line\\\ +#second line without space\\\ +third line: +echo :'first line\\\ + second line spaces should be retained': +:first line\\\ + second line spaces should be retained: +echo :'first line\\\ +second line tab should be elided': +:first line\\\ +second line tab should be elided: +echo :'first line\\\ + only one tab should be elided, second tab remains' +:first line\\\ + only one tab should be elided, second tab remains +exit status 0 Index: head/contrib/bmake/unit-tests/escape.mk =================================================================== --- head/contrib/bmake/unit-tests/escape.mk (nonexistent) +++ head/contrib/bmake/unit-tests/escape.mk (revision 281812) @@ -0,0 +1,246 @@ +# $Id: escape.mk,v 1.1.1.2 2014/11/06 01:40:37 sjg Exp $ +# +# Test backslash escaping. + +# Extracts from the POSIX 2008 specification +# : +# +# Comments start with a ( '#' ) and continue until an +# unescaped is reached. +# +# When an escaped (one preceded by a ) is found +# anywhere in the makefile except in a command line, an include +# line, or a line immediately preceding an include line, it shall +# be replaced, along with any leading white space on the following +# line, with a single . +# +# When an escaped is found in a command line in a +# makefile, the command line shall contain the , the +# , and the next line, except that the first character of +# the next line shall not be included if it is a . +# +# When an escaped is found in an include line or in a +# line immediately preceding an include line, the behavior is +# unspecified. +# +# Notice that the behaviour of or +# is not mentioned. I think +# this implies that should be taken literally everywhere +# except before . +# +# Our practice, despite what POSIX might say, is that "\#" +# in a variable assignment stores "#" as part of the value. +# The "\" is not taken literally, and the "#" does not begin a comment. +# +# Also, our practice is that an even number of backslashes before a +# newline in a variable assignment simply stores the backslashes as part +# of the value, and treats the newline as though it was not escaped. +# Similarly, ann even number of backslashes before a newline in a +# command simply uses the backslashes as part of the command test, but +# does not escape the newline. This is compatible with GNU make. + +all: .PHONY +# We will add dependencies like "all: yet-another-test" later. + +# Some variables to be expanded in tests +# +a = aaa +A = ${a} + +# Backslash at end of line in a comment\ +should continue the comment. \ +# This is also tested in comment.mk. + +__printvars: .USE .MADE + @echo ${.TARGET} + ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @} + +# Embedded backslash in variable should be taken literally. +# +VAR1BS = 111\111 +VAR1BSa = 111\${a} +VAR1BSA = 111\${A} +VAR1BSda = 111\$${a} +VAR1BSdA = 111\$${A} +VAR1BSc = 111\# backslash escapes comment char, so this is part of the value +VAR1BSsc = 111\ # This is a comment. Value ends with + +all: var-1bs +var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ + VAR1BSc VAR1BSsc + +# Double backslash in variable should be taken as two literal backslashes. +# +VAR2BS = 222\\222 +VAR2BSa = 222\\${a} +VAR2BSA = 222\\${A} +VAR2BSda = 222\\$${a} +VAR2BSdA = 222\\$${A} +VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment +VAR2BSsc = 222\\ # This is a comment. Value ends with + +all: var-2bs +var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ + VAR2BSc VAR2BSsc + +# Backslash-newline in a variable setting is replaced by a single space. +# +VAR1BSNL = 111\ +111 +VAR1BSNLa = 111\ +${a} +VAR1BSNLA = 111\ +${A} +VAR1BSNLda = 111\ +$${a} +VAR1BSNLdA = 111\ +$${A} +VAR1BSNLc = 111\ +# this should be processed as a comment +VAR1BSNLsc = 111\ + # this should be processed as a comment + +all: var-1bsnl +var-1bsnl: .PHONY +var-1bsnl: .PHONY __printvars \ + VAR1BSNL VAR1BSNLa VAR1BSNLA VAR1BSNLda VAR1BSNLdA \ + VAR1BSNLc VAR1BSNLsc + +# Double-backslash-newline in a variable setting. +# Both backslashes should be taken literally, and the newline is NOT escaped. +# +# The second lines below each end with '=' so that they will not +# generate syntax errors regardless of whether or not they are +# treated as part of the value. +# +VAR2BSNL = 222\\ +222= +VAR2BSNLa = 222\\ +${a}= +VAR2BSNLA = 222\\ +${A}= +VAR2BSNLda = 222\\ +$${a}= +VAR2BSNLdA = 222\\ +$${A}= +VAR2BSNLc = 222\\ +# this should be processed as a comment +VAR2BSNLsc = 222\\ + # this should be processed as a comment + +all: var-2bsnl +var-2bsnl: .PHONY __printvars \ + VAR2BSNL VAR2BSNLa VAR2BSNLA VAR2BSNLda VAR2BSNLdA \ + VAR2BSNLc VAR2BSNLsc + +# Triple-backslash-newline in a variable setting. +# First two should be taken literally, and last should escape the newline. +# +# The second lines below each end with '=' so that they will not +# generate syntax errors regardless of whether or not they are +# treated as part of the value. +# +VAR3BSNL = 333\\\ +333= +VAR3BSNLa = 333\\\ +${a}= +VAR3BSNLA = 333\\\ +${A}= +VAR3BSNLda = 333\\\ +$${a}= +VAR3BSNLdA = 333\\\ +$${A}= +VAR3BSNLc = 333\\\ +# this should be processed as a comment +VAR3BSNLsc = 333\\\ + # this should be processed as a comment + +all: var-3bsnl +var-3bsnl: .PHONY __printvars \ + VAR3BSNL VAR3BSNLa VAR3BSNLA VAR3BSNLda VAR3BSNLdA \ + VAR3BSNLc VAR3BSNLsc + +# Backslash-newline in a variable setting, plus any amount of white space +# on the next line, is replaced by a single space. +# +VAR1BSNL00= first line\ + +# above line is entirely empty, and this is a comment +VAR1BSNL0= first line\ +no space on second line +VAR1BSNLs= first line\ + one space on second line +VAR1BSNLss= first line\ + two spaces on second line +VAR1BSNLt= first line\ + one tab on second line +VAR1BSNLtt= first line\ + two tabs on second line +VAR1BSNLxx= first line\ + many spaces and tabs [ ] on second line + +all: var-1bsnl-space +var-1bsnl-space: .PHONY __printvars \ + VAR1BSNL00 VAR1BSNL0 VAR1BSNLs VAR1BSNLss VAR1BSNLt VAR1BSNLtt \ + VAR1BSNLxx + +# Backslash-newline in a command is retained. +# +# The "#" in "# second line without space" makes it a comment instead +# of a syntax error if the preceding line is parsed incorretly. +# The ":" in "third line':" makes it look like the start of a +# target instead of a syntax error if the first line is parsed incorrectly. +# +all: cmd-1bsnl +cmd-1bsnl: .PHONY + @echo ${.TARGET} + echo :'first line\ +#second line without space\ +third line': + echo :'first line\ + second line spaces should be retained': + echo :'first line\ + second line tab should be elided': + echo :'first line\ + only one tab should be elided, second tab remains' + +# When backslash-newline appears at the end of a command script, +# both the backslash and the newline should be passed to the shell. +# The shell should elide the backslash-newline. +# +all: cmd-1bsnl-eof +cmd-1bsnl-eof: + @echo ${.TARGET} + echo :'command ending with backslash-newline'; \ + +# above line must be blank + +# Double-backslash-newline in a command. +# Both backslashes are retained, but the newline is not escaped. +# XXX: This may differ from POSIX, but matches gmake. +# +# When make passes two backslashes to the shell, the shell will pass one +# backslash to the echo commant. +# +all: cmd-2bsnl +cmd-2bsnl: .PHONY + @echo ${.TARGET} + echo take one\\ +# this should be a comment + echo take two\\ + echo take three\\ + +# Triple-backslash-newline in a command is retained. +# +all: cmd-3bsnl +cmd-3bsnl: .PHONY + @echo ${.TARGET} + echo :'first line\\\ +#second line without space\\\ +third line': + echo :'first line\\\ + second line spaces should be retained': + echo :'first line\\\ + second line tab should be elided': + echo :'first line\\\ + only one tab should be elided, second tab remains' Property changes on: head/contrib/bmake/unit-tests/escape.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/export-all.exp =================================================================== --- head/contrib/bmake/unit-tests/export-all.exp (nonexistent) +++ head/contrib/bmake/unit-tests/export-all.exp (revision 281812) @@ -0,0 +1,12 @@ +UT_ALL=even this gets exported +UT_BADDIR=unit-tests +UT_DOLLAR=This is $UT_FU +UT_F=fine +UT_FOO=foobar is fubar +UT_FU=fubar +UT_NO=all +UT_OK=good +UT_OKDIR=unit-tests +UT_TEST=export-all +UT_ZOO=hoopie +exit status 0 Index: head/contrib/bmake/unit-tests/export-all.mk =================================================================== --- head/contrib/bmake/unit-tests/export-all.mk (nonexistent) +++ head/contrib/bmake/unit-tests/export-all.mk (revision 281812) @@ -0,0 +1,23 @@ +# $Id: export-all.mk,v 1.1.1.2 2015/04/10 20:43:38 sjg Exp $ + +UT_OK=good +UT_F=fine + +# the old way to do :tA +M_tAbad = C,.*,cd & \&\& 'pwd',:sh +# the new +M_tA = tA + +here := ${.PARSEDIR} + +# this will cause trouble (recursing if we let it) +UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T} +# this will be ok +UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T} + +.export + +.include "export.mk" + +UT_TEST=export-all +UT_ALL=even this gets exported Property changes on: head/contrib/bmake/unit-tests/export-all.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/export-env.exp =================================================================== --- head/contrib/bmake/unit-tests/export-env.exp (nonexistent) +++ head/contrib/bmake/unit-tests/export-env.exp (revision 281812) @@ -0,0 +1,9 @@ +make: +UT_TEST=export-env.mk +UT_ENV=not-exported +UT_EXP=not-exported +env: +UT_TEST=export-env.mk +UT_ENV=exported +UT_EXP=exported +exit status 0 Index: head/contrib/bmake/unit-tests/export-env.mk =================================================================== --- head/contrib/bmake/unit-tests/export-env.mk (nonexistent) +++ head/contrib/bmake/unit-tests/export-env.mk (revision 281812) @@ -0,0 +1,24 @@ +# $Id: export-env.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +# our normal .export, subsequent changes affect the environment +UT_TEST=this +.export UT_TEST +UT_TEST:= ${.PARSEFILE} + +# not so with .export-env +UT_ENV=exported +.export-env UT_ENV +UT_ENV=not-exported + +# gmake style export goes further; affects nothing but the environment +UT_EXP=before-export +export UT_EXP=exported +UT_EXP=not-exported + +all: + @echo make:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=${$v};@} + @echo env:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=$${$v};@} + + + + Property changes on: head/contrib/bmake/unit-tests/export-env.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/export.exp =================================================================== --- head/contrib/bmake/unit-tests/export.exp (nonexistent) +++ head/contrib/bmake/unit-tests/export.exp (revision 281812) @@ -0,0 +1,6 @@ +UT_DOLLAR=This is $UT_FU +UT_FOO=foobar is fubar +UT_FU=fubar +UT_TEST=export +UT_ZOO=hoopie +exit status 0 Index: head/contrib/bmake/unit-tests/export.mk =================================================================== --- head/contrib/bmake/unit-tests/export.mk (nonexistent) +++ head/contrib/bmake/unit-tests/export.mk (revision 281812) @@ -0,0 +1,22 @@ +# $Id: export.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +UT_TEST=export +UT_FOO=foo${BAR} +UT_FU=fubar +UT_ZOO=hoopie +UT_NO=all +# belive it or not, we expect this one to come out with $UT_FU unexpanded. +UT_DOLLAR= This is $$UT_FU + +.export UT_FU UT_FOO +.export UT_DOLLAR +# this one will be ignored +.export .MAKE.PID + +BAR=bar is ${UT_FU} + +.MAKE.EXPORTED+= UT_ZOO UT_TEST + +all: + @env | grep '^UT_' | sort + Property changes on: head/contrib/bmake/unit-tests/export.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/forloop.exp =================================================================== --- head/contrib/bmake/unit-tests/forloop.exp (nonexistent) +++ head/contrib/bmake/unit-tests/forloop.exp (revision 281812) @@ -0,0 +1,19 @@ +x=one +x="two and three" +x=four +x="five" +x=-I/this +x=-I"This or that" +x=-Ithat +x="-DTHIS=\"this and that\"" +cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +a=one b="two and three" +a=four b="five" +a=ONE b="TWO AND THREE" +a=FOUR b="FIVE" +We expect an error next: +make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +OK +exit status 0 Index: head/contrib/bmake/unit-tests/forloop.mk =================================================================== --- head/contrib/bmake/unit-tests/forloop.mk (nonexistent) +++ head/contrib/bmake/unit-tests/forloop.mk (revision 281812) @@ -0,0 +1,45 @@ +# $Id: forloop.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +all: for-loop + +LIST = one "two and three" four "five" + +.if make(for-fail) +for-fail: + +XTRA_LIST = xtra +.else + +.for x in ${LIST} +X!= echo 'x=$x' >&2; echo +.endfor + +CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +cfl= +.for x in ${CFL} +X!= echo 'x=$x' >&2; echo +.if empty(cfl) +cfl= $x +.else +cfl+= $x +.endif +.endfor +X!= echo 'cfl=${cfl}' >&2; echo + +.if ${cfl} != ${CFL} +.error ${.newline}'${cfl}' != ${.newline}'${CFL}' +.endif + +.for a b in ${EMPTY} +X!= echo 'a=$a b=$b' >&2; echo +.endfor +.endif + +.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} +X!= echo 'a=$a b=$b' >&2; echo +.endfor + +for-loop: + @echo We expect an error next: + @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \ + { echo "Oops that should have failed!"; exit 1; } || echo OK Property changes on: head/contrib/bmake/unit-tests/forloop.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/forsubst.exp =================================================================== --- head/contrib/bmake/unit-tests/forsubst.exp (nonexistent) +++ head/contrib/bmake/unit-tests/forsubst.exp (revision 281812) @@ -0,0 +1,2 @@ +.for with :S;... OK +exit status 0 Index: head/contrib/bmake/unit-tests/forsubst.mk =================================================================== --- head/contrib/bmake/unit-tests/forsubst.mk (nonexistent) +++ head/contrib/bmake/unit-tests/forsubst.mk (revision 281812) @@ -0,0 +1,10 @@ +# $Id: forsubst.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +all: for-subst + +here := ${.PARSEDIR} +# this should not run foul of the parser +.for file in ${.PARSEFILE} +for-subst: ${file:S;^;${here}/;g} + @echo ".for with :S;... OK" +.endfor Property changes on: head/contrib/bmake/unit-tests/forsubst.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/hash.exp =================================================================== --- head/contrib/bmake/unit-tests/hash.exp (nonexistent) +++ head/contrib/bmake/unit-tests/hash.exp (revision 281812) @@ -0,0 +1,9 @@ +b2af338b +3360ac65 +7747f046 +9ca87054 +880fe816 +208fcbd3 +d5d376eb +de41416c +exit status 0 Index: head/contrib/bmake/unit-tests/hash.mk =================================================================== --- head/contrib/bmake/unit-tests/hash.mk (nonexistent) +++ head/contrib/bmake/unit-tests/hash.mk (revision 281812) @@ -0,0 +1,18 @@ +STR1= +STR2= a +STR3= ab +STR4= abc +STR5= abcd +STR6= abcde +STR7= abcdef +STR8= abcdefghijklmnopqrstuvwxyz + +all: + @echo ${STR1:hash} + @echo ${STR2:hash} + @echo ${STR3:hash} + @echo ${STR4:hash} + @echo ${STR5:hash} + @echo ${STR6:hash} + @echo ${STR7:hash} + @echo ${STR8:hash} Property changes on: head/contrib/bmake/unit-tests/hash.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/impsrc.exp =================================================================== --- head/contrib/bmake/unit-tests/impsrc.exp (nonexistent) +++ head/contrib/bmake/unit-tests/impsrc.exp (revision 281812) @@ -0,0 +1,13 @@ +expected: source4 +actual: source4 +expected: target1.x +actual: target1.x +expected: target1.y +actual: target1.y +expected: source1 +actual: source1 +expected: source2 +actual: source2 +expected: source1 +actual: source1 +exit status 0 Index: head/contrib/bmake/unit-tests/impsrc.mk =================================================================== --- head/contrib/bmake/unit-tests/impsrc.mk (nonexistent) +++ head/contrib/bmake/unit-tests/impsrc.mk (revision 281812) @@ -0,0 +1,43 @@ +# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $ + +# Does ${.IMPSRC} work properly? +# It should be set, in order of precedence, to ${.TARGET} of: +# 1) the implied source of a transformation rule, +# 2) the first prerequisite from the dependency line of an explicit rule, or +# 3) the first prerequisite of an explicit rule. +# + +all: target1.z target2 target3 target4 + +.SUFFIXES: .x .y .z + +.x.y: source1 + @echo 'expected: target1.x' + @echo 'actual: $<' + +.y.z: source2 + @echo 'expected: target1.y' + @echo 'actual: $<' + +target1.y: source3 + +target1.x: source4 + @echo 'expected: source4' + @echo 'actual: $<' + +target2: source1 source2 + @echo 'expected: source1' + @echo 'actual: $<' + +target3: source1 +target3: source2 source3 + @echo 'expected: source2' + @echo 'actual: $<' + +target4: source1 +target4: + @echo 'expected: source1' + @echo 'actual: $<' + +source1 source2 source3 source4: + Property changes on: head/contrib/bmake/unit-tests/impsrc.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/misc.exp =================================================================== --- head/contrib/bmake/unit-tests/misc.exp (nonexistent) +++ head/contrib/bmake/unit-tests/misc.exp (revision 281812) @@ -0,0 +1 @@ +exit status 0 Index: head/contrib/bmake/unit-tests/misc.mk =================================================================== --- head/contrib/bmake/unit-tests/misc.mk (nonexistent) +++ head/contrib/bmake/unit-tests/misc.mk (revision 281812) @@ -0,0 +1,16 @@ +# $Id: misc.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +.if !exists(${.CURDIR}/) +.warning ${.CURDIR}/ doesn't exist ? +.endif + +.if !exists(${.CURDIR}/.) +.warning ${.CURDIR}/. doesn't exist ? +.endif + +.if !exists(${.CURDIR}/..) +.warning ${.CURDIR}/.. doesn't exist ? +.endif + +all: + @: all is well Property changes on: head/contrib/bmake/unit-tests/misc.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/moderrs.exp =================================================================== --- head/contrib/bmake/unit-tests/moderrs.exp (nonexistent) +++ head/contrib/bmake/unit-tests/moderrs.exp (revision 281812) @@ -0,0 +1,16 @@ +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unclosed variable specification for VAR +make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S +VAR:S,V,v,=Thevariable +Expect: Unclosed variable specification for VAR +make: Unclosed variable specification after complex modifier (expecting '}') for VAR +VAR:S,V,v,=Thevariable +Expect: Unclosed substitution for VAR (, missing) +make: Unclosed substitution for VAR (, missing) +VAR:S,V,v= +exit status 0 Index: head/contrib/bmake/unit-tests/moderrs.mk =================================================================== --- head/contrib/bmake/unit-tests/moderrs.mk (nonexistent) +++ head/contrib/bmake/unit-tests/moderrs.mk (revision 281812) @@ -0,0 +1,31 @@ +# $Id: moderrs.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# +# various modifier error tests + +VAR=TheVariable +# incase we have to change it ;-) +MOD_UNKN=Z +MOD_TERM=S,V,v +MOD_S:= ${MOD_TERM}, + +all: modunkn modunknV varterm vartermV modtermV + +modunkn: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:Z=${VAR:Z}" + +modunknV: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" + +varterm: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:S,V,v,=${VAR:S,V,v, + +vartermV: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:${MOD_TERM},=${VAR:${MOD_S} + +modtermV: + @echo "Expect: Unclosed substitution for VAR (, missing)" + -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" Property changes on: head/contrib/bmake/unit-tests/moderrs.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/modmatch.exp =================================================================== --- head/contrib/bmake/unit-tests/modmatch.exp (nonexistent) +++ head/contrib/bmake/unit-tests/modmatch.exp (revision 281812) @@ -0,0 +1,17 @@ +LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a" +LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a" +LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A" +LIB=b X_LIBS:M${LIB${LIB:tu}} is "" +LIB=b X_LIBS:M*/lib${LIB}.a is "" +LIB=b X_LIBS:M*/lib${LIB}.a:tu is "" +LIB=c X_LIBS:M${LIB${LIB:tu}} is "" +LIB=c X_LIBS:M*/lib${LIB}.a is "" +LIB=c X_LIBS:M*/lib${LIB}.a:tu is "" +LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a" +LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a" +LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A" +LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a" +LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a" +LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A" +Mscanner=OK +exit status 0 Index: head/contrib/bmake/unit-tests/modmatch.mk =================================================================== --- head/contrib/bmake/unit-tests/modmatch.mk (nonexistent) +++ head/contrib/bmake/unit-tests/modmatch.mk (revision 281812) @@ -0,0 +1,25 @@ + +X=a b c d e + +.for x in $X +LIB${x:tu}=/tmp/lib$x.a +.endfor + +X_LIBS= ${LIBA} ${LIBD} ${LIBE} + +LIB?=a + +var = head +res = no +.if !empty(var:M${:Uhead\:tail:C/:.*//}) +res = OK +.endif + +all: + @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done + @echo "Mscanner=${res}" + +show: + @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"' + @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"' + @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"' Property changes on: head/contrib/bmake/unit-tests/modmatch.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/modmisc.exp =================================================================== --- head/contrib/bmake/unit-tests/modmisc.exp (nonexistent) +++ head/contrib/bmake/unit-tests/modmisc.exp (revision 281812) @@ -0,0 +1,10 @@ +path=':/bin:/tmp::/:.:/no/such/dir:.' +path='/bin:/tmp:/:/no/such/dir' +path='/bin:/tmp:/:/no/such/dir' +path='/bin':'/tmp':'/':'/no/such/dir' +path='/bin':'/tmp':'/':'/no/such/dir' +path_/usr/xbin=/opt/xbin/ +paths=/bin /tmp / /no/such/dir /opt/xbin +PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN +The answer is 42 +exit status 0 Index: head/contrib/bmake/unit-tests/modmisc.mk =================================================================== --- head/contrib/bmake/unit-tests/modmisc.mk (nonexistent) +++ head/contrib/bmake/unit-tests/modmisc.mk (revision 281812) @@ -0,0 +1,38 @@ +# $Id: modmisc.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# +# miscellaneous modifier tests + +# do not put any dirs in this list which exist on some +# but not all target systems - an exists() check is below. +path=:/bin:/tmp::/:.:/no/such/dir:. +# strip cwd from path. +MOD_NODOT=S/:/ /g:N.:ts: +# and decorate, note that $'s need to be doubled. Also note that +# the modifier_variable can be used with other modifiers. +MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ +# another mod - pretend it is more interesting +MOD_HOMES=S,/home/,/homes/, +MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ +MOD_SEP=S,:, ,g + +all: modvar modvarloop modsysv + +modsysv: + @echo "The answer is ${libfoo.a:L:libfoo.a=42}" + +modvar: + @echo "path='${path}'" + @echo "path='${path:${MOD_NODOT}}'" + @echo "path='${path:S,home,homes,:${MOD_NODOT}}'" + @echo "path=${path:${MOD_NODOTX}:ts:}" + @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" + +.for d in ${path:${MOD_SEP}:N.} /usr/xbin +path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ +paths+= ${d:${MOD_OPT}:${MOD_HOMES}} +.endfor + +modvarloop: + @echo "path_/usr/xbin=${path_/usr/xbin}" + @echo "paths=${paths}" + @echo "PATHS=${paths:tu}" Property changes on: head/contrib/bmake/unit-tests/modmisc.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/modorder.exp =================================================================== --- head/contrib/bmake/unit-tests/modorder.exp (nonexistent) +++ head/contrib/bmake/unit-tests/modorder.exp (revision 281812) @@ -0,0 +1,11 @@ +LIST = one two three four five six seven eight nine ten +LIST:O = eight five four nine one seven six ten three two +LIST:Ox = Ok +LIST:O:Ox = Ok +LISTX = Ok +LISTSX = Ok +make: Bad modifier `:OX' for LIST +BADMOD 1 = } +make: Bad modifier `:OxXX' for LIST +BADMOD 2 = XX} +exit status 0 Index: head/contrib/bmake/unit-tests/modorder.mk =================================================================== --- head/contrib/bmake/unit-tests/modorder.mk (nonexistent) +++ head/contrib/bmake/unit-tests/modorder.mk (revision 281812) @@ -0,0 +1,22 @@ +# $NetBSD: modorder.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +LIST= one two three four five six seven eight nine ten +LISTX= ${LIST:Ox} +LISTSX:= ${LIST:Ox} +TEST_RESULT= && echo Ok || echo Failed + +# unit-tests have to produce the same results on each run +# so we cannot actually include :Ox output. +all: + @echo "LIST = ${LIST}" + @echo "LIST:O = ${LIST:O}" + # Note that 1 in every 10! trials two independently generated + # randomized orderings will be the same. The test framework doesn't + # support checking probabilistic output, so we accept that the test + # will incorrectly fail with probability 2.8E-7. + @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`" + @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`" + @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`" + @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`" + @echo "BADMOD 1 = ${LIST:OX}" + @echo "BADMOD 2 = ${LIST:OxXX}" Property changes on: head/contrib/bmake/unit-tests/modorder.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/modts.exp =================================================================== --- head/contrib/bmake/unit-tests/modts.exp (nonexistent) +++ head/contrib/bmake/unit-tests/modts.exp (revision 281812) @@ -0,0 +1,33 @@ +LIST="one two three four five six" +LIST:ts,="one,two,three,four,five,six" +LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX" +LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX" +LIST:ts:tu="ONETWOTHREEFOURFIVESIX" +LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX" +LIST:ts:="one:two:three:four:five:six" +LIST:ts="onetwothreefourfivesix" +LIST:ts:S/two/2/="one2threefourfivesix" +LIST:S/two/2/:ts="one2threefourfivesix" +LIST:ts/:S/two/2/="one/2/three/four/five/six" +Pretend the '/' in '/n' etc. below are back-slashes. +LIST:ts/n="one +two +three +four +five +six" +LIST:ts/t="one two three four five six" +LIST:ts/012:tu="ONE +TWO +THREE +FOUR +FIVE +SIX" +make: Bad modifier `:tx' for LIST +LIST:tx="}" +make: Bad modifier `:ts\x' for LIST +LIST:ts/x:tu="\x:tu}" +FU_mod-ts="a/b/cool" +FU_mod-ts:ts:T="cool" == cool? +B.${AAA:ts}="Baaa" == Baaa? +exit status 0 Index: head/contrib/bmake/unit-tests/modts.mk =================================================================== --- head/contrib/bmake/unit-tests/modts.mk (nonexistent) +++ head/contrib/bmake/unit-tests/modts.mk (revision 281812) @@ -0,0 +1,43 @@ + +LIST= one two three +LIST+= four five six + +FU_mod-ts = a / b / cool + +AAA= a a a +B.aaa= Baaa + +all: mod-ts + +# Use print or printf iff they are builtin. +# XXX note that this causes problems, when make decides +# there is no need to use a shell, so avoid where possible. +.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != "" +PRINT= print -r -- +.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != "" +PRINT= printf '%s\n' +.else +PRINT= echo +.endif + +mod-ts: + @echo 'LIST="${LIST}"' + @echo 'LIST:ts,="${LIST:ts,}"' + @echo 'LIST:ts/:tu="${LIST:ts/:tu}"' + @echo 'LIST:ts::tu="${LIST:ts::tu}"' + @echo 'LIST:ts:tu="${LIST:ts:tu}"' + @echo 'LIST:tu:ts/="${LIST:tu:ts/}"' + @echo 'LIST:ts:="${LIST:ts:}"' + @echo 'LIST:ts="${LIST:ts}"' + @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"' + @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"' + @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"' + @echo "Pretend the '/' in '/n' etc. below are back-slashes." + @${PRINT} 'LIST:ts/n="${LIST:ts\n}"' + @${PRINT} 'LIST:ts/t="${LIST:ts\t}"' + @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"' + @${PRINT} 'LIST:tx="${LIST:tx}"' + @${PRINT} 'LIST:ts/x:tu="${LIST:ts\x:tu}"' + @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"' + @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?' + @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?' Property changes on: head/contrib/bmake/unit-tests/modts.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/modword.exp =================================================================== --- head/contrib/bmake/unit-tests/modword.exp (nonexistent) +++ head/contrib/bmake/unit-tests/modword.exp (revision 281812) @@ -0,0 +1,122 @@ +make: Bad modifier `:[]' for LIST +LIST:[]="" is an error +LIST:[0]="one two three four five six" +LIST:[0x0]="one two three four five six" +LIST:[000]="one two three four five six" +LIST:[*]="one two three four five six" +LIST:[@]="one two three four five six" +LIST:[0]:C/ /,/="one,two three four five six" +LIST:[0]:C/ /,/g="one,two,three,four,five,six" +LIST:[0]:C/ /,/1g="one,two,three,four,five,six" +LIST:[*]:C/ /,/="one,two three four five six" +LIST:[*]:C/ /,/g="one,two,three,four,five,six" +LIST:[*]:C/ /,/1g="one,two,three,four,five,six" +LIST:[@]:C/ /,/="one two three four five six" +LIST:[@]:C/ /,/g="one two three four five six" +LIST:[@]:C/ /,/1g="one two three four five six" +LIST:[@]:[0]:C/ /,/="one,two three four five six" +LIST:[0]:[@]:C/ /,/="one two three four five six" +LIST:[@]:[*]:C/ /,/="one,two three four five six" +LIST:[*]:[@]:C/ /,/="one two three four five six" +EMPTY="" +EMPTY:[#]="1" == 1 ? +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[#]="1" == 1 ? +REALLYSPACE=" " +REALLYSPACE:[#]="1" == 1 ? +LIST:[#]="6" +LIST:[0]:[#]="1" == 1 ? +LIST:[*]:[#]="1" == 1 ? +LIST:[@]:[#]="6" +LIST:[1]:[#]="1" +LIST:[1..3]:[#]="3" +EMPTY:[1]="" +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[1]="\ " +REALLYSPACE=" " +REALLYSPACE:[1]="" == "" ? +REALLYSPACE:[*]:[1]=" " == " " ? +LIST:[1]="one" +make: Bad modifier `:[1.]' for LIST +LIST:[1.]="" is an error +make: Bad modifier `:[1].' for LIST +LIST:[1].="}" is an error +LIST:[2]="two" +LIST:[6]="six" +LIST:[7]="" +LIST:[999]="" +make: Bad modifier `:[-]' for LIST +LIST:[-]="" is an error +make: Bad modifier `:[--]' for LIST +LIST:[--]="" is an error +LIST:[-1]="six" +LIST:[-2]="five" +LIST:[-6]="one" +LIST:[-7]="" +LIST:[-999]="" +LONGLIST:[17]="17" +LONGLIST:[0x11]="17" +LONGLIST:[021]="17" +LIST:[0]:[1]="one two three four five six" +LIST:[*]:[1]="one two three four five six" +LIST:[@]:[1]="one" +LIST:[0]:[2]="" +LIST:[*]:[2]="" +LIST:[@]:[2]="two" +LIST:[*]:C/ /,/:[2]="" +LIST:[*]:C/ /,/:[*]:[2]="" +LIST:[*]:C/ /,/:[@]:[2]="three" +make: Bad modifier `:[1.]' for LIST +LIST:[1.]="" is an error +make: Bad modifier `:[1..]' for LIST +LIST:[1..]="" is an error +LIST:[1..1]="one" +make: Bad modifier `:[1..1.]' for LIST +LIST:[1..1.]="" is an error +LIST:[1..2]="one two" +LIST:[2..1]="two one" +LIST:[3..-2]="three four five" +LIST:[-4..4]="three four" +make: Bad modifier `:[0..1]' for LIST +LIST:[0..1]="" is an error +make: Bad modifier `:[-1..0]' for LIST +LIST:[-1..0]="" is an error +LIST:[-1..1]="six five four three two one" +LIST:[0..0]="one two three four five six" +LIST:[3..99]="three four five six" +LIST:[-3..-99]="four three two one" +LIST:[-99..-3]="one two three four" +HASH="#" == "#" ? +LIST:[${HASH}]="6" +LIST:[${ZERO}]="one two three four five six" +LIST:[${ZERO}x${ONE}]="one" +LIST:[${ONE}]="one" +LIST:[${MINUSONE}]="six" +LIST:[${STAR}]="one two three four five six" +LIST:[${AT}]="one two three four five six" +make: Bad modifier `:[${EMPTY' for LIST +LIST:[${EMPTY}]="" is an error +LIST:[${LONGLIST:[21]:S/2//}]="one" +LIST:[${LIST:[#]}]="six" +LIST:[${LIST:[${HASH}]}]="six" +LIST:S/ /,/="one two three four five six" +LIST:S/ /,/W="one,two three four five six" +LIST:S/ /,/gW="one,two,three,four,five,six" +EMPTY:S/^/,/="," +EMPTY:S/^/,/W="," +LIST:C/ /,/="one two three four five six" +LIST:C/ /,/W="one,two three four five six" +LIST:C/ /,/gW="one,two,three,four,five,six" +EMPTY:C/^/,/="," +EMPTY:C/^/,/W="," +LIST:tW="one two three four five six" +LIST:tw="one two three four five six" +LIST:tW:C/ /,/="one,two three four five six" +LIST:tW:C/ /,/g="one,two,three,four,five,six" +LIST:tW:C/ /,/1g="one,two,three,four,five,six" +LIST:tw:C/ /,/="one two three four five six" +LIST:tw:C/ /,/g="one two three four five six" +LIST:tw:C/ /,/1g="one two three four five six" +LIST:tw:tW:C/ /,/="one,two three four five six" +LIST:tW:tw:C/ /,/="one two three four five six" +exit status 0 Index: head/contrib/bmake/unit-tests/modword.mk =================================================================== --- head/contrib/bmake/unit-tests/modword.mk (nonexistent) +++ head/contrib/bmake/unit-tests/modword.mk (revision 281812) @@ -0,0 +1,151 @@ +# $Id: modword.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# +# Test behaviour of new :[] modifier + +all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw + +LIST= one two three +LIST+= four five six +LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + +EMPTY= # the space should be ignored +ESCAPEDSPACE=\ # escaped space before the '#' +REALLYSPACE:=${EMPTY:C/^/ /W} +HASH= \# +AT= @ +STAR= * +ZERO= 0 +ONE= 1 +MINUSONE= -1 + +mod-squarebrackets: mod-squarebrackets-0-star-at \ + mod-squarebrackets-hash \ + mod-squarebrackets-n \ + mod-squarebrackets-start-end \ + mod-squarebrackets-nested + +mod-squarebrackets-0-star-at: + @echo 'LIST:[]="${LIST:[]}" is an error' + @echo 'LIST:[0]="${LIST:[0]}"' + @echo 'LIST:[0x0]="${LIST:[0x0]}"' + @echo 'LIST:[000]="${LIST:[000]}"' + @echo 'LIST:[*]="${LIST:[*]}"' + @echo 'LIST:[@]="${LIST:[@]}"' + @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' + @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' + @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' + @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' + @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' + @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' + @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' + @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' + @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' + @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' + @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' + @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' + @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' + +mod-squarebrackets-hash: + @echo 'EMPTY="${EMPTY}"' + @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' + @echo 'LIST:[#]="${LIST:[#]}"' + @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' + @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' + @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' + @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' + @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' + +mod-squarebrackets-n: + @echo 'EMPTY:[1]="${EMPTY:[1]}"' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' + @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' + @echo 'LIST:[1]="${LIST:[1]}"' + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1].="${LIST:[1].}" is an error' + @echo 'LIST:[2]="${LIST:[2]}"' + @echo 'LIST:[6]="${LIST:[6]}"' + @echo 'LIST:[7]="${LIST:[7]}"' + @echo 'LIST:[999]="${LIST:[999]}"' + @echo 'LIST:[-]="${LIST:[-]}" is an error' + @echo 'LIST:[--]="${LIST:[--]}" is an error' + @echo 'LIST:[-1]="${LIST:[-1]}"' + @echo 'LIST:[-2]="${LIST:[-2]}"' + @echo 'LIST:[-6]="${LIST:[-6]}"' + @echo 'LIST:[-7]="${LIST:[-7]}"' + @echo 'LIST:[-999]="${LIST:[-999]}"' + @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' + @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' + @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' + @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' + @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' + @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' + @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' + @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' + @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' + @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' + +mod-squarebrackets-start-end: + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1..]="${LIST:[1..]}" is an error' + @echo 'LIST:[1..1]="${LIST:[1..1]}"' + @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' + @echo 'LIST:[1..2]="${LIST:[1..2]}"' + @echo 'LIST:[2..1]="${LIST:[2..1]}"' + @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' + @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' + @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' + @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' + @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' + @echo 'LIST:[0..0]="${LIST:[0..0]}"' + @echo 'LIST:[3..99]="${LIST:[3..99]}"' + @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' + @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' + +mod-squarebrackets-nested: + @echo 'HASH="${HASH}" == "#" ?' + @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' + @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' + @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' + @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' + @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' + @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' + @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' + @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' + @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' + @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' + @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' + +mod-C-W: + @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' + @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' + @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' + @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' + @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' + +mod-S-W: + @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' + @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' + @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' + @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' + @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' + +mod-tW-tw: + @echo 'LIST:tW="${LIST:tW}"' + @echo 'LIST:tw="${LIST:tw}"' + @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' + @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' + @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' + @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' + @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' + @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' + @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' + @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' Property changes on: head/contrib/bmake/unit-tests/modword.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/order.exp =================================================================== --- head/contrib/bmake/unit-tests/order.exp (nonexistent) +++ head/contrib/bmake/unit-tests/order.exp (revision 281812) @@ -0,0 +1,4 @@ +Making the.c +Making the.h +Making the.o from the.h the.c +exit status 0 Index: head/contrib/bmake/unit-tests/order.mk =================================================================== --- head/contrib/bmake/unit-tests/order.mk (nonexistent) +++ head/contrib/bmake/unit-tests/order.mk (revision 281812) @@ -0,0 +1,20 @@ +# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +# Test that .ORDER is handled correctly. +# The explicit dependency the.o: the.h will make us examine the.h +# the .ORDER will prevent us building it immediately, +# we should then examine the.c rather than stop. + +all: the.o + +.ORDER: the.c the.h + +the.c the.h: + @echo Making $@ + +.SUFFIXES: .o .c + +.c.o: + @echo Making $@ from $? + +the.o: the.h Property changes on: head/contrib/bmake/unit-tests/order.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/phony-end.exp =================================================================== --- head/contrib/bmake/unit-tests/phony-end.exp (nonexistent) +++ head/contrib/bmake/unit-tests/phony-end.exp (revision 281812) @@ -0,0 +1,6 @@ +.TARGET="phony" .PREFIX="phony" .IMPSRC="" +.TARGET="all" .PREFIX="all" .IMPSRC="phony" +.TARGET="ok" .PREFIX="ok" .IMPSRC="" +.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC="" +.TARGET="bug" .PREFIX="bug" .IMPSRC="" +exit status 0 Index: head/contrib/bmake/unit-tests/phony-end.mk =================================================================== --- head/contrib/bmake/unit-tests/phony-end.mk (nonexistent) +++ head/contrib/bmake/unit-tests/phony-end.mk (revision 281812) @@ -0,0 +1,9 @@ +# $Id: phony-end.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +all ok also.ok bug phony: + @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}' + +.END: ok also.ok bug + +phony bug: .PHONY +all: phony Property changes on: head/contrib/bmake/unit-tests/phony-end.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/posix.exp =================================================================== --- head/contrib/bmake/unit-tests/posix.exp (nonexistent) +++ head/contrib/bmake/unit-tests/posix.exp (revision 281812) @@ -0,0 +1,23 @@ +Posix says we should execute the command as if run by system(3) +Expect 'Hello,' and 'World!' +Hello, +World! +a command +a command prefixed by '+' executes even with -n +another command +make -n +echo a command +echo "a command prefixed by '+' executes even with -n" +a command prefixed by '+' executes even with -n +echo another command +make -n -j1 +{ echo a command +} || exit $? +echo "a command prefixed by '+' executes even with -n" +a command prefixed by '+' executes even with -n +{ echo another command +} || exit $? +Now we expect an error... +*** Error code 1 (continuing) +`all' not remade because of errors. +exit status 0 Index: head/contrib/bmake/unit-tests/posix.mk =================================================================== --- head/contrib/bmake/unit-tests/posix.mk (nonexistent) +++ head/contrib/bmake/unit-tests/posix.mk (revision 281812) @@ -0,0 +1,24 @@ +# $Id: posix.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +all: x plus subs err + +x: + @echo "Posix says we should execute the command as if run by system(3)" + @echo "Expect 'Hello,' and 'World!'" + @echo Hello,; false; echo "World!" + +plus: + @echo a command + +@echo "a command prefixed by '+' executes even with -n" + @echo another command + +subs: + @echo make -n + @${.MAKE} -f ${MAKEFILE} -n plus + @echo make -n -j1 + @${.MAKE} -f ${MAKEFILE} -n -j1 plus + +err: + @(echo Now we expect an error...; exit 1) + @echo "Oops! you shouldn't see this!" + Property changes on: head/contrib/bmake/unit-tests/posix.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/posix1.exp =================================================================== --- head/contrib/bmake/unit-tests/posix1.exp (nonexistent) +++ head/contrib/bmake/unit-tests/posix1.exp (revision 281812) @@ -0,0 +1,185 @@ +${VAR} = "foo bar baz" +a +b +c +foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd +mkdir -p 'dir' +touch 'dir/obj_1.h' +mkdir -p 'dir' +printf '#include "obj_1.h"\nconst char* obj_1 = "dir/obj_1.c";\n' \ + >'dir/obj_1.c' +Local variables + ${@}="dir/obj_1.o" ${<}="dir/obj_1.c" + ${*}="dir/obj_1" ${?}="dir/obj_1.h dir/obj_1.c" + ${%}="" + +Directory and filename parts of local variables + ${@D}="dir" ${@F}="obj_1.o" + ${'obj_2.c' +mkdir -p '.' +touch 'obj_2.h' +Local variables + ${@}="obj2.o" ${<}="obj_2.c" + ${*}="obj2" ${?}="obj_2.c obj_2.h dir/obj_1.h" + ${%}="" + +Directory and filename parts of local variables + ${@D}="." ${@F}="obj2.o" + ${'obj3.c' +Local variables + ${@}="lib.a" ${<}="obj3.c" + ${*}="obj3" ${?}="obj3.h dir/dummy obj3.c" + ${%}="obj3.o" + +Directory and filename parts of local variables + ${@D}="." ${@F}="lib.a" + ${'${@}' + +dir/obj_1.h obj_2.h obj3.h dummy dir/dummy: + mkdir -p '${@D}' + touch '${@}' Property changes on: head/contrib/bmake/unit-tests/posix1.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/qequals.exp =================================================================== --- head/contrib/bmake/unit-tests/qequals.exp (nonexistent) +++ head/contrib/bmake/unit-tests/qequals.exp (revision 281812) @@ -0,0 +1,2 @@ +V.i386 ?= OK +exit status 0 Index: head/contrib/bmake/unit-tests/qequals.mk =================================================================== --- head/contrib/bmake/unit-tests/qequals.mk (nonexistent) +++ head/contrib/bmake/unit-tests/qequals.mk (revision 281812) @@ -0,0 +1,8 @@ +# $Id: qequals.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +M= i386 +V.i386= OK +V.$M ?= bug + +all: + @echo 'V.$M ?= ${V.$M}' Property changes on: head/contrib/bmake/unit-tests/qequals.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/suffixes.exp =================================================================== --- head/contrib/bmake/unit-tests/suffixes.exp (nonexistent) +++ head/contrib/bmake/unit-tests/suffixes.exp (revision 281812) @@ -0,0 +1,35 @@ +make: don't know how to make issue3 (continuing) +There should be no text after the colon: +touch .a +There should be no text after the colon: +touch .a.b +There should be no text after the colon: +touch .b.a +touch issue5a.c +first set +cp issue5a.c issue5a.d +touch issue5b.d +first set +cp issue5b.d issue5b.c +touch issue5c.d +first set +cp issue5c.d issue5c +touch issue5d.d +first set +cp issue5d.d issue5d.e +touch issue5e.e +first set +cp issue5e.e issue5e.d +make: don't know how to make issue6.f (continuing) +touch issue10.d +first set +cp issue10.d issue10.e +touch issue11.h +touch issue11.first +.ALLSRC: issue11.h issue11.first +cp issue11.h issue11.i +touch issue11.second +.ALLSRC: issue11.i issue11.second +cp issue11.i issue11.j +`all' not remade because of errors. +exit status 0 Index: head/contrib/bmake/unit-tests/suffixes.mk =================================================================== --- head/contrib/bmake/unit-tests/suffixes.mk (nonexistent) +++ head/contrib/bmake/unit-tests/suffixes.mk (revision 281812) @@ -0,0 +1,89 @@ +# $NetBSD: suffixes.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $ + +# Issues from PR 49086 + +# Issue 3: single suffix rules remain active after .SUFFIXES is cleared +# +# There's a rule for issue3.a, but .a is no longer a known suffix when +# targets are being made, so issue3 should not get made. +all: issue3 + +# Issue 4: suffix rules do not become regular rules when .SUFFIXES is cleared +# +# When the rules were encountered, .a and .b were known suffices, but later +# on they were forgotten. These should get created as regular targets. +all: .a .a.b .b.a + +# Issue 5: adding more suffixes does not make existing rules into suffix rules +# +# When the targets .c.d, .d.c, .d, .d.e, and .e.d were encountered, only .a, +# .b and .c were known suffixes, so all of them were regular rules. Later +# rest of the suffixes were made known, so they should all be suffix +# transformation rules. +all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d + +# Issue 6: transformation search can end up in an infinite loop +# +# There is no file or target from which issue6.f could be made from so +# this should fail. The bug was that because rules .e.f, .d.e and .e.d +# exist, make would try to make .f from .e and then infinitely try +# to do .e from .d and vice versa. +all: issue6.f + +# Issue 10: explicit dependencies affect transformation rule selection +# +# If issue10.e is wanted and both issue10.d and issue10.f are available, +# make should choose the .d.e rule, because .d is before .f in .SUFFIXES. +# The bug was that if issue10.d had an explicit dependency on issue10.f, +# it would choose .f.e instead. +all: issue10.e + +# Issue 11: sources from transformation rules are expanded incorrectly +# +# issue11.j should depend on issue11.i and issue11.second and issue11.i +# should depend on issue11.h and issue11.first. The bug was that +# the dynamic sources were expanded before ${.PREFIX} and ${.TARGET} were +# available, so they would have expanded to a null string. +all: issue11.j + +# we need to clean for repeatable results +.BEGIN: clean +clean: + @rm -f issue* .[ab]* + +.SUFFIXES: .a .b .c + +.a .a.b .b.a: + @echo 'There should be no text after the colon: ${.IMPSRC}' + touch ${.TARGET} + +.c.d .d.c .d .d.e .e.d: + @echo 'first set' + cp ${.IMPSRC} ${.TARGET} + +.SUFFIXES: +.SUFFIXES: .c .d .e .f .g + +.e .e.f .f.e: + @echo 'second set' + cp ${.IMPSRC} ${.TARGET} + +issue3.a: + @echo 'There is a bug if you see this.' + touch ${.TARGET} + +issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e issue10.d issue10.f: + touch ${.TARGET} + +.SUFFIXES: .h .i .j + +.h.i: ${.PREFIX}.first + @echo '.ALLSRC: ${.ALLSRC}' + cp ${.IMPSRC} ${.TARGET} + +.i.j: ${.PREFIX}.second + @echo '.ALLSRC: ${.ALLSRC}' + cp ${.IMPSRC} ${.TARGET} + +issue11.h issue11.first issue11.second: + touch ${.TARGET} Property changes on: head/contrib/bmake/unit-tests/suffixes.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/sunshcmd.exp =================================================================== --- head/contrib/bmake/unit-tests/sunshcmd.exp (nonexistent) +++ head/contrib/bmake/unit-tests/sunshcmd.exp (revision 281812) @@ -0,0 +1,4 @@ +TEST1=hello +TEST2=bye +TEST3=later +exit status 0 Index: head/contrib/bmake/unit-tests/sunshcmd.mk =================================================================== --- head/contrib/bmake/unit-tests/sunshcmd.mk (nonexistent) +++ head/contrib/bmake/unit-tests/sunshcmd.mk (revision 281812) @@ -0,0 +1,10 @@ +BYECMD = echo bye +LATERCMD = echo later +TEST1 :sh = echo hello +TEST2 :sh = ${BYECMD} +TEST3 = ${LATERCMD:sh} + +all: + @echo "TEST1=${TEST1}" + @echo "TEST2=${TEST2}" + @echo "TEST3=${TEST3}" Property changes on: head/contrib/bmake/unit-tests/sunshcmd.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/sysv.exp =================================================================== --- head/contrib/bmake/unit-tests/sysv.exp (nonexistent) +++ head/contrib/bmake/unit-tests/sysv.exp (revision 281812) @@ -0,0 +1,7 @@ +FOOBAR = +FOOBAR = foobar fubar +fun +fun +fun +In the Sun +exit status 0 Index: head/contrib/bmake/unit-tests/sysv.mk =================================================================== --- head/contrib/bmake/unit-tests/sysv.mk (nonexistent) +++ head/contrib/bmake/unit-tests/sysv.mk (revision 281812) @@ -0,0 +1,26 @@ +# $Id: sysv.mk,v 1.2 2014/08/30 22:25:14 sjg Exp $ + +FOO ?= +FOOBAR = ${FOO:=bar} + +_this := ${.PARSEDIR}/${.PARSEFILE} + +B = /b +S = / +FUN = ${B}${S}fun +SUN = the Sun + +# we expect nothing when FOO is empty +all: foo fun + +foo: + @echo FOOBAR = ${FOOBAR} +.if empty(FOO) + @FOO="foo fu" ${.MAKE} -f ${_this} foo +.endif + +fun: + @echo ${FUN:T} + @echo ${FUN:${B}${S}fun=fun} + @echo ${FUN:${B}${S}%=%} + @echo ${In:L:%=% ${SUN}} Property changes on: head/contrib/bmake/unit-tests/sysv.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/ternary.exp =================================================================== --- head/contrib/bmake/unit-tests/ternary.exp (nonexistent) +++ head/contrib/bmake/unit-tests/ternary.exp (revision 281812) @@ -0,0 +1,10 @@ +The answer is unknown +The answer is unknown +The answer is empty +The answer is known +The answer is +The answer is empty +The answer is known +The answer is 42 +The answer is 42 +exit status 0 Index: head/contrib/bmake/unit-tests/ternary.mk =================================================================== --- head/contrib/bmake/unit-tests/ternary.mk (nonexistent) +++ head/contrib/bmake/unit-tests/ternary.mk (revision 281812) @@ -0,0 +1,8 @@ + +all: + @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done + +show: + @echo "The answer is ${A:?known:unknown}" + @echo "The answer is ${A:?$A:unknown}" + @echo "The answer is ${empty(A):?empty:$A}" Property changes on: head/contrib/bmake/unit-tests/ternary.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/unexport-env.exp =================================================================== --- head/contrib/bmake/unit-tests/unexport-env.exp (nonexistent) +++ head/contrib/bmake/unit-tests/unexport-env.exp (revision 281812) @@ -0,0 +1,2 @@ +UT_TEST=unexport-env +exit status 0 Index: head/contrib/bmake/unit-tests/unexport-env.mk =================================================================== --- head/contrib/bmake/unit-tests/unexport-env.mk (nonexistent) +++ head/contrib/bmake/unit-tests/unexport-env.mk (revision 281812) @@ -0,0 +1,14 @@ +# $Id: unexport-env.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +# pick up a bunch of exported vars +.include "export.mk" + +# an example of setting up a minimal environment. +PATH = /bin:/usr/bin:/sbin:/usr/sbin + +# now clobber the environment to just PATH and UT_TEST +UT_TEST = unexport-env + +# this removes everything +.unexport-env +.export PATH UT_TEST Property changes on: head/contrib/bmake/unit-tests/unexport-env.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/unexport.exp =================================================================== --- head/contrib/bmake/unit-tests/unexport.exp (nonexistent) +++ head/contrib/bmake/unit-tests/unexport.exp (revision 281812) @@ -0,0 +1,4 @@ +UT_DOLLAR=This is $UT_FU +UT_FU=fubar +UT_TEST=unexport +exit status 0 Index: head/contrib/bmake/unit-tests/unexport.mk =================================================================== --- head/contrib/bmake/unit-tests/unexport.mk (nonexistent) +++ head/contrib/bmake/unit-tests/unexport.mk (revision 281812) @@ -0,0 +1,8 @@ +# $Id: unexport.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ + +# pick up a bunch of exported vars +.include "export.mk" + +.unexport UT_ZOO UT_FOO + +UT_TEST = unexport Property changes on: head/contrib/bmake/unit-tests/unexport.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/varcmd.exp =================================================================== --- head/contrib/bmake/unit-tests/varcmd.exp (nonexistent) +++ head/contrib/bmake/unit-tests/varcmd.exp (revision 281812) @@ -0,0 +1,9 @@ +default FU=fu FOO=foo VAR= +two FU=bar FOO=goo VAR= +three FU=bar FOO=goo VAR= +four FU=bar FOO=goo VAR=Internal +five FU=bar FOO=goo VAR=Internal +five v=is x k=is x +six v=is y k=is y +show-v v=override k=override +exit status 0 Index: head/contrib/bmake/unit-tests/varcmd.mk =================================================================== --- head/contrib/bmake/unit-tests/varcmd.mk (nonexistent) +++ head/contrib/bmake/unit-tests/varcmd.mk (revision 281812) @@ -0,0 +1,49 @@ +# $Id: varcmd.mk,v 1.1.1.1 2014/08/30 18:57:18 sjg Exp $ +# +# Test behaviour of recursive make and vars set on command line. + +FU=fu +FOO?=foo +.if !empty(.TARGETS) +TAG=${.TARGETS} +.endif +TAG?=default + +all: one + +show: + @echo "${TAG} FU=${FU} FOO=${FOO} VAR=${VAR}" + +one: show + @${.MAKE} -f ${MAKEFILE} FU=bar FOO=goo two + +two: show + @${.MAKE} -f ${MAKEFILE} three + +three: show + @${.MAKE} -f ${MAKEFILE} four + + +.ifmake four +VAR=Internal +.MAKEOVERRIDES+= VAR +.endif + +four: show + @${.MAKE} -f ${MAKEFILE} five + +M = x +V.y = is y +V.x = is x +V := ${V.$M} +K := ${V} + +show-v: + @echo '${TAG} v=${V} k=${K}' + +five: show show-v + @${.MAKE} -f ${MAKEFILE} M=y six + +six: show-v + @${.MAKE} -f ${MAKEFILE} V=override show-v + Property changes on: head/contrib/bmake/unit-tests/varcmd.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/varmisc.exp =================================================================== --- head/contrib/bmake/unit-tests/varmisc.exp (nonexistent) +++ head/contrib/bmake/unit-tests/varmisc.exp (revision 281812) @@ -0,0 +1,2 @@ + +exit status 0 Index: head/contrib/bmake/unit-tests/varmisc.mk =================================================================== --- head/contrib/bmake/unit-tests/varmisc.mk (nonexistent) +++ head/contrib/bmake/unit-tests/varmisc.mk (revision 281812) @@ -0,0 +1,8 @@ +# $Id: varmisc.mk,v 1.2 2014/08/30 22:25:14 sjg Exp $ +# +# Miscellaneous variable tests. + +all: unmatched_var_paren + +unmatched_var_paren: + @echo ${foo::=foo-text} Property changes on: head/contrib/bmake/unit-tests/varmisc.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/unit-tests/varshell.exp =================================================================== --- head/contrib/bmake/unit-tests/varshell.exp (nonexistent) +++ head/contrib/bmake/unit-tests/varshell.exp (revision 281812) @@ -0,0 +1,10 @@ +make: "varshell.mk" line 5: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status +make: "varshell.mk" line 8: warning: "false" returned non-zero status +make: "varshell.mk" line 9: warning: "echo "output before the error"; false" returned non-zero status +EXEC_FAILED='' +TERMINATED_BY_SIGNAL='' +ERROR_NO_OUTPUT='' +ERROR_WITH_OUTPUT='output before the error' +NO_ERROR_NO_OUTPUT='' +NO_ERROR_WITH_OUTPUT='this is good' +exit status 0 Index: head/contrib/bmake/unit-tests/varshell.mk =================================================================== --- head/contrib/bmake/unit-tests/varshell.mk (nonexistent) +++ head/contrib/bmake/unit-tests/varshell.mk (revision 281812) @@ -0,0 +1,19 @@ +# $Id: varshell.mk,v 1.4 2015/04/20 03:16:39 sjg Exp $ +# +# Test VAR != shell command + +EXEC_FAILED != /bin/no/such/command 2> /dev/null +# SunOS cannot handle this one +#TERMINATED_BY_SIGNAL != kill -14 $$$$ +ERROR_NO_OUTPUT != false +ERROR_WITH_OUTPUT != echo "output before the error"; false +NO_ERROR_NO_OUTPUT != true +NO_ERROR_WITH_OUTPUT != echo "this is good" + +allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \ + NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT + +all: +.for v in ${allvars} + @echo ${v}=\'${${v}}\' +.endfor Property changes on: head/contrib/bmake/unit-tests/varshell.mk ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/bmake/var.c =================================================================== --- head/contrib/bmake/var.c (revision 281811) +++ head/contrib/bmake/var.c (revision 281812) @@ -1,4199 +1,4194 @@ -/* $NetBSD: var.c,v 1.186 2014/06/20 06:13:45 sjg Exp $ */ +/* $NetBSD: var.c,v 1.191 2014/09/14 02:32:51 dholland 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.186 2014/06/20 06:13:45 sjg Exp $"; +static char rcsid[] = "$NetBSD: var.c,v 1.191 2014/09/14 02:32:51 dholland 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.186 2014/06/20 06:13:45 sjg Exp $"); +__RCSID("$NetBSD: var.c,v 1.191 2014/09/14 02:32:51 dholland 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" extern int makelevel; /* * This lets us tell if we have replaced the original environ * (which we cannot free). */ char **savedEnv = NULL; /* * This is a harmless return value for Var_Parse that can be used by Var_Subst * to determine if there was an error in parsing -- easier than returning * a flag, as things outside this module don't give a hoot. */ char var_Error[] = ""; /* * Similar to var_Error, but returned when the 'errnum' flag for Var_Parse is * set false. Why not just use a constant? Well, gcc likes to condense * identical string instances... */ static char varNoError[] = ""; /* * Internally, variables are contained in four different contexts. * 1) the environment. They may not be changed. If an environment * variable is appended-to, the result is placed in the global * context. * 2) the global context. Variables set in the Makefile are located in * the global context. It is the penultimate context searched when * substituting. * 3) the command-line context. All variables set on the command line * are placed in this context. They are UNALTERABLE once placed here. * 4) the local context. Each target has associated with it a context * list. On this list are located the structures describing such * local variables as $(@) and $(*) * The four contexts are searched in the reverse order from which they are * listed. */ GNode *VAR_INTERNAL; /* variables from make itself */ GNode *VAR_GLOBAL; /* variables from the makefile */ GNode *VAR_CMD; /* variables defined on the command-line */ #define FIND_CMD 0x1 /* look in VAR_CMD when searching */ #define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ #define FIND_ENV 0x4 /* look in the environment also */ typedef struct Var { char *name; /* the variable's name */ Buffer val; /* its value */ int flags; /* miscellaneous status flags */ #define VAR_IN_USE 1 /* Variable's value currently being used. * Used to avoid recursion */ #define VAR_FROM_ENV 2 /* Variable comes from the environment */ #define VAR_JUNK 4 /* Variable is a junk variable that * should be destroyed when done with * it. Used by Var_Parse for undefined, * modified variables */ #define VAR_KEEP 8 /* Variable is VAR_JUNK, but we found * a use for it in some modifier and * the value is therefore valid */ #define VAR_EXPORTED 16 /* Variable is exported */ #define VAR_REEXPORT 32 /* Indicate if var needs re-export. * This would be true if it contains $'s */ #define VAR_FROM_CMD 64 /* Variable came from command line */ } Var; /* * Exporting vars is expensive so skip it if we can */ #define VAR_EXPORTED_NONE 0 #define VAR_EXPORTED_YES 1 #define VAR_EXPORTED_ALL 2 static int var_exportedVars = VAR_EXPORTED_NONE; /* * We pass this to Var_Export when doing the initial export * or after updating an exported var. */ #define VAR_EXPORT_PARENT 1 /* Var*Pattern flags */ #define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */ #define VAR_SUB_ONE 0x02 /* Apply substitution to one word */ #define VAR_SUB_MATCHED 0x04 /* There was a match */ #define VAR_MATCH_START 0x08 /* Match at start of word */ #define VAR_MATCH_END 0x10 /* Match at end of word */ #define VAR_NOSUBST 0x20 /* don't expand vars in VarGetPattern */ /* Var_Set flags */ #define VAR_NO_EXPORT 0x01 /* do not export */ typedef struct { /* * The following fields are set by Var_Parse() when it * encounters modifiers that need to keep state for use by * subsequent modifiers within the same variable expansion. */ Byte varSpace; /* Word separator in expansions */ Boolean oneBigWord; /* TRUE if we will treat the variable as a * single big word, even if it contains * embedded spaces (as opposed to the * usual behaviour of treating it as * several space-separated words). */ } Var_Parse_State; /* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/", * to VarSYSVMatch() for ":lhs=rhs". */ typedef struct { const char *lhs; /* String to match */ int leftLen; /* Length of string */ const char *rhs; /* Replacement string (w/ &'s removed) */ int rightLen; /* Length of replacement */ int flags; } VarPattern; /* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */ typedef struct { GNode *ctxt; /* variable context */ char *tvar; /* name of temp var */ int tvarLen; char *str; /* string to expand */ int strLen; int errnum; /* errnum for not defined */ } VarLoop_t; #ifndef NO_REGEX /* struct passed as 'void *' to VarRESubstitute() for ":C///" */ typedef struct { regex_t re; int nsub; regmatch_t *matches; char *replace; int flags; } VarREPattern; #endif /* struct passed to VarSelectWords() for ":[start..end]" */ typedef struct { int start; /* first word to select */ int end; /* last word to select */ } VarSelectWords_t; static Var *VarFind(const char *, GNode *, int); static void VarAdd(const char *, const char *, GNode *); static Boolean VarHead(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static Boolean VarTail(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static Boolean VarSuffix(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static Boolean VarRoot(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static Boolean VarMatch(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); #ifdef SYSVVARSUB static Boolean VarSYSVMatch(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); #endif static Boolean VarNoMatch(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); #ifndef NO_REGEX static void VarREError(int, regex_t *, const char *); static Boolean VarRESubstitute(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); #endif static Boolean VarSubstitute(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static Boolean VarLoopExpand(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *); static char *VarGetPattern(GNode *, Var_Parse_State *, int, const char **, int, int *, int *, VarPattern *); static char *VarQuote(char *); static char *VarHash(char *); static char *VarModify(GNode *, Var_Parse_State *, const char *, Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *), void *); static char *VarOrder(const char *, const char); static char *VarUniq(const char *); static int VarWordCompare(const void *, const void *); static void VarPrintVar(void *); #define BROPEN '{' #define BRCLOSE '}' #define PROPEN '(' #define PRCLOSE ')' /*- *----------------------------------------------------------------------- * VarFind -- * Find the given variable in the given context and any other contexts * indicated. * * Input: * name name to find * ctxt context in which to find it * flags FIND_GLOBAL set means to look in the * VAR_GLOBAL context as well. FIND_CMD set means * to look in the VAR_CMD context also. FIND_ENV * set means to look in the environment * * Results: * A pointer to the structure describing the desired variable or * NULL if the variable does not exist. * * Side Effects: * None *----------------------------------------------------------------------- */ static Var * VarFind(const char *name, GNode *ctxt, int flags) { Hash_Entry *var; Var *v; /* * If the variable name begins with a '.', it could very well be one of * the local ones. We check the name against all the local variables * and substitute the short version in for 'name' if it matches one of * them. */ if (*name == '.' && isupper((unsigned char) name[1])) switch (name[1]) { case 'A': if (!strcmp(name, ".ALLSRC")) name = ALLSRC; if (!strcmp(name, ".ARCHIVE")) name = ARCHIVE; break; case 'I': if (!strcmp(name, ".IMPSRC")) name = IMPSRC; break; case 'M': if (!strcmp(name, ".MEMBER")) name = MEMBER; break; case 'O': if (!strcmp(name, ".OODATE")) name = OODATE; break; case 'P': if (!strcmp(name, ".PREFIX")) name = PREFIX; break; case 'T': if (!strcmp(name, ".TARGET")) name = TARGET; break; } #ifdef notyet /* for compatibility with gmake */ if (name[0] == '^' && name[1] == '\0') name = ALLSRC; #endif /* * First look for the variable in the given context. If it's not there, * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, * depending on the FIND_* flags in 'flags' */ var = Hash_FindEntry(&ctxt->context, name); if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { var = Hash_FindEntry(&VAR_CMD->context, name); } if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL)) { var = Hash_FindEntry(&VAR_GLOBAL->context, name); if ((var == NULL) && (ctxt != VAR_INTERNAL)) { /* VAR_INTERNAL is subordinate to VAR_GLOBAL */ var = Hash_FindEntry(&VAR_INTERNAL->context, name); } } if ((var == NULL) && (flags & FIND_ENV)) { char *env; if ((env = getenv(name)) != NULL) { int len; v = bmake_malloc(sizeof(Var)); v->name = bmake_strdup(name); len = strlen(env); Buf_Init(&v->val, len + 1); Buf_AddBytes(&v->val, len, env); v->flags = VAR_FROM_ENV; return (v); } else if (checkEnvFirst && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL)) { var = Hash_FindEntry(&VAR_GLOBAL->context, name); if ((var == NULL) && (ctxt != VAR_INTERNAL)) { var = Hash_FindEntry(&VAR_INTERNAL->context, name); } if (var == NULL) { return NULL; } else { return ((Var *)Hash_GetValue(var)); } } else { return NULL; } } else if (var == NULL) { return NULL; } else { return ((Var *)Hash_GetValue(var)); } } /*- *----------------------------------------------------------------------- * VarFreeEnv -- * If the variable is an environment variable, free it * * Input: * v the variable * destroy true if the value buffer should be destroyed. * * Results: * 1 if it is an environment variable 0 ow. * * Side Effects: * The variable is free'ed if it is an environent variable. *----------------------------------------------------------------------- */ static Boolean VarFreeEnv(Var *v, Boolean destroy) { if ((v->flags & VAR_FROM_ENV) == 0) return FALSE; free(v->name); Buf_Destroy(&v->val, destroy); free(v); return TRUE; } /*- *----------------------------------------------------------------------- * VarAdd -- * Add a new variable of name name and value val to the given context * * Input: * name name of variable to add * val value to set it to * ctxt context in which to set it * * Results: * None * * Side Effects: * The new variable is placed at the front of the given context * The name and val arguments are duplicated so they may * safely be freed. *----------------------------------------------------------------------- */ static void VarAdd(const char *name, const char *val, GNode *ctxt) { Var *v; int len; Hash_Entry *h; v = bmake_malloc(sizeof(Var)); len = val ? strlen(val) : 0; Buf_Init(&v->val, len+1); Buf_AddBytes(&v->val, len, val); v->flags = 0; h = Hash_CreateEntry(&ctxt->context, name, NULL); Hash_SetValue(h, v); v->name = h->name; if (DEBUG(VAR)) { fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val); } } /*- *----------------------------------------------------------------------- * Var_Delete -- * Remove a variable from a context. * * Results: * None. * * Side Effects: * The Var structure is removed and freed. * *----------------------------------------------------------------------- */ void Var_Delete(const char *name, GNode *ctxt) { Hash_Entry *ln; char *cp; if (strchr(name, '$')) { cp = Var_Subst(NULL, name, VAR_GLOBAL, 0); } 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; /* * Several make's support this sort of mechanism for tracking * recursion - but each uses a different name. * We allow the makefiles to update MAKELEVEL and ensure * children see a correctly incremented value. */ snprintf(tmp, sizeof(tmp), "%d", makelevel + 1); setenv(MAKE_LEVEL_ENV, tmp, 1); if (VAR_EXPORTED_NONE == var_exportedVars) return; if (VAR_EXPORTED_ALL == var_exportedVars) { /* * Ouch! This is crazy... */ for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state); var != NULL; var = Hash_EnumNext(&state)) { v = (Var *)Hash_GetValue(var); Var_Export1(v->name, 0); } return; } /* * We have a number of exported vars, */ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}"); if (n < (int)sizeof(tmp)) { char **av; char *as; int ac; int i; val = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); 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_ENV); /* we should preserve this */ if (environ == savedEnv) { /* we have been here before! */ newenv = bmake_realloc(environ, 2 * sizeof(char *)); } else { if (savedEnv) { free(savedEnv); savedEnv = NULL; } newenv = bmake_malloc(2 * sizeof(char *)); } if (!newenv) return; /* Note: we cannot safely free() the original environ. */ environ = savedEnv = newenv; newenv[0] = NULL; newenv[1] = NULL; setenv(MAKE_LEVEL_ENV, cp, 1); } else { for (; *str != '\n' && isspace((unsigned char) *str); str++) continue; if (str[0] && str[0] != '\n') { vlist = str; } } if (!vlist) { /* Using .MAKE.EXPORTED */ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}"); if (n < (int)sizeof(tmp)) { vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); } } 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) { if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) { /* * This var would normally prevent the same name being added * to VAR_GLOBAL, so delete it from there if needed. * Otherwise -V name may show the wrong value. */ Var_Delete(name, VAR_GLOBAL); } VarAdd(name, val, ctxt); } else { Buf_Empty(&v->val); Buf_AddBytes(&v->val, strlen(val), val); if (DEBUG(VAR)) { fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val); } if ((v->flags & VAR_EXPORTED)) { Var_Export1(name, VAR_EXPORT_PARENT); } } /* * Any variables given on the command line are automatically exported * to the environment (as per POSIX standard) */ if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) { if (v == NULL) { /* we just added it */ v = VarFind(name, ctxt, 0); } if (v != NULL) v->flags |= VAR_FROM_CMD; /* * If requested, don't export these in the environment * individually. We still put them in MAKEOVERRIDES so * that the command-line settings continue to override * Makefile settings. */ if (varNoExportEnv != TRUE) setenv(name, val, 1); Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL); } out: if (expanded_name != NULL) free(expanded_name); 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; h = 0x971e137bU; c1 = 0x95543787U; c2 = 0x2ad7eb25U; len2 = strlen(str); for (len = len2; len; ) { k = 0; switch (len) { default: k = (ustr[3] << 24) | (ustr[2] << 16) | (ustr[1] << 8) | ustr[0]; len -= 4; ustr += 4; break; case 3: k |= (ustr[2] << 16); case 2: k |= (ustr[1] << 8); case 1: k |= ustr[0]; len = 0; } c1 = c1 * 5 + 0x7b7d159cU; c2 = c2 * 5 + 0x6bce6396U; k *= c1; k = (k << 11) ^ (k >> 21); k *= c2; h = (h << 13) ^ (h >> 19); h = h * 5 + 0x52dce729U; h ^= k; } h ^= len2; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; Buf_Init(&buf, 0); for (len = 0; len < 8; ++len) { Buf_AddByte(&buf, hexdigits[h & 15]); h >>= 4; } return Buf_Destroy(&buf, FALSE); } static char * VarStrftime(const char *fmt, int zulu) { char buf[BUFSIZ]; time_t utc; time(&utc); if (!*fmt) fmt = "%c"; strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&utc) : localtime(&utc)); buf[sizeof(buf) - 1] = '\0'; return bmake_strdup(buf); } /* * Now we need to apply any modifiers the user wants applied. * These are: * :M 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 = varNoError; 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 */ + const char *extramodifiers; /* extra modifiers to apply first */ char name[2]; *freePtr = NULL; + extramodifiers = 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 */ int depth = 1; endc = startc == PROPEN ? PRCLOSE : BRCLOSE; Buf_Init(&buf, 0); /* * Skip to the end character or a colon, whichever comes first. */ for (tstr = str + 2; *tstr != '\0'; tstr++) { /* * Track depth so we can spot parse errors. */ if (*tstr == startc) { depth++; } if (*tstr == endc) { if (--depth == 0) break; } if (depth == 1 && *tstr == ':') { break; } /* * A variable inside a variable, expand */ if (*tstr == '$') { int rlen; void *freeIt; char *rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt); 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 == endc) { haveModifier = FALSE; } else { /* * If we never did find the end character, return NULL * right now, setting the length to be the distance to * the end of the string, since that's what make does. */ *lengthPtr = tstr - str; Buf_Destroy(&buf, TRUE); return (var_Error); } str = Buf_GetAll(&buf, &vlen); /* * At this point, str points into newly allocated memory from * buf, containing only the name of the variable. * * start and tstr point into the const string that was pointed * to by the original value of the str parameter. start points * to the '$' at the beginning of the string, while tstr points * to the char just after the end of the variable name -- this * will be '\0', ':', PRCLOSE, or BRCLOSE. */ v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); /* * Check also for bogus D and F forms of local variables since we're * in a local context and the name is the right length. */ if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && (vlen == 2) && (str[1] == 'F' || str[1] == 'D') && - strchr("@%*!<>", str[0]) != NULL) { + 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); + extramodifiers = "H:"; } - /* - * 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; + else { /* F */ + extramodifiers = "T:"; + } } } if (v == NULL) { if (((vlen == 1) || (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ switch (*str) { case '@': case '%': case '*': case '!': dynamic = TRUE; break; } } else if ((vlen > 2) && (*str == '.') && isupper((unsigned char) str[1]) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { int len; len = vlen - 1; if ((strncmp(str, ".TARGET", len) == 0) || (strncmp(str, ".ARCHIVE", len) == 0) || (strncmp(str, ".PREFIX", len) == 0) || (strncmp(str, ".MEMBER", len) == 0)) { dynamic = TRUE; } } if (!haveModifier) { /* * No modifiers -- have specification length so we can return * now. */ *lengthPtr = tstr - start + 1; if (dynamic) { char *pstr = bmake_strndup(start, *lengthPtr); *freePtr = pstr; Buf_Destroy(&buf, TRUE); return(pstr); } else { Buf_Destroy(&buf, TRUE); return (errnum ? var_Error : varNoError); } } else { /* * Still need to get to the end of the variable specification, * so kludge up a Var structure for the modifications */ v = bmake_malloc(sizeof(Var)); v->name = UNCONST(str); Buf_Init(&v->val, 1); v->flags = VAR_JUNK; Buf_Destroy(&buf, FALSE); } } else Buf_Destroy(&buf, TRUE); } if (v->flags & VAR_IN_USE) { Fatal("Variable %s is recursive.", v->name); /*NOTREACHED*/ } else { v->flags |= VAR_IN_USE; } /* * Before doing any modification, we have to make sure the value * has been fully expanded. If it looks like recursion might be * necessary (there's a dollar sign somewhere in the variable's value) * we just call Var_Subst to do any other substitutions that are * necessary. Note that the value returned by Var_Subst will have * been dynamically-allocated, so it will need freeing when we * return. */ nstr = Buf_GetAll(&v->val, NULL); if (strchr(nstr, '$') != NULL) { nstr = Var_Subst(NULL, nstr, ctxt, errnum); *freePtr = nstr; } v->flags &= ~VAR_IN_USE; - if ((nstr != NULL) && haveModifier) { + if ((nstr != NULL) && (haveModifier || extramodifiers != NULL)) { + void *extraFree; int used; - /* - * Skip initial colon. - */ - tstr++; - nstr = ApplyModifiers(nstr, tstr, startc, endc, - v, ctxt, errnum, &used, freePtr); - tstr += used; + extraFree = NULL; + if (extramodifiers != NULL) { + nstr = ApplyModifiers(nstr, extramodifiers, '(', ')', + v, ctxt, errnum, &used, &extraFree); + } + + if (haveModifier) { + /* Skip initial colon. */ + tstr++; + + nstr = ApplyModifiers(nstr, tstr, startc, endc, + v, ctxt, errnum, &used, freePtr); + tstr += used; + if (extraFree) { + free(extraFree); + } + } else { + *freePtr = extraFree; + } } if (*tstr) { *lengthPtr = tstr - start + 1; } else { *lengthPtr = tstr - start; } if (v->flags & VAR_FROM_ENV) { Boolean destroy = FALSE; if (nstr != Buf_GetAll(&v->val, NULL)) { destroy = TRUE; } else { /* * Returning the value unmodified, so tell the caller to free * the thing. */ *freePtr = nstr; } VarFreeEnv(v, destroy); } else if (v->flags & VAR_JUNK) { /* * Perform any free'ing needed and set *freePtr to NULL so the caller * doesn't try to free a static pointer. * If VAR_KEEP is also set then we want to keep str as is. */ if (!(v->flags & VAR_KEEP)) { if (*freePtr) { free(nstr); *freePtr = NULL; } if (dynamic) { nstr = bmake_strndup(start, *lengthPtr); *freePtr = nstr; } else { nstr = errnum ? var_Error : varNoError; } } if (nstr != Buf_GetAll(&v->val, NULL)) Buf_Destroy(&v->val, TRUE); free(v->name); free(v); } return (nstr); } /*- *----------------------------------------------------------------------- * Var_Subst -- * Substitute for all variables in the given string in the given context * If undefErr is TRUE, Parse_Error will be called when an undefined * variable is encountered. * * Input: * var Named variable || NULL for all * str the string which to substitute * ctxt the context wherein to find variables * undefErr TRUE if undefineds are an error * * 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 || val == var_Error) { /* * If variable is undefined, complain and skip the * variable. The complaint will stop us from doing anything * when the file is parsed. */ if (!errorReported) { Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",length,str); } str += length; errorReported = TRUE; } else { Buf_AddByte(&buf, *str); str += 1; } } else { /* * We've now got a variable structure to store in. But first, * advance the string pointer. */ str += length; /* * Copy all the characters from the variable value straight * into the new string. */ length = strlen(val); Buf_AddBytes(&buf, length, val); trailingBslash = length > 0 && val[length - 1] == '\\'; } if (freeIt) { free(freeIt); freeIt = NULL; } } } return Buf_DestroyCompact(&buf); } /*- *----------------------------------------------------------------------- * Var_GetTail -- * Return the tail from each of a list of words. Used to set the * System V local variables. * * Input: * file Filename to modify * * Results: * The resulting string. * * Side Effects: * None. * *----------------------------------------------------------------------- */ #if 0 char * Var_GetTail(char *file) { return(VarModify(file, VarTail, NULL)); } /*- *----------------------------------------------------------------------- * Var_GetHead -- * Find the leading components of a (list of) filename(s). * XXX: VarHead does not replace foo by ., as (sun) System V make * does. * * Input: * file Filename to manipulate * * Results: * The leading components. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_GetHead(char *file) { return(VarModify(file, VarHead, NULL)); } #endif /*- *----------------------------------------------------------------------- * Var_Init -- * Initialize the module * * Results: * None * * Side Effects: * The VAR_CMD and VAR_GLOBAL contexts are created *----------------------------------------------------------------------- */ void Var_Init(void) { VAR_INTERNAL = Targ_NewGN("Internal"); VAR_GLOBAL = Targ_NewGN("Global"); VAR_CMD = Targ_NewGN("Command"); } void Var_End(void) { } /****************** PRINT DEBUGGING INFO *****************/ static void VarPrintVar(void *vp) { Var *v = (Var *)vp; fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL)); } /*- *----------------------------------------------------------------------- * Var_Dump -- * print all variables in a context *----------------------------------------------------------------------- */ void Var_Dump(GNode *ctxt) { Hash_Search search; Hash_Entry *h; for (h = Hash_EnumFirst(&ctxt->context, &search); h != NULL; h = Hash_EnumNext(&search)) { VarPrintVar(Hash_GetValue(h)); } } Index: head/contrib/bmake =================================================================== --- head/contrib/bmake (revision 281811) +++ head/contrib/bmake (revision 281812) Property changes on: head/contrib/bmake ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /vendor/NetBSD/bmake/dist:r268193-281761 Index: head/usr.bin/bmake/Makefile =================================================================== --- head/usr.bin/bmake/Makefile (revision 281811) +++ head/usr.bin/bmake/Makefile (revision 281812) @@ -1,176 +1,176 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # # $FreeBSD$ .sinclude "Makefile.inc" SRCTOP?= ${.CURDIR:H:H} # look here first for config.h CFLAGS+= -I${.CURDIR} # for after-import CLEANDIRS+= FreeBSD CLEANFILES+= bootstrap -# $Id: Makefile,v 1.27 2014/06/20 14:51:54 sjg Exp $ +# $Id: Makefile,v 1.36 2015/04/18 19:58:53 sjg Exp $ # Base version on src date -MAKE_VERSION= 20140620 +MAKE_VERSION= 20150418 PROG?= ${.CURDIR:T} SRCS= \ arch.c \ buf.c \ compat.c \ cond.c \ dir.c \ for.c \ hash.c \ job.c \ main.c \ make.c \ make_malloc.c \ meta.c \ parse.c \ str.c \ strlist.c \ suff.c \ targ.c \ trace.c \ util.c \ var.c # from lst.lib/ SRCS+= \ lstAppend.c \ lstAtEnd.c \ lstAtFront.c \ lstClose.c \ lstConcat.c \ lstDatum.c \ lstDeQueue.c \ lstDestroy.c \ lstDupl.c \ lstEnQueue.c \ lstFind.c \ lstFindFrom.c \ lstFirst.c \ lstForEach.c \ lstForEachFrom.c \ lstInit.c \ lstInsert.c \ lstIsAtEnd.c \ lstIsEmpty.c \ lstLast.c \ lstMember.c \ lstNext.c \ lstOpen.c \ lstPrev.c \ lstRemove.c \ lstReplace.c \ lstSucc.c # this file gets generated by configure .sinclude "Makefile.config" .if !empty(LIBOBJS) SRCS+= ${LIBOBJS:T:.o=.c} .endif # just in case prefix?= /usr srcdir?= ${.CURDIR} DEFAULT_SYS_PATH?= ${prefix}/share/mk CPPFLAGS+= -DUSE_META CFLAGS+= ${CPPFLAGS} CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} COPTS.main.c+= "-DMAKE_VERSION=\"${MAKE_VERSION}\"" # meta mode can be useful even without filemon FILEMON_H ?= /usr/include/dev/filemon/filemon.h .if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif .PATH: ${srcdir} .PATH: ${srcdir}/lst.lib .if make(obj) || make(clean) SUBDIR+= unit-tests .endif MAN= ${PROG}.1 MAN1= ${MAN} .if (${PROG} != "make") CLEANFILES+= my.history .if make(${MAN}) || !exists(${srcdir}/${MAN}) my.history: ${MAKEFILE} @(echo ".Nm"; \ echo "is derived from NetBSD"; \ echo ".Xr make 1 ."; \ echo "It uses autoconf to facilitate portability to other platforms."; \ echo ".Pp") > $@ .NOPATH: ${MAN} ${MAN}: make.1 my.history @echo making $@ @sed -e 's/^.Nx/NetBSD/' -e '/^.Nm/s/make/${PROG}/' \ -e '/^.Sh HISTORY/rmy.history' \ -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@ all beforeinstall: ${MAN} _mfromdir=. .endif .endif MANTARGET?= cat MANDEST?= ${MANDIR}/${MANTARGET}1 .if ${MANTARGET} == "cat" _mfromdir=${srcdir} .endif .include 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 +SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share} +BINDIR= ${BINDIR.bmake:U${prefix}/bin} +MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} .if !exists(.depend) ${OBJS}: config.h .endif # make sure that MAKE_VERSION gets updated. main.o: ${SRCS} ${MAKEFILE} # A simple unit-test driver to help catch regressions accept test: cd ${.CURDIR}/unit-tests && MAKEFLAGS= ${.MAKE} -r -m / TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET} # override some simple things BINDIR= /usr/bin MANDIR= /usr/share/man/man # make sure we get this CFLAGS+= ${COPTS.${.IMPSRC:T}} after-import: ${SRCTOP}/contrib/bmake/bsd.after-import.mk cd ${.CURDIR} && ${.MAKE} -f ${SRCTOP}/contrib/bmake/bsd.after-import.mk Index: head/usr.bin/bmake/unit-tests/Makefile =================================================================== --- head/usr.bin/bmake/unit-tests/Makefile (revision 281811) +++ head/usr.bin/bmake/unit-tests/Makefile (revision 281812) @@ -1,109 +1,157 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # # $FreeBSD$ SRCTOP?= ${.CURDIR:H:H:H} -# $Id: Makefile.in,v 1.44 2013/08/28 22:09:29 sjg Exp $ +# $Id: Makefile.in,v 1.46 2014/11/06 01:47:57 sjg Exp $ # -# $NetBSD: Makefile,v 1.38 2013/08/28 21:56:50 sjg Exp $ +# $NetBSD: Makefile,v 1.51 2014/10/20 23:21:11 sjg Exp $ # # Unit tests for make(1) # The main targets are: # # all: run all the tests -# test: run 'all', capture output and compare to expected results +# test: run 'all', and compare to expected results # accept: move generated output to expected results # # Adding a test case. # Each feature should get its own set of tests in its own suitably -# named makefile which should be added to SUBFILES to hook it in. +# named makefile (*.mk), with its own set of expected results (*.exp), +# and it should be added to the TESTNAMES list. # srcdir= ${SRCTOP}/contrib/bmake/unit-tests .MAIN: all UNIT_TESTS:= ${srcdir} +.PATH: ${UNIT_TESTS} -# Simple sub-makefiles - we run them as a black box -# keep the list sorted. -SUBFILES= \ +# Each test is in a sub-makefile. +# Keep the list sorted. +TESTNAMES= \ comment \ cond1 \ error \ export \ export-all \ export-env \ doterror \ dotwait \ forloop \ forsubst \ hash \ misc \ moderrs \ modmatch \ modmisc \ modorder \ modts \ modword \ order \ - phony-end \ posix \ qequals \ sunshcmd \ sysv \ ternary \ unexport \ unexport-env \ - varcmd + varcmd \ + varmisc \ + varshell -all: ${SUBFILES} +# these tests were broken by referting POSIX chanegs +STRICT_POSIX_TESTS = \ + escape \ + impsrc \ + phony-end \ + posix1 \ + suffixes +# Override make flags for certain tests flags.doterror= flags.order=-j1 -# the tests are actually done with sub-makes. -.PHONY: ${SUBFILES} -.PRECIOUS: ${SUBFILES} -${SUBFILES}: - -@${.MAKE} ${flags.$@:U-k} -f ${UNIT_TESTS}/$@ +OUTFILES= ${TESTNAMES:S/$/.out/} -clean: - rm -f *.out *.fail *.core +all: ${OUTFILES} -.sinclude +CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp +CLEANFILES += obj*.[och] lib*.a # posix1.mk +CLEANFILES += issue* .[ab]* # suffixes.mk +CLEANRECURSIVE += dir dummy # posix1.mk +clean: + rm -f ${CLEANFILES} +.if !empty(CLEANRECURSIVE) + rm -rf ${CLEANRECURSIVE} +.endif + TEST_MAKE?= ${.MAKE} TOOL_SED?= sed TOOL_TR?= tr TOOL_DIFF?= diff DIFF_FLAGS?= -u .if defined(.PARSEDIR) # ensure consistent results from sort(1) LC_ALL= C LANG= C .export LANG LC_ALL .endif -# The driver. +# some tests need extra post-processing +SED_CMDS.varshell = -e 's,^[a-z]*sh: ,,' \ + -e '/command/s,No such.*,not found,' + +# the tests are actually done with sub-makes. +.SUFFIXES: .mk .rawout .out +.mk.rawout: + @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} + -@cd ${.OBJDIR} && \ + { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \ + 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} + # We always pretend .MAKE was called 'make' # and strip ${.CURDIR}/ from the output # and replace anything after 'stopped in' with unit-tests # so the results can be compared. -test: - @echo "${TEST_MAKE} -f ${MAKEFILE} > ${.TARGET}.out 2>&1" - @cd ${.OBJDIR} && ${TEST_MAKE} -f ${MAKEFILE} 2>&1 | \ - ${TOOL_TR} -d '\015' | \ - ${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[^:]*:,make:,' \ - -e '/stopped/s, /.*, unit-tests,' \ - -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ - -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' > ${.TARGET}.out || { \ - tail ${.TARGET}.out; mv ${.TARGET}.out ${.TARGET}.fail; exit 1; } - ${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/${.TARGET}.exp ${.TARGET}.out +.rawout.out: + @echo postprocess ${.TARGET} + @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \ + -e 's,${TEST_MAKE:C/\./\\\./g},make,' \ + -e '/stopped/s, /.*, unit-tests,' \ + -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ + -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' ${SED_CMDS.${.TARGET:T:R}} \ + < ${.IMPSRC} > ${.TARGET}.tmp + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} +# Compare all output files +test: ${OUTFILES} .PHONY + @failed= ; \ + for test in ${TESTNAMES}; do \ + ${TOOL_DIFF} -u ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || failed="$${failed}$${failed:+ }$${test}" ; \ + done ; \ + if [ -n "$${failed}" ]; then \ + echo "Failed tests: $${failed}" ; false ; \ + else \ + echo "All tests passed" ; \ + fi + accept: - mv test.out ${srcdir}/test.exp + @for test in ${TESTNAMES}; do \ + cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || { echo "Replacing $${test}.exp" ; \ + cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ + done +.if exists(${TEST_MAKE}) +${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE} +.endif + +.sinclude